Level Grid Proposal

'''NOTICE: This proposal is henceforth DEPRECATED and shall not be implemented. This page is kept for historical purposes only.'''

This page serves as a proposal to change levels to support a grid-based system of tiles. As of the current code, levels support placing tiles anywhere within the level, which could be made workable, but a grid-based system may reduce complications in the future, especially with regards to collision detection and resolution. Additionally, a proposal to add two generic grid collections will also be considered.

This page shall detail the advantages and disadvantages of the system and implementation details for the system.

The TileGrid
In SML, the SMLimitless.Sprites.Collections.TileGrid type would store a collection of tiles as a two-dimensional array of a specified size. The tile grid defines a cell size in pixels and a grid size in cells. The bounding rectangle and the total number of cells can be derived from these values.

The constructor will initialize a new grid by creating the internal Tile array using the four specified parameters: cell width, cell height, grid width, and grid height. An overload would accept two Vector2 parameters, cell size and grid size, in place of the four integer parameters.

Each element in the two-dimensional internal Tile array would either contain a null reference or a reference to a Tile derived class.

The indexer of the tile grid would accept two parameters, x and y, which are used to get or set the element at the same position in the internal Tile array. The getter would merely return the value. The setter would set the value and adjust the number of sloped tiles (Tiles with the IsSlopedTile field set) in the grid. To determine how to adjust the number of sloped tiles, the current Tile at that element is retrieved, and a conditional statement is executed: A property, HasSlopedTiles, can tell callers if this grid contains sloped tiles by checking the number of sloped tiles.
 * If the current tile is null OR not sloped, and the new tile is not null AND sloped, the value is incremented.
 * If the current tile is not null AND sloped, and the new tile is not null AND sloped, the value is not changed.
 * If the current tile is not null AND sloped, and the new tile is null OR sloped, the value is decremented.

Grid Behavior for Large Tiles
A tile that is larger than a single grid cell will have its reference stored in multiple grid cells. All tiles must have a size that is a multiple of the grid cell size - for example, a grid of 32x32 cells can accept 64x64 sized tiles and even 256x32 or 64x1024 sized tiles but not 295x387.

The Generic Grids
The generic grids, Grid and SizedGrid, operate on the fact that only Tile grids need to access anything particular about a Tile, and that is its IsSlopedTile property. No other type requires such type-specific checks.

Both grids shall be stored in the SMLimitless.Collection namespace.

The only difference between SizedGrid and Grid is that SizedGrid defines cell sizes, but Grid does not. Otherwise, both grids work similarly to the TileGrid type, except for the checking of sloped tiles. They both use an internal two-dimensional array of values.

Subgrids
A subgrid is a grid that is taken from another grid. Two methods in the grid types are capable of returning subgrids. The first is. This method returns a subgrid by taking all the values starting at [gridX, gridY] through the specified width and height.

The second method is. This method returns a subgrid between the two specified corner cells.

Collision Resolution in Levels using Grids
One of the problems with using a non-grid approach to storing tiles in a Level instance is detecting if a sprite is colliding with a row of tiles, a column of tiles, or both. With grids, this problem becomes marginally easier to solve, although it isn't very intuitive.

The collision method works on a two-pass system. The first pass resolves for only non-sloped tiles and offsets a sprite outside of any non-sloped tiles. The second pass resolves for only sloped tiles and offsets a sprite outside of any sloped tiles.

For each sprite in a level, the first step is to calculate all the tiles that a sprite is colliding with: The next step is to determine if the sprite is colliding with a row of tiles, a column of tiles, both, or neither: To construct the row rectangle, make a bounding rectangle with a height of the tile grid cell height and a width of the tile grid's bound's width. To construct the column rectangle, make a bounding rectangle with a width of the tile grid cell width and a height of the tile grid's bound's height.
 * 1) Get the cell on the tile grid that the top-left corner of the sprite would be contained in. Next, get the cell on the tile grid that the bottom-right corner of the sprite would be contained in. These are the top-left cell and the bottom-right cell, respectively.
 * 2) Move the top-left cell up and to the left by one cell. Move the bottom-right cell down and to the right by one cell.
 * 3) Retrieve the subgrid from the level's tile grid between these two corners. These are the collidable tiles, that is, all the tiles this sprite could be colliding with.
 * 4) Create a few local fields for later use: a collection of tiles that the sprite is colliding with (Collection), a collection of resolution distances between the sprite and all collidable tiles (Collection), and a Grid of the same size as the collidable tiles grid to indicate which tiles that there are collisions with.
 * 5) For each tile in the collidable tile grid (enumeration order is irrelevant, but use for loops to keep track of the current position), calculate the resolution distance between this sprite and the current tile if the tile isn't null and isn't sloped. If the resolution is non-zero, place the tile in the collection of collidable tiles, the resolution distance in the collection of resolution distances, and set the value in the boolean grid at this position. If the sprite isn't colliding with any tiles, continue to the next one.
 * 1) Create three boolean locals: a flag indicating if the sprite is colliding with a row, a flag indicating if the sprite is colliding with a column, and a flag indicating the last value checked in the loop below.
 * 2) Create two BoundingRectangle locals to represent the row and the column. These locals will be used in the calculation of the final resolution distance.
 * 3) Loop horizontally (left-to-right, top-to-bottom) across the boolean grid. If the current value is set AND the last value is set, set the row collision flag (unless it's already set, in which case the sprite is embedded, and the loop should go to the next sprite), clear the last flag, construct the row rectangle (see below), and start at the first cell of the next column. Else, set the last flag to the current value. At the end of every row, the last flag should be cleared.
 * 4) Loop vertically (top-to-bottom, left-to-right) across the boolean grid. If the current value is set AND the last value is set, set the column collision flag (unless it's already set, in which case the sprite is embedded, and the loop should go to the next sprite, construct the column rectangle (see below) and break out of the loop. Else, set the last flag to the current value. At the end of every column, the last flag should be cleared.

The last step in the first pass is to calculate the final resolution distance of the sprite and resolve it: The second pass will resolve collisions between the sprite and any sloped tiles it may be intersecting with.
 * 1) Create a Vector2 local to represent the final resolution distance.
 * 2) If the sprite is colliding with a row of tiles, calculate the resolution distance between the row rectangle and the sprite's hitbox. Set the Y component of the final resolution to the Y component of the resolution distance.
 * 3) If the sprite is colliding with a column of tiles, calculate the resolution distance between the column rectangle and the sprite's hitbox. Set the X component of the final resolution to the X component of the resolution distance.
 * 4) If the sprite is colliding with both a row and a column, by this point, both of the above conditions have executed, and the final resolution is constructured.
 * 5) If the sprite isn't colliding with a row or column, then it is colliding with a single tile, as the zero-intersection case wouldn't have got this far in execution. Retrieve the first element of the intersection collection and set the final resolution to that.
 * 6) If the final resolution is larger than the sprite's size on one axis, the sprite is considered embedded. Otherise, move the sprite by the distance of the final resolution.
 * 1) Recalcuate the grid cells of the corners of the sprite. Offset them by one grid cell again and retrieve the new subgrid of collidable tiles. Continue only if the subgrid's HasSlopedTiles property is true.
 * 2) Create a local field to keep track of the final resolution distance as the loop iterates through the tiles.
 * 3) Iterate over every grid cell in the subgrid. If the grid cell is empty (the reference is null), or if isn't a sloped tile, continue to the next tile.
 * 4) If the current tile is a sloped tile, calculate the collision resolution distance between the tile and the sprite. If the final resolution distance is zero, set the final resolution to the collision resolution. If the final resolution is already set in a direction consistent with the collision resolution, set the final resolution to the collision resolution if the collision resolution is greater. Finally, if the final resolution is already set in a direction opposite of the collision resolution, the sprite is embedded.
 * 5) Offset the sprite by the final resolution distance.