mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 05:10:58 +02:00
update voxelizations of celadon diner stools and tables. Update square table voxelization in celadon mansion. Update mansion computer desks
This commit is contained in:
@@ -61,6 +61,10 @@ cues generalize:
|
||||
| Band containing window/door frames | Vertical facade | Straight extrusion |
|
||||
| Full-width band with a black underline sitting above an inset band | Ledge / awning overhang | Extrusion + protrusion |
|
||||
| Dark `#555` runs beside a facade under a taper | Shadow on the wall beneath an eave | Leave as wall — the geometry above produces the shadow's meaning |
|
||||
| Scattered light shapes on a dark field, bracketed by TWO full-width black rims, shallow band below the lower rim | The **inside of an open container** seen from above, with contents lying in it | Hollow tray: walls to the rims, floor slab, air between — never an extrusion |
|
||||
| Ellipse drawn wider than tall (e.g. 9x5) | A horizontal circle seen from above — a mouth, a lid, a pot rim | Cut face of a round hull; the aspect ratio is the proof of the top view |
|
||||
| Arcs above/below a round object's straight flanks, lowest point at the centre column, often a 1px #555 halo outside | The SAME circles seen curving — ground contact and mouth back-edge, i.e. depth, not narrowing | Strip them from the revolve; run the last body row's disc to the floor |
|
||||
| A side band shearing sideways as it descends (¾-view) | The projection sliding a receding wall, not the wall's position | Un-project: the wall goes where the plan says |
|
||||
|
||||
The band table for Red's house, which Blue's house shares verbatim:
|
||||
|
||||
@@ -156,9 +160,12 @@ Tooling: `voxel_build_verify.py` (builds, asserts, renders previews).
|
||||
|
||||
1. Obtain the sprite; sample to native resolution via block centers.
|
||||
2. Extract palette + silhouette (light-only flood fill, threshold 130);
|
||||
review the ASCII mask.
|
||||
3. Segment rows into bands using the Stage-2 cues; write the band table
|
||||
before writing any geometry code.
|
||||
review the ASCII mask — rendered large, not hand-counted.
|
||||
3. Name the real object first (including whether it is hollow, round or
|
||||
thin — see "Beyond the house"), then segment rows into bands using the
|
||||
Stage-2 cues; write the band table as prose, one line per row range
|
||||
with where each band lands, before writing any geometry code. The
|
||||
correct reading makes the row arithmetic land exactly.
|
||||
4. Measure taper rates from the mask; derive `T(x)`, `YTOP`, overhangs, `D`.
|
||||
5. Build: extrude verticals (de-outlined interiors) → ledges → recesses →
|
||||
flat top (mid-row cycling) → sloped solids (overwrite, then trim) →
|
||||
@@ -214,3 +221,75 @@ right for the raw GB palette but comes out white once the atlas is
|
||||
recoloured, turning every sloped end into a black-and-white zip. The
|
||||
drawing's own eave is black / `#555` / black, and using that reads correctly
|
||||
under every palette.
|
||||
|
||||
## Beyond the house: the forms later objects added
|
||||
|
||||
The house is all solid masses — every band either lies flat or extrudes.
|
||||
Later objects forced the taxonomy open, and each addition came from the
|
||||
same root move: **name the real 3D form first, then ask which surfaces the
|
||||
drawing shows.** The recurring failure at every step was the *extruded
|
||||
picture* — and it has a second-order form that survives re-segmentation.
|
||||
The Bike Shop's toolbox was re-read from "a prop" into "a cabinet with a
|
||||
pump beside it": named parts, correct plot, de-outlined sides, and still
|
||||
wrong, because the region read as a cabinet *front* was the inside of an
|
||||
open box seen from above. Naming the parts is not enough; every REGION
|
||||
must answer "what surface of the real object is this?" The reliable
|
||||
arbiter is arithmetic: the correct reading makes the drawn row counts land
|
||||
exactly (the toolbox: 1 back-wall rim + 6 interior rows + 1 front rim = 8
|
||||
= the one-tile plot depth). Forcing rows to fit means the reading is wrong.
|
||||
|
||||
**Hollow forms.** An open container is the one shape whose model must
|
||||
contain AIR, which no band table or extrusion can produce. The tray
|
||||
treatment builds four walls to the drawn rims, lays the top-view band on
|
||||
the floor of the cavity (its contents — a wrench — come along free, since
|
||||
they are just pixels of that band), and leaves the space between empty.
|
||||
Two rules only containers hit: the pane-recess pass must never run on a
|
||||
one-voxel wall (it deletes the front voxel to expose the one behind, and
|
||||
there is nothing behind — the wall becomes a hole), and the hollowness
|
||||
needs its own verification assert, because a later change that refills the
|
||||
cavity leaves every count looking plausible.
|
||||
|
||||
**Round forms.** A drawn ellipse wider than tall is a horizontal circle
|
||||
seen from above — that one aspect-ratio measurement settles the whole
|
||||
reading. Straight flanks give diameter and height at once (round in plan,
|
||||
so drawn width IS depth — the one depth never authored). The arcs above
|
||||
and below the straight run are the same top and base circles seen curving:
|
||||
ground contact and mouth edge, not narrowing — revolving them puts the
|
||||
object on a stem. The hull's chord representation stores one z-interval
|
||||
per column/row, so a taper is expressible (re-cut the chords, squeeze the
|
||||
art into the narrowed span so the rim outline survives) but a hollow ring
|
||||
needs a second chord. Voxel resolution bounds taste: on an 11-wide object
|
||||
a one-step taper reads as damage and two steps as a cone; pick the step
|
||||
count and derive the amount.
|
||||
|
||||
**Thin forms.** A line drawing cannot be thick. The air inside a bicycle's
|
||||
frame is what makes it read as a bicycle; extrude each stroke 5 voxels and
|
||||
the side faces of neighbouring strokes close every gap off-axis — six
|
||||
bikes become one dark mass. Standee thickness is a vocabulary
|
||||
(`PINNED_DEPTH`: 1 for paper, 2 for plates and side-on vehicles, 5 for
|
||||
silhouettes, 10 for objects with a body), and when a standee looks wrong
|
||||
the first move is to dump the detector's mask — if the mask is a clean
|
||||
object, thickness is the problem, not segmentation.
|
||||
|
||||
**Authored masks.** When a drawing shares its tiles and shades with what
|
||||
it is painted into, nothing automatic can separate them; the profile
|
||||
carries a pixel mask instead. A person becomes a `figures` card (flat,
|
||||
leaning with the camera, standing on its feet — because GB character art
|
||||
is face-on iconography); an object becomes a `mounted` slab (fixed in the
|
||||
world, holding the wall's plane, keeping its drawn elevation — because a
|
||||
side-on drawing is a plane parallel to the wall). And when the backdrop is
|
||||
a *regular* pattern, the mask should be MEASURED, not hand-drawn:
|
||||
composite the plain backdrop tile over the same grid and flood from the
|
||||
border through pixels that still match it — what the flood cannot reach is
|
||||
the object, sprite-pure and exact.
|
||||
|
||||
**Verification, extended.** Isometric previews miss what only the game
|
||||
shows: shoot in-game at both the ¾ rung and the low rung (front-face holes
|
||||
and proportion errors are invisible from above), crop and NEAREST-upscale
|
||||
before judging, and remember the flat rung renders no model at all. Two
|
||||
cheap renders beat argument: the front-most voxel per (x, y) laid beside
|
||||
the composited drawing catches anchoring and texel leaks instantly, and
|
||||
the same render with sunk voxels flagged turns the recess pass into
|
||||
something you look at. When shared builder code moves, a saved count
|
||||
baseline diffed after every edit (mind the line endings) is what proves a
|
||||
generalization is an identity for every model that already shipped.
|
||||
|
||||
+179
-23
@@ -197,7 +197,25 @@ return {
|
||||
-- cutouts both read wrong for them; the cylinder archetype carves
|
||||
-- one voxel ball per 16x16 cell from the canopy's darkest-pixel
|
||||
-- outline, round in depth, so tree rows become rows of real canopies
|
||||
cylinder = { 42, 43, 58, 59, 64, 65, 80, 81 },
|
||||
--
|
||||
-- The cuttable tree ($2D/$2E/$3D/$3E, the four tiles Cut deletes)
|
||||
-- joins them: it is Celadon Gym's little tree redrawn pixel-for-
|
||||
-- pixel on this atlas (same 146/256 silhouette, only canopy
|
||||
-- highlight texels differ), and takes the identical hull -- the
|
||||
-- scraggly canopy revolves into a gapped ball, the 2-4px trunk
|
||||
-- into a thin round column, the root flare into a round mound.
|
||||
-- It used to sit in the `prop` pool as a 5-voxel standee; see
|
||||
-- GYM's cylinder entry for the reading. Scanned: the 2x2 grid
|
||||
-- occurs 32 times on this atlas across the towns and routes, and
|
||||
-- per-tile counts equal the grid count (32 hits for $2D alone),
|
||||
-- so no stray occurrence renders as a lone hull. The same four
|
||||
-- ids form grids on FOREST (8, Safari Zone ground art), HOUSE,
|
||||
-- MANSION, SHIP_PORT and REDS_HOUSE_2 -- different drawings, id
|
||||
-- collisions, none of this entry's business. Cut itself only
|
||||
-- swaps the map block to plain grass, so the pin never sees a
|
||||
-- cut stump.
|
||||
cylinder = { 42, 43, 58, 59, 64, 65, 80, 81,
|
||||
45, 46, 61, 62 },
|
||||
-- the town sign (blockset 8's SE cell): a standing per-pixel slab
|
||||
-- 2 voxels thin, transparency respected -- never a solid box
|
||||
signpost = { 70, 71, 86, 87 },
|
||||
@@ -220,15 +238,11 @@ return {
|
||||
-- instead -- see there.)
|
||||
wall = { 2, 36 },
|
||||
|
||||
-- the cuttable bush ($2D/$2E/$3D/$3E, the four tiles Cut deletes
|
||||
-- -- across the whole tileset they appear only in the five
|
||||
-- cut-tree blocks): a standing per-pixel cutout 5 voxels deep,
|
||||
-- black-outline segmented with the pixels the outline encloses
|
||||
-- kept, its drawn grass dither flooding away as background
|
||||
prop = { 45, 46, 61, 62 },
|
||||
-- the ground painted under those pinned props, by the prop tile's
|
||||
-- own id: the bush stands on plain grass ($2C) -- the very tile
|
||||
-- Cut leaves behind (field.cutTreeSwaps' after-blocks) -- rather
|
||||
-- (the cuttable tree $2D/$2E/$3D/$3E moved to the cylinder hull
|
||||
-- above)
|
||||
-- the ground painted under the claimed tree cells, by tile id:
|
||||
-- the tree stands on plain grass ($2C) -- the very tile Cut
|
||||
-- leaves behind (field.cutTreeSwaps' after-blocks) -- rather
|
||||
-- than whatever flat tile its neighbours vote
|
||||
prop_ground = { [45] = 44, [46] = 44, [61] = 44, [62] = 44 },
|
||||
},
|
||||
@@ -343,8 +357,25 @@ return {
|
||||
-- -- PEWTER_GYM's walls and rock maze, and BRUNOS_ROOM's
|
||||
-- clusters. DOJO shares gym.png but places none of them, so
|
||||
-- the pin is not copied there.
|
||||
-- Celadon's three little trees ($40/$41 canopy over $50/$51
|
||||
-- trunk) round out the same family: a scraggly canopy over a
|
||||
-- 2-4px trunk flaring into a round root mound. Every drawn row
|
||||
-- states its own width, which is exactly what the hull revolves
|
||||
-- -- the canopy turns into a ball with its drawn gaps kept, the
|
||||
-- trunk into a thin round column, the mound into a round foot --
|
||||
-- so the tree stands beside the hedge balls as a solid object
|
||||
-- of the same construction. It used to sit in the `prop` pool
|
||||
-- ("a trunk is not round"), but the standee rendered as the
|
||||
-- whole 16x16 cell extruded, background and all -- the extruded
|
||||
-- picture -- and the trunk IS round; the hull reads it right.
|
||||
-- Scanned: the 2x2 grid occurs 3 times on this atlas and only
|
||||
-- in CELADON_GYM -- cells (2,4), (7,5), (5,7) -- and per-tile
|
||||
-- counts equal the grid count (3 hits for $40 alone), so no
|
||||
-- stray occurrence renders as a lone hull. DOJO shares gym.png
|
||||
-- and places none, so the pin is not copied there.
|
||||
cylinder = { 44, 45, 46, 47,
|
||||
7, 8, 23, 24 },
|
||||
7, 8, 23, 24,
|
||||
64, 65, 80, 81 },
|
||||
-- Vermilion Gym's trash cans ($0B/$0C over $1B/$1C), the switch
|
||||
-- puzzle's fifteen cans plus the sixteenth beside the leader's
|
||||
-- platform. An open galvanised bin in the 3/4 view, and its plan is
|
||||
@@ -405,13 +436,9 @@ return {
|
||||
can_well = 5,
|
||||
can_taper = 4,
|
||||
heights = { can = 9 },
|
||||
-- The statues and Celadon's three little trees ($40/$41 canopy over
|
||||
-- $50/$51 trunk). A trunk is not round, so the tree cannot be a
|
||||
-- ball like the shrubs beside it -- it takes the thin standee pool
|
||||
-- every interior plant takes, which is also a pool apart from the
|
||||
-- cylinders it touches.
|
||||
prop = { 2, 56, 18, 19,
|
||||
64, 65, 80, 81 },
|
||||
-- The statues. (Celadon's trees $40/$41/$50/$51 lived here
|
||||
-- too until they moved to the cylinder hull above.)
|
||||
prop = { 2, 56, 18, 19 },
|
||||
-- The Hall of Fame's recording machine, the one piece of real
|
||||
-- furniture in the tileset. It is drawn 32px wide and THREE tile
|
||||
-- rows tall against the north band, and the detector made a mess
|
||||
@@ -685,7 +712,15 @@ return {
|
||||
-- per-cell hulls rather than boxes
|
||||
canopy = { 4 },
|
||||
cylinder = { 5, 6, 7, 21, 22, 23,
|
||||
35, 36, 37, 38, 39, 53, 54 },
|
||||
35, 36, 37, 38, 39, 53, 54,
|
||||
-- the Safari Zone's small round trees ($54/$55/$56/$57,
|
||||
-- one cell, 376 placements across the four safari maps
|
||||
-- and nowhere else on this tileset): drawn as a canopy
|
||||
-- ball like the overworld's lone tree, and the detector
|
||||
-- was boxing them into 16px dither-textured crates.
|
||||
-- One voxel ball per cell, the same hull the big trees'
|
||||
-- quarter tiles degrade to
|
||||
84, 85, 86, 87 },
|
||||
-- the stumps ($02/$03/$12/$13): a hull whose drawn top is a CUT
|
||||
-- FACE. The body builds from the bark rows alone, and the drawn
|
||||
-- ellipse of growth rings projects onto the hull's round flat
|
||||
@@ -1488,9 +1523,14 @@ return {
|
||||
-- terrace: their north rim (9/25 = $09/$19) and the
|
||||
-- pedestal course at the south (85/86/87).
|
||||
--
|
||||
-- THE ROUND TABLES in full, because the shape is a compromise. The
|
||||
-- drawing (block 29, and the same four rows split across blocks 45
|
||||
-- and 49 in the diner) is
|
||||
-- THE ROUND TABLES in full, because the shape is a compromise.
|
||||
-- (The `diner_round_table` template under `buildings` below now
|
||||
-- models all four placements in full -- octagonal top on its
|
||||
-- pedestal -- by matching the whole 4x4 grid, which is what a
|
||||
-- per-tile pin can never do. Everything here stays as its
|
||||
-- degradation path and as the record of why the pins look the
|
||||
-- way they do.) The drawing (block 29, and the same four rows
|
||||
-- split across blocks 45 and 49 in the diner) is
|
||||
-- $09 $27 $27 $19 an octagonal top seen from above, with
|
||||
-- $36 $37 $37 $39 a pedestal drawn below its southern
|
||||
-- $46 $37 $37 $47 rim
|
||||
@@ -1511,7 +1551,7 @@ return {
|
||||
-- 8px is the FAR rim (9/25, and the shared 39/54/57) and the
|
||||
-- pedestal (85/86/87): the far rim is occluded by the 16px top in
|
||||
-- front of it, and the pedestal is meant to sit low. 16px is also
|
||||
-- the right height against the 8px `stool` chairs drawn around it
|
||||
-- the right height against the seat-high `stool` chairs around it
|
||||
-- -- a terrace table you sit at, not a footstool.
|
||||
counter = { 9, 21, 25, 36, 37, 38, 39, 41, 48, 49, 52, 53, 54,
|
||||
57, 85, 86, 87 },
|
||||
@@ -1541,7 +1581,15 @@ return {
|
||||
-- drawing carries a full black outline with the floor dither
|
||||
-- showing at all four corners, so the standee segments cleanly --
|
||||
-- and `stool` keeps its own pool, apart from anything it touches.
|
||||
-- The `diner_stool` template (see `buildings` below) now models
|
||||
-- every placement in full, like the house stool it copies; these
|
||||
-- pins are its degradation path.
|
||||
stool = { 7, 8, 23, 24 },
|
||||
-- the `diner_stool` template stands 5 voxels (the drawn
|
||||
-- elevation: the seat's front edge at row 10 over legs 11-14),
|
||||
-- as the house stool does: whoever sits on a stool cell rides
|
||||
-- this height, not the 8px class default
|
||||
heights = { stool = 5 },
|
||||
-- Deliberately NOT pinned:
|
||||
-- $37 (55) is three different things -- the light half of the
|
||||
-- checkerboard floor, the interior of the round tables, and
|
||||
@@ -4169,6 +4217,26 @@ return {
|
||||
roofRows = 28, roofBack = 24, roofFront = 0, roofCycle = { 2, 23 },
|
||||
slab = 3, frontEave = 0, ledge = nil,
|
||||
},
|
||||
-- F07b: the SQUARE table of CELADON_MANSION_1F cells (0,6):(1,7)
|
||||
-- (1 placement, scan.lua) -- the long table (F07) at two cells
|
||||
-- wide, the same drawing to the tile everywhere but the interior
|
||||
-- column count, and the same read to the row: 0-23 the tabletop
|
||||
-- seen from above, 24-26 the slab's black/#555/black front edge,
|
||||
-- 27 the #555 shadow that closes it (slab = 3, folded into the
|
||||
-- band), 28-30 the base with the legs stopping one row short of
|
||||
-- the grid. Family numbers unchanged; the `table` pin stays as
|
||||
-- the degradation path, neutralized where this stamps.
|
||||
{
|
||||
id = "mansion_square_table",
|
||||
tiles = {
|
||||
{ 38, 39, 39, 41 },
|
||||
{ 54, 55, 55, 57 },
|
||||
{ 54, 55, 55, 57 },
|
||||
{ 60, 58, 58, 59 },
|
||||
},
|
||||
roofRows = 28, roofBack = 24, roofFront = 0, roofCycle = { 2, 23 },
|
||||
slab = 3, frontEave = 0, ledge = nil,
|
||||
},
|
||||
},
|
||||
|
||||
HOUSE = {
|
||||
@@ -4480,5 +4548,93 @@ return {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
LOBBY = {
|
||||
-- F09 on the lobby atlas: the Celadon department store's stools
|
||||
-- -- the diner's chairs, the roof terrace's, the Game Corner's
|
||||
-- six rows and the four on 1F (58 placements, the scan's only
|
||||
-- matches on this atlas). A DIFFERENT drawing from the house
|
||||
-- stool -- it sits one row HIGHER in the tile (seat top rows 4-9
|
||||
-- over its front edge at 10 and the legs at 11-14, with a clear
|
||||
-- floor row below) and its leg detail differs -- but the same
|
||||
-- object band for band, so it takes the house part table with
|
||||
-- the bands shifted up one row. The measured ground line (15
|
||||
-- here, 16 in the house) shifts with them, so the stand is the
|
||||
-- same 5 voxels, and the tileset's `stool = 5` height override
|
||||
-- keeps whoever sits here ON the seat. The old stool standee
|
||||
-- pins stay as the degradation path.
|
||||
{
|
||||
id = "diner_stool",
|
||||
tiles = {
|
||||
{ 7, 8 },
|
||||
{ 23, 24 },
|
||||
},
|
||||
roofRows = 0, roofBack = 0, roofFront = 0, roofCycle = { 0, 0 },
|
||||
slab = 0, frontEave = 0, ledge = nil,
|
||||
panes = false,
|
||||
parts = {
|
||||
{ kind = "upright", x = { 2, 13 }, top = { 4, 9 },
|
||||
facade = { 10, 14 }, z = 3, depth = 11,
|
||||
stretch = true }, -- the stool
|
||||
},
|
||||
},
|
||||
-- F11: the ROUND TABLE of the diner and the roof terrace -- 4
|
||||
-- placements, all on this atlas (CELADON_DINER cells (0,2) and
|
||||
-- (0,5), CELADON_MART_ROOF (4,2) and (8,4); scan
|
||||
-- "9,39,39,25;54,55,55,57;70,55,55,71;85,86,87,55" matches
|
||||
-- nowhere else). This is the drawing the long `counter` note
|
||||
-- above calls a compromise -- its four interior tiles are $37,
|
||||
-- unpinnable, so the flat treatment let the whole top BE a 16px
|
||||
-- disc. The template matches the exact 4x4 grid instead, which
|
||||
-- is what a per-tile pin can never do, and un-projects the three
|
||||
-- facings: rows 0-23 the OCTAGONAL top seen from above (24
|
||||
-- top-view rows = 24 depth rows, so the plan is the silhouette
|
||||
-- itself, a `plan` slab 32x24), rows 24-25 the slab's #555/black
|
||||
-- fascia folded down its rim, rows 26-31 the PEDESTAL seen under
|
||||
-- the front edge -- two flattened circles, i.e. horizontal discs:
|
||||
-- the base (diameter 16, drawn cols 8-23, side rows 30-31, its
|
||||
-- top wearing the drawn shadow-and-white rows 26-29) and the dark
|
||||
-- column (diameter 6, cols 13-18, rows 26-27 repeating up the
|
||||
-- shaft), both on the drawn centre x 16 / plan centre z 12.
|
||||
-- MEASURED: plan, diameters, centres, slab 3. AUTHORED: tabletop
|
||||
-- plane 8 -- counter height, developer-tuned (the first cut
|
||||
-- stood it at the flat compromise's 16px and it read too tall)
|
||||
-- -- plus base height 2 and the 3-voxel column between. `depth`
|
||||
-- 3 keeps the plot to the drawn plan; the grid's 4th tile row is
|
||||
-- the pedestal's own drawing plus one floor tile ($37 again, at
|
||||
-- the southeast corner), which the claim paints as ground.
|
||||
-- `scrub` repoints the top's interior field -- the four $37
|
||||
-- tiles, ALSO the checkerboard floor's light half, which carry
|
||||
-- the floor's palette in a colorized atlas -- at the same grey
|
||||
-- sourced from the rim's own field, so the whole top wears the
|
||||
-- table's palette (the drawn field there is uniform grey;
|
||||
-- nothing drawn is lost). The old wall/counter pins on the rim
|
||||
-- tiles stay as the degradation path, and `support` carries the
|
||||
-- top plane so anything the standee scan finds on these cells
|
||||
-- rides the tabletop.
|
||||
{
|
||||
id = "diner_round_table",
|
||||
tiles = {
|
||||
{ 9, 39, 39, 25 },
|
||||
{ 54, 55, 55, 57 },
|
||||
{ 70, 55, 55, 71 },
|
||||
{ 85, 86, 87, 55 },
|
||||
},
|
||||
roofRows = 0, roofBack = 0, roofFront = 0, roofCycle = { 0, 0 },
|
||||
slab = 0, frontEave = 0, ledge = nil, depth = 3,
|
||||
panes = false, support = 8,
|
||||
scrub = { { 8, 8, 23, 23 } },
|
||||
parts = {
|
||||
{ kind = "disc", cx2 = 32, cz2 = 24, r = 8, rise = 0, h = 2,
|
||||
side = { rows = { 30, 31 }, x = { 13, 18 } },
|
||||
cap = { rows = { 26, 29 }, x = { 9, 22 } } }, -- the base
|
||||
{ kind = "disc", cx2 = 32, cz2 = 24, r = 3, rise = 2, h = 3,
|
||||
side = { rows = { 26, 27 }, x = { 14, 17 } } }, -- the column
|
||||
{ kind = "plan", x = { 0, 31 }, rows = { 0, 23 },
|
||||
fascia = { 24, 25 }, fasciaX = { 8, 23 },
|
||||
rise = 5 }, -- the top
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+90
-1
@@ -436,7 +436,8 @@ local function deskSetModel(sp, pr, t)
|
||||
local function buildParts(plane)
|
||||
for _, p in ipairs(t.parts) do
|
||||
Budget.tick()
|
||||
local x0, x1 = p.x[1], p.x[2]
|
||||
local x0 = p.x and p.x[1] or 0
|
||||
local x1 = p.x and p.x[2] or (W - 1)
|
||||
if p.kind == "flat" then
|
||||
-- drawn row = depth row by default; `z` renames the origin when
|
||||
-- the flat sits below the desk's own drawn top span (the Center
|
||||
@@ -561,6 +562,94 @@ local function deskSetModel(sp, pr, t)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif p.kind == "plan" then
|
||||
-- A PLAN part is a slab whose plan IS the drawn top view: the
|
||||
-- band's silhouette becomes the footprint pixel for pixel
|
||||
-- (drawn row = depth row, the same 1:1 every tabletop is drawn
|
||||
-- with), so an octagonal top stands as an octagon rather than
|
||||
-- the box no rectangular band can escape. The top layer wears
|
||||
-- the band itself, outline and all; the rim layers below wear
|
||||
-- the drawn fascia rows folded down the edge (x clamped into
|
||||
-- the drawn fascia's span), and the slab's unseen interior the
|
||||
-- field's dark texel.
|
||||
local r0, r1 = p.rows[1], p.rows[2]
|
||||
local f0, f1 = p.fascia[1], p.fascia[2]
|
||||
local fx0, fx1 = p.fasciaX[1], p.fasciaX[2]
|
||||
local rise = p.rise or 0
|
||||
local h = (f1 - f0 + 1) + 1
|
||||
if rise + h > ytop then ytop = rise + h end
|
||||
local function drawn(sx, z)
|
||||
return sx >= x0 and sx <= x1 and z >= 0 and z <= r1 - r0
|
||||
and inside[(r0 + z) * W + sx]
|
||||
end
|
||||
for z = 0, r1 - r0 do
|
||||
if z >= 0 and z < D then
|
||||
local sy = r0 + z
|
||||
for sx = x0, x1 do
|
||||
if inside[sy * W + sx] then
|
||||
put(sx, rise + h - 1, z, sy * W + sx)
|
||||
local edge = not (drawn(sx - 1, z) and drawn(sx + 1, z)
|
||||
and drawn(sx, z - 1) and drawn(sx, z + 1))
|
||||
for y = rise, rise + h - 2 do
|
||||
if edge then
|
||||
local fsx = math.max(fx0, math.min(fx1, sx))
|
||||
put(sx, y, z, (f0 + (rise + h - 2 - y)) * W + fsx)
|
||||
else
|
||||
put(sx, y, z, pr.shadeTexel[DARK])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif p.kind == "disc" then
|
||||
-- A DISC part is ROUND IN PLAN -- the pedestal column and base
|
||||
-- the projection can only draw from the front. Centre and
|
||||
-- radius are measured off the drawn widths (a flattened arc is
|
||||
-- a horizontal circle seen from above); the circular footprint
|
||||
-- is synthesized like any continued geometry, and every voxel
|
||||
-- still wears the drawing: the side folds the drawn face-on
|
||||
-- rows around the hull (x clamped into the drawn span, rows
|
||||
-- repeating up the height), and `cap` lays the drawn top-view
|
||||
-- rows over the top layer's interior, drawn north rows to the
|
||||
-- plan's north. `cx2`/`cz2` are DOUBLED plan centres, so an
|
||||
-- even diameter keeps its centre between two voxels instead of
|
||||
-- limping one off.
|
||||
local r, rise, h = p.r, p.rise or 0, p.h
|
||||
local s0, s1 = p.side.rows[1], p.side.rows[2]
|
||||
local sa0, sa1 = p.side.x[1], p.side.x[2]
|
||||
local sn = s1 - s0 + 1
|
||||
if rise + h > ytop then ytop = rise + h end
|
||||
local function inDisc(x, z)
|
||||
local dx = 2 * x + 1 - p.cx2
|
||||
local dz = 2 * z + 1 - p.cz2
|
||||
return dx * dx + dz * dz <= 4 * r * r
|
||||
end
|
||||
local zlo = math.floor((p.cz2 - 2 * r) / 2)
|
||||
for x = math.floor((p.cx2 - 2 * r) / 2),
|
||||
math.floor((p.cx2 + 2 * r) / 2) do
|
||||
for z = math.max(0, zlo),
|
||||
math.min(D - 1, math.floor((p.cz2 + 2 * r) / 2)) do
|
||||
if inDisc(x, z) then
|
||||
local edge = not (inDisc(x - 1, z) and inDisc(x + 1, z)
|
||||
and inDisc(x, z - 1) and inDisc(x, z + 1))
|
||||
for y = rise, rise + h - 1 do
|
||||
local sx, sy
|
||||
if p.cap and y == rise + h - 1 and not edge then
|
||||
local c0, c1 = p.cap.rows[1], p.cap.rows[2]
|
||||
sy = math.min(c1, c0 + math.floor((z - zlo)
|
||||
* (c1 - c0 + 1)
|
||||
/ (2 * r)))
|
||||
sx = math.max(p.cap.x[1], math.min(p.cap.x[2], x))
|
||||
else
|
||||
sy = s0 + (rise + h - 1 - y) % sn
|
||||
sx = math.max(sa0, math.min(sa1, x))
|
||||
end
|
||||
put(x, y, z, sy * W + sx)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
local tr0, tr1 = p.top[1], p.top[2]
|
||||
local fr0, fr1 = p.facade[1], p.facade[2]
|
||||
|
||||
+208
-2
@@ -680,6 +680,23 @@ TEMPLATES = {
|
||||
roof_rows=28, roof_back=24, roof_front=0, roof_cycle=(2, 23),
|
||||
slab=3, front_eave=0, ledge=None, tileset="mansion",
|
||||
),
|
||||
# F07b: the SQUARE table of CELADON_MANSION_1F cells (0,6):(1,7)
|
||||
# (1 placement) -- the long table (F07) at two cells wide, the same
|
||||
# drawing to the tile everywhere but the interior column count, and
|
||||
# the same read to the row: 0-23 the tabletop seen from above, 24-26
|
||||
# the slab's black/#555/black front edge, 27 the #555 shadow that
|
||||
# closes it (slab = 3, folded into the band), 28-30 the base with the
|
||||
# legs stopping one row short of the grid. Family numbers unchanged.
|
||||
"mansion_square_table": dict(
|
||||
tiles=[
|
||||
[38, 39, 39, 41],
|
||||
[54, 55, 55, 57],
|
||||
[54, 55, 55, 57],
|
||||
[60, 58, 58, 59],
|
||||
],
|
||||
roof_rows=28, roof_back=24, roof_front=0, roof_cycle=(2, 23),
|
||||
slab=3, front_eave=0, ledge=None, tileset="mansion",
|
||||
),
|
||||
# F08: the dining table of the generic town house -- 18 placements,
|
||||
# every home's cells (3,3):(4,4) -- the chief's long table (F07) at
|
||||
# two cells wide. The same read to the row: 0-23 the rounded
|
||||
@@ -853,6 +870,79 @@ TEMPLATES = {
|
||||
z=3, depth=11, stretch=True), # the stool
|
||||
],
|
||||
),
|
||||
# F09 on the lobby atlas: the Celadon department store's stools --
|
||||
# the diner's chairs, the roof terrace's, the Game Corner's six
|
||||
# rows and the four on 1F (58 placements). A DIFFERENT drawing from
|
||||
# the house stool -- it sits one row HIGHER in the tile (seat top
|
||||
# rows 4-9 over front edge 10 and legs 11-14, with a clear floor
|
||||
# row below) and its leg detail differs -- but the same object band
|
||||
# for band, so it takes the same part table with the bands shifted
|
||||
# up one row. The measured ground line (15 here, 16 in the house)
|
||||
# shifts with them, so the stand is the same 5 voxels.
|
||||
"diner_stool": dict(
|
||||
tiles=[
|
||||
[7, 8],
|
||||
[23, 24],
|
||||
],
|
||||
roof_rows=0, roof_back=0, roof_front=0, roof_cycle=(0, 0),
|
||||
slab=0, front_eave=0, ledge=None, tileset="lobby",
|
||||
panes=False,
|
||||
parts=[
|
||||
dict(kind="upright", x=(2, 13), top=(4, 9), facade=(10, 14),
|
||||
z=3, depth=11, stretch=True), # the stool
|
||||
],
|
||||
),
|
||||
# F11: the ROUND TABLE of the Celadon diner and the roof terrace --
|
||||
# 4 placements, all on the lobby atlas (CELADON_DINER cells (0,2)
|
||||
# and (0,5), CELADON_MART_ROOF (4,2) and (8,4); scan
|
||||
# "9,39,39,25;54,55,55,57;70,55,55,71;85,86,87,55", no matches on
|
||||
# any other atlas). The drawing packs three facings no band split
|
||||
# can reach: rows 0-23 are the OCTAGONAL top seen from above (24
|
||||
# top-view rows = 24 depth rows, the same 1:1 every tabletop is
|
||||
# drawn with -- so the plan is the silhouette itself, 32 wide by 24
|
||||
# deep), rows 24-25 the slab's own fascia (#555 over black, folded
|
||||
# down the rim under the band's drawn outline -> slab 3), and rows
|
||||
# 26-31 the PEDESTAL seen under the front edge: the base's top
|
||||
# surface with the dark column rising from its middle (26-27), its
|
||||
# lit south half (28-29), and its front arc curving to the floor
|
||||
# (30-31). The flattened arcs are horizontal CIRCLES seen from
|
||||
# above -- depth, not narrowing -- so the pedestal is two discs:
|
||||
# base diameter 16 (drawn cols 8-23), column diameter 6 (the dark
|
||||
# blob's cols 13-18), both centred on the drawn centre x=16, plan
|
||||
# centre z=12. MEASURED: plan, diameters, centre, slab 3. AUTHORED:
|
||||
# tabletop plane 8 -- counter height, developer-tuned (the first
|
||||
# cut stood it at the flat compromise's 16px and it read too tall)
|
||||
# -- plus base height 2 and the 3-voxel column between (the
|
||||
# projection cannot state either; the drawn base arc suggests a
|
||||
# low disc). depth 3 keeps the plot to the drawn plan's 24 rows;
|
||||
# the grid's 4th tile row is the pedestal's own drawing and the
|
||||
# floor tile at its southeast corner, which the claim paints as
|
||||
# ground. `scrub` repoints the top's interior field -- the four
|
||||
# $37 tiles, which are ALSO the checkerboard floor's light half
|
||||
# and carry the floor's palette in a colorized atlas -- at the
|
||||
# same grey sourced from the rim's own field, so the whole top
|
||||
# wears the table's palette (the drawn field there is uniform
|
||||
# grey; nothing drawn is lost).
|
||||
"diner_round_table": dict(
|
||||
tiles=[
|
||||
[9, 39, 39, 25],
|
||||
[54, 55, 55, 57],
|
||||
[70, 55, 55, 71],
|
||||
[85, 86, 87, 55],
|
||||
],
|
||||
roof_rows=0, roof_back=0, roof_front=0, roof_cycle=(0, 0),
|
||||
slab=0, front_eave=0, ledge=None, tileset="lobby", depth=3,
|
||||
panes=False, scrub=[(8, 8, 23, 23)],
|
||||
parts=[
|
||||
dict(kind="disc", cx2=32, cz2=24, r=8, rise=0, h=2,
|
||||
side=dict(rows=(30, 31), x=(13, 18)),
|
||||
cap=dict(rows=(26, 29), x=(9, 22))), # the base
|
||||
dict(kind="disc", cx2=32, cz2=24, r=3, rise=2, h=3,
|
||||
side=dict(rows=(26, 27), x=(14, 17))), # the column
|
||||
dict(kind="plan", x=(0, 31), rows=(0, 23),
|
||||
fascia=(24, 25), fascia_x=(8, 23), rise=5), # the top
|
||||
],
|
||||
),
|
||||
# F04: the Pokemon Center's PC -- every Center's northeast corner
|
||||
# (11 placements) plus the Indigo Plateau lobby, whose MART tileset
|
||||
# shares this atlas. The lab desk-set read again: a
|
||||
@@ -1333,7 +1423,7 @@ def build_desk_set(sp, pr, t):
|
||||
|
||||
def build_parts(plane):
|
||||
for p in t["parts"]:
|
||||
x0, x1 = p["x"]
|
||||
x0, x1 = p.get("x", (0, W - 1))
|
||||
if p["kind"] == "flat":
|
||||
r0, r1 = p["rows"]
|
||||
# `at` names the sheet's own height when it does not lie
|
||||
@@ -1445,6 +1535,91 @@ def build_desk_set(sp, pr, t):
|
||||
if pr0 <= sy <= pr1 and inside(sx, sy):
|
||||
put(sx, plane + y, z, sx, sy)
|
||||
continue
|
||||
if p["kind"] == "plan":
|
||||
# A PLAN part is a slab whose plan IS the drawn top view:
|
||||
# the band's silhouette becomes the footprint pixel for
|
||||
# pixel (drawn row = depth row, the same 1:1 every
|
||||
# tabletop is drawn with), so an octagonal top stands as
|
||||
# an octagon rather than the box no rectangular band can
|
||||
# escape. The top layer wears the band itself, outline
|
||||
# and all; the rim layers below wear the drawn fascia
|
||||
# rows folded down the edge (x clamped into the drawn
|
||||
# fascia's span), and the slab's unseen interior the
|
||||
# field's dark texel.
|
||||
r0, r1 = p["rows"]
|
||||
f0, f1 = p["fascia"]
|
||||
fx0, fx1 = p["fascia_x"]
|
||||
rise = p.get("rise", 0)
|
||||
h = (f1 - f0 + 1) + 1
|
||||
dark = shade_px.get(DARK) or shade_px[BLACK]
|
||||
|
||||
def drawn(sx, z):
|
||||
return (x0 <= sx <= x1 and 0 <= z <= r1 - r0
|
||||
and inside(sx, r0 + z))
|
||||
|
||||
for z in range(r1 - r0 + 1):
|
||||
if not 0 <= z < D:
|
||||
continue
|
||||
sy = r0 + z
|
||||
for sx in range(x0, x1 + 1):
|
||||
if not inside(sx, sy):
|
||||
continue
|
||||
put(sx, rise + h - 1, z, sx, sy)
|
||||
edge = not (drawn(sx - 1, z) and drawn(sx + 1, z)
|
||||
and drawn(sx, z - 1) and drawn(sx, z + 1))
|
||||
for y in range(rise, rise + h - 1):
|
||||
if edge:
|
||||
put(sx, y, z, max(fx0, min(fx1, sx)),
|
||||
f0 + (rise + h - 2 - y))
|
||||
else:
|
||||
put(sx, y, z, dark[0], dark[1])
|
||||
continue
|
||||
if p["kind"] == "disc":
|
||||
# A DISC part is ROUND IN PLAN -- the pedestal column
|
||||
# and base the projection can only draw from the front.
|
||||
# Centre and radius are measured off the drawn widths
|
||||
# (a flattened arc is a horizontal circle seen from
|
||||
# above); the circular footprint is synthesized like any
|
||||
# continued geometry, and every voxel still wears the
|
||||
# drawing: the side folds the drawn face-on rows around
|
||||
# the hull (x clamped into the drawn span, rows
|
||||
# repeating up the height), and `cap` lays the drawn
|
||||
# top-view rows over the top layer's interior, drawn
|
||||
# north rows to the plan's north. `cx2`/`cz2` are
|
||||
# DOUBLED plan centres, so an even diameter keeps its
|
||||
# centre between two voxels instead of limping one off.
|
||||
r, rise, h = p["r"], p.get("rise", 0), p["h"]
|
||||
s0, s1 = p["side"]["rows"]
|
||||
sa0, sa1 = p["side"]["x"]
|
||||
sn = s1 - s0 + 1
|
||||
cap = p.get("cap")
|
||||
|
||||
def in_disc(x, z):
|
||||
dx = 2 * x + 1 - p["cx2"]
|
||||
dz = 2 * z + 1 - p["cz2"]
|
||||
return dx * dx + dz * dz <= 4 * r * r
|
||||
|
||||
zlo = (p["cz2"] - 2 * r) // 2
|
||||
for x in range((p["cx2"] - 2 * r) // 2,
|
||||
(p["cx2"] + 2 * r) // 2 + 1):
|
||||
for z in range(max(0, zlo),
|
||||
min(D - 1, (p["cz2"] + 2 * r) // 2) + 1):
|
||||
if not in_disc(x, z):
|
||||
continue
|
||||
edge = not (in_disc(x - 1, z) and in_disc(x + 1, z)
|
||||
and in_disc(x, z - 1)
|
||||
and in_disc(x, z + 1))
|
||||
for y in range(rise, rise + h):
|
||||
if cap and y == rise + h - 1 and not edge:
|
||||
c0, c1 = cap["rows"]
|
||||
sy = min(c1, c0 + ((z - zlo)
|
||||
* (c1 - c0 + 1)) // (2 * r))
|
||||
sx = max(cap["x"][0], min(cap["x"][1], x))
|
||||
else:
|
||||
sy = s0 + (rise + h - 1 - y) % sn
|
||||
sx = max(sa0, min(sa1, x))
|
||||
put(x, y, z, sx, sy)
|
||||
continue
|
||||
tr0, tr1 = p["top"]
|
||||
fr0, fr1 = p["facade"]
|
||||
pd = p["depth"]
|
||||
@@ -1933,7 +2108,7 @@ def verify_desk_set(vox, pr, t):
|
||||
# sink) -- and nothing stands anywhere else
|
||||
tops = {}
|
||||
for p in t["parts"]:
|
||||
x0, x1 = p["x"]
|
||||
x0, x1 = p.get("x", (0, W - 1))
|
||||
if p["kind"] == "flat":
|
||||
r0, r1 = p["rows"]
|
||||
z0 = p.get("z", r0)
|
||||
@@ -1979,6 +2154,37 @@ def verify_desk_set(vox, pr, t):
|
||||
for y in range(plane, plane + h + 1):
|
||||
assert (x, y, z) in vox, \
|
||||
f"iso box hole at {x},{y},{z}"
|
||||
elif p["kind"] == "plan":
|
||||
# its one geometric intent: the plan IS the drawn band's
|
||||
# silhouette -- a solid slab column wherever the band draws
|
||||
r0, r1 = p["rows"]
|
||||
h = (p["fascia"][1] - p["fascia"][0] + 1) + 1
|
||||
rise = p.get("rise", 0)
|
||||
for x in range(x0, x1 + 1):
|
||||
for z in range(min(r1 - r0 + 1, D)):
|
||||
if not pr["inside"](x, r0 + z):
|
||||
continue
|
||||
tops[(x, z)] = max(tops.get((x, z), 0), rise + h - 1)
|
||||
for y in range(rise, rise + h):
|
||||
assert (x, y, z) in vox, \
|
||||
f"plan slab hole at {x},{y},{z}"
|
||||
elif p["kind"] == "disc":
|
||||
# its one geometric intent: a solid circle in plan at every
|
||||
# layer, symmetric about the authored centre
|
||||
r, rise, h = p["r"], p.get("rise", 0), p["h"]
|
||||
in_disc = lambda x, z: ((2 * x + 1 - p["cx2"]) ** 2
|
||||
+ (2 * z + 1 - p["cz2"]) ** 2
|
||||
<= 4 * r * r)
|
||||
for x in range((p["cx2"] - 2 * r) // 2,
|
||||
(p["cx2"] + 2 * r) // 2 + 1):
|
||||
for z in range(D):
|
||||
if not in_disc(x, z):
|
||||
continue
|
||||
assert in_disc(p["cx2"] - 1 - x, z), \
|
||||
f"disc asymmetric at {x},{z}"
|
||||
tops[(x, z)] = max(tops.get((x, z), 0), rise + h - 1)
|
||||
for y in range(rise, rise + h):
|
||||
assert (x, y, z) in vox, f"disc hole at {x},{y},{z}"
|
||||
else:
|
||||
fr0, fr1 = p["facade"]
|
||||
ytp = plane + p.get("rise", 0) + (fr1 - fr0)
|
||||
|
||||
Reference in New Issue
Block a user