import AABB from '../AABB.js';
import CollisionShape from '../CollisionShape.js';
import Data from '../Data.js';
import {arrayCache} from '../utils/array.js';
import createComponentClass from '../factory.js';
export default (function () {
var maskJumpThrough = 0x10000000,
maskRotation = 0x20000000,
maskXFlip = 0x80000000,
maskYFlip = 0x40000000,
maskIndex = 0x0fffffff,
getDefaultType = function () {
return this.collisionType;
},
getCollisionType = function (index) {
return this.collisionTypeMap[index & maskIndex] || this.collisionType;
},
flipDiagonal = function (num) {
if (num === 0) {
return num;
} else {
return num ^ maskYFlip ^ maskRotation;
}
},
flipDiagonalInverse = function (num) {
if (num === 0) {
return num;
} else {
return num ^ maskXFlip ^ maskRotation;
}
},
flipX = function (num) {
if (num === 0) {
return num;
} else {
return num ^ maskXFlip;
}
},
flipY = function (num) {
if (num === 0) {
return num;
} else {
return num ^ maskYFlip;
}
},
rotate90 = function (num) {
if (num === 0) {
return num;
} else if (maskRotation & num) {
return num ^ maskYFlip ^ maskRotation;
} else {
return num ^ maskXFlip ^ maskRotation;
}
},
rotate180 = function (num) {
if (num === 0) {
return num;
} else {
return num ^ maskXFlip ^ maskYFlip;
}
},
rotate270 = function (num) {
if (num === 0) {
return num;
} else if (maskRotation & num) {
return num ^ maskXFlip ^ maskRotation;
} else {
return num ^ maskYFlip ^ maskRotation;
}
},
copySection = function (array, originX, originY, width, height) {
var x = 0,
y = 0,
arr = arrayCache.setUp();
for (y = 0; y < height; y++) {
arr[y] = arrayCache.setUp();
for (x = 0; x < width; x++) {
arr[y][x] = array[originX + x][originY + y];
}
}
return arr;
},
cutSection = function (array, originX, originY, width, height) {
var x = 0,
y = 0,
arr = arrayCache.setUp();
for (y = 0; y < height; y++) {
arr[y] = arrayCache.setUp();
for (x = 0; x < width; x++) {
arr[y][x] = array[originX + x][originY + y];
array[originX + x][originY + y] = -1;
}
}
return arr;
},
pasteSection = function (destinationArray, sourceArray, originX, originY, width, height) {
var x = 0,
y = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
destinationArray[originX + x][originY + y] = sourceArray[y][x];
}
}
return destinationArray;
},
transforms = {
"diagonal": function (array, originX, originY, width, height) {
var arr = copySection(array, originX, originY, width, height),
fD = flipDiagonal,
x = 0,
y = 0;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
array[originX + x][originY + y] = fD(arr[x][y]);
}
}
arrayCache.recycle(arr, 2);
return array;
},
"diagonal-inverse": function (array, originX, originY, width, height) {
var arr = copySection(array, originX, originY, width, height),
fDI = flipDiagonalInverse,
x = 0,
y = 0;
for (x = 0; x < width; x++) {
for (y = 0; y < height; y++) {
array[originX + width - x - 1][originY + height - y - 1] = fDI(arr[x][y]);
}
}
arrayCache.recycle(arr, 2);
return array;
},
"horizontal": function (array, originX, originY, width, height) {
var arr = copySection(array, originX, originY, width, height),
fX = flipX,
x = 0,
y = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
array[originX + width - x - 1][originY + y] = fX(arr[y][x]);
}
}
arrayCache.recycle(arr, 2);
return array;
},
"vertical": function (array, originX, originY, width, height) {
var arr = copySection(array, originX, originY, width, height),
fY = flipY,
x = 0,
y = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
array[originX + x][originY + height - y - 1] = fY(arr[y][x]);
}
}
arrayCache.recycle(arr, 2);
return array;
},
"rotate-90": function (array, originX, originY, width, height) {
var arr = copySection(array, originX, originY, width, height),
r90 = rotate90,
x = 0,
y = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
array[originX + height - y - 1][originY + x] = r90(arr[y][x]);
}
}
arrayCache.recycle(arr, 2);
return array;
},
"rotate-180": function (array, originX, originY, width, height) {
var arr = copySection(array, originX, originY, width, height),
r180 = rotate180,
x = 0,
y = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
array[originX + width - x - 1][originY + height - y - 1] = r180(arr[y][x]);
}
}
arrayCache.recycle(arr, 2);
return array;
},
"rotate-270": function (array, originX, originY, width, height) {
var arr = copySection(array, originX, originY, width, height),
r270 = rotate270,
x = 0,
y = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
array[originX + y][originY + width - x - 1] = r270(arr[y][x]);
}
}
arrayCache.recycle(arr, 2);
return array;
},
"translate": function (array, originX, originY, width, height, dx, dy) {
var arr = cutSection(array, originX, originY, width, height),
x = 0,
y = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
array[originX + x + dx][originY + y + dy] = arr[y][x];
}
}
arrayCache.recycle(arr, 2);
return array;
}
};
return createComponentClass(/** @lends platypus.components.CollisionTiles.prototype */{
id: 'CollisionTiles',
properties: {
/**
* Maps tile indexes to particular collision types. This defaults to a "tiles" collision type for all non-zero values if a particular collision map is not provided.
*
* @property collisionTypeMap
* @type Object
* @default null
*/
collisionTypeMap: null,
/**
* Sets the default collision type for non-zero map tiles.
*
* @property collisionType
* @type String
* @default "tiles"
*/
collisionType: 'tiles',
/**
* The map's top offset.
*
* @property top
* @type Number
* @default 0
*/
top: 0,
/**
* The map's left offset.
*
* @property left
* @type Number
* @default 0
*/
left: 0
},
publicProperties: {
/**
* A 2D array describing the tile-map with off (0) and on (!0) states. The indexes match Tiled map data indexes with an additional bit setting (0x2000000) for jumpthrough tiles. Example: `[[0, 0, 0], [1, 0, 0], [1, 1, 1]]`. Available on the entity as `entity.collisionMap`.
*
* @property collisionMap
* @type Array
* @default []
*/
collisionMap: [],
/**
* The width of tiles in world coordinates. Available on the entity as `entity.tileWidth`.
*
* @property tileWidth
* @type number
* @default 10
*/
tileWidth: 10,
/**
* The height of tiles in world coordinates. Available on the entity as `entity.tileHeight`.
*
* @property tileWidth
* @type number
* @default 10
*/
tileHeight: 10
},
/**
* This component causes the tile-map to collide with other entities. It must be part of a collision group and will cause "hit-by-tile" messages to fire on colliding entities.
*
* @memberof platypus.components
* @uses platypus.Component
* @constructs
* @listens platypus.Entity#transform
* @listens platypus.Entity#translate
*/
initialize: function () {
this.tileOffsetLeft = this.tileWidth / 2 + this.left;
this.tileOffsetTop = this.tileHeight / 2 + this.top;
this.columns = this.collisionMap.length;
this.rows = this.collisionMap[0].length;
this.shapeDefinition = Data.setUp(
"x", 0,
"y", 0,
"type", 'rectangle',
"width", this.tileWidth,
"height", this.tileHeight
);
this.storedTiles = arrayCache.setUp();
this.serveTiles = arrayCache.setUp();
this.storedTileIndex = 0;
this.aabb = AABB.setUp();
this.aabb.setBounds(this.left, this.top, this.tileWidth * this.columns + this.left, this.tileHeight * this.rows + this.top);
if (this.collisionTypeMap) {
this.getType = getCollisionType;
} else {
this.getType = getDefaultType;
}
},
events: {
"transform": function (transform) {
this.transform(transform);
},
"translate": function (translate) {
this.translate(translate);
}
},
methods: {
getShape: function (x, y, type) {
var i = this.storedTileIndex,
shape = null,
storedTiles = this.storedTiles;
if (i === storedTiles.length) {
shape = CollisionShape.setUp(null, this.shapeDefinition, type);
storedTiles.push(shape);
} else {
shape = storedTiles[i];
shape.collisionType = type;
}
shape.update(x * this.tileWidth + this.tileOffsetLeft, y * this.tileHeight + this.tileOffsetTop);
this.storedTileIndex += 1;
return shape;
},
addShape: function (shapes, prevAABB, x, y, collisionType) {
var xy = this.collisionMap[x][y],
index = xy & maskIndex,
jumpThrough = maskJumpThrough,
rotation = maskRotation,
xFlip = maskXFlip,
yFlip = maskYFlip;
if (xy && (this.getType(index) === collisionType)) {
jumpThrough &= xy;
if (jumpThrough) {
rotation &= xy;
xFlip &= xy;
yFlip &= xy;
if (rotation && xFlip) { // Right
if (prevAABB.left >= (x + 1) * this.tileWidth + this.left) {
shapes.push(this.getShape(x, y, collisionType));
}
} else if (rotation) { // Left
if (prevAABB.right <= x * this.tileWidth + this.left) {
shapes.push(this.getShape(x, y, collisionType));
}
} else if (yFlip) { // Bottom
if (prevAABB.top >= (y + 1) * this.tileHeight + this.top) {
shapes.push(this.getShape(x, y, collisionType));
}
} else if (prevAABB.bottom <= y * this.tileHeight + this.top) { // Top
shapes.push(this.getShape(x, y, collisionType));
}
} else {
shapes.push(this.getShape(x, y, collisionType));
}
}
return shapes;
},
destroy: function () {
var store = this.storedTiles,
i = store.length;
this.shapeDefinition.recycle();
this.shapeDefinition = null;
while (i--) {
store[i].recycle();
}
arrayCache.recycle(store);
this.storedTiles = null;
arrayCache.recycle(this.serveTiles);
this.serveTiles = null;
this.aabb.recycle();
this.aabb = null;
}
},
publicMethods: {
/**
* Returns the axis-aligned bounding box of the entire map.
*
* @method platypus.components.CollisionTiles#getAABB
* @return aabb {platypus.AABB} The returned object provides the top, left, width, and height of the collision map.
*/
getAABB: function () {
return this.aabb;
},
/**
* Confirms whether a particular map grid coordinate contains a tile.
*
* @method platypus.components.CollisionTiles#isTile
* @param x {number} Integer specifying the column of tiles in the collision map to check.
* @param y {number} Integer specifying the row of tiles in the collision map to check.
* @return {boolean} Returns `true` if the coordinate contains a collision tile, `false` if it does not.
*/
isTile: function (x, y) {
return !((x < 0) || (y < 0) || (x >= this.columns) || (y >= this.rows) || (this.collisionMap[x][y] === -1));
},
/**
* Returns all the collision tiles within the provided axis-aligned bounding box as an array of shapes.
*
* @method platypus.components.CollisionTiles#getTileShapes
* @param aabb {platypus.AABB} The axis-aligned bounding box for which tiles should be returned.
* @param prevAABB {platypus.AABB} The axis-aligned bounding box for a previous location to test for jump-through tiles.
* @param [collisionType] {String} The type of collision to check for. If not specified, "tiles" is used. (Since 0.8.3)
* @return {Array} Each returned object provides the [CollisionShape](CollisionShape.html) of a tile.
*/
getTileShapes: function (aabb, prevAABB, collisionType) {
var colType = collisionType || 'tiles',
l = this.left,
t = this.top,
th = this.tileHeight,
tw = this.tileWidth,
left = Math.max(Math.floor((aabb.left - l) / tw), 0),
top = Math.max(Math.floor((aabb.top - t) / th), 0),
right = Math.min(Math.ceil((aabb.right - l) / tw), this.columns),
bottom = Math.min(Math.ceil((aabb.bottom - t) / th), this.rows),
x = 0,
y = 0,
shapes = this.serveTiles;
shapes.length = 0;
this.storedTileIndex = 0;
for (x = left; x < right; x++) {
for (y = top; y < bottom; y++) {
this.addShape(shapes, prevAABB, x, y, colType);
}
}
return shapes;
},
/**
* Performs a transform of a subset of the collision tile grid.
*
* @method platypus.components.CollisionTiles#transform
* @param [transform] {Object} A list of key/value pairs describing the transform.
* @param [transform.type="horizontal"] {String} The type of transform; one of the following: "horizontal", "vertical", "diagonal", "diagonal-inverse", "rotate-90", "rotate-180", "rotate-270". Height and width should match for diagonal flips and 90 degree rotations.
* @param [transform.left=0] {number} Grid coordinate for the left side of the bounding box.
* @param [transform.top=0] {number} Grid coordinate for the top of the bounding box.
* @param [transform.width=grid.width] {number} Cell width of the bounding box.
* @param [transform.height=grid.height] {number} Cell height of the bounding box.
*/
transform: function (transform) {
var t = transform || {},
x = t.left || 0,
y = t.top || 0,
width = t.width || this.rows,
height = t.height || this.columns,
type = t.type || "horizontal";
if (transforms[type]) {
return transforms[type](this.collisionMap, x, y, width, height);
} else {
return null;
}
},
/**
* Performs a translation of a subset of the collision tile grid.
*
* @method platypus.components.CollisionTiles#translate
* @param [translate] {Object} A list of key/value pairs describing the translation.
* @param [translate.dx=0] {number} Movement in columns.
* @param [translate.dy=0] {number} Movement in rows.
* @param [translate.left=0] {number} Grid coordinate for the left side of the bounding box.
* @param [translate.top=0] {number} Grid coordinate for the top of the bounding box.
* @param [translate.width=grid.width] {number} Cell width of the bounding box.
* @param [translate.height=grid.height] {number} Cell height of the bounding box.
*/
translate: function (translate) {
var t = translate || {},
x = t.left || 0,
y = t.top || 0,
width = t.width || this.rows,
height = t.height || this.columns,
dx = t.dx || 0,
dy = t.dy || 0;
return transforms.translate(this.collisionMap, x, y, width, height, dx, dy);
},
/**
* Gets a subset of the collision tile grid as a 2D array.
*
* @method platypus.components.CollisionTiles#getCollisionMatrix
* @param originX {number} Grid coordinate for the left side of the bounding box.
* @param originY {number} Grid coordinate for the top of the bounding box.
* @param width {number} Cell width of the bounding box.
* @param height {number} Cell height of the bounding box.
* @return {Array}
*/
getCollisionMatrix: function (originX, originY, width, height) {
return copySection(this.collisionMap, originX, originY, width, height);
},
/**
* Sets a subset of the collision tile grid.
*
* @method platypus.components.CollisionTiles#setCollisionMatrix
* @param sourceArray {Array} A 2D array describing the collision tiles to insert into the collision tile grid.
* @param originX {number} Grid coordinate for the left side of the bounding box.
* @param originY {number} Grid coordinate for the top of the bounding box.
* @param width {number} Cell width of the bounding box.
* @param height {number} Cell height of the bounding box.
*/
setCollisionMatrix: function (sourceArray, originX, originY, width, height) {
return pasteSection(this.collisionMap, sourceArray, originX, originY, width, height);
}
}
});
}());