mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 08:01:09 +02:00
Merge pull request #125 from Code-Grub/fix/flat-top-rim-repeat
stop a flat top stamping its rim down the plateau
This commit is contained in:
+21
-2
@@ -65,6 +65,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
|
||||
@@ -561,8 +581,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
|
||||
|
||||
@@ -2253,6 +2253,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
|
||||
@@ -2300,6 +2337,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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user