feat(maps): v3 cells_material field foundation
Adds the optional layer.cells_material array to the v3 schema validator, the per-cell read helper cells_material_at, the lazy-allocator ensure_cells_material, and extends cell_has_material plus compute_cell_bitmask_v3 to consult it. No public-API change yet; cell_has_material's signature gains a cells_material parameter that all internal callers thread through. A layer with no cells_material entry remains behaviourally identical to v0.5.6. Public APIs and the bitmask branch land in later commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
61
init.lua
61
init.lua
@@ -257,11 +257,34 @@ local function vertex_at(vertices, vw, vh, vx, vy)
|
|||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Any-corner rule: cell (x, y) is material iff at least one of its 4
|
-- 0.5.7: read the cell-tile material flag at (cx, cy). Returns 0 if
|
||||||
-- corner vertices is painted. A single painted vertex affects the
|
-- layer.cells_material is absent or the entry is unset/false.
|
||||||
-- 2x2 cells surrounding it. Out-of-grid cells are non-material.
|
local function cells_material_at(cells_material, w, h, cx, cy)
|
||||||
local function cell_has_material(vertices, w, h, x, y)
|
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 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 vw = w + 1
|
||||||
local vh = h + 1
|
local vh = h + 1
|
||||||
if vertex_at(vertices, vw, vh, x, y ) ~= 0 then return true end
|
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
|
-- corner of either was painted — producing connected blobs across
|
||||||
-- visually-empty map-tile gaps. See plan
|
-- visually-empty map-tile gaps. See plan
|
||||||
-- 2026-05-29-painting-model-rethink for the discovery.
|
-- 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 vw = w + 1
|
||||||
local vh = h + 1
|
local vh = h + 1
|
||||||
local TL = vertex_at(vertices, vw, vh, x, y ) ~= 0
|
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
|
if not norm then return false end
|
||||||
slot = norm.slot
|
slot = norm.slot
|
||||||
else
|
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
|
return false
|
||||||
end
|
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]
|
local rec = SLOT_LOOKUP[bitmask]
|
||||||
if not rec then return false end
|
if not rec then return false end
|
||||||
slot = rec.slot
|
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
|
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))
|
error(string.format("maps.load: schema violation in %s: vertices must be array", lsrc))
|
||||||
end
|
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 layer_data.overrides ~= nil then
|
||||||
if type(layer_data.overrides) ~= "table" then
|
if type(layer_data.overrides) ~= "table" then
|
||||||
error(string.format("maps.load: schema violation in %s: overrides must be table", lsrc))
|
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 y = 0, sz.h - 1 do
|
||||||
for x = 0, sz.w - 1 do
|
for x = 0, sz.w - 1 do
|
||||||
local has_ovr = overrides and overrides[x .. ":" .. y] ~= nil
|
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)
|
engine.render.draw_rect(x * ts, y * ts, ts, ts, MISSING_ASSET_COLOR)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -920,7 +955,7 @@ local function draw_layer(m, layer_name)
|
|||||||
end
|
end
|
||||||
local override_entry = overrides and overrides[x .. ":" .. y]
|
local override_entry = overrides and overrides[x .. ":" .. y]
|
||||||
local has_material = override_entry ~= nil
|
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
|
if not has_material then
|
||||||
goto continue
|
goto continue
|
||||||
end
|
end
|
||||||
@@ -934,7 +969,7 @@ local function draw_layer(m, layer_name)
|
|||||||
slot_index, rot, flip = norm.slot, norm.rot, norm.flip
|
slot_index, rot, flip = norm.slot, norm.rot, norm.flip
|
||||||
end
|
end
|
||||||
else
|
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]
|
local rec = SLOT_LOOKUP[bitmask]
|
||||||
if rec then
|
if rec then
|
||||||
slot_index, rot, flip = rec.slot, rec.rot, rec.flip
|
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
|
end
|
||||||
-- Vertex-derived bitmask path (only if the layer has a vertex grid)
|
-- Vertex-derived bitmask path (only if the layer has a vertex grid)
|
||||||
if not layer.vertices then return nil end
|
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
|
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, m.size.w, m.size.h, x, y)
|
local bitmask = compute_cell_bitmask_v3(layer.vertices, layer.cells_material, m.size.w, m.size.h, x, y)
|
||||||
return SLOT_LOOKUP[bitmask]
|
return SLOT_LOOKUP[bitmask]
|
||||||
end
|
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
|
if layer.overrides and layer.overrides[x .. ":" .. y] ~= nil then
|
||||||
return 1
|
return 1
|
||||||
end
|
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
|
end
|
||||||
return layer.tiles and layer.tiles[y * m.size.w + x + 1] or 0
|
return layer.tiles and layer.tiles[y * m.size.w + x + 1] or 0
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user