import isInteger from 'lodash/isInteger';
const flagValues = [];
let f = 1;
for ( let i = 0; i < 16; i += 1 ) {
flagValues.push( f );
f = f * 2;
}
/**
* Holds all of the tile data.
*/
class TileData {
constructor() {
/**
* The size of each tile in pixels.
* Used for both width and height.
*/
this.tileSize = 16;
/**
* Array of tileset data.
* All tiledata is added to this.data when init is called.
* More tilesets can not be added after this.
*/
this.tilesets = [];
/**
* All of the tile data in a single Uint8ClampedArray.
* This is whats used by Screen to draw tiles.
*/
this.data = null;
/**
* All of the flag data in a single Uint16Array.
* Each item is a flag value for each tile.
*/
this.flagData = null;
}
/**
* Parse all of the tilesets and add them to the data array
*/
init() {
let numberOfTiles = 0;
for ( let i = 0; i < this.tilesets.length; i += 1 ) {
const currentTileset = this.tilesets[i];
numberOfTiles += currentTileset.width * currentTileset.height;
}
this.data = new Uint8ClampedArray( numberOfTiles * this.tileSize * this.tileSize );
this.flagData = new Uint16Array( numberOfTiles + 1 );
this.flagData.fill( 0 );
let startPosition = 0;
let firstGid = 1;
for ( let i = 0; i < this.tilesets.length; i += 1 ) {
const currentTileset = this.tilesets[i];
currentTileset.firstGid = firstGid;
const { data } = currentTileset;
if ( currentTileset.format === 'array' ) {
for ( let j = 0; j < data.length; j += 1 ) {
this.data[startPosition + j] = parseInt( data[j], 10 );
}
}
else if ( currentTileset.format === 'run' ) {
let runPosition = 0;
let dataPosition = 0;
while ( runPosition < data.length ) {
const runLength = data[runPosition];
const paletteId = parseInt( data[runPosition + 1], 10 );
for ( let j = 0; j < runLength; j += 1 ) {
this.data[startPosition + dataPosition] = paletteId;
dataPosition += 1;
}
runPosition += 2;
}
}
const { flags } = currentTileset;
for ( let j = 0; j < flags.length; j += 1 ) {
this.flagData[firstGid + j] = parseInt( flags[j], 10 );
}
firstGid += currentTileset.width * currentTileset.height;
startPosition += currentTileset.width * currentTileset.height * this.tileSize * this.tileSize;
// data is no longer needed in the current tileset as it has been added to this.data.
delete currentTileset.data;
// flags are no longer needed
delete currentTileset.flags;
}
}
/**
* Add a tileset.
* All tilesets must be added before init is called.
* @param {Object} tileset - the tileset data
*/
addTileset( tileset ) {
this.tilesets.push( tileset );
}
/**
* Get the GID for a tile
* @param {number} x - x position of the tile
* @param {number} y - y position of the tile
* @param {number} tileset - the index of the tileset
*/
getGid( x, y, tileset = 0 ) {
if ( x < 0 || y < 0 ) {
return -1;
}
const currentTileset = this.tilesets[tileset];
const { width, height, firstGid } = currentTileset;
if ( x >= width || y >= height ) {
return -1;
}
return y * width + x + firstGid;
}
/**
* Get the tileset info for a given gid
* @param {number} gid - the gid of the tile
*/
getTilesetInfoForGid( gid ) {
if ( !this.tilesets || this.tilesets.length === 0 || !gid ) {
return null;
}
let tilesetIndex = 0;
for ( let i = 0; i < this.tilesets.length; i += 1 ) {
const tileset = this.tilesets[i];
if ( tileset.firstGid <= gid ) {
tilesetIndex = i;
}
else {
break;
}
}
return {
firstGid: this.tilesets[tilesetIndex].firstGid,
width: this.tilesets[tilesetIndex].width,
height: this.tilesets[tilesetIndex].height,
index: tilesetIndex,
};
}
/**
* Get the array data for a tile section
* @param {number} gid - the gid of the bottom left tile
* @param {number} tileWidth - the width in tiles of the tile section
* @param {number} tileHeight - the height in tiles of the tile section
*/
getTileSectionData( gid, tileWidth = 1, tileHeight = 1 ) {
const tilesetInfo = this.getTilesetInfoForGid( gid );
if ( !tilesetInfo ) {
return null;
}
const { tileSize } = this;
const localGid = gid - tilesetInfo.firstGid;
const localY = Math.floor( localGid / tilesetInfo.width );
const localX = localGid - localY * tilesetInfo.width;
const data = new Uint8ClampedArray( tileWidth * tileHeight * tileSize * tileSize );
const width = tileWidth * tileSize;
const height = tileHeight * tileSize;
for ( let tileY = 0; tileY < tileHeight; tileY += 1 ) {
for ( let tileX = 0; tileX < tileWidth; tileX += 1 ) {
if ( tileX + localX >= tilesetInfo.width || tileY + localY >= tilesetInfo.height ) {
// tile is outside of tileset
break;
}
const tileGid = localGid + tileY * tilesetInfo.width + tileX + tilesetInfo.firstGid;
const basePosition = ( tileGid - 1 ) * tileSize * tileSize;
for ( let y = 0; y < tileSize; y += 1 ) {
for ( let x = 0; x < tileSize; x += 1 ) {
const paletteId = this.data[basePosition + y * tileSize + x];
const targetX = tileX * tileSize + x;
const targetY = tileY * tileSize + y;
data[targetY * width + targetX] = paletteId;
}
}
}
}
return { data, width, height };
}
/**
* Get flag for a gid. If the flagNumber is not specified or negative, returns the bit field for a gid
* @param {number} gid - the gid of the tile
* @param {number} flagNumber - the flag number, 0 - 15. If negative the number of the bit field will be returned
*/
getFlag( gid, flagNumber = -1 ) {
if ( gid < 0 || gid >= this.flagData.length ) {
return 0;
}
const gidFlags = this.flagData[gid];
if ( flagNumber < 0 || flagNumber >= 16 ) {
return gidFlags;
}
if ( flagValues[flagNumber] & gidFlags ) {
return true;
}
return false;
}
/**
* Set flag for a gid. If the flagNumber is negative, set the bit field for a gid
* @param {number} gid - the gid of the tile
* @param {number} flagNumber - the flag number, 0 - 15. Set negative to set the entire bit field for the flags
* @param {number | boolean} value - true or false if setting a flag, number if setting the bit field
*/
setFlag( gid, flagNumber, value ) {
if ( gid < 0 || gid >= this.flagData.length ) {
return;
}
if ( flagNumber < 0 ) {
// set the entire bitfield
if ( isInteger( value ) && value >= 0 && value <= 65535 ) {
this.flagData[gid] = value;
}
}
else if ( isInteger( flagNumber ) && flagNumber >= 0 && flagNumber < 16 ) {
const flagValue = flagValues[flagNumber];
if ( value ) {
// add the flag
this.flagData[gid] = this.flagData[gid] | flagValue;
}
else {
// remove the flag
this.flagData[gid] = this.flagData[gid] & ~flagValue;
}
}
}
}
export default TileData;