update 2d shiny sprites and shiny animation

This commit is contained in:
DramaticShape
2026-08-08 19:55:32 -04:00
parent 34e7da5c12
commit 99de8c2760
6 changed files with 485 additions and 12 deletions
+44
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+7
View File
@@ -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
View File
@@ -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")