Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions lib/ChunkMesher.lua
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ end

local ChunkMesher = {}

-- Which drawn row a FLAT-topped volume's top face wears at depth `ty`.
--
-- A structure is usually deeper than the art that draws it, so the rows
-- cycle and the drawing repeats down the top. That is right for art which
-- genuinely repeats -- the Safari Zone's fence alternates two tiles the
-- whole way down -- and wrong for a RIM over a uniform body: a cliff
-- mound's first row is its top edge, and cycling lays that edge again
-- every second tile, striping a plateau with rims it should not have.
--
-- Where Structures found the body uniform, the rim is laid once at the
-- north edge and the body held after it. Everything else cycles as before.
function ChunkMesher.flatTopRow(run, ty)
local m = math.min(2, run.extent)
local d = ty - run.north
if run.topUniform then
return run.north + math.min(d, m - 1)
end
return run.north + (d % m)
end

-- Ring of border blocks meshed around the body, matching the width
-- TileRenderer draws so the two modes end at the same place.
local RING = 3
Expand Down Expand Up @@ -528,8 +548,7 @@ local function runGeometry(map, bodyOnly, masks, sink, waterSink)
{ x0 + 8, neY, z0 }, { x0, nwY, z0 } },
{ { u0, v1 }, { u1, v1 }, { u1, v0 }, { u0, v0 } }, 0.95)
elseif run then
local m = math.min(2, run.extent)
local topTile = map:tileAt(tx, run.north + ((ty - run.north) % m))
local topTile = map:tileAt(tx, ChunkMesher.flatTopRow(run, ty))
topQuad(x0, z0, h, topTile, VOLUME_TOP_SHADE)
else
local topTile = tile
Expand Down
38 changes: 38 additions & 0 deletions lib/Structures.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2225,6 +2225,43 @@ function Structures.buildVolume(S, map, tiles)
-- whether the region's dominant columns are flat repeats (a cliff
-- mound's plateau) rather than drawn facades (a house's front)
local modeRepeat = (repeatVotes[modeH] or 0) * 2 > modeN

-- Whether this REGION's tops are a rim over a uniform body -- what every
-- cliff mound is drawn as: a top edge, then the same rock the whole way
-- down. The top face may then lay that rim once along its north edge and
-- hold the body after it, instead of cycling the rim back every second
-- tile and striping a plateau with edges it should not have.
--
-- Answered per column AND per region, because each catches what the
-- other misses. A mound is one structure many columns wide, and the
-- columns carrying its cave mouth read differently from their neighbours
-- (their drawing ends in the mouth's own tiles): per column alone, those
-- kept cycling while the rest held, leaving rim stubs above the doorway.
-- But a region vote alone silences a genuine rim-over-body column that
-- happens to stand in a region of repeating art -- three of them in the
-- Safari Zone. A column holds if EITHER says so.
--
-- Art that genuinely repeats is not uniform and keeps cycling: the
-- Safari Zone's fence alternates two tiles the whole way down, and there
-- the repeat IS what the drawing says.
local uniformVotes, uniformTotal = 0, 0
for _, r in ipairs(runs) do
local run = r.run
if run.extent > 2 then
uniformTotal = uniformTotal + 1
local body = map:tileAt(r.tx, run.north + 1)
local uniform = true
for d = 2, run.extent - 1 do
if map:tileAt(r.tx, run.north + d) ~= body then
uniform = false
break
end
end
run.ownUniform = uniform
if uniform then uniformVotes = uniformVotes + 1 end
end
end
local regionUniform = uniformTotal > 0 and uniformVotes * 2 > uniformTotal
for _, r in ipairs(runs) do
local run = r.run
local h = run.unit * 8
Expand Down Expand Up @@ -2272,6 +2309,7 @@ function Structures.buildVolume(S, map, tiles)
run.rise = roofRows * 8
run.peak = h
run.h = h - run.rise -- facade height: what sides build to
run.topUniform = run.ownUniform or regionUniform
for ty = run.north, run.front do
S.runs[keyOf(r.tx, ty)] = run
end
Expand Down
95 changes: 95 additions & 0 deletions tests/flat_top_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
-- A flat top must not stamp its rim twice.
--
-- ChunkMesher.flatTopRow decides which drawn row a flat-topped volume's top
-- face wears at each depth. Where the drawing is a RIM over a uniform body
-- -- every cliff mound in the game, and the mound the Diglett's Cave mouth
-- is cut into -- the rim belongs at the plateau's north edge and nowhere
-- else. Cycling the first two rows lays it again every second tile.
--
-- The invariant: on such a run the sampled row never goes BACKWARDS as ty
-- moves south. Art that genuinely repeats (the Safari Zone's fence
-- alternates two tiles the whole way down) is exempt: there the repeat is
-- what the drawing says, and the run is not rim-over-body.
--
-- POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/flat_top_test.lua lovec .
return function(game)
local U = dofile("tests/drivers/util.lua")

local V = game.mods.exports["DRAMATIC_SHAPE"]
V = V and V.lib
local Structures = V and V.require("Structures")
local ChunkMesher = V and V.require("ChunkMesher")
if not (Structures and ChunkMesher and ChunkMesher.flatTopRow) then
print("[flattop] FAIL mod, Structures or ChunkMesher.flatTopRow missing")
love.event.quit(1)
return
end
local function keyOf(tx, ty) return (ty + 64) * 4096 + (tx + 64) end

local MAPS = {}
for id in pairs((game.data and game.data.maps) or {}) do
MAPS[#MAPS + 1] = id
end
table.sort(MAPS)

local checked, offenders, examples = 0, 0, {}
for _, mapId in ipairs(MAPS) do
U.teleport(game, mapId, 5, 5, "up")
U.wait(6)
local ow = game.overworld
if ow and ow.map and ow.map.def and ow.map.def.id == mapId then
local map = ow.map
local S = Structures.forMap(map)
local seen = {}
for tx = 0, map.def.width * 4 - 1 do
for ty = 0, map.def.height * 4 - 1 do
local run = S.runs[keyOf(tx, ty)]
local sig = run and (tostring(run) .. ":" .. tx)
if run and not seen[sig] and (run.rise or 0) == 0 then
seen[sig] = true
local ext = run.front - run.north + 1
-- rim over a uniform body: the shape the rim must not repeat on
local uniform = ext > 2
if uniform then
local body = map:tileAt(tx, run.north + 1)
for d = 2, ext - 1 do
if map:tileAt(tx, run.north + d) ~= body then
uniform = false
break
end
end
end
if uniform then
checked = checked + 1
local prev = -1
for ty2 = run.north, run.front do
local row = ChunkMesher.flatTopRow(run, ty2)
if row < prev then
offenders = offenders + 1
if #examples < 5 then
examples[#examples + 1] = ("%s tx=%d north=%d ext=%d "
.. "went back to row %d at ty %d")
:format(mapId, tx, run.north, ext, row, ty2)
end
break
end
prev = row
end
end
end
end
end
end
end

print(("[flattop] %d rim-over-body runs checked, %d repeat their rim")
:format(checked, offenders))
for _, e in ipairs(examples) do print("[flattop] " .. e) end
if offenders > 0 then
print("[flattop] FAIL")
love.event.quit(1)
else
print("[flattop] PASS")
love.event.quit(0)
end
end