From 8a75aee48ada1635626067b73e97b959f9ccd6f1 Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Thu, 21 May 2026 22:57:12 +0200 Subject: [PATCH] Rewrite load_textures for paired-PNG atlas format Replaces the per-tile texture-handle pattern with per-atlas handles plus per-tile UV-rects. Each map-lib atlas record now holds a tiles-by- id dictionary loaded from tiles.atlas.json and two texture handles (diffuse + height) loaded from the paired PNGs. Tile-records expose {id, name, uv, blocks_sight} fields; the breaking-change to numeric id + named name flows to all consumers in this slice. Adds get_layer_diffuse_texture and get_layer_height_texture that aggregate to a single handle when every cell in a layer references the same atlas, returning nil for multi-atlas layers so lighting can fall back to per-cell sampling. Co-Authored-By: Claude Opus 4.7 (1M context) --- init.lua | 142 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 103 insertions(+), 39 deletions(-) diff --git a/init.lua b/init.lua index 9681948..5bc2aac 100644 --- a/init.lua +++ b/init.lua @@ -609,52 +609,116 @@ function M.is_indoor(x, y, map_id) return m.roof[y * m.size.w + x + 1] == 1 end --- ==================================================================== --- Resolve tilemap-tile atlas-ids to texture-handles via asset-lib. --- Operates on the current map's tilemap; call after maps.set_current. --- asset_aliases: { [alias-key] = asset-lib-id } from module's manifest. --- ==================================================================== -local function load_textures_for_tilemap(tm, asset_aliases) - if tm.asset_pack == nil then return end -- color-only tilemap, no textures - local lib_id = asset_aliases[tm.asset_pack] - if lib_id == nil then - error("maps.load_textures: asset-pack alias '" .. tm.asset_pack - .. "' not in asset_aliases") - end - local atlas_path = lib_id .. "/assets/atlas.json" - local atlas = engine.asset.load_json(atlas_path) - local pack = atlas[tm.asset_pack] - if pack == nil then - error("maps.load_textures: asset_pack '" .. tm.asset_pack - .. "' not declared in atlas of '" .. lib_id .. "'") - end - for _, tile in ipairs(tm.tiles) do - if tile.texture then - local entry = pack[tile.texture] - if entry == nil then - error("maps.load_textures: atlas-id '" .. tile.texture - .. "' not in asset_pack '" .. tm.asset_pack .. "'") - end - tile.texture_handle = engine.asset.load_texture(lib_id .. "/assets/" .. entry.file) - end - end -end - +-- ===================================================================== +-- Atlas-Loading (M.2: paired-PNG diffuse + height atlases) +-- Replaces the M.1-era tilemap-indexed loader. Each atlas-set lives at +-- /assets/atlases// with four files: +-- tiles.diffuse.atlas.png — RGBA8888 packed atlas +-- tiles.height.atlas.png — L8 grayscale heightmap +-- tiles.atlas.json — metadata + per-tile UV-rects +-- tiles.atlas.lock.json — name -> id stable bindings (not loaded +-- at runtime; only used by the baker) +-- ===================================================================== function M.load_textures(asset_aliases) local map_id = M.current() if map_id == nil then error("maps.load_textures: no current map; call maps.set_current first") end local m = map_registry[map_id] - - if m.schema_version == 2 then - for _, tm in ipairs(m.atlases) do - load_textures_for_tilemap(tm, asset_aliases) - end - return + if m.schema_version ~= 2 then + error("maps.load_textures: only schema-v2 maps supported in v0.3.0+") end - -- v1 legacy: kept for M.create with manual v1 table - load_textures_for_tilemap(m.tilemap, asset_aliases) + + for atlas_idx, atlas_alias in ipairs(m.atlas_aliases) do + local lib_id = asset_aliases[atlas_alias] + if lib_id == nil then + error(string.format( + "maps.load_textures: asset-alias '%s' not in module asset_aliases", + atlas_alias)) + end + local base = lib_id .. "/assets/atlases/" .. atlas_alias + local meta = engine.asset.load_json(base .. "/tiles.atlas.json") + if meta.atlas_id ~= atlas_alias then + error(string.format( + "maps.load_textures: atlas_id mismatch in %s (json has '%s')", + base, meta.atlas_id)) + end + -- Build tile-record dictionary keyed by integer ID + local tiles_by_id = {} + for _, t in ipairs(meta.tiles) do + tiles_by_id[t.id] = { + id = t.id, + name = t.name, + uv = { x = t.uv[1], y = t.uv[2], w = t.uv[3], h = t.uv[4] }, + blocks_sight = t.blocks_sight, + } + end + m.atlases[atlas_idx] = { + id = atlas_alias, + atlas_id = meta.atlas_id, + atlas_size_px = meta.atlas_size_px, + tile_size_px = meta.tile_size_px, + tiles = tiles_by_id, + diffuse_texture_handle = engine.asset.load_texture(base .. "/tiles.diffuse.atlas.png"), + height_texture_handle = engine.asset.load_texture(base .. "/tiles.height.atlas.png"), + } + end + + -- Update m.tile_size from first atlas (assume uniform; first atlas wins) + if m.atlases[1] then + m.tile_size = m.atlases[1].tile_size_px + end +end + +-- Returns the diffuse atlas-texture-handle for a layer, IF all cells in +-- the layer reference the same atlas-index. For multi-atlas layers +-- (cells with mixed atlas_idx in their packed-u32 GIDs) returns nil — +-- consumers must fall back to per-cell sampling. (M.2) +function M.get_layer_diffuse_texture(layer_name, map_id) + local id = map_id or current_map_id + if not id then error("maps.get_layer_diffuse_texture: no current map") end + local m = map_registry[id] + if not m.layers or not m.layers[layer_name] then return nil end + local tiles = m.layers[layer_name].tiles + local atlas_idx = nil + for i = 1, #tiles do + local gid = tiles[i] or 0 + if gid ~= 0 then + local a = (gid >> 24) & 0xFF + if atlas_idx == nil then + atlas_idx = a + elseif atlas_idx ~= a then + return nil -- mixed atlases + end + end + end + if atlas_idx == nil then return nil end -- empty layer + local atlas = m.atlases[atlas_idx + 1] + return atlas and atlas.diffuse_texture_handle or nil +end + +-- Same as above but for the height-channel. (M.2) +function M.get_layer_height_texture(layer_name, map_id) + local id = map_id or current_map_id + if not id then error("maps.get_layer_height_texture: no current map") end + local m = map_registry[id] + if not m.layers or not m.layers[layer_name] then return nil end + local tiles = m.layers[layer_name].tiles + local atlas_idx = nil + for i = 1, #tiles do + local gid = tiles[i] or 0 + if gid ~= 0 then + local a = (gid >> 24) & 0xFF + if atlas_idx == nil then + atlas_idx = a + elseif atlas_idx ~= a then + return nil + end + end + end + if atlas_idx == nil then return nil end + local atlas = m.atlases[atlas_idx + 1] + return atlas and atlas.height_texture_handle or nil end function M.iterate_layers_pre_entities(fn, map_id)