// tilemap function tilemap() { this.tiles = []; this.offset_x = 0; this.offset_y = 0; this.layer = 0; // Default layer for scene tree sorting this._geometry_cache = {}; // Cache actual geometry data by texture this._dirty = true; this.scale_x = 1 this.scale_y = 1 return this; } tilemap.for = function tilemap_for(map, fn) { for (var x = 0; x < map.tiles.length; x++) { if (!map.tiles[x]) continue; for (var y = 0; y < map.tiles[x].length; y++) { if (map.tiles[x][y] != null) { var result = fn(map.tiles[x][y], { x: x + map.offset_x, y: y + map.offset_y }); if (result != null) { map.tiles[x][y] = result; } } } } } tilemap.prototype = { at(pos) { var x = pos.x - this.offset_x; var y = pos.y - this.offset_y; if (!this.tiles[x]) return null; return this.tiles[x][y]; }, set(pos, image) { // Shift arrays if negative indices if (pos.x < this.offset_x) { var shift = this.offset_x - pos.x; var new_tiles = []; for (var i = 0; i < shift; i++) new_tiles[i] = []; this.tiles = new_tiles.concat(this.tiles); this.offset_x = pos.x; } if (pos.y < this.offset_y) { var shift = this.offset_y - pos.y; for (var i = 0; i < this.tiles.length; i++) { if (!this.tiles[i]) this.tiles[i] = []; var new_col = []; for (var j = 0; j < shift; j++) new_col[j] = null; this.tiles[i] = new_col.concat(this.tiles[i]); } this.offset_y = pos.y; } var x = pos.x - this.offset_x; var y = pos.y - this.offset_y; // Ensure array exists up to x while (this.tiles.length <= x) this.tiles.push([]); // Convert string to image object if needed, or handle null to remove tile if (image && typeof image == 'string') { var graphics = use('graphics'); image = graphics.texture(image); } // Note: if image is null, it will remove the tile // Set the value (null removes the tile) this.tiles[x][y] = image; // Mark cache as dirty when tiles change this._dirty = true; }, clear() { this.tiles = []; this.offset_x = 0; this.offset_y = 0; this._geometry_cache = {}; this._dirty = true; }, // Build cached geometry grouped by texture _build_geometry_cache(pos = {x: 0, y: 0}) { var geometry = use('geometry'); // Group tiles by texture (using a unique key per image object) var textureGroups = {}; var imageToKey = new Map(); // Map image objects to unique keys var keyCounter = 0; // Collect all tiles and their positions for (var x = 0; x < this.tiles.length; x++) { if (!this.tiles[x]) continue; for (var y = 0; y < this.tiles[x].length; y++) { var tile = this.tiles[x][y]; if (tile) { if (!imageToKey.has(tile)) { var key = `texture_${keyCounter++}`; imageToKey.set(tile, key); log.console(`New texture key: ${key} for tile ${tile}`) } var textureKey = imageToKey.get(tile); if (!textureGroups[textureKey]) { textureGroups[textureKey] = { tiles: [], image: tile, // Store the image object offset_x: this.offset_x, offset_y: this.offset_y, size_x: this.scale_x, size_y: this.scale_y }; } textureGroups[textureKey].tiles.push({ x: x + this.offset_x, y: y + this.offset_y, image: tile }); } } } // Generate and cache geometry for each texture group this._geometry_cache = {}; for (var textureKey in textureGroups) { var group = textureGroups[textureKey]; if (group.tiles.length == 0) continue; // Create a temporary tilemap for this texture group var tempMap = { tiles: [], offset_x: group.offset_x, offset_y: group.offset_y, size_x: group.size_x, // now in world-units size_y: group.size_y, pos_x: pos.x, pos_y: pos.y }; // Build sparse array for this texture's tiles group.tiles.forEach(({x, y, image}) => { var arrayX = x - group.offset_x; var arrayY = y - group.offset_y; if (!tempMap.tiles[arrayX]) tempMap.tiles[arrayX] = []; tempMap.tiles[arrayX][arrayY] = image; }); // Generate and cache geometry for this group var geom = geometry.tilemap_to_data(tempMap); this._geometry_cache[textureKey] = { geometry: geom, image: group.image }; } this._dirty = false; }, draw() { var pos = this.pos || {x:0,y:0} // Rebuild cache if dirty or position changed if (this._dirty || Object.keys(this._geometry_cache).length == 0 || this._last_pos?.x != pos.x || this._last_pos?.y != pos.y) { this._build_geometry_cache(pos); this._last_pos = {x: pos.x, y: pos.y}; } // Generate commands from cached geometry, pulling texture_id dynamically var commands = []; var i = 0 for (var textureKey in this._geometry_cache) { var cached = this._geometry_cache[textureKey]; commands.push({ cmd: "geometry", geometry: cached.geometry, image: cached.image, texture_id: cached.image.gpu // Pull GPU ID dynamically on each draw }); } return commands; }, } return tilemap