mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 11:50:50 +02:00
Compare commits
13 Commits
stadium_fixes
...
v1.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
| e14afd8dfa | |||
| 301bd4c1f9 | |||
| 00f2b0bb9a | |||
| 6bcd4d3144 | |||
| b50bbe0782 | |||
| 9f74ea7e73 | |||
| 790c34efff | |||
| e14cf3de90 | |||
| dde0879527 | |||
| 43385c8bf6 | |||
| c404c766cd | |||
| 9700806a92 | |||
| 6e9b787e41 |
+28
-5
@@ -264,6 +264,27 @@ local function measure(sp, t)
|
||||
top[x] = r
|
||||
end
|
||||
|
||||
-- The row a column's roof SURFACE may sink to. `top[x]` is the
|
||||
-- silhouette cap -- the black the drawing closes its shape with -- and
|
||||
-- the depth map spends most of a tapered column's depth above it, so
|
||||
-- clamping onto `top[x]` paints that one outline pixel the length of
|
||||
-- the slope and the courses beat against it. The surface belongs on the
|
||||
-- first PAINTED row instead: the same refusal to let the outline stand
|
||||
-- as a face that the side faces already make below.
|
||||
local surfaceTop = {}
|
||||
for x = 0, W - 1 do
|
||||
local y = top[x]
|
||||
while y < roofRows and sp.inside[y * W + x]
|
||||
and sp.col[y * W + x] == BLACK do
|
||||
y = y + 1
|
||||
end
|
||||
if y < roofRows and sp.inside[y * W + x] then
|
||||
surfaceTop[x] = y
|
||||
else
|
||||
surfaceTop[x] = top[x]
|
||||
end
|
||||
end
|
||||
|
||||
-- The drawing's own ground line: the row after the last drawn one. A
|
||||
-- building ends on the black threshold row it stands on (ground == H),
|
||||
-- but furniture is drawn standing on open floor -- the lab table's
|
||||
@@ -386,7 +407,7 @@ local function measure(sp, t)
|
||||
-- building. `depthPx` names it in voxels, for an object whose real
|
||||
-- depth is not a whole tile row -- the Bike Shop toolbox is a box
|
||||
-- standing in the middle of its own cell, not a thing that fills a plot.
|
||||
return { top = top, ytop = ytop,
|
||||
return { top = top, surfaceTop = surfaceTop, ytop = ytop,
|
||||
D = t.depthPx or ((t.depth or #t.tiles) * 8),
|
||||
ground = ground,
|
||||
recess = recess, interior = interior, shadeTexel = shadeTexel }
|
||||
@@ -971,6 +992,7 @@ local function model(sp, pr, t)
|
||||
local W, H, D = sp.W, sp.H, pr.D
|
||||
local slab, roofRows = t.slab, t.roofRows
|
||||
local top, ytop, ground = pr.top, pr.ytop, pr.ground
|
||||
local surfaceTop = pr.surfaceTop
|
||||
|
||||
-- The roof's drawn span. A sprite inset from its box (B03) leaves outer
|
||||
-- columns undrawn in the roof band; they carry no roof at all, and the
|
||||
@@ -1020,11 +1042,12 @@ local function model(sp, pr, t)
|
||||
if top[x] < roofRows
|
||||
and y > tx - slab and y <= tx and z >= rz0 and z <= rz1 then
|
||||
if y == tx and x > x0d and x < x1d and z > rz0 and z < rz1 then
|
||||
-- the surface itself. Clamping the row into the column's first
|
||||
-- drawn row keeps the flank battens running down the slope
|
||||
-- instead of falling off the silhouette.
|
||||
-- the surface itself. Lifting the row into the column's first
|
||||
-- PAINTED row keeps the flank battens running down the slope
|
||||
-- instead of falling off the silhouette -- and off its cap, which
|
||||
-- is outline black and belongs to the rim, not to the surface.
|
||||
local sy = roofSy[z]
|
||||
if sy < top[x] then sy = top[x] end
|
||||
if sy < surfaceTop[x] then sy = surfaceTop[x] end
|
||||
return sy * W + x
|
||||
end
|
||||
-- The rim reproduces the eave the drawing itself paints under the
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
-- Driver: the two sloped-roof drawings in Pallet Town, shot at the orbit
|
||||
-- rungs, to see what the roof surface wears down a TAPERED flank.
|
||||
--
|
||||
-- A tapered column's first drawn row is its silhouette cap, and the cap is
|
||||
-- outline black. The depth map spends most of the roof's depth above that
|
||||
-- cap, so a surface clamped onto it paints the outline the length of the
|
||||
-- slope and the course cycle beats against it -- black teeth marching down
|
||||
-- the hip. The surface belongs on the column's first PAINTED row instead.
|
||||
--
|
||||
-- POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/roof_cap_shots.lua \
|
||||
-- SHOT_DIR=.scratchpad/roofcap AB_TAG=before lovec.exe .
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local Pipelines = require("src.render.Pipelines")
|
||||
|
||||
local ROOT = (os.getenv("SHOT_DIR") or "shots/roofcap")
|
||||
.. "/" .. (os.getenv("AB_TAG") or "after")
|
||||
|
||||
local handle = game.mods.exports["DRAMATIC_SHAPE"]
|
||||
if not (handle and handle.lib) then
|
||||
print("[cap] DRAMATIC_SHAPE mod not loaded")
|
||||
return
|
||||
end
|
||||
local V = handle.lib
|
||||
local DayNight = V.require("DayNight")
|
||||
local ChunkMesher = V.require("ChunkMesher")
|
||||
local Voxel = V.require("VoxelState")
|
||||
|
||||
require("src.world.OverworldController").rollEncounter = function() return nil end
|
||||
local TileRenderer = require("src.render.TileRenderer")
|
||||
TileRenderer.tick = function() end
|
||||
TileRenderer.animFrame = function() return 0 end
|
||||
DayNight.setting:sync("day")
|
||||
|
||||
pcall(os.execute, 'mkdir -p "' .. ROOT .. '" 2>/dev/null')
|
||||
pcall(os.execute, 'mkdir "' .. ROOT:gsub("/", "\\") .. '" 2>nul')
|
||||
|
||||
local Zoom = require("src.render.Zoom")
|
||||
pcall(function()
|
||||
game.save.options.zoom = 1
|
||||
Zoom.applyOptions(game.save.options)
|
||||
end)
|
||||
|
||||
local function settle()
|
||||
for _ = 1, 900 do
|
||||
if ChunkMesher.pending() == 0 then break end
|
||||
U.wait(1)
|
||||
end
|
||||
for _ = 1, 300 do
|
||||
if Voxel.t >= 1 and Voxel.ready and ChunkMesher.pending() == 0 then break end
|
||||
U.wait(1)
|
||||
end
|
||||
U.wait(30)
|
||||
end
|
||||
|
||||
-- B07 (the gabled house: Red's and Blue's) and B31 (Oak's lab) are the
|
||||
-- two tapered roof bands standing on one map -- 16 drawn rows and 32,
|
||||
-- the two groups every other sloped building borrows its band table
|
||||
-- from. Standing south of each puts the hip end across the frame.
|
||||
local SCENES = {
|
||||
{ map = "PALLET_TOWN", x = 5, y = 6, face = "up", label = "reds_house" },
|
||||
{ map = "PALLET_TOWN", x = 13, y = 3, face = "up", label = "blues_house" },
|
||||
{ map = "PALLET_TOWN", x = 12, y = 12, face = "up", label = "oaks_lab" },
|
||||
}
|
||||
|
||||
local shots = 0
|
||||
for _, s in ipairs(SCENES) do
|
||||
for _, rung in ipairs({ 3, 5 }) do
|
||||
U.teleport(game, s.map, s.x, s.y, s.face)
|
||||
Pipelines.setLevel("voxel", rung)
|
||||
Pipelines.setLevel("tiltshift", 0)
|
||||
settle()
|
||||
local path = ("%s/%s_v%d.png"):format(ROOT, s.label, rung)
|
||||
game.capturePath = path
|
||||
U.wait(8)
|
||||
local f = io.open(path, "rb")
|
||||
if f then f:close() shots = shots + 1
|
||||
else print("[cap] capture missed: " .. path) end
|
||||
end
|
||||
end
|
||||
print(("[cap] %d shots into %s"):format(shots, ROOT))
|
||||
love.event.quit()
|
||||
end
|
||||
+56
-16
@@ -1393,6 +1393,22 @@ def profile(sp, t):
|
||||
wall_h = ground - t["roof_rows"]
|
||||
ytop = wall_h - 1 + t["slab"]
|
||||
|
||||
# The row a column's roof SURFACE may sink to. `top[x]` is the
|
||||
# silhouette cap -- the black the drawing closes its shape with -- and
|
||||
# the depth map spends most of a tapered column's depth above it, so a
|
||||
# clamp onto `top[x]` paints that one outline pixel across the slope
|
||||
# and the courses beat against it. The surface belongs on the first
|
||||
# PAINTED row instead, which is the same thing the side faces already
|
||||
# do when the drawing's own pixel is the outline.
|
||||
surface_top = []
|
||||
for x in range(W):
|
||||
y = top[x]
|
||||
while (y < t["roof_rows"] and inside(x, y)
|
||||
and sp["col"][y][x] == BLACK):
|
||||
y += 1
|
||||
surface_top.append(y if y < t["roof_rows"] and inside(x, y)
|
||||
else top[x])
|
||||
|
||||
# Recesses: the panes the art seals behind a black frame. Non-black
|
||||
# pixels of the facade split into components across the black outline;
|
||||
# a component small enough to be a window or a doorway sinks one voxel.
|
||||
@@ -1434,7 +1450,8 @@ def profile(sp, t):
|
||||
# Depth is the PLOT. For a whole-drawing building that is the grid
|
||||
# itself; `depth` (in tile rows) names it when the grid runs past the
|
||||
# plot onto ground the drawing merely stands its legs on.
|
||||
return dict(top=top, wall_h=wall_h, ytop=ytop, recess=recess,
|
||||
return dict(top=top, surface_top=surface_top,
|
||||
wall_h=wall_h, ytop=ytop, recess=recess,
|
||||
inside=inside,
|
||||
# `depth` names the plot in TILE ROWS, which is the right
|
||||
# grain for a building. `depth_px` names it in voxels, for
|
||||
@@ -1929,6 +1946,29 @@ def build_desk_set(sp, pr, t):
|
||||
return vox
|
||||
|
||||
|
||||
def roof_surface_row(pr, t):
|
||||
"""Which drawn row the roof surface wears at column x, depth z.
|
||||
|
||||
The drawing looks at the roof from the north, so its rows ARE depth: the
|
||||
rims map one row per voxel and the middle cycles a run whose period is
|
||||
the course rhythm. A tapered column's band starts lower down the
|
||||
drawing, and the row it wears is lifted to stay inside it."""
|
||||
z0, z1 = 0, pr["D"] - 1 + t["front_eave"]
|
||||
back, front = t["roof_back"], t["roof_front"]
|
||||
c0, c1 = t["roof_cycle"]
|
||||
rows, floor = t["roof_rows"], pr["surface_top"]
|
||||
|
||||
def depth_row(z):
|
||||
df, db = z - z0, z1 - z # from the north / the south edge
|
||||
if df < back:
|
||||
return df # north rim: the drawing's top rows
|
||||
if db < front:
|
||||
return rows - 1 - db # south rim: fascia and eave course
|
||||
return c0 + (df - c0) % (c1 - c0 + 1)
|
||||
|
||||
return (lambda x, z: max(depth_row(z), floor[x])), z0, z1
|
||||
|
||||
|
||||
def build(sp, pr, t):
|
||||
"""The voxel model. Order is load-bearing: walls, ledge, recesses, then
|
||||
the roof solid overwrites what it intersects and the walls are trimmed
|
||||
@@ -2007,17 +2047,7 @@ def build(sp, pr, t):
|
||||
vox.pop((sx, pr["ground"] - 1 - sy, D - 1), None)
|
||||
|
||||
# ---- roof: flat top over the plateau, stepped diagonal ends
|
||||
z0, z1 = 0, D - 1 + t["front_eave"]
|
||||
back, front = t["roof_back"], t["roof_front"]
|
||||
c0, c1 = t["roof_cycle"]
|
||||
|
||||
def roof_sy(z):
|
||||
df, db = z - z0, z1 - z # from the north / the south edge
|
||||
if df < back:
|
||||
return df # north rim: the drawing's top rows
|
||||
if db < front:
|
||||
return t["roof_rows"] - 1 - db # south rim: fascia and eave course
|
||||
return c0 + (df - c0) % (c1 - c0 + 1)
|
||||
surface_row, z0, z1 = roof_surface_row(pr, t)
|
||||
|
||||
shade_px = {}
|
||||
for sy in range(H):
|
||||
@@ -2029,10 +2059,7 @@ def build(sp, pr, t):
|
||||
tt = T(x)
|
||||
for z in range(z0, z1 + 1):
|
||||
outer = x == x0d or x == x1d or z == z0 or z == z1
|
||||
# the slope's texture is the drawing's own: clamping into the
|
||||
# column's first drawn row keeps flank battens running down the
|
||||
# slope instead of falling off the silhouette
|
||||
sy = max(roof_sy(z), pr["top"][x])
|
||||
sy = surface_row(x, z)
|
||||
for y in range(tt - slab + 1, tt + 1):
|
||||
if y == tt and not outer:
|
||||
put(x, y, z, x, sy)
|
||||
@@ -2306,6 +2333,19 @@ def verify_roof(vox, pr, t):
|
||||
# drawing's own corner rounding
|
||||
assert all(ytop - v <= 1 for v in prof), "the flat roof is not level"
|
||||
|
||||
# The surface is surface, not outline. `top[x]` is the column's own
|
||||
# silhouette cap -- the black the drawing closes the shape with, the
|
||||
# same black measure() already refuses to let stand as a wall's side
|
||||
# face -- so a surface that samples it promotes a boundary decoration
|
||||
# into texture. A tapered column's cap sits well down the band, where
|
||||
# the depth map spends most of the roof's depth above it.
|
||||
surface_row, sz0, sz1 = roof_surface_row(pr, t)
|
||||
for x in roofed[1:-1]:
|
||||
for z in range(sz0 + 1, sz1):
|
||||
assert surface_row(x, z) != top[x], \
|
||||
f"roof surface wears the silhouette cap at {x},{z} " \
|
||||
f"(row {top[x]})"
|
||||
|
||||
# every wall column carries roof over it -- and a column the roof never
|
||||
# reaches carries nothing at all, rather than being silently trimmed away
|
||||
over = set(roofed)
|
||||
|
||||
Reference in New Issue
Block a user