Tily.Cell = (function() {
"use strict";
/**
* A rectangular cell of tiles displayed in a cell buffer.
* @class
* @memberof Tily
* @param {Tily.CellBuffer} buffer The cell buffer that this cell belongs to.
*/
function Cell(buffer) {
/**
* The cell buffer that this cell belongs to.
* @type {Tily.CellBuffer}
*/
this.buffer = buffer;
/**
* An array of layers contained in this cell.
* @type {Tily.TilyLayer[]}
*/
this.layers = [];
}
/**
* Check if position p is inside the region between tl and br.
* @param {Tily.utility.vec2} p The position to check.
* @param {Tily.utility.vec2} tl The top-left corner of the region.
* @param {Tily.utility.vec2} br The bottom-right corner of the region.
* @returns {boolean} True if the position p is inside the region.
*/
function checkBounds(p, tl, br) {
return (p.x >= tl.x && p.x <= br.x && p.y >= tl.y && p.y <= br.y);
}
/**
* Add a layer to this cell at the specified z-index. If the z-index is undefined, add the
* layer on top of existing layers, and if the z-index is -1, add the layer below existing
* layers.
* @name addLayer
* @function
* @instance
* @memberof Tily.Cell
* @param {Tily.TileLayer} layer The layer to add.
* @param {number} [z] The z-index at which to add the layer. If this is -1, the layer will be
* added below existing layers and if it is undefined the layer will be added above existing
* layers.
*/
Cell.prototype.addLayer = function(layer, z) {
// If no layer is specified, create a new one
layer = layer || new Tily.TileLayer(this);
// Make sure the layer has a reference to this buffer
layer.container = this;
if (z === undefined) {
this.layers.push(layer);
} else if (z == -1) {
this.layers.unshift(layer);
} else {
this.layers.splice(z, 0, layer);
}
return layer;
};
/**
* Remove a layer at the specified z-index. If the z-index is undefined, remove the top layer
* and if the z-index is -1, remove the bottom layer. The removed layer is returned.
* @name removeLayer
* @function
* @instance
* @memberof Tily.Cell
* @param {number} [z] The z-index of the layer to remove. If this is -1, the bottom layer will
* be removed and if it is undefined the top layer will be removed.
* @returns {Tily.TileLayer} The layer that was removed.
*/
Cell.prototype.removeLayer = function(z) {
if (this.layers.length < 1) { return null; }
if (z === undefined) {
return this.layers.pop();
} else if (z == -1) {
return this.layers.shift();
}
return this.layers.splice(z, 1)[0];
};
/**
* Remove all layers from this cell.
* @name removeAllLayers
* @function
* @instance
* @memberof Tily.Cell
*/
Cell.prototype.removeAllLayers = function() {
this.layers = [];
};
/**
* Move a layer from one z-index to another z-index, either an absolute value or relative to
* the layer's current z-index.
* @name moveLayer
* @function
* @instance
* @memberof Tily.Cell
* @param {number} zFrom The z-index of the layer to move.
* @param {number} zTo The z-index to move the layer to.
* @param {boolean} relative If this is true, the layer will be moved relative to it's current
* z-index.
* @returns {boolean} True if a layer was moved successfully.
*/
Cell.prototype.moveLayer = function(zFrom, zTo, relative) {
if (this.layers.length < 2) { return false; }
if (zFrom < 0 || zFrom >= this.layers.length) { return false; }
const layer = this.layers.splice(zFrom, 1)[0],
toIndex = Tily.utility.clamp(relative ? zFrom + zTo : zTo, 0, this.layers.length);
this.layers.splice(toIndex, 0, layer);
return true;
};
/**
* @name size
* @description The size of this cell, as defined in the cell buffer options.
* @instance
* @memberof Tily.Cell
* @type {Size}
*/
Object.defineProperty(Cell.prototype, "size", {
get: function() {
return {
width: this.buffer.options.cellWidth,
height: this.buffer.options.cellHeight
};
}
});
/**
* Render this layer onto the specified context.
* @name draw
* @function
* @instance
* @memberof Tily.Cell
* @param {CanvasRenderingContext2D} context The context to render the layer onto.
* @param {number} elapsedTime The time elapsed in seconds since the last draw call.
* @param {number} x The cell x-coordinate.
* @param {number} y The cell y-coordinate.
* @param {number} tileSize The size of each tile measured in pixels.
* @param {Tily.utility.vec2} tl The top-left tile position currently in view.
* @param {Tily.utility.vec2} br The bottom-right tile position currently in view.
* @param {Tily.ActiveTile[]} activeTiles A list of active tiles currently in view, sorted by
* z-index (ascending).
*/
Cell.prototype.draw = function(context, elapsedTime, x, y, tileSize, tl, br, activeTiles) {
context.save();
// Translate to cell position
const size = this.size;
context.translate(x * tileSize * size.width, y * tileSize * size.height);
// Get a list of active tiles in this cell
const activeTilesCell = activeTiles.filter(i => checkBounds(
Tily.utility.vec2.add(i.position, i.offset),
Tily.utility.vec2(x * size.width, y * size.height),
Tily.utility.vec2((x + 1) * size.width, (y + 1) * size.height)
));
// Get tl and br in terms of cell offsets
const cellOffset = Tily.utility.vec2(x * size.width, y * size.height),
tlCell = Tily.utility.vec2.sub(tl, cellOffset),
brCell = Tily.utility.vec2.sub(br, cellOffset);
var j = 0;
for (let i = 0, length = this.layers.length; i < length; i++) {
this.layers[i].draw(context, tileSize, tlCell, brCell);
// Draw active tiles on or below this layer
while (j < activeTilesCell.length && activeTilesCell[j].zIndex < i + 1) {
activeTilesCell[j].draw(context, elapsedTime, tileSize);
j++;
}
}
// Draw any remaining active tiles in this cell (ie. on the top layer)
while (j < activeTilesCell.length) {
activeTilesCell[j].draw(context, elapsedTime, tileSize);
j++;
}
context.restore();
};
/**
* Get serializable data for this cell.
* @name getData
* @function
* @instance
* @memberof Tily.Cell
* @returns {Object} This cell's data.
*/
Cell.prototype.getData = function() {
return {
layers: this.layers.map(i => i.getData())
};
};
/**
* Create a cell from data.
* @name fromData
* @function
* @static
* @memberof Tily.Cell
* @param {Tily.CellBuffer} buffer The cell buffer that the cell belongs to.
* @param {Object} data Serialized cell data.
* @returns {Tily.Cell} A cell created from the provided data.
*/
Cell.fromData = function(buffer, data) {
const cell = new Tily.Cell(buffer);
cell.layers = data.layers.map(i => Tily.TileLayer.fromData(cell, i));
return cell;
};
/**
* Serialize this cell and return the serialized JSON data.
* @name serialize
* @function
* @instance
* @memberof Tily.Cell
* @returns {string} This cell serialized as JSON data.
*/
Cell.prototype.serialize = function() {
return JSON.stringify(this.getData());
};
/**
* Deserialize the JSON data into a cell.
* @name deserialize
* @function
* @static
* @memberof Tily.Cell
* @param {Tily.CellBuffer} buffer The cell buffer that the cell belongs to.
* @param {string} s The JSON data to deserialize.
* @returns {Tily.Cell} The deserialized cell.
*/
Cell.deserialize = function(buffer, s) {
var data = null;
try {
data = JSON.parse(s);
} catch (e) {
console.log("Couldn't deserialize data: %O", e);
return null;
}
return Tily.Cell.fromData(buffer, data);
};
return Cell;
}());