package net.flashpunk.masks
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;
import net.flashpunk.*;
/**
* Uses a hash grid to determine collision, faster than
* using hundreds of Entities for tiled levels, etc.
*/
public class Grid extends Hitbox
{
/**
* If x/y positions should be used instead of columns/rows.
*/
public var usePositions:Boolean;
/**
* Constructor.
* @param width Width of the grid, in pixels.
* @param height Height of the grid, in pixels.
* @param tileWidth Width of a grid tile, in pixels.
* @param tileHeight Height of a grid tile, in pixels.
* @param x X offset of the grid.
* @param y Y offset of the grid.
*/
public function Grid(width:uint, height:uint, tileWidth:uint, tileHeight:uint, x:int = 0, y:int = 0)
{
if (!width || !height || !tileWidth || !tileHeight) throw new Error("Illegal Grid, sizes cannot be 0.");
_columns = width / tileWidth;
_rows = height / tileHeight;
_data = new BitmapData(_columns, _rows, true, 0);
_tile = new Rectangle(0, 0, tileWidth, tileHeight);
_x = x;
_y = y;
_width = width;
_height = height;
_check[Mask] = collideMask;
_check[Hitbox] = collideHitbox;
_check[Pixelmask] = collidePixelmask;
}
/**
* Sets the value of the tile.
* @param column Tile column.
* @param row Tile row.
* @param solid If the tile should be solid.
*/
public function setTile(column:uint = 0, row:uint = 0, solid:Boolean = true):void
{
if (usePositions)
{
column /= _tile.width;
row /= _tile.height;
}
_data.setPixel32(column, row, solid ? 0xFFFFFFFF : 0);
}
/**
* Makes the tile non-solid.
* @param column Tile column.
* @param row Tile row.
*/
public function clearTile(column:uint = 0, row:uint = 0):void
{
setTile(column, row, false);
}
/**
* Gets the value of a tile.
* @param column Tile column.
* @param row Tile row.
* @return tile value.
*/
public function getTile(column:uint = 0, row:uint = 0):Boolean
{
if (usePositions)
{
column /= _tile.width;
row /= _tile.height;
}
return _data.getPixel32(column, row) > 0;
}
/**
* Sets the value of a rectangle region of tiles.
* @param column First column.
* @param row First row.
* @param width Columns to fill.
* @param height Rows to fill.
* @param fill Value to fill.
*/
public function setRect(column:uint = 0, row:uint = 0, width:int = 1, height:int = 1, solid:Boolean = true):void
{
if (usePositions)
{
column /= _tile.width;
row /= _tile.height;
width /= _tile.width;
height /= _tile.height;
}
_rect.x = column;
_rect.y = row;
_rect.width = width;
_rect.height = height;
_data.fillRect(_rect, solid ? 0xFFFFFFFF : 0);
}
/**
* Makes the rectangular region of tiles non-solid.
* @param column First column.
* @param row First row.
* @param width Columns to fill.
* @param height Rows to fill.
*/
public function clearRect(column:uint = 0, row:uint = 0, width:int = 1, height:int = 1):void
{
setRect(column, row, width, height, false);
}
/**
* Loads the grid data from a string.
* @param str The string data, which is a set of tile values (0 or 1) separated by the columnSep and rowSep strings.
* @param columnSep The string that separates each tile value on a row, default is ",".
* @param rowSep The string that separates each row of tiles, default is "\n".
*/
public function loadFromString(str:String, columnSep:String = ",", rowSep:String = "\n"):void
{
var row:Array = str.split(rowSep),
rows:int = row.length,
col:Array, cols:int, x:int, y:int;
for (y = 0; y < rows; y ++)
{
if (row[y] == '') continue;
col = row[y].split(columnSep),
cols = col.length;
for (x = 0; x < cols; x ++)
{
if (col[x] == '') continue;
setTile(x, y, uint(col[x]) > 0);
}
}
}
/**
* Saves the grid data to a string.
* @param columnSep The string that separates each tile value on a row, default is ",".
* @param rowSep The string that separates each row of tiles, default is "\n".
*/
public function saveToString(columnSep:String = ",", rowSep:String = "\n"): String
{
var s:String = '',
x:int, y:int;
for (y = 0; y < _rows; y ++)
{
for (x = 0; x < _columns; x ++)
{
s += String(getTile(x, y));
if (x != _columns - 1) s += columnSep;
}
if (y != _rows - 1) s += rowSep;
}
return s;
}
/**
* The tile width.
*/
public function get tileWidth():uint { return _tile.width; }
/**
* The tile height.
*/
public function get tileHeight():uint { return _tile.height; }
/**
* How many columns the grid has
*/
public function get columns():uint { return _columns; }
/**
* How many rows the grid has.
*/
public function get rows():uint { return _rows; }
/**
* The grid data.
*/
public function get data():BitmapData { return _data; }
/** @private Collides against an Entity. */
private function collideMask(other:Mask):Boolean
{
_rect.x = other.parent.x - other.parent.originX - parent.x + parent.originX;
_rect.y = other.parent.y - other.parent.originY - parent.y + parent.originY;
_point.x = int((_rect.x + other.parent.width - 1) / _tile.width) + 1;
_point.y = int((_rect.y + other.parent.height -1) / _tile.height) + 1;
_rect.x = int(_rect.x / _tile.width);
_rect.y = int(_rect.y / _tile.height);
_rect.width = _point.x - _rect.x;
_rect.height = _point.y - _rect.y;
return _data.hitTest(FP.zero, 1, _rect);
}
/** @private Collides against a Hitbox. */
private function collideHitbox(other:Hitbox):Boolean
{
_rect.x = other.parent.x + other._x - parent.x - _x;
_rect.y = other.parent.y + other._y - parent.y - _y;
_point.x = int((_rect.x + other._width - 1) / _tile.width) + 1;
_point.y = int((_rect.y + other._height -1) / _tile.height) + 1;
_rect.x = int(_rect.x / _tile.width);
_rect.y = int(_rect.y / _tile.height);
_rect.width = _point.x - _rect.x;
_rect.height = _point.y - _rect.y;
return _data.hitTest(FP.zero, 1, _rect);
}
/** @private Collides against a Pixelmask. */
private function collidePixelmask(other:Pixelmask):Boolean
{
var x1:int = other.parent.x + other._x - parent.x - _x,
y1:int = other.parent.y + other._y - parent.y - _y,
x2:int = ((x1 + other._width - 1) / _tile.width),
y2:int = ((y1 + other._height - 1) / _tile.height);
_point.x = x1;
_point.y = y1;
x1 /= _tile.width;
y1 /= _tile.height;
_tile.x = x1 * _tile.width;
_tile.y = y1 * _tile.height;
var xx:int = x1;
while (y1 <= y2)
{
while (x1 <= x2)
{
if (_data.getPixel32(x1, y1))
{
if (other._data.hitTest(_point, 1, _tile)) return true;
}
x1 ++;
_tile.x += _tile.width;
}
x1 = xx;
y1 ++;
_tile.x = x1 * _tile.width;
_tile.y += _tile.height;
}
return false;
}
/** @private */ private var _data:BitmapData;
/** @private */ private var _columns:uint;
/** @private */ private var _rows:uint;
/** @private */ private var _tile:Rectangle;
/** @private */ private var _rect:Rectangle = FP.rect;
/** @private */ private var _point:Point = FP.point;
/** @private */ private var _point2:Point = FP.point2;
}
}