mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 07:00:51 +02:00
update 2d shiny sprites and shiny animation
This commit is contained in:
@@ -87,6 +87,50 @@
|
||||
on the pattern by luck -- without that, every setting would be itself
|
||||
and 1/8192 in parallel, and no setting could ever be rarer than 8192.
|
||||
|
||||
- **DOORS ON A GATE HOUSE'S OTHER SIDES.** A route gate is walked
|
||||
through, so it opens on two opposite faces -- and the overworld drawing
|
||||
can only show one. The sprite is a facade seen face-on under a roof
|
||||
seen from above, so a SOUTH entrance is drawn (a doorway block in the
|
||||
facade's last rows, which `Structures` folds up into the front face)
|
||||
and a north, east or west one is drawn as nothing at all: the warp
|
||||
sits on the ground cell outside, the art beside it is plain wall.
|
||||
Top-down that reads fine, because the wall is never seen. In 3D you
|
||||
walked into a blank slab -- 33 of them, on eleven gates.
|
||||
|
||||
Each is now a real doorway, one cell of the tileset's own door art
|
||||
standing on the ground of the face you walk into, hung by the SAME
|
||||
rule the drawn facade hangs its own door by: the black frame stays
|
||||
flush with the wall and what it seals sinks a voxel behind it, so a
|
||||
side door and a front door are the same opening at the same depth, and
|
||||
the jambs the recess exposes fall out of the mesher wearing the
|
||||
frame's own texels. The art's outer ring is left alone -- a doorway
|
||||
cell is a cell OF a facade, its border is the wall beside and above
|
||||
the frame, and painting all 16x16 would stamp a one-pixel strip of
|
||||
front-wall art around every door. The same flood the facade tells wall
|
||||
from pane with, bounded to the cell, tells them apart here.
|
||||
|
||||
**Nothing is authored but the art.** `data/voxel_heights.lua` names
|
||||
one door cell per tileset and no coordinates: where the doors go is
|
||||
read off the map, from the warps that land in a gate house and the
|
||||
building standing against them. A warp is an entrance when it leads
|
||||
into a GATE-tileset map, is not already ON a door tile (a drawn south
|
||||
door would be fought over), and stands on a WALKABLE cell -- the ROM
|
||||
gives several gates an unreachable twin warp on the fence or tree
|
||||
beside the real opening, and a door behind a fence is a door into
|
||||
nothing. What is left is the entrance, and the two cells side by side
|
||||
that most gates do have come out as the double door they always were.
|
||||
So no hand list to drift out of step with a map edit, and every other
|
||||
building in the game is untouched: exactly the eleven gate placements
|
||||
get doors.
|
||||
|
||||
Doors belong to the PLACEMENT, not the drawing -- the same 6x4 block
|
||||
is the gate on four routes and the warps sit at different rows of it
|
||||
on each -- so the model cache is keyed by the openings as well, and
|
||||
only placements that agree share a model. It costs about 290 quads a
|
||||
door (a flank quad carries one texel, so the art cannot merge into
|
||||
strips the way a facade's does) and no voxels: the recess removes as
|
||||
many faces as it exposes.
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### Added
|
||||
|
||||
+56
-4
@@ -890,9 +890,8 @@ return {
|
||||
-- which has a PERSON drawn into the tile art -- becomes a monolith
|
||||
-- wearing his face.
|
||||
POKECENTER = {
|
||||
-- the wall band stays one 16px face: striped panels (40), the high
|
||||
-- windows (92-95; 94 doubles as the map's warp tile, and pins are
|
||||
-- look-only), the pokeball poster (2/3/18/19), and the pillars
|
||||
-- the wall band stays one 16px face: striped panels (40), the
|
||||
-- pokeball poster (2/3/18/19), and the pillars
|
||||
-- (16/41) with their bases (4/5/20/21; 20 is the $14 water-fallback
|
||||
-- trap and would recess into a pond lip). The healing machines'
|
||||
-- console face (76/77) and button panel (6/22) are ALSO wall:
|
||||
@@ -905,7 +904,30 @@ return {
|
||||
-- void rule flattens them. What is NOT wall is the machines'
|
||||
-- two flanks -- see `prop` below.
|
||||
wall = { 2, 3, 4, 5, 6, 16, 18, 19, 20, 21, 22, 40, 41,
|
||||
76, 77, 92, 93, 94, 95 },
|
||||
76, 77 },
|
||||
-- THE CABLE CLUB STEPS, cut into the back wall at cells (10,0) and
|
||||
-- (12,0) of every Center -- a flight going DOWN, away from the room,
|
||||
-- and the reason `stair_down_n` exists at all: the profile's other
|
||||
-- stairs run east or west and are drawn from the SIDE, where a drawn
|
||||
-- column is a step; these are drawn HEAD-ON, where a drawn row is,
|
||||
-- and no rotation of the east/west reading produces that.
|
||||
--
|
||||
-- The drawing is its own band table, and it lands exactly on an even
|
||||
-- four-step division of the cell: 4 white rows (the near tread), a
|
||||
-- black nosing, 3 grey, a nosing, 3 checker, then 4 black rows -- the
|
||||
-- dark the flight leaves into, which is also what the far end wall
|
||||
-- wears. Its first and last COLUMNS are the well's black side walls.
|
||||
-- Drawn row = depth row throughout; the rise is the only number the
|
||||
-- head-on view cannot state, and it takes the class height over the
|
||||
-- four steps like every other flight here.
|
||||
--
|
||||
-- Pinned as one cell (the class resolves off the top-left tile) but
|
||||
-- all four ids carry it, and the scan says they cannot reach anything
|
||||
-- else: 22 placements, exactly the two cells in each of the eleven
|
||||
-- Centers, and the Celadon Hotel on the same id places none of them.
|
||||
-- 94 is also the map's warp tile; pins are look-only, so the warp is
|
||||
-- untouched and the steps stay walk-through.
|
||||
stair_down_n = { 92, 93, 94, 95 },
|
||||
-- the counters, half a cell high: top band (8) and the one cell of
|
||||
-- it that carries the push bell (10, lifted off as a figure below --
|
||||
-- the pin stays as the degradation path), front face (24/25, the
|
||||
@@ -3072,6 +3094,36 @@ return {
|
||||
FOREST = { 42, 43, 58, 59 },
|
||||
},
|
||||
|
||||
-- THE DOOR A GATE HOUSE IS ENTERED BY FROM ANY SIDE BUT THE SOUTH.
|
||||
--
|
||||
-- A route gate is walked THROUGH, so it has an opening on two opposite
|
||||
-- sides -- and the drawing can only show one of them. The overworld
|
||||
-- sprite is a facade seen face-on with a roof laid over it, so a south
|
||||
-- entrance is drawn (a doorway block in the facade's last rows, folded up
|
||||
-- by lib/Structures.lua) and a north, east or west one is drawn as
|
||||
-- NOTHING: the warp sits on the ground cell outside, the art beside it is
|
||||
-- plain wall, and top-down that reads fine because you never see the
|
||||
-- wall. In 3D you walk straight into a blank slab.
|
||||
--
|
||||
-- So the door is put back, on the face the player walks into. This names
|
||||
-- only the ART -- one 16x16 cell of the tileset's own doorway block, rows
|
||||
-- north-first, the same ids `frontOnly` above lists as facade-only.
|
||||
-- WHERE it goes is not authored at all: lib/Buildings.lua reads it off
|
||||
-- the map, from the warps that land in a gate and the building standing
|
||||
-- against them (see `sideDoors` there), because the map already states it
|
||||
-- and a hand list of thirty-odd coordinates would only be a chance to get
|
||||
-- one wrong.
|
||||
--
|
||||
-- OVERWORLD is the whole table because every such entrance in the game
|
||||
-- stands against an OVERWORLD building: the Safari Zone's north gate is a
|
||||
-- gap between two fence stubs with no drawing to carve, and the Route 22
|
||||
-- league gate on ROUTE_23 puts its warps on the road THROUGH the arch
|
||||
-- rather than against a wall. Both come out with no door, which is what
|
||||
-- they always had.
|
||||
sideDoors = {
|
||||
OVERWORLD = { { 11, 12 }, { 27, 28 } },
|
||||
},
|
||||
|
||||
-- Buildings whose whole sprite is voxelized band by band (lib/Buildings.lua,
|
||||
-- the pipeline in assets/docs/buidling_to_voxel/). A building is matched by its
|
||||
-- exact tile grid -- the drawings are catalogued in assets/docs/buildings/ -- so
|
||||
|
||||
+275
-5
@@ -136,6 +136,13 @@ end
|
||||
local models = {} -- "<tileset>:<index>" -> prebuilt local quads
|
||||
local frontSets = {} -- tileset id -> { [tile] = true } or false
|
||||
|
||||
-- The tileset's side-door art: the 2x2 tile grid of one doorway cell
|
||||
-- (data/voxel_heights.lua `sideDoors`), or nil when the tileset names none.
|
||||
function Buildings.sideDoorCell(tilesetId)
|
||||
local s = profile()
|
||||
return s and s.sideDoors and s.sideDoors[tilesetId] or nil
|
||||
end
|
||||
|
||||
-- The tileset's front-only tiles as a set (data/voxel_heights.lua
|
||||
-- `frontOnly`): the doorways, shop signs and painted lettering that belong
|
||||
-- on a facade and on no other face of the same building. nil when the
|
||||
@@ -252,6 +259,82 @@ end
|
||||
-- topRows (placement is still by `tiles` alone); they exist so the MODEL
|
||||
-- is built from the complete drawing and the tower rises to its real
|
||||
-- height instead of folding as two half-buildings.
|
||||
-- Composite one doorway cell PAST the end of the sprite, at indices
|
||||
-- W*H .. W*H+255, and hand back its base. The side-door pass paints with
|
||||
-- sprite indices like everything else -- `emit` resolves a voxel's colour
|
||||
-- through sp.ax/sp.ay and knows nothing about where the index came from --
|
||||
-- so the door only has to BE in the sprite arrays to travel the rest of
|
||||
-- the pipeline untouched. Appending rather than drawing into the grid is
|
||||
-- the point: the drawing itself must not change, or the silhouette flood,
|
||||
-- the taper and every measured band would be read off art the tileset
|
||||
-- never placed here.
|
||||
--
|
||||
-- Nothing else walks past W*H (measure's shadeTexel scan and the pane
|
||||
-- flood both stop there), so the block is invisible to measurement and
|
||||
-- visible only to the code that asks for it by index.
|
||||
--
|
||||
-- WHICH OF THE 256 TEXELS ARE THE DOOR. A doorway cell is not a doorway
|
||||
-- edge to edge: the tileset draws it as a cell OF A FACADE, so its outer
|
||||
-- ring is the wall beside and above the frame, and its last row is the
|
||||
-- black threshold the building stands on with the door's own step cut into
|
||||
-- it. Painting all 16x16 onto a flank would stamp a one-pixel border of
|
||||
-- front-wall art around every door.
|
||||
--
|
||||
-- The front facade tells the ring from the door by flooding: the wall
|
||||
-- around the door is one region with the whole facade, far too big to be a
|
||||
-- pane, and only what the black frame SEALS sinks. The same test, bounded
|
||||
-- to the block: flood the left, right and top edges through their own
|
||||
-- shade class, and what the flood reaches is context -- left unpainted, so
|
||||
-- the flank keeps the texel it already had. Not the bottom edge, because
|
||||
-- the bottom edge is the ground: the step under the door is sealed there
|
||||
-- on the drawn facade too, and it recesses with the rest of the doorway.
|
||||
--
|
||||
-- What is painted then splits the way a facade's does: black is frame and
|
||||
-- stays flush with the wall, everything it seals sinks a voxel behind it.
|
||||
local function readDoor(sp, data, perRow, cell)
|
||||
local base = sp.W * sp.H
|
||||
local black = {}
|
||||
for dy = 0, 15 do
|
||||
local row = cell[math.floor(dy / 8) + 1]
|
||||
for dx = 0, 15 do
|
||||
local tile = row[math.floor(dx / 8) + 1]
|
||||
local px = (tile % perRow) * 8 + dx % 8
|
||||
local py = math.floor(tile / perRow) * 8 + dy % 8
|
||||
local k = dy * 16 + dx
|
||||
local i = base + k
|
||||
sp.ax[i], sp.ay[i] = px, py
|
||||
local r, g, b, a = data:getPixel(px, py)
|
||||
sp.col[i] = shadeOf(r, g, b, a)
|
||||
sp.inside[i] = true
|
||||
black[k] = sp.col[i] == BLACK
|
||||
end
|
||||
end
|
||||
|
||||
local context, stack = {}, {}
|
||||
local function seed(dx, dy, cls)
|
||||
if dx < 0 or dx > 15 or dy < 0 or dy > 15 then return end
|
||||
local k = dy * 16 + dx
|
||||
if context[k] or black[k] ~= cls then return end
|
||||
context[k] = true
|
||||
stack[#stack + 1] = k
|
||||
end
|
||||
for dy = 0, 15 do
|
||||
seed(0, dy, black[dy * 16])
|
||||
seed(15, dy, black[dy * 16 + 15])
|
||||
end
|
||||
for dx = 0, 15 do seed(dx, 0, black[dx]) end
|
||||
while #stack > 0 do
|
||||
local k = table.remove(stack)
|
||||
local dx, dy, cls = k % 16, math.floor(k / 16), black[k]
|
||||
seed(dx + 1, dy, cls)
|
||||
seed(dx - 1, dy, cls)
|
||||
seed(dx, dy + 1, cls)
|
||||
seed(dx, dy - 1, cls)
|
||||
end
|
||||
|
||||
sp.door = { base = base, black = black, context = context }
|
||||
end
|
||||
|
||||
local function read(t, data, perRow, frontOnly)
|
||||
local tiles = t.tiles
|
||||
if t.topRows then
|
||||
@@ -1087,11 +1170,113 @@ local function deskSetModel(sp, pr, t)
|
||||
W = W, ytop = ytop, zmin = 0, zmax = D - 1 }
|
||||
end
|
||||
|
||||
-- ------- the doorway a gate house is entered by from a side the drawing
|
||||
-- never shows it on (data/voxel_heights.lua `sideDoors`, and `sideDoorsAt`
|
||||
-- below for how the placements are found).
|
||||
--
|
||||
-- One cell of the tileset's own doorway art, standing on the ground of the
|
||||
-- face the player walks into, and hung by the SAME rule the drawn facade
|
||||
-- hangs its own door by: the art's black frame stays flush with the wall
|
||||
-- and everything it seals sinks a voxel behind it (`measure`'s pane pass,
|
||||
-- applied here by hand because the art is not in the drawing to be flooded
|
||||
-- with it). So a side door and a front door are the same depth of the same
|
||||
-- opening, and the jamb faces the recess exposes come out of the mesher for
|
||||
-- free, wearing the frame's own texels.
|
||||
--
|
||||
-- ORIENTATION IS NOT FREE. A flank quad carries one texel and the mesher
|
||||
-- picks it per voxel, so which art column lands at which world coordinate
|
||||
-- is decided HERE and nowhere else -- and a face is read from outside, so
|
||||
-- the art's own left-to-right runs with the viewer's, not with the world's:
|
||||
-- facing east at a west wall, south is to your right (+z); facing west at
|
||||
-- an east wall, north is (-z); facing south at a north wall, west is (-x).
|
||||
-- Two of the three are mirrored against the axis, which is the same reason
|
||||
-- `backMap` exists -- a wall seen from behind IS the drawing mirrored.
|
||||
local DOOR = 16
|
||||
|
||||
local function sideDoors(at, sp, doors, W)
|
||||
if not (doors and #doors > 0 and sp.door) then return at end
|
||||
local art = sp.door
|
||||
|
||||
-- The face's OUTER surface, walked in from the box edge until the wall
|
||||
-- answers. A drawing inset from its own grid (B03's outer columns are
|
||||
-- terrain, not building) stands its flank a column or two in, and a door
|
||||
-- pinned to the box edge would hang in the air beside it.
|
||||
local function faceX(from, step, off)
|
||||
for k = 0, W - 1 do
|
||||
local x = from + step * k
|
||||
for y = 0, DOOR - 1 do
|
||||
for z = off, off + DOOR - 1 do
|
||||
if at(x, y, z) then return x end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local list = {}
|
||||
for _, d in ipairs(doors) do
|
||||
local face = 0 -- north: the facade's own z origin
|
||||
if d.side == "w" then face = faceX(0, 1, d.at)
|
||||
elseif d.side == "e" then face = faceX(W - 1, -1, d.at) end
|
||||
if face then
|
||||
list[#list + 1] = { side = d.side, off = d.at, face = face }
|
||||
end
|
||||
end
|
||||
if #list == 0 then return at end
|
||||
|
||||
-- art column at (x, z) for door `e`, or nil when the voxel is not in it
|
||||
local function column(e, x, z)
|
||||
if e.side == "n" then
|
||||
if (z == 0 or z == 1) and x >= e.off and x < e.off + DOOR then
|
||||
return e.off + DOOR - 1 - x, z == 0
|
||||
end
|
||||
elseif z >= e.off and z < e.off + DOOR then
|
||||
if e.side == "w" then
|
||||
if x == e.face then return z - e.off, true end
|
||||
if x == e.face + 1 then return z - e.off, false end
|
||||
else
|
||||
if x == e.face then return e.off + DOOR - 1 - z, true end
|
||||
if x == e.face - 1 then return e.off + DOOR - 1 - z, false end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return function(x, y, z)
|
||||
local v = at(x, y, z)
|
||||
-- no wall here is the end of it: a door is hung ON the building, and
|
||||
-- nothing about it may add geometry the drawing does not stand up
|
||||
if v == nil or y >= DOOR then return v end
|
||||
for _, e in ipairs(list) do
|
||||
local c, outer = column(e, x, z)
|
||||
if c then
|
||||
-- art row 0 is the door's head, so the ground row is its last
|
||||
local k = (DOOR - 1 - y) * DOOR + c
|
||||
-- the art's own ring: wall, not door. The flank keeps its texel.
|
||||
if art.context[k] then return v end
|
||||
if art.black[k] then
|
||||
-- the frame, flush with the wall; behind it the wall stands on
|
||||
if outer then return art.base + k end
|
||||
return v
|
||||
end
|
||||
-- and what the frame seals sinks: the face voxel goes, the one
|
||||
-- behind it wears the art. (Written long: `outer and nil or i`
|
||||
-- returns i for BOTH, nil being false to `and`.)
|
||||
if outer then return nil end
|
||||
return art.base + k
|
||||
end
|
||||
end
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
-- The voxel model as a lookup: `at(x, y, z)` is the index of the sprite
|
||||
-- pixel that voxel wears, or nil. Build ORDER is expressed as lookup
|
||||
-- order -- roof first, so it overwrites the walls it intersects, and walls
|
||||
-- are trimmed to its underside so nothing pokes through the surface.
|
||||
local function model(sp, pr, t)
|
||||
-- are trimmed to its underside so nothing pokes through the surface. A
|
||||
-- gate's side doors are hung on the finished lookup, last of all, because
|
||||
-- they answer to the FACE rather than to any band of the drawing.
|
||||
local function model(sp, pr, t, doors)
|
||||
if t.parts then return deskSetModel(sp, pr, t) end
|
||||
local W, H, D = sp.W, sp.H, pr.D
|
||||
local slab, roofRows = t.slab, t.roofRows
|
||||
@@ -1209,6 +1394,8 @@ local function model(sp, pr, t)
|
||||
return pr.interior[i]
|
||||
end
|
||||
|
||||
at = sideDoors(at, sp, doors, W)
|
||||
|
||||
return { at = at, W = W, ytop = ytop,
|
||||
zmin = ledge0 and -2 or 0,
|
||||
zmax = math.max(rz1, ledge0 and (D + 1) or 0) }
|
||||
@@ -1420,6 +1607,76 @@ local function matches(S, t, tx, ty)
|
||||
return true
|
||||
end
|
||||
|
||||
-- ------- which of a placement's faces a gate is entered by
|
||||
--
|
||||
-- Read off the MAP, not authored: a gate entrance is a warp that lands in a
|
||||
-- gate house, and the face is whichever one of this placement the warp cell
|
||||
-- stands against. Thirty-odd doors fall out of two lines of geometry, and
|
||||
-- none of them can drift out of step with a map edit the way a hand list
|
||||
-- would. Three tests, each of them load-bearing:
|
||||
--
|
||||
-- the destination is a GATE tileset -- what makes a building a gate house
|
||||
-- rather than a house with a back door. GATE and FOREST_GATE both, so
|
||||
-- the Viridian Forest pair count; the Safari rest houses are on GATE
|
||||
-- too and are excluded by the next test, their warps being drawn doors
|
||||
-- already.
|
||||
-- the cell is not already a door tile -- a south entrance IS drawn, as a
|
||||
-- doorway block in the facade, and lib/Structures.lua folds it up into
|
||||
-- the front face. Adding a second one there would fight it.
|
||||
-- the cell is WALKABLE -- the ROM gives an unreachable twin warp to
|
||||
-- several gates (a fence cell beside the real opening on Route 7 west
|
||||
-- and Route 16 east, a tree beside Route 6's), and a door on the wall
|
||||
-- behind a fence is a door into nothing. The reachable cells are the
|
||||
-- entrance, and two of them side by side are the gate's real two-cell
|
||||
-- opening, which comes out as the double door it always was.
|
||||
--
|
||||
-- The south face is skipped whether or not it is drawn: it is the one face
|
||||
-- the drawing states in full, so anything it needs it already has.
|
||||
local function sideDoorsAt(map, tileset, tx, ty, bw, bh)
|
||||
local defs = _G.Game and Game.data and Game.data.maps
|
||||
local warps = map.def and map.def.warps
|
||||
if not (defs and warps and warps[1]) then return nil end
|
||||
if not Buildings.sideDoorCell(tileset.id) then return nil end
|
||||
|
||||
local out = nil
|
||||
for _, w in ipairs(warps) do
|
||||
local dest = defs[w.destMap]
|
||||
if dest and dest.tileset and dest.tileset:find("GATE", 1, true)
|
||||
and map:isWalkableCell(w.x, w.y)
|
||||
and not map:isDoorTileCell(w.x, w.y) then
|
||||
-- the cell in the model's own pixels: a cell is two tiles, a tile
|
||||
-- eight pixels, and the placement's origin is (tx, ty) in tiles
|
||||
local lx, lz = w.x * 16 - tx * 8, w.y * 16 - ty * 8
|
||||
local side = nil
|
||||
if lz == -16 and lx >= 0 and lx < bw * 8 then side = "n"
|
||||
elseif lx == -16 and lz >= 0 and lz < bh * 8 then side = "w"
|
||||
elseif lx == bw * 8 and lz >= 0 and lz < bh * 8 then side = "e" end
|
||||
if side then
|
||||
out = out or {}
|
||||
out[#out + 1] = { side = side, at = side == "n" and lx or lz }
|
||||
end
|
||||
end
|
||||
end
|
||||
if out then
|
||||
table.sort(out, function(a, b)
|
||||
if a.side ~= b.side then return a.side < b.side end
|
||||
return a.at < b.at
|
||||
end)
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- The model cache key's door half. A template's doors belong to the
|
||||
-- PLACEMENT -- the same 6x4 block is the gate on four routes and the warps
|
||||
-- sit at different rows of it on each -- so two placements of one drawing
|
||||
-- are two models, and only placements that agree share one.
|
||||
local function doorKey(doors)
|
||||
if not doors then return "" end
|
||||
local parts = {}
|
||||
for i, d in ipairs(doors) do parts[i] = d.side .. d.at end
|
||||
return "#" .. table.concat(parts, ",")
|
||||
end
|
||||
|
||||
-- Find every placement of every template for this map's tileset, build one
|
||||
-- model per template, and stamp it. Returns nothing; the quads land in
|
||||
-- S.objectQuads and the tiles are claimed so the volume path never boxes a
|
||||
@@ -1440,6 +1697,9 @@ function Buildings.build(S, map, data, perRow)
|
||||
if type(t.tiles) == "table" and #t.tiles > 0 then
|
||||
local bh, bw = #t.tiles, #t.tiles[1]
|
||||
local first = t.tiles[1][1]
|
||||
-- the model this placement stamps. Not hoisted out of the loops any
|
||||
-- more: a template's doors belong to the placement, so two hits of
|
||||
-- one drawing on the same map can be two models (see doorKey).
|
||||
local built = nil
|
||||
for ty = 0, th - bh do
|
||||
Budget.tick()
|
||||
@@ -1465,8 +1725,13 @@ function Buildings.build(S, map, data, perRow)
|
||||
end
|
||||
end
|
||||
if free and matches(S, t, tx, ty) then
|
||||
if not built then
|
||||
local key = tileset.id .. ":" .. index
|
||||
do
|
||||
-- keyed per PLACEMENT once a gate's doors are in play (see
|
||||
-- doorKey): the drawing is shared, the openings are not
|
||||
local doors = not t.claimOnly
|
||||
and sideDoorsAt(map, tileset, tx, ty, bw, bh)
|
||||
or nil
|
||||
local key = tileset.id .. ":" .. index .. doorKey(doors)
|
||||
if not models[key] then
|
||||
if t.claimOnly then
|
||||
-- claim the cells, stamp nothing: the drawing here is
|
||||
@@ -1478,8 +1743,13 @@ function Buildings.build(S, map, data, perRow)
|
||||
else
|
||||
local sp = read(t, data, perRow,
|
||||
Buildings.frontOnly(tileset.id))
|
||||
if doors then
|
||||
readDoor(sp, data, perRow,
|
||||
Buildings.sideDoorCell(tileset.id))
|
||||
end
|
||||
local pr = measure(sp, t)
|
||||
models[key] = emit(model(sp, pr, t), sp, atlasW, atlasH)
|
||||
models[key] = emit(model(sp, pr, t, doors), sp,
|
||||
atlasW, atlasH)
|
||||
end
|
||||
end
|
||||
built = models[key]
|
||||
|
||||
+88
-1
@@ -2004,7 +2004,9 @@ local function stairCell(S, map, data, cx, cy, s)
|
||||
local atlasW = map.tileset.imageWidth or 128
|
||||
local atlasH = map.tileset.imageHeight or 48
|
||||
local quads = S.objectQuads
|
||||
local down = s.class == "stair_down_e" or s.class == "stair_down_w"
|
||||
local north = s.class == "stair_down_n"
|
||||
local down = north or s.class == "stair_down_e"
|
||||
or s.class == "stair_down_w"
|
||||
local east = s.class == "stair_e" or s.class == "stair_down_e"
|
||||
local mx, mz = cx * 16, cy * 16
|
||||
local h = s.h or 16
|
||||
@@ -2053,6 +2055,90 @@ local function stairCell(S, map, data, cx, cy, s)
|
||||
end
|
||||
end
|
||||
|
||||
-- A flight running INTO the map instead of across it. The drawing is
|
||||
-- the same staircase seen head-on rather than from the side, and that
|
||||
-- changes which axis of the art means what: a drawn ROW is a step here,
|
||||
-- and -- because looking down a well is looking along its depth -- drawn
|
||||
-- row IS depth row, 1:1 across the cell's 16.
|
||||
--
|
||||
-- The Centers' steps state their own band table and it lands exactly:
|
||||
-- 4 white rows, 1 black, 3 grey, 1 black, 3 checker, 4 black = 16. So
|
||||
-- an even four-step division puts a black NOSING on the southmost row of
|
||||
-- every band (15, 11, 7, 3) and leaves the rows behind it as that step's
|
||||
-- tread. Nothing is authored but the RISE, which no head-on drawing can
|
||||
-- state; the depths, the treads and the nosings are all measured.
|
||||
--
|
||||
-- A nosing is drawn as one row because it is seen nearly edge-on, so
|
||||
-- un-projected it has real height and no depth: its row lies flat as the
|
||||
-- tread's front lip AND stands as the riser under it. That is the one
|
||||
-- texel in the flight used twice, and using it twice is what a nosing is.
|
||||
--
|
||||
-- The well's own walls come free as well: the drawing's first and last
|
||||
-- COLUMNS are its black side walls, and its top band is the darkness the
|
||||
-- flight leaves by, which is what the far end wants to wear.
|
||||
--
|
||||
-- Every quad here is split at the cell's own 8px seam, in x and in rows
|
||||
-- both: `uv` resolves ONE tile per corner, and these four tiles are not
|
||||
-- neighbours in the atlas, so a quad that spans a seam interpolates
|
||||
-- between two unrelated corners of the sheet.
|
||||
if north then
|
||||
local runD = 16 / STAIR_STEPS
|
||||
local HALVES = { { 0.2, 7.9, 0, 8 }, { 8.1, 15.8, 8, 16 } }
|
||||
for i = 0, STAIR_STEPS - 1 do
|
||||
local a0 = 16 - (i + 1) * runD -- band i, in art rows
|
||||
local a1 = a0 + runD
|
||||
local yTop = -(i + 1) * rise
|
||||
local z0b, z1b = mz + a0, mz + a1
|
||||
|
||||
for _, H in ipairs(HALVES) do
|
||||
local ax0, ax1, wx0, wx1 = H[1], H[2], mx + H[3], mx + H[4]
|
||||
|
||||
-- the tread: the whole band, drawn row = depth row, so the nosing
|
||||
-- lies on its front lip exactly where the artist drew it
|
||||
face({ wx0, yTop, z0b }, { wx1, yTop, z0b },
|
||||
{ wx1, yTop, z1b }, { wx0, yTop, z1b },
|
||||
ax0, a1, ax1, a0, STAIR_SHADE.wellTread)
|
||||
|
||||
-- the riser under that lip. It faces NORTH -- a flight descending
|
||||
-- away from you turns its risers away with it, and they close the
|
||||
-- steps from below rather than being looked at. One art row tall,
|
||||
-- so it needs none of `banded`'s row splitting; written straight
|
||||
-- keeps the geometry flush at the seam while the art stays inside
|
||||
-- its tile
|
||||
local ry = -i * rise
|
||||
face({ wx1, yTop, z1b }, { wx0, yTop, z1b },
|
||||
{ wx0, ry, z1b }, { wx1, ry, z1b },
|
||||
ax1, a1 - 1, ax0, a1, STAIR_SHADE.riser)
|
||||
|
||||
-- the deep end, closing the opening this flight is cut into: from
|
||||
-- the floor of the well up to the top of the wall band beside it,
|
||||
-- in the drawing's own black top rows
|
||||
if i == STAIR_STEPS - 1 then
|
||||
face({ wx1, -h, mz }, { wx0, -h, mz },
|
||||
{ wx0, h, mz }, { wx1, h, mz },
|
||||
ax1, 3.9, ax0, 0.1, STAIR_SHADE.wellEnd)
|
||||
end
|
||||
end
|
||||
|
||||
-- the well's side walls above this tread, wearing the drawing's own
|
||||
-- black edge columns -- the excavation is walled in its own texels
|
||||
local function sideWall(px, sx0, sx1, inward)
|
||||
local c
|
||||
if inward then -- west wall, faces E
|
||||
c = { { px, yTop, z1b }, { px, yTop, z0b },
|
||||
{ px, 0, z0b }, { px, 0, z1b } }
|
||||
else -- east wall, faces W
|
||||
c = { { px, yTop, z0b }, { px, yTop, z1b },
|
||||
{ px, 0, z1b }, { px, 0, z0b } }
|
||||
end
|
||||
face(c[1], c[2], c[3], c[4], sx0, a1, sx1, a0, STAIR_SHADE.wellN)
|
||||
end
|
||||
sideWall(mx, 0.1, 1.3, true)
|
||||
sideWall(mx + 16, 14.7, 15.9, false)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
for i = 0, STAIR_STEPS - 1 do
|
||||
local sx0 = east and (i * runW) or (16 - (i + 1) * runW)
|
||||
local sx1 = sx0 + runW
|
||||
@@ -2152,6 +2238,7 @@ function Structures.buildStairs(S, map, x0, x1, y0, y1)
|
||||
-- box or floor it. A rising flight stands on the map's common
|
||||
-- floor; a stairwell IS the hole, so nothing is painted under it
|
||||
local down = s.class == "stair_down_e" or s.class == "stair_down_w"
|
||||
or s.class == "stair_down_n"
|
||||
for dy = 0, 1 do
|
||||
for dx = 0, 1 do
|
||||
local tk = keyOf(cx * 2 + dx, cy * 2 + dy)
|
||||
|
||||
@@ -125,6 +125,12 @@ local FALLBACK_HEIGHTS = {
|
||||
stair_w = 16,
|
||||
stair_down_e = 16,
|
||||
stair_down_w = 16,
|
||||
-- a stairwell descending toward the BACK of the map, drawn head-on
|
||||
-- instead of from the side (the Centers' Cable Club steps). Its own
|
||||
-- class because the art reading is not the east/west one turned: there
|
||||
-- a drawn COLUMN is a step and a drawn row is height, here a drawn ROW
|
||||
-- is a step and drawn row = depth row, 1:1 down the well
|
||||
stair_down_n = 16,
|
||||
}
|
||||
|
||||
-- class -> how the mesher draws it (see the header). The last three are
|
||||
@@ -215,6 +221,7 @@ local ART = {
|
||||
stair_w = "stair",
|
||||
stair_down_e = "stair",
|
||||
stair_down_w = "stair",
|
||||
stair_down_n = "stair",
|
||||
}
|
||||
|
||||
local spec = nil -- the loaded data file, or false when absent
|
||||
|
||||
+15
-2
@@ -503,12 +503,22 @@ local active = false
|
||||
-- which is exactly the old behaviour minus the reflections.
|
||||
local DEPTH_FORMATS = { "depth24", "depth24stencil8", "depth32f", "depth16" }
|
||||
|
||||
-- dpiscale = 1, for the same reason PixelCanvas pins it and for one more:
|
||||
-- newCanvas otherwise takes the WINDOW's scale, and every canvas bound
|
||||
-- together must agree on PIXEL dimensions. The colour canvas beside this one
|
||||
-- comes from PixelCanvas at scale 1, so on any surface whose scale is not 1
|
||||
-- -- Android's density is routinely 2.625, and a retina Mac's is 2 -- this
|
||||
-- one came back 2.625x larger and the pair would not bind. beginScene then
|
||||
-- dropped the readable depth for the session (see below), depthReadable()
|
||||
-- went false, and the water pass never ran at all: the reflections were
|
||||
-- missing on every high-density display, with nothing in the log to say so,
|
||||
-- because a canvas that will not BIND is not a canvas the driver refused.
|
||||
local function newDepth(w, h)
|
||||
if not (love.graphics and love.graphics.newCanvas) then return nil end
|
||||
local c = nil
|
||||
for _, format in ipairs(DEPTH_FORMATS) do
|
||||
local ok, made = pcall(love.graphics.newCanvas, w, h,
|
||||
{ format = format, readable = true })
|
||||
{ format = format, readable = true, dpiscale = 1 })
|
||||
if ok and made then c = made break end
|
||||
end
|
||||
if not c then return nil end
|
||||
@@ -1344,7 +1354,10 @@ end
|
||||
function Voxel3D.beginWater(paint)
|
||||
if not (active and canvas and held and held.depth) then return nil end
|
||||
if not held.mirror then
|
||||
local ok, c = pcall(love.graphics.newCanvas, held.w, held.h)
|
||||
-- through PixelCanvas, because this one is bound WITH held.depth a few
|
||||
-- lines down and the two must agree on pixel dimensions -- the same
|
||||
-- scale trap newDepth documents
|
||||
local ok, c = PixelCanvas.new(held.w, held.h)
|
||||
if not (ok and c) then return nil end
|
||||
pcall(c.setFilter, c, "nearest", "nearest")
|
||||
pcall(c.setWrap, c, "clamp", "clamp")
|
||||
|
||||
Reference in New Issue
Block a user