mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 11:30:51 +02:00
fixed disconnected shadows
This commit is contained in:
@@ -161,6 +161,18 @@
|
||||
Pokemon go solid white for those frames, silhouette and all, and nothing
|
||||
else in the frame moves.
|
||||
|
||||
- **A standing figure's shadow detached from its feet under a low sun.** The
|
||||
shadow compare forgives `slack` world pixels so lit ground does not acne
|
||||
against its own texels, and that same forgiveness lit the first `slack` of
|
||||
every cast shadow -- so the shadow started a bias-width away from the feet,
|
||||
further the lower the sun reached (the classic peter-panning, invisible at
|
||||
the old fixed 45 degrees and plain at a day/night golden hour). Sprite
|
||||
cards -- characters, authored figures, flowers, battle mons -- are now
|
||||
drawn into the shadow map sunk exactly `slack` down the sun ray
|
||||
(`ShadowMap.sink`), which cancels the bias for the shadow they throw and
|
||||
nothing else: no terrain moved, so the acne margin is untouched, and the
|
||||
shadow root lands back under the feet at every hour.
|
||||
|
||||
### Changed
|
||||
|
||||
- **The two HP boxes snap to the window's edges during a staged battle.** The
|
||||
|
||||
+7
-3
@@ -237,17 +237,21 @@ local function castShadows(state, arena, terrain, nbMesh, cx, cy, vw, vh,
|
||||
for i, nb in ipairs(neighbors) do
|
||||
ShadowMap.draw(nbMesh[i], atlasFor(nb.map), Mat4.translate(nb.ox, 0, nb.oy))
|
||||
end
|
||||
ShadowMap.draw(ChunkMesher.flowers(host), atlasFor(host), nil)
|
||||
-- thin cards are sunk along the ray (ShadowMap.sink) so their shadows
|
||||
-- keep contact with their bases instead of starting a bias-width away
|
||||
ShadowMap.draw(ChunkMesher.flowers(host), atlasFor(host),
|
||||
ShadowMap.sink(nil))
|
||||
for _, nb in ipairs(neighbors) do
|
||||
ShadowMap.draw(ChunkMesher.flowers(nb.map), atlasFor(nb.map),
|
||||
Mat4.translate(nb.ox, 0, nb.oy))
|
||||
ShadowMap.sink(Mat4.translate(nb.ox, 0, nb.oy)))
|
||||
end
|
||||
|
||||
-- the mons themselves, as the same cards the camera will see. Their alpha
|
||||
-- is the silhouette, so what lands on the ground is the shape of the
|
||||
-- Pokemon rather than a blob standing in for one.
|
||||
for _, card in ipairs(cards or {}) do
|
||||
ShadowMap.draw(BattleBillboard.mesh(), card.tex, card.model)
|
||||
ShadowMap.draw(BattleBillboard.mesh(), card.tex,
|
||||
ShadowMap.sink(card.model))
|
||||
end
|
||||
|
||||
ShadowMap.finish(sig)
|
||||
|
||||
@@ -362,6 +362,34 @@ local function fit(cx, cy, vw, vh)
|
||||
ShadowMap.bias = ShadowMap.slack / math.max(1, far - near)
|
||||
end
|
||||
|
||||
-- A CASTER sunk slightly down the sun ray before it is drawn into the map.
|
||||
--
|
||||
-- The depth compare forgives `slack` world pixels (BIAS + the SLOPE term)
|
||||
-- so lit surfaces do not acne against their own texels -- but that same
|
||||
-- forgiveness is what lets the ground right next to a standing figure read
|
||||
-- as lit: the first `slack` of its shadow falls inside the compare's
|
||||
-- tolerance, so on screen the shadow starts that far from the feet, and the
|
||||
-- gap grows as the sun drops (the same bias reaches further horizontally
|
||||
-- the more oblique the ray). Peter-panning, in the literature; it went
|
||||
-- unseen while the sun hung at a fixed 45 degrees and shows plainly at a
|
||||
-- day/night golden hour.
|
||||
--
|
||||
-- Storing the card that much DEEPER along the ray cancels the bias for the
|
||||
-- shadow it throws -- the root lands back under the feet -- while touching
|
||||
-- nothing else: the card's own lookup now reads a deeper stored value and
|
||||
-- is more confidently lit, no terrain moved, and the acne margin the slack
|
||||
-- exists for is intact. For sprite cards and other thin stand-ins only;
|
||||
-- sinking the TERRAIN would cancel the bias everywhere, which is the acne.
|
||||
--
|
||||
-- Valid between begin() and finish(), which is when `slack` matches the
|
||||
-- frustum the map is being drawn with.
|
||||
function ShadowMap.sink(model)
|
||||
local f = sunDir()
|
||||
local s = ShadowMap.slack
|
||||
return Mat4.mul(Mat4.translate(f[1] * s, f[2] * s, f[3] * s),
|
||||
model or IDENTITY)
|
||||
end
|
||||
|
||||
-- Whether the map has to be redrawn for `sig` -- a caller-built stamp of
|
||||
-- everything the pass depends on (camera, terrain meshes, every pose). A
|
||||
-- frame that changes none of it reuses the map it already has, which is
|
||||
|
||||
+12
-7
@@ -520,20 +520,24 @@ local function castShadows(state, terrain, nbMesh, posed, cx, cy, vw, vh,
|
||||
end
|
||||
-- flower billboards live outside the terrain mesh (they draw after the
|
||||
-- characters, pulled -- see render), but the sun still sees them: a
|
||||
-- handful of cutouts per meadow, unlike the grass left out below
|
||||
ShadowMap.draw(ChunkMesher.flowers(state.map), atlasFor(state.map), nil)
|
||||
-- handful of cutouts per meadow, unlike the grass left out below.
|
||||
-- Every thin card from here down is SUNK slightly along the ray
|
||||
-- (ShadowMap.sink) so its shadow keeps contact with its feet instead of
|
||||
-- starting a bias-width away.
|
||||
ShadowMap.draw(ChunkMesher.flowers(state.map), atlasFor(state.map),
|
||||
ShadowMap.sink(nil))
|
||||
for _, nb in ipairs(state.neighbors or {}) do
|
||||
ShadowMap.draw(ChunkMesher.flowers(nb.map), atlasFor(nb.map),
|
||||
Mat4.translate(nb.ox, 0, nb.oy))
|
||||
ShadowMap.sink(Mat4.translate(nb.ox, 0, nb.oy)))
|
||||
end
|
||||
-- authored figures cast too, for the same reason the flowers do: a
|
||||
-- handful of cards per map, and a person with no shadow reads as pasted on
|
||||
eachFigure(state.map, 0, 0, function(mesh, _, caster)
|
||||
ShadowMap.draw(mesh, atlasFor(state.map), caster)
|
||||
ShadowMap.draw(mesh, atlasFor(state.map), ShadowMap.sink(caster))
|
||||
end)
|
||||
for _, nb in ipairs(state.neighbors or {}) do
|
||||
eachFigure(nb.map, nb.ox, nb.oy, function(mesh, _, caster)
|
||||
ShadowMap.draw(mesh, atlasFor(nb.map), caster)
|
||||
ShadowMap.draw(mesh, atlasFor(nb.map), ShadowMap.sink(caster))
|
||||
end)
|
||||
end
|
||||
for _, p in ipairs(posed) do
|
||||
@@ -542,8 +546,9 @@ local function castShadows(state, terrain, nbMesh, posed, cx, cy, vw, vh,
|
||||
local mesh = SpriteBillboards.shadowQuad(def, frame)
|
||||
if mesh then
|
||||
ShadowMap.draw(mesh, p.sprite:resolveImage(),
|
||||
Voxel3D.casterMatrix(p.px, p.py, p.gh + (p.lift or 0),
|
||||
mirror))
|
||||
ShadowMap.sink(
|
||||
Voxel3D.casterMatrix(p.px, p.py, p.gh + (p.lift or 0),
|
||||
mirror)))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2106,6 +2106,28 @@ Voxel3D.tint = { 1, 1, 1 }
|
||||
Voxel3D.vp = nil
|
||||
end
|
||||
|
||||
-- ------- a shadow keeps hold of the feet that throw it
|
||||
--
|
||||
-- The depth compare forgives `slack` world pixels so lit ground does not
|
||||
-- acne, and that forgiveness detaches a standing card's shadow from its
|
||||
-- feet by the same amount -- worse the lower the sun. Cards are drawn into
|
||||
-- the map sunk exactly `slack` down the ray, which cancels the bias for the
|
||||
-- shadow they throw and nothing else.
|
||||
do
|
||||
local ShadowMap = run.loader.exports.DRAMATIC_SHAPE.lib.require("ShadowMap")
|
||||
local Mat4 = run.loader.exports.DRAMATIC_SHAPE.lib.require("Mat4")
|
||||
local dir = ShadowMap.sunDir()
|
||||
local m = ShadowMap.sink(nil)
|
||||
T.check(math.abs(m[4] - dir[1] * ShadowMap.slack) < 1e-9
|
||||
and math.abs(m[8] - dir[2] * ShadowMap.slack) < 1e-9
|
||||
and math.abs(m[12] - dir[3] * ShadowMap.slack) < 1e-9,
|
||||
"sink() moves a caster exactly `slack` down the sun ray")
|
||||
local sunk = ShadowMap.sink(Mat4.translate(10, 0, 6))
|
||||
T.check(math.abs(sunk[4] - (10 + dir[1] * ShadowMap.slack)) < 1e-9
|
||||
and math.abs(sunk[12] - (6 + dir[3] * ShadowMap.slack)) < 1e-9,
|
||||
"and composes over the caster's own transform, not instead of it")
|
||||
end
|
||||
|
||||
Pipelines.reset()
|
||||
run.release()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user