Files
prosperon/tilemap.cm
2025-11-22 09:43:51 -06:00

193 lines
5.4 KiB
Plaintext

// 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