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) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-05-21 22:57:12 +02:00
parent 6b2dc22227
commit 8a75aee48a

142
init.lua
View File

@@ -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
-- <asset-lib>/assets/atlases/<atlas_id>/ 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)