mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 08:01:09 +02:00
update battle water
This commit is contained in:
@@ -147,6 +147,32 @@
|
||||
painted. The reflection lands a hair off the sprite's own depth and exactly
|
||||
on its colour, which at a lake's worth of wave is the same picture.
|
||||
|
||||
### Changed
|
||||
|
||||
- **The waves arrive in sets now, and a little slower.** Three fixed trains
|
||||
are an exactly periodic field -- every forty-odd pixels of sea wore the
|
||||
same crest at the same height, which reads as wallpaper the moment a lake
|
||||
is bigger than the repeat. Two long-wavelength fields now ride the
|
||||
dominant train, four to five carrier wavelengths apiece so neither reads
|
||||
as a wave itself: a SWELL that breathes its amplitude, so a few tall
|
||||
crests march through and hand over to a lull that is itself moving, and a
|
||||
BEND that bows its phase, so a crest line curves across the surface
|
||||
instead of ruling itself over all of it. The two lesser trains stay
|
||||
plain: they are texture rather than structure, and a third modulator is
|
||||
the soup the train weights exist to avoid. The step beat comes down from
|
||||
15 to 12 a second -- the crests were hurrying, and a big wave is slower
|
||||
than a walk cycle -- still a clean divisor of the engine's 60, and still
|
||||
exactly one world pixel of dominant-crest travel per step.
|
||||
|
||||
- **Staged battles draw their water plain, whatever the WATER row says.**
|
||||
The reflective pass is tuned for the overworld's ladder of cameras; a
|
||||
battle's camera is PLACED -- low, tilted, framed like a picture -- and
|
||||
under it the pass read wrong: Fresnel opened all the way up, the leaned
|
||||
sky landed on bands the framing never shows, and a lake-sized arena came
|
||||
out as murk wearing the tile art. The battle is a stage set, and stage
|
||||
water is painted: the flat animated tiles the mode always drew, with the
|
||||
mons compositing over them like everything else on the set.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **On Android the water stayed flat, as if the row were off -- and once it
|
||||
|
||||
+12
-21
@@ -435,31 +435,22 @@ function BattleScene.render(state, arena, textures, token)
|
||||
Voxel3D.draw(nbMesh[i], atlasFor(nb.map),
|
||||
Mat4.translate(nb.ox, 0, nb.oy))
|
||||
end
|
||||
-- and the water over it, reflecting the arena and the hour's sky (see
|
||||
-- VoxelScene.drawWater -- the arena fights on the same lakes). The two
|
||||
-- mons are this shot's cast: painted into the reflection copy alone, so
|
||||
-- a fight staged at the water's edge has both of them in the water while
|
||||
-- they still composite over it, exactly as the overworld's walkers do.
|
||||
local waterDraws = {}
|
||||
if water then waterDraws[#waterDraws + 1] = { water, atlasFor(host) } end
|
||||
-- and the water over it -- PLAIN, always: the flat animated tiles, never
|
||||
-- the reflective pass, whatever the WATER row says. The reflection is
|
||||
-- tuned for the overworld's ladder of cameras; this shot's is PLACED --
|
||||
-- low, tilted and framed like a picture -- and under it the pass reads
|
||||
-- wrong: Fresnel opens all the way up, the leaned sky lands on bands the
|
||||
-- framing never shows, and a lake-sized arena comes out as murk wearing
|
||||
-- the tile art. The battle is a stage set, and stage water is painted.
|
||||
-- (No mirror also means the mons need no second draw into one -- they
|
||||
-- just composite over the water below, like everything else on the set.)
|
||||
if water then Voxel3D.draw(water, atlasFor(host)) end
|
||||
for i, nb in ipairs(neighbors) do
|
||||
if nbWater and nbWater[i] then
|
||||
waterDraws[#waterDraws + 1] = { nbWater[i], atlasFor(nb.map),
|
||||
Mat4.translate(nb.ox, 0, nb.oy) }
|
||||
Voxel3D.draw(nbWater[i], atlasFor(nb.map),
|
||||
Mat4.translate(nb.ox, 0, nb.oy))
|
||||
end
|
||||
end
|
||||
if #waterDraws > 0 then
|
||||
VoxelScene.drawWater(waterDraws, function()
|
||||
Voxel3D.seams(false)
|
||||
Voxel3D.glass(false)
|
||||
for _, card in ipairs(monCards(arena, groundY, textures)) do
|
||||
Voxel3D.draw(BattleBillboard.mesh(), card.tex, card.model,
|
||||
BattleBillboard.PULL, ShadowMap.snug(card.model))
|
||||
end
|
||||
Voxel3D.glass(true)
|
||||
Voxel3D.seams(true)
|
||||
end)
|
||||
end
|
||||
-- The mons, standing on their tiles. Depth-tested like everything else,
|
||||
-- so a ledge or a tree between the camera and a Pokemon really is in
|
||||
-- front of it, and the alpha discard cuts the sprite's own outline out of
|
||||
|
||||
+3
-2
@@ -580,8 +580,9 @@ end
|
||||
-- with the row OFF, no depth texture to read, or a shader that would not
|
||||
-- build, the same meshes go through the ordinary scene shader and come out
|
||||
-- as the flat animated water this mode always drew.
|
||||
-- Shared with the overworld battle, which stages its fights on the same
|
||||
-- lakes and wants the same water under them.
|
||||
-- The overworld's alone: the staged battle draws its water plain, always --
|
||||
-- its placed camera reads this pass wrong, and a stage set wants painted
|
||||
-- water anyway (see BattleScene, where the choice is argued).
|
||||
function VoxelScene.drawWater(draws, cast)
|
||||
local plain = true
|
||||
if Water.enabled() and Voxel3D.depthReadable() then
|
||||
|
||||
+70
-10
@@ -237,14 +237,56 @@ Water.WAVE_TRAINS = {
|
||||
{ -0.041, 0.033, 0.55, 0.11 },
|
||||
}
|
||||
|
||||
-- ------- and what keeps them from reading as one pattern
|
||||
--
|
||||
-- Three fixed trains are still an exactly periodic field: every forty-odd
|
||||
-- pixels of sea wears the same crest at the same height, and a lake's worth
|
||||
-- of that reads as wallpaper. Real swell varies two ways a sum of sines
|
||||
-- cannot: waves arrive in SETS -- a few tall ones, then a lull -- and a
|
||||
-- crest line curves as it runs rather than ruling itself across the whole
|
||||
-- surface. Both are put back with one long-wavelength field each, riding
|
||||
-- the DOMINANT train only; the two lesser trains stay plain, because they
|
||||
-- are texture rather than structure and three modulators is soup again.
|
||||
--
|
||||
-- Both wear the trains' own shape, { fx, fz, speed, x }: a direction whose
|
||||
-- length is the spatial frequency, a phase rate, and what the field does.
|
||||
-- Their wavelengths sit four to five times the carrier's, far enough apart
|
||||
-- that neither reads as a wave itself -- the swell as slow weather over the
|
||||
-- crests, the bend as the crests' own drift.
|
||||
--
|
||||
-- THE SWELL scales the dominant train's amplitude; `x` is the DEPTH of the
|
||||
-- deepest lull, as the fraction of the train it takes away. It runs roughly
|
||||
-- along the carrier's own direction and slower than it, which is a wave
|
||||
-- group's honest habit (deep-water groups travel at about half the phase
|
||||
-- speed) -- so sets of crests swell up, march a while, and hand over to a
|
||||
-- calm patch that is itself moving.
|
||||
Water.WAVE_SWELL = { 0.0325, 0.0134, 0.55, 0.35 }
|
||||
|
||||
-- THE BEND adds a slow wobble to the dominant train's phase; `x` is the
|
||||
-- wobble's reach in RADIANS of carrier phase. 1.1 radians against a carrier
|
||||
-- of about forty pixels bows a crest some seven pixels off its line over
|
||||
-- the bend's own hundred-and-seventy-five -- a visible curve, not a
|
||||
-- scribble -- and it runs ACROSS the carrier, which is the direction a
|
||||
-- crest line actually wanders. What it costs is exactness in waveRate's
|
||||
-- derivation: the carrier's local frequency now breathes around the number
|
||||
-- the rate is derived from, so the one-pixel step is the average step
|
||||
-- rather than every step's. The step CLOCK is untouched; only how far a
|
||||
-- bowed stretch of crest moves on one tick varies, and by under a pixel.
|
||||
Water.WAVE_BEND = { -0.0138, 0.0333, 0.35, 1.10 }
|
||||
|
||||
-- ------- and the beat they move on
|
||||
--
|
||||
-- The surface does not slide, it advances in STEPS -- 15 a second, off the
|
||||
-- engine's own frame counter, which is the cadence hand-drawn pixel art is
|
||||
-- animated at and the reason this reads as art rather than as a simulation
|
||||
-- someone forgot to stylise. A surface built out of whole pixels that crawls
|
||||
-- between them smoothly gives away that the quantisation is only skin deep.
|
||||
Water.WAVE_FPS = 15
|
||||
-- The surface does not slide, it advances in STEPS, off the engine's own
|
||||
-- frame counter -- the move that makes this read as art rather than as a
|
||||
-- simulation someone forgot to stylise. A surface built out of whole pixels
|
||||
-- that crawls between them smoothly gives away that the quantisation is
|
||||
-- only skin deep.
|
||||
--
|
||||
-- 12 a second, a shade under the 15 hand-drawn pixel art is usually
|
||||
-- animated at: the crests were hurrying, and a big wave is slower than a
|
||||
-- sprite's walk cycle. Still a clean divisor of the engine's 60, so every
|
||||
-- step spans the same whole number of frames.
|
||||
Water.WAVE_FPS = 12
|
||||
|
||||
-- How far the dominant train advances each of those steps, in WORLD PIXELS.
|
||||
-- One is the honest choice for a stepped surface: the whole field shifts by
|
||||
@@ -984,12 +1026,30 @@ Water._craterSource = craterSource -- named for the suite
|
||||
|
||||
-- and the wave trains, pasted in for the same reason: the rate is derived
|
||||
-- from this table (Water.waveRate), so the field the shader sums has to be
|
||||
-- the one that table describes rather than a copy of it kept in step by hand
|
||||
-- the one that table describes rather than a copy of it kept in step by hand.
|
||||
--
|
||||
-- The dominant train carries the swell and the bend (see WAVE_SWELL): its
|
||||
-- phase wobbles by the bend field and its amplitude breathes with the swell
|
||||
-- envelope, both off the same tables the constants above document. One
|
||||
-- statement per train either way, which is what the suite counts.
|
||||
local function trainSource()
|
||||
local out = {}
|
||||
for _, t in ipairs(Water.WAVE_TRAINS) do
|
||||
out[#out + 1] = (" h += sin(dot(q, vec2(%.4f, %.4f)) + waveT * %.4f)"
|
||||
.. " * %.4f;"):format(t[1], t[2], t[3], t[4])
|
||||
for i, t in ipairs(Water.WAVE_TRAINS) do
|
||||
if i == 1 then
|
||||
local s = Water.WAVE_SWELL
|
||||
local b = Water.WAVE_BEND
|
||||
out[#out + 1] = (
|
||||
" h += sin(dot(q, vec2(%.4f, %.4f)) + waveT * %.4f\n"
|
||||
.. " + %.4f * sin(dot(q, vec2(%.4f, %.4f)) + waveT * %.4f))\n"
|
||||
.. " * %.4f * (1.0 - %.4f * (0.5 + 0.5 *\n"
|
||||
.. " sin(dot(q, vec2(%.4f, %.4f)) + waveT * %.4f)));")
|
||||
:format(t[1], t[2], t[3],
|
||||
b[4], b[1], b[2], b[3],
|
||||
t[4], s[4], s[1], s[2], s[3])
|
||||
else
|
||||
out[#out + 1] = (" h += sin(dot(q, vec2(%.4f, %.4f)) + waveT * %.4f)"
|
||||
.. " * %.4f;"):format(t[1], t[2], t[3], t[4])
|
||||
end
|
||||
end
|
||||
return table.concat(out, "\n")
|
||||
end
|
||||
|
||||
@@ -1542,7 +1542,10 @@ local function at(f)
|
||||
end
|
||||
|
||||
local period = 60 / Water.WAVE_FPS
|
||||
T.eq(period, 4, "15 steps a second is one every four engine frames")
|
||||
T.eq(period, 5, "12 steps a second is one every five engine frames")
|
||||
T.eq(math.floor(period), period,
|
||||
"and the beat divides the engine's 60 exactly, so every step spans the "
|
||||
.. "same whole number of frames")
|
||||
-- inside one step nothing moves; crossing one, it does
|
||||
T.eq(at(0), at(period - 1),
|
||||
"every frame inside one wave step gets the same phase -- the surface "
|
||||
@@ -1575,6 +1578,24 @@ T.eq(select(2, trains:gsub("h %+= sin", "")), #Water.WAVE_TRAINS,
|
||||
T.check(trains:find(("%.4f"):format(t[1]), 1, true) ~= nil,
|
||||
"at the frequency the table states")
|
||||
|
||||
-- the variation that keeps three periodic trains from reading as wallpaper:
|
||||
-- the dominant train's amplitude breathes with the swell and its crests bow
|
||||
-- with the bend, both pasted from their own tables like the trains are
|
||||
T.check(trains:find(("%.4f"):format(Water.WAVE_SWELL[1]), 1, true) ~= nil
|
||||
and trains:find(("%.4f"):format(Water.WAVE_BEND[1]), 1, true) ~= nil,
|
||||
"the swell and the bend reach the shader off the tables that document "
|
||||
.. "them, not off copies kept in step by hand")
|
||||
T.check(Water.WAVE_SWELL[4] > 0 and Water.WAVE_SWELL[4] < 1,
|
||||
"the swell's deepest lull thins the dominant train without deleting or "
|
||||
.. "inverting it -- a sea with sets in it, not a sea that turns off")
|
||||
for _, mod in ipairs({ Water.WAVE_SWELL, Water.WAVE_BEND }) do
|
||||
local mf = math.sqrt(mod[1] * mod[1] + mod[2] * mod[2])
|
||||
T.check(mf * 3.5 < freq,
|
||||
"a modulator's wavelength sits several times the carrier's, far enough "
|
||||
.. "apart that it reads as weather over the waves rather than as a "
|
||||
.. "fourth wave -- which would be the soup the weights exist to avoid")
|
||||
end
|
||||
|
||||
T.check(Water.WAVE_HEIGHT > -TileShapeHeights.water,
|
||||
"the crests stand taller than the recess TileShape sinks water into -- "
|
||||
.. "they are RELIEF inside the quad's own footprint, so a bar that reaches "
|
||||
|
||||
Reference in New Issue
Block a user