Tily.BufferBase = (function() {
"use strict";
/**
* @typedef BufferOptions
* @type {Object}
* @property {string} [lockedAxis="x"] Indicates which axis is used for scaling the tiles, the
* scale parameter will indicate how many tiles are visible on the locked axis. Should be
* either "x" or "y".
* @property {number} [initialOffsetX=0] The starting offset x-coordinate for this buffer.
* @property {number} [initialOffsetY=0] The starting offset y-coordinate for this buffer.
* @property {number} [initialScale=16] The starting scale, represents the number of visible
* tiles along the locked axis.
* @property {number} [maximumScale=32] The maximum number of tiles visible along the locked
* axis.
* @property {number} [minimumScale=4] The minimum number of tiles visible along the locked
* axis.
* @property {boolean} [clampCamera=false] Clamp the camera offset and zoom so that the buffer
* always fills the canvas.
*/
/**
* Default buffer options, used as a fall-back for options passed to the constructor.
* @type {BufferOptions}
*/
const _defaultBufferOptions = {
lockedAxis: "x",
initialOffsetX: 0,
initialOffsetY: 0,
initialScale: 16,
maximumScale: 32,
minimumScale: 4,
clampCamera: false
};
/**
* Implements basic functionality for buffers and cell buffers.
* @class
* @memberof Tily
* @param {BufferOptions} [options] An optional options object for configuring the buffer.
*/
function BufferBase(options) {
/**
* The canvas element to render this buffer onto.
* @type {HTMLElement}
*/
this.canvas = document.createElement("canvas");
/**
* The canvas context to render onto.
* @type {CanvasRenderingContext2D}
*/
this.context = this.canvas.getContext("2d");
/**
* An array of active tiles contained in this buffer.
* @type {Tily.ActiveTile[]}
*/
this.activeTiles = [];
/**
* A map of active tiles, with hashed tile positions as keys and an array of tiles at a
* position as values.
* @type {Object}
*/
this.activeTilesMap = {};
/**
* Options for configuring this buffer.
* @type {BufferOptions}
*/
this.options = { ..._defaultBufferOptions, ...options || {} };
/**
* A camera offset position for this buffer measured in pixels.
* @type {Tily.utility.vec2}
*/
this.offset = Tily.utility.vec2(this.options.initialOffsetX, this.options.initialOffsetY);
/**
* The currently running offset transition or null if there is no transition currently
* running.
* @default null
* @type {?Tily.OffsetTransition}
*/
this.offsetTransition = null;
/**
* The number of tiles currently visible along the locked axis.
* @type {number}
*/
this.scale = this.options.initialScale;
/**
* The currently running scale transition or null if there is no transition currently
* running.
* @default null
* @type {?Tily.ScaleTransition}
*/
this.scaleTransition = null;
/**
* The size of this buffer measured in tiles.
* @type {Size}
*/
this.size = { width: 0, height: 0 };
/**
* The side length of each tile measured in pixels.
* @type {number}
*/
this.tileSize = 0;
/**
* The size of the viewport measured in tiles.
* @type {Size}
*/
this.viewSize = { width: 0, height: 0 };
}
/**
* 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);
}
/**
* Get a string representation of the specified position for use as a hash.
* @param {Tily.utility.vec2} p The position to hash.
* @returns {string} A hash string for the specified position.
*/
function hash(p) {
return Tily.utility.vec2.toString(p, "_");
}
/**
* Add an active tile or multiple active tiles to this buffer.
* @name addActiveTile
* @function
* @instance
* @memberof Tily.BufferBase
* @param {...Tily.ActiveTile} tiles The tile(s) to add.
* @returns {Tily.ActiveTile|array} The tile(s) that were added
*/
BufferBase.prototype.addActiveTile = function(...tiles) {
this.activeTiles.push(...tiles);
return tiles.length == 1 ? tiles[0] : tiles;
};
/**
* Remove an active tile from this buffer.
* @name removeActiveTile
* @function
* @instance
* @memberof Tily.BufferBase
* @param {Tily.ActiveTile} tile The tile to remove.
*/
BufferBase.prototype.removeActiveTile = function(tile) {
tile.destroyed = true;
};
/**
* Remove all active tiles from this buffer.
* @name removeAllActiveTiles
* @function
* @instance
* @memberof Tily.BufferBase
*/
BufferBase.prototype.removeAllActiveTiles = function() {
this.activeTiles = [];
};
/**
* @typedef MoveOffsetTransitionOptions
* @type {TransitionOptions}
* @property {string} [unit=""] The unit of measurement for the new offset. If this is set to
* 'px', the unit will be pixels and if it is set to anything else the unit will be tiles.
* @property {boolean} [relative=false] True if the movement should be relative to the current
* offset.
*/
/**
* Move the offset with an optional transition animation.
* @name moveOffset
* @function
* @instance
* @memberof Tily.BufferBase
* @param {number} x The x-coordinate of the target offset position.
* @param {number} y The y-coordinate of the target offset position.
* @param {MoveOffsetTransitionOptions} [options] An optional options object.
*/
BufferBase.prototype.moveOffset = function(x, y, options) {
options = {
unit: "",
relative: false,
...options
};
var offset = Tily.utility.vec2(x, y);
if (options && options.unit == "px") { // Adjust the offset position if moving in pixels
offset = Tily.utility.vec2.div(offset, this.tileSize);
}
if (this.offsetTransition) {
this.offset = this.offsetTransition.update(0);
}
if (options && options.relative === true) { // Add the current position if moving relatively
offset = Tily.utility.vec2.add(this.offset, offset);
}
const transition = new Tily.OffsetTransition(Tily.utility.vec2(this.offset), Tily.utility.vec2(offset), options);
this.offsetTransition = transition;
this.offset = offset;
return new Promise(function(resolve, reject) { transition.finishedCallback = resolve; });
};
/**
* @name offsetPixels
* @description The offset of this buffer measured in pixels, as a Tily.utility.vec2 object.
* @instance
* @memberof Tily.BufferBase
* @type {Tily.utility.vec2}
*/
Object.defineProperty(BufferBase.prototype, "offsetPixels", {
get: function() {
return Tily.utility.vec2.mul(this.offset, this.tileSize);
}
});
/**
* Zoom the scale with an optional transition animation.
* @name zoom
* @function
* @instance
* @memberof Tily.BufferBase
* @param {number} scale The target scale.
* @param {TransitionOptions} [options] An optional options object.
*/
BufferBase.prototype.zoom = function(scale, options) {
if (this.scaleTransition) {
this.scale = this.scaleTransition.update(0);
}
const transition = new Tily.ScaleTransition(this.scale, scale, options);
this.scaleTransition = transition;
this.scale = scale;
return new Promise(function(resolve, reject) { transition.finishedCallback = resolve; });
};
/**
* Return the tile position for the specified pixel position, based on the current offset and
* scale.
* @name getPosition
* @function
* @instance
* @memberof Tily.BufferBase
* @param {number} x The x-coordinate of the pixel position.
* @param {number} y The y-coordinate of the pixel position.
* @returns {Tily.utility.vec2} The tile position currently at the specified pixel position.
*/
BufferBase.prototype.getPosition = function(x, y) {
const tl = Tily.utility.vec2.sub(
Tily.utility.vec2.add(this.offset, 0.5),
Tily.utility.vec2.div(Tily.utility.vec2(this.viewSize.width, this.viewSize.height), 2)
);
return Tily.utility.vec2.map(Tily.utility.vec2.add(tl, Tily.utility.vec2.div(Tily.utility.vec2(x, y), this.tileSize)), Math.floor);
};
/**
* @typedef BufferBaseTileInfo
* @type {Object}
* @property {Tily.utility.vec2} position The tile position.
* @property {ActiveTile[]} activeTiles A list of active tiles at a tile position.
*/
/**
* Get information about the active tiles at a tile position.
* @name getTileInfo
* @function
* @instance
* @memberof Tily.BufferBase
* @param {number} x The x-coordinate of the tile position.
* @param {number} y The y-coordinate of the tile position.
* @returns {BufferBaseTileInfo} Information about the tiles at the specified position.
*/
BufferBase.prototype.getTileInfo = function(x, y) {
const p = Tily.utility.vec2(x, y);
return {
position: p,
activeTiles: this.activeTilesMap[hash(p)] || []
};
};
/**
* Update offset and scale transitions.
* @name updateTransitions
* @function
* @instance
* @memberof Tily.BufferBase
* @param {number} elapsedTime The time elapsed in seconds since the last draw call.
* @returns {Tily.utility.vec2} The interpolated offset position.
*/
BufferBase.prototype.updateTransitions = function(elapsedTime) {
// Update offset transition
var offset = this.offset;
if (this.offsetTransition) {
offset = this.offsetTransition.update(elapsedTime);
if (this.offsetTransition.finished) { // Remove the transition when it has finished
this.offsetTransition = null;
}
}
// Update scale transition
if (this.scaleTransition) {
this.scale = this.scaleTransition.update(elapsedTime);
if (this.scaleTransition.finished) { // Remove the transition when it has finished
this.scaleTransition = null;
}
}
return offset;
};
/**
* Update the active tiles map and get a list of active tiles currently in view.
* @name updateActiveTilesMap
* @function
* @instance
* @memberof Tily.BufferBase
* @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.
* @returns {Tily.ActiveTile[]} A list of active tiles current in view, sorted by z-index.
*/
BufferBase.prototype.updateActiveTilesMap = function(tl, br) {
const activeTiles = [];
var h = null;
this.activeTilesMap = {};
// Remove destroyed active tiles
this.activeTiles = this.activeTiles.filter(i => !i.destroyed);
// Get active tiles currently in view
for (let i = 0, length = this.activeTiles.length; i < length; i++) {
if (checkBounds(
Tily.utility.vec2.add(this.activeTiles[i].position, this.activeTiles[i].offset),
tl, br
)) {
activeTiles.push(this.activeTiles[i]);
}
// Update the active tiles map
h = hash(this.activeTiles[i].position);
if (this.activeTilesMap[h] === undefined) {
this.activeTilesMap[h] = [];
}
this.activeTilesMap[h].push(this.activeTiles[i]);
}
activeTiles.sort((a, b) => a.zIndex - b.zIndex);
return activeTiles;
};
return BufferBase;
}());