diff --git a/init.lua b/init.lua index 1b5b30a..f99b65f 100644 --- a/init.lua +++ b/init.lua @@ -257,11 +257,34 @@ local function vertex_at(vertices, vw, vh, vx, vy) return 1 end --- Any-corner rule: cell (x, y) is material iff at least one of its 4 --- corner vertices is painted. A single painted vertex affects the --- 2x2 cells surrounding it. Out-of-grid cells are non-material. -local function cell_has_material(vertices, w, h, x, y) +-- 0.5.7: read the cell-tile material flag at (cx, cy). Returns 0 if +-- layer.cells_material is absent or the entry is unset/false. +local function cells_material_at(cells_material, w, h, cx, cy) + if cells_material == nil then return 0 end + if cx < 0 or cx >= w or cy < 0 or cy >= h then return 0 end + local v = cells_material[cy * w + cx + 1] + if v == nil or v == 0 or v == false then return 0 end + return 1 +end + +-- 0.5.7: lazy-allocate the per-cell material array on a layer. Returns +-- the (now-guaranteed-present) array. Safe to call repeatedly. +local function ensure_cells_material(layer, w, h) + if layer.cells_material == nil then + local n = w * h + local cm = {} + for i = 1, n do cm[i] = 0 end + layer.cells_material = cm + end + return layer.cells_material +end + +-- Any-corner rule + 0.5.7 cell-tile rule: cell (x, y) is material iff +-- (any of its 4 corner vertices is painted) OR (cells_material[x,y] is +-- set). Out-of-grid cells are non-material. +local function cell_has_material(vertices, cells_material, w, h, x, y) if x < 0 or x >= w or y < 0 or y >= h then return false end + if cells_material_at(cells_material, w, h, x, y) ~= 0 then return true end local vw = w + 1 local vh = h + 1 if vertex_at(vertices, vw, vh, x, y ) ~= 0 then return true end @@ -285,7 +308,7 @@ end -- corner of either was painted — producing connected blobs across -- visually-empty map-tile gaps. See plan -- 2026-05-29-painting-model-rethink for the discovery. -local function compute_cell_bitmask_v3(vertices, w, h, x, y) +local function compute_cell_bitmask_v3(vertices, cells_material, w, h, x, y) local vw = w + 1 local vh = h + 1 local TL = vertex_at(vertices, vw, vh, x, y ) ~= 0 @@ -371,10 +394,10 @@ local function cell_is_opaque_on_layer(map, layer_name, x, y) if not norm then return false end slot = norm.slot else - if not cell_has_material(layer.vertices, map.size.w, map.size.h, x, y) then + if not cell_has_material(layer.vertices, layer.cells_material, map.size.w, map.size.h, x, y) then return false end - local bitmask = compute_cell_bitmask_v3(layer.vertices, map.size.w, map.size.h, x, y) + local bitmask = compute_cell_bitmask_v3(layer.vertices, layer.cells_material, map.size.w, map.size.h, x, y) local rec = SLOT_LOOKUP[bitmask] if not rec then return false end slot = rec.slot @@ -539,6 +562,18 @@ local function validate_map_table_v3(t, source) if layer_data.vertices ~= nil and type(layer_data.vertices) ~= "table" then error(string.format("maps.load: schema violation in %s: vertices must be array", lsrc)) end + -- 0.5.7: optional cells_material (W*H bool array, lazy-allocated) + if layer_data.cells_material ~= nil then + if type(layer_data.cells_material) ~= "table" then + error(string.format("maps.load: schema violation in %s: layer '%s' field 'cells_material' must be table, got %s", + source, layer_name, type(layer_data.cells_material))) + end + local expected_cm = size.w * size.h + if #layer_data.cells_material ~= expected_cm then + error(string.format("maps.load: schema violation in %s: layer '%s' field 'cells_material' length %d != W*H %d", + source, layer_name, #layer_data.cells_material, expected_cm)) + end + end if layer_data.overrides ~= nil then if type(layer_data.overrides) ~= "table" then error(string.format("maps.load: schema violation in %s: overrides must be table", lsrc)) @@ -904,7 +939,7 @@ local function draw_layer(m, layer_name) for y = 0, sz.h - 1 do for x = 0, sz.w - 1 do local has_ovr = overrides and overrides[x .. ":" .. y] ~= nil - if has_ovr or cell_has_material(layer.vertices, sz.w, sz.h, x, y) then + if has_ovr or cell_has_material(layer.vertices, layer.cells_material, sz.w, sz.h, x, y) then engine.render.draw_rect(x * ts, y * ts, ts, ts, MISSING_ASSET_COLOR) end end @@ -920,7 +955,7 @@ local function draw_layer(m, layer_name) end local override_entry = overrides and overrides[x .. ":" .. y] local has_material = override_entry ~= nil - or cell_has_material(layer.vertices, sz.w, sz.h, x, y) + or cell_has_material(layer.vertices, layer.cells_material, sz.w, sz.h, x, y) if not has_material then goto continue end @@ -934,7 +969,7 @@ local function draw_layer(m, layer_name) slot_index, rot, flip = norm.slot, norm.rot, norm.flip end else - local bitmask = compute_cell_bitmask_v3(layer.vertices, sz.w, sz.h, x, y) + local bitmask = compute_cell_bitmask_v3(layer.vertices, layer.cells_material, sz.w, sz.h, x, y) local rec = SLOT_LOOKUP[bitmask] if rec then slot_index, rot, flip = rec.slot, rec.rot, rec.flip @@ -1283,8 +1318,8 @@ function M.cell_material_slot(layer_name, x, y, map_id) end -- Vertex-derived bitmask path (only if the layer has a vertex grid) if not layer.vertices then return nil end - if not cell_has_material(layer.vertices, m.size.w, m.size.h, x, y) then return nil end - local bitmask = compute_cell_bitmask_v3(layer.vertices, m.size.w, m.size.h, x, y) + if not cell_has_material(layer.vertices, layer.cells_material, m.size.w, m.size.h, x, y) then return nil end + local bitmask = compute_cell_bitmask_v3(layer.vertices, layer.cells_material, m.size.w, m.size.h, x, y) return SLOT_LOOKUP[bitmask] end @@ -1325,7 +1360,7 @@ function M.cell_gid(layer_name, x, y, map_id) if layer.overrides and layer.overrides[x .. ":" .. y] ~= nil then return 1 end - return cell_has_material(layer.vertices, m.size.w, m.size.h, x, y) and 1 or 0 + return cell_has_material(layer.vertices, layer.cells_material, m.size.w, m.size.h, x, y) and 1 or 0 end return layer.tiles and layer.tiles[y * m.size.w + x + 1] or 0 end