mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 07:00:51 +02:00
water updates
This commit is contained in:
@@ -259,10 +259,15 @@ local function castShadows(state, arena, terrain, nbMesh, cx, cy, vw, vh,
|
||||
-- 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.
|
||||
-- marked as the CAST, so a fight staged at the water's edge does not lay a
|
||||
-- cut-out of a Pokemon across the lake (see ShadowMap.sprites); the arena's
|
||||
-- own floor still takes them, which is the shadow that matters here
|
||||
ShadowMap.sprites(true)
|
||||
for _, card in ipairs(cards or {}) do
|
||||
ShadowMap.draw(BattleBillboard.mesh(), card.tex,
|
||||
ShadowMap.snug(card.model))
|
||||
end
|
||||
ShadowMap.sprites(false)
|
||||
|
||||
ShadowMap.finish(sig)
|
||||
end
|
||||
@@ -309,9 +314,36 @@ end
|
||||
BattleScene.FLASH_COLOR = { 1, 1, 1 }
|
||||
BattleScene.FLASH_STRENGTH = 0.5
|
||||
|
||||
-- ------- the tile clock, while the overworld is not the one drawing
|
||||
--
|
||||
-- Water and flowers animate off TileRenderer's 60Hz counter, and the ENGINE
|
||||
-- only advances it from OverworldState:drawWorld -- which runs under dialogs
|
||||
-- and menus, but not under a battle, because a battle draws instead of the
|
||||
-- overworld rather than over it. So for the length of a staged fight the
|
||||
-- counter stood still: the water tiles stopped rotating their pixels and the
|
||||
-- wave field, which is driven off the same number so the two cannot drift
|
||||
-- (see Water), stopped with them. A lake in the background of a battle was a
|
||||
-- photograph.
|
||||
--
|
||||
-- Ticked HERE rather than from the mod's update hook, because here is the
|
||||
-- one place that means "a staged battle is drawing this frame, and the
|
||||
-- overworld is not". From the update hook the condition would have to be
|
||||
-- guessed at, and a frame where both ran would double the rate.
|
||||
local function tickTiles()
|
||||
local Game = require("src.core.Game")
|
||||
local ow = Game and Game.overworld
|
||||
local top = Game and Game.stack and Game.stack:top()
|
||||
-- during the wipe INTO a battle the overworld can still be the one
|
||||
-- drawing, and it is ticking the clock itself; two ticks in a frame would
|
||||
-- run the water at double speed
|
||||
if top and ow and top == ow then return end
|
||||
pcall(require("src.render.TileRenderer").tick)
|
||||
end
|
||||
|
||||
function BattleScene.render(state, arena, textures, token)
|
||||
if not (state and state.map and arena) then return nil end
|
||||
if not Voxel3D.available() then return nil end
|
||||
tickTiles()
|
||||
|
||||
-- the floor the fight is staged on: normally the player's own, sometimes
|
||||
-- another floor of the same cave or building (see BattleArena)
|
||||
|
||||
+30
-2
@@ -130,17 +130,23 @@ local SHADER = [[
|
||||
}
|
||||
#endif
|
||||
#ifdef PIXEL
|
||||
uniform float sprite; // 1 while the CAST is being drawn; see ShadowMap.sprites
|
||||
vec4 effect(vec4 color, Image tex, vec2 tc, vec2 sc) {
|
||||
// the same alpha discard the main pass uses: a sprite card casts its
|
||||
// silhouette, not its 16x16 bounding box
|
||||
if (Texel(tex, tc).a < 0.5) discard;
|
||||
// pack into two channels: the high byte in red, the low in green
|
||||
// pack into two channels: the high byte in red, the low in green.
|
||||
// Blue says WHAT cast this, which costs a channel that was zero anyway
|
||||
// and lets a surface decline one kind of caster -- water does, for the
|
||||
// people (see Water's sunLit).
|
||||
float d = clamp(vDepth, 0.0, 1.0) * 255.0;
|
||||
return vec4(floor(d) / 255.0, fract(d), 0.0, 1.0);
|
||||
return vec4(floor(d) / 255.0, fract(d), sprite, 1.0);
|
||||
}
|
||||
#endif
|
||||
]]
|
||||
|
||||
ShadowMap._source = function() return SHADER end -- named for the suite
|
||||
|
||||
local shader = nil -- nil = untried, false = unavailable
|
||||
local canvas = nil -- nil = untried, false = unavailable
|
||||
local canvasRes = 0 -- the edge `canvas` was made at
|
||||
@@ -440,6 +446,9 @@ function ShadowMap.begin(cx, cy, vw, vh)
|
||||
love.graphics.setShader(sh)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
pcall(sh.send, sh, "lightVP", "row", ShadowMap.clipVP)
|
||||
-- the world until a cast pass says otherwise, reset per pass so one that
|
||||
-- forgot to put it back cannot leak into the next map's terrain
|
||||
pcall(sh.send, sh, "sprite", 0)
|
||||
drawing = true
|
||||
ready = false
|
||||
return true
|
||||
@@ -448,6 +457,25 @@ end
|
||||
-- Draw one caster. Same signature as Voxel3D.draw minus the camera-ward
|
||||
-- pull, which is a trick for the VIEW's depth buffer and would drag a
|
||||
-- shadow off whatever throws it.
|
||||
-- Whether what is drawn next is one of the CAST -- a walker, an authored
|
||||
-- figure, a battle's Pokemon -- rather than part of the world. false for the
|
||||
-- length of such a pass, true to put it back.
|
||||
--
|
||||
-- The map records it per texel (the shader's blue channel) so a surface can
|
||||
-- decline that kind of caster, and exactly one does: water. A character
|
||||
-- standing at a lake's edge threw a hard cut-out of its own sprite across
|
||||
-- the surface, which on something showing the sky and the shoreline reads as
|
||||
-- a sticker rather than as a shadow in the water. Everything else -- ground,
|
||||
-- roofs, ledges, the characters themselves -- still takes them.
|
||||
--
|
||||
-- Sent rather than branched, so a caller that forgets to put it back only
|
||||
-- mislabels casters rather than losing them; begin() resets it per pass.
|
||||
function ShadowMap.sprites(on)
|
||||
if not drawing then return end
|
||||
local sh = getShader()
|
||||
if sh then pcall(sh.send, sh, "sprite", on and 1 or 0) end
|
||||
end
|
||||
|
||||
function ShadowMap.draw(mesh, texture, model)
|
||||
if not (drawing and mesh) then return end
|
||||
local sh = getShader()
|
||||
|
||||
+29
-16
@@ -699,6 +699,11 @@ local function castShadows(state, terrain, nbMesh, posed, cx, cy, vw, vh,
|
||||
ShadowMap.draw(ChunkMesher.flowers(nb.map), atlasFor(nb.map),
|
||||
ShadowMap.snug(Mat4.translate(nb.ox, 0, nb.oy)))
|
||||
end
|
||||
-- From here down it is the CAST, marked as such in the map (see
|
||||
-- ShadowMap.sprites) so water can decline them: everything the world casts
|
||||
-- still shades a lake, a silhouette of somebody standing beside it does
|
||||
-- not. Ground, roofs and the characters themselves take them as before.
|
||||
ShadowMap.sprites(true)
|
||||
-- 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)
|
||||
@@ -720,6 +725,7 @@ local function castShadows(state, terrain, nbMesh, posed, cx, cy, vw, vh,
|
||||
mirror)))
|
||||
end
|
||||
end
|
||||
ShadowMap.sprites(false)
|
||||
|
||||
ShadowMap.finish(sig)
|
||||
end
|
||||
@@ -779,8 +785,30 @@ function VoxelScene.render(state, w, h, vw, vh, paletteFor)
|
||||
Mat4.translate(nb.ox, 0, nb.oy))
|
||||
end
|
||||
|
||||
-- Without a shadow map (headless, or a driver that could not make the
|
||||
-- canvas) the old flat decals stand in: ground-only, characters only,
|
||||
-- but better than a world with nothing under anybody. They go down
|
||||
-- first, as decals the characters then stand over -- depth-tested
|
||||
-- against the terrain just drawn (a shadow behind a building stays
|
||||
-- hidden) but never depth-writing, so the grass pass at the end of the
|
||||
-- frame still wins its feet-overdraw fights.
|
||||
if not Voxel3D.shadowsActive() then
|
||||
Voxel3D.beginShadows()
|
||||
for _, p in ipairs(posed) do
|
||||
drawShadow(p.sprite, p.px, p.py, p.facing, p.phase, p.flip, p.gh,
|
||||
p.lift)
|
||||
end
|
||||
Voxel3D.endShadows()
|
||||
end
|
||||
|
||||
-- and the water over the top of it, reflecting everything just drawn plus
|
||||
-- the sky the frame opened with (see drawWater)
|
||||
-- the sky the frame opened with (see drawWater).
|
||||
--
|
||||
-- After the fallback decals deliberately: those are the stand-in drop
|
||||
-- shadows for a frame with no shadow map, they write no depth, and a
|
||||
-- lake would otherwise wear one as a black smear. Water covers them,
|
||||
-- which is the same answer the shadow map's own pass gives (see
|
||||
-- ShadowMap.sprites) -- people do not shadow water either way.
|
||||
local waterDraws = {}
|
||||
if water then
|
||||
waterDraws[#waterDraws + 1] = { water, atlasFor(state.map), nil }
|
||||
@@ -800,21 +828,6 @@ function VoxelScene.render(state, w, h, vw, vh, paletteFor)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Without a shadow map (headless, or a driver that could not make the
|
||||
-- canvas) the old flat decals stand in: ground-only, characters only,
|
||||
-- but better than a world with nothing under anybody. They go down
|
||||
-- first, as decals the characters then stand over -- depth-tested
|
||||
-- against the terrain just drawn (a shadow behind a building stays
|
||||
-- hidden) but never depth-writing, so the grass pass at the end of the
|
||||
-- frame still wins its feet-overdraw fights.
|
||||
if not Voxel3D.shadowsActive() then
|
||||
Voxel3D.beginShadows()
|
||||
for _, p in ipairs(posed) do
|
||||
drawShadow(p.sprite, p.px, p.py, p.facing, p.phase, p.flip, p.gh,
|
||||
p.lift)
|
||||
end
|
||||
Voxel3D.endShadows()
|
||||
end
|
||||
|
||||
-- Sprite sheets from here to the figure pass: their texture coordinates
|
||||
-- mean nothing to the tileset-shaped glass mask, so the glass is off or
|
||||
|
||||
+1201
File diff suppressed because it is too large
Load Diff
@@ -1675,6 +1675,31 @@ T.check(math.abs(math.sin(Water.LEAN_ELEV) - Water.LEAN_FROM) < 1e-12,
|
||||
VoxelState.angle, Voxel3D.camera = wasAngle, wasCam
|
||||
end
|
||||
|
||||
-- ------- people do not shadow water
|
||||
--
|
||||
-- The sun pass is ONE map, so a surface cannot ask what threw a shadow
|
||||
-- unless the map says -- and it does, in the blue channel, which was zero
|
||||
-- anyway. Water is the only surface that asks: a character standing at a
|
||||
-- lake's edge laid a hard cut-out of its own sprite across a surface already
|
||||
-- showing the sky and the shoreline, which reads as a sticker rather than as
|
||||
-- a shadow. Everything the world casts still shades it.
|
||||
do
|
||||
local ShadowMap = run.loader.exports.DRAMATIC_SHAPE.lib.require("ShadowMap")
|
||||
T.check(type(ShadowMap.sprites) == "function",
|
||||
"the sun pass can be told it is drawing the cast rather than the world")
|
||||
-- inert outside a pass, like every other toggle on it -- a caller that
|
||||
-- brackets a draw it never made must not send to a shader that is not bound
|
||||
T.check(pcall(ShadowMap.sprites, true) and pcall(ShadowMap.sprites, false),
|
||||
"and saying so outside one is harmless")
|
||||
|
||||
local shadowSrc = ShadowMap._source and ShadowMap._source() or nil
|
||||
if shadowSrc then
|
||||
T.check(shadowSrc:find("fract(d), sprite", 1, true) ~= nil,
|
||||
"the marker rides the channel the depth pack left free, so it costs "
|
||||
.. "nothing: the map is still two channels of depth")
|
||||
end
|
||||
end
|
||||
|
||||
-- ------- the compiled variants
|
||||
local plain = Water._source(false)
|
||||
local gridded = Water._source(true)
|
||||
@@ -1704,14 +1729,19 @@ T.check(plain:find("relief(vBent, view, hit, col, face, axis)", 1, true) ~= nil,
|
||||
-- the march's reach grows as one over the ray's descent, so a grazing camera
|
||||
-- asks for hundreds of world pixels of it from a fixed number of samples --
|
||||
-- which stepped over whole crests and smeared the surface into streaks
|
||||
-- a sample is worth a SCREEN pixel of surface, so that is the stride: held
|
||||
-- at a world pixel up close (finer buys nothing and skipping costs the
|
||||
-- pepper) and opened out with distance (holding it there just runs the march
|
||||
-- out of samples part-way down the slab, which flattened the lowest rung's
|
||||
-- whole middle distance)
|
||||
T.check(plain:find("#define WAVE_STRIDE", 1, true) ~= nil
|
||||
and plain:find("float maxSpan = float(WAVE_STEPS) * WAVE_STRIDE;",
|
||||
1, true) ~= nil,
|
||||
"and its span is capped to a stride a sample can actually resolve")
|
||||
and plain:find("max(WAVE_STRIDE, dist * pxAngle / dy)", 1, true) ~= nil,
|
||||
"the relief stride is a screen pixel's worth of surface, floored at a "
|
||||
.. "world pixel")
|
||||
T.check(Water.WAVE_STRIDE <= 1,
|
||||
"which is at most ONE world pixel, because a column is one world pixel "
|
||||
.. "wide -- a longer stride steps over columns, and which ones it misses "
|
||||
.. "changes fragment to fragment, which is the peppery noise")
|
||||
"and that floor is at most ONE world pixel, because a column is one world "
|
||||
.. "pixel wide -- a longer one steps over columns, and which ones it "
|
||||
.. "misses changes fragment to fragment, which is the peppery noise")
|
||||
-- and the art is read off the COLUMN rather than by offsetting the
|
||||
-- fragment's own uv by however far the march happened to travel: one world
|
||||
-- pixel is one texel, so a column's texel follows from where it stands and
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
-- Driver: one overworld-battle screenshot per species, the mon fighting
|
||||
-- ITSELF -- its back pic on the player's mark and its front pic on the
|
||||
-- enemy's, so a single frame shows both sprites the 3D mode draws for it.
|
||||
--
|
||||
-- The point is a visual sweep for pic glitches (holes the paper-fill missed,
|
||||
-- a silhouette cut wrong, a pin that leaves the mon floating), so every shot
|
||||
-- is staged identically: same map, same cells, same beat -- the battle menu,
|
||||
-- both HUD panels up. Whatever differs between two shots is the mon.
|
||||
--
|
||||
-- SHOT_DIR=.scratchpad/mon_shots \
|
||||
-- POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/mon_shots.lua love .
|
||||
--
|
||||
-- Files land as NNN_species.png in dex order.
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local DIR = os.getenv("SHOT_DIR") or ".scratchpad/mon_shots"
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
|
||||
-- every real species the merged data carries, walked in dex order
|
||||
local species = {}
|
||||
for id, def in pairs(game.data.pokemon) do
|
||||
if type(id) == "string" and type(def) == "table"
|
||||
and def.dex and def.dex >= 1 and def.dex <= 151 then
|
||||
species[#species + 1] = { id = id, dex = def.dex }
|
||||
end
|
||||
end
|
||||
table.sort(species, function(a, b) return a.dex < b.dex end)
|
||||
U.log(("%d species"):format(#species))
|
||||
|
||||
game.save.player.name = "RED"
|
||||
|
||||
for _, s in ipairs(species) do
|
||||
-- level 50 both sides: high enough that nothing about the staging is
|
||||
-- species-specific, and a wild battle never awards exp off a menu shot
|
||||
game.save.party = { Pokemon.new(game.data, s.id, 50) }
|
||||
|
||||
U.teleport(game, "ROUTE_1", 5, 8, "down")
|
||||
-- let the neighbourhood's meshes land so the first battle frame is the
|
||||
-- real arena rather than the flat fallback
|
||||
U.wait(60)
|
||||
|
||||
local battle = BattleState.newWild(game, s.id, 50)
|
||||
battle.onFinish = function() end
|
||||
game.overworld:pushBattle(battle)
|
||||
|
||||
-- the wipe, then tap through "Wild X appeared!" and the send-out until
|
||||
-- the battle MENU is actually up -- a fixed tap count lands on whatever
|
||||
-- beat the intro happened to be on, which is how a shot ends up with the
|
||||
-- trainer still standing where the mon should be
|
||||
U.wait(70)
|
||||
for _ = 1, 200 do
|
||||
if battle.phase == "menu" then break end
|
||||
U.tap(game, "a")
|
||||
U.wait(6)
|
||||
end
|
||||
if battle.phase ~= "menu" then
|
||||
U.log(("STUCK before menu: %s (phase %s)"):format(s.id, tostring(battle.phase)))
|
||||
end
|
||||
-- let the send-out slide/ball beat finish so the mon is standing still
|
||||
U.wait(40)
|
||||
U.shot(game, ("%s/%03d_%s.png"):format(DIR, s.dex, s.id:lower()))
|
||||
|
||||
while game.stack:top() and game.stack:top() ~= game.overworld do
|
||||
game.stack:pop()
|
||||
end
|
||||
U.wait(10)
|
||||
end
|
||||
|
||||
U.log("done -- " .. DIR)
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
-- Driver: dump the exact pic textures a live 3D battle draws, per stage --
|
||||
-- the sprite as loaded (raw) and what picImage hands the billboard after the
|
||||
-- palette bake and BattlePics' paper fill (final). Diagnostic for pics that
|
||||
-- render with holes: whichever stage the transparency first appears in is
|
||||
-- the stage that made it.
|
||||
--
|
||||
-- SHOT_DIR=.scratchpad/pic_dump \
|
||||
-- POKEPORT_DRIVER=mods/DramaticShapeVoxelMod/tests/pic_dump.lua love .
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local DIR = os.getenv("SHOT_DIR") or ".scratchpad/pic_dump"
|
||||
local Pokemon = require("src.pokemon.Pokemon")
|
||||
local BattleState = require("src.battle.BattleState")
|
||||
|
||||
local function save(img, path)
|
||||
if not img then U.log("NIL image for " .. path) return end
|
||||
local w, h = img:getDimensions()
|
||||
local g = love.graphics
|
||||
local prev = g.getCanvas()
|
||||
local canvas = g.newCanvas(w, h, { dpiscale = 1 })
|
||||
g.setCanvas(canvas)
|
||||
g.clear(0, 0, 0, 0)
|
||||
g.setBlendMode("replace", "premultiplied")
|
||||
g.setColor(1, 1, 1, 1)
|
||||
g.draw(img, 0, 0)
|
||||
g.setCanvas(prev)
|
||||
g.setBlendMode("alpha")
|
||||
local f = assert(io.open(path, "wb"))
|
||||
f:write(canvas:newImageData():encode("png"):getString())
|
||||
f:close()
|
||||
end
|
||||
|
||||
local SPECIES = os.getenv("PIC_SPECIES")
|
||||
local list = {}
|
||||
if SPECIES then
|
||||
for id in SPECIES:gmatch("[^,%s]+") do list[#list + 1] = id:upper() end
|
||||
else
|
||||
list = { "PIKACHU", "SEEL", "BULBASAUR", "MEWTWO" }
|
||||
end
|
||||
|
||||
for _, id in ipairs(list) do
|
||||
game.save.party = { Pokemon.new(game.data, id, 50) }
|
||||
U.teleport(game, "ROUTE_1", 5, 8, "down")
|
||||
U.wait(30)
|
||||
local battle = BattleState.newWild(game, id, 50)
|
||||
battle.onFinish = function() end
|
||||
game.overworld:pushBattle(battle)
|
||||
U.wait(80)
|
||||
local lo = id:lower()
|
||||
save(battle.enemy.sprite, ("%s/%s_front_raw.png"):format(DIR, lo))
|
||||
save(battle:picImage(battle.enemy.sprite), ("%s/%s_front_final.png"):format(DIR, lo))
|
||||
save(battle.player.sprite, ("%s/%s_back_raw.png"):format(DIR, lo))
|
||||
save(battle:picImage(battle.player.sprite), ("%s/%s_back_final.png"):format(DIR, lo))
|
||||
U.log("dumped " .. id)
|
||||
while game.stack:top() and game.stack:top() ~= game.overworld do
|
||||
game.stack:pop()
|
||||
end
|
||||
U.wait(5)
|
||||
end
|
||||
|
||||
U.log("done -- " .. DIR)
|
||||
love.event.quit()
|
||||
end
|
||||
@@ -0,0 +1,127 @@
|
||||
-- Driver: WHY is the water not reflecting anything?
|
||||
--
|
||||
-- The reflective pass has several links and every one of them fails quietly
|
||||
-- back to flat water, which looks exactly like the row being off. This walks
|
||||
-- the chain in the LIVE game and prints where it stops.
|
||||
--
|
||||
-- POKEPORT_DRIVER=mods/DRAMATIC_SHAPE/tests/water_reflect_probe.lua lovec .
|
||||
--
|
||||
-- knobs (env):
|
||||
-- REFL_MAP map id (default PALLET_TOWN)
|
||||
-- REFL_SPOT "x,y[,facing]" (default 5,6,down)
|
||||
-- REFL_LEVEL voxel rung (default 5, the 75-degree camera,
|
||||
-- which is where a reflection is
|
||||
-- most of what you can see)
|
||||
-- REFL_TIME daytime pin (day/dusk/...) (default: leave as-is)
|
||||
return function(game)
|
||||
local U = dofile("tests/drivers/util.lua")
|
||||
local Pipelines = require("src.render.Pipelines")
|
||||
|
||||
local mapId = os.getenv("REFL_MAP") or "PALLET_TOWN"
|
||||
local level = math.floor(tonumber(os.getenv("REFL_LEVEL")) or 5)
|
||||
local sx, sy, facing = (os.getenv("REFL_SPOT") or "5,6,down")
|
||||
:match("^%s*(%d+)%s*,%s*(%d+)%s*,?%s*(%a*)")
|
||||
facing = (facing ~= "" and facing) or "down"
|
||||
|
||||
local function say(...) print("[water-ssr] " .. string.format(...)) end
|
||||
|
||||
U.teleport(game, mapId, tonumber(sx), tonumber(sy), facing)
|
||||
U.wait(20)
|
||||
Pipelines.setLevel("voxel", level)
|
||||
U.wait(40) -- outlast the camera tween and the build
|
||||
|
||||
local V = game.mods.exports["DRAMATIC_SHAPE"]
|
||||
V = V and V.lib
|
||||
if not V then return say("mod exports unreachable -- is it enabled?") end
|
||||
|
||||
local Water = V.require("Water")
|
||||
local Voxel3D = V.require("Voxel3D")
|
||||
local ChunkMesher = V.require("ChunkMesher")
|
||||
local Sky = V.require("Sky")
|
||||
local DayNight = V.require("DayNight")
|
||||
|
||||
if os.getenv("REFL_TIME") then
|
||||
DayNight.setting:sync(os.getenv("REFL_TIME"))
|
||||
U.wait(5)
|
||||
end
|
||||
|
||||
local ow = game.overworld
|
||||
local map = ow and ow.map
|
||||
if not map then return say("no live map") end
|
||||
|
||||
-- 1. is the row on at all?
|
||||
say("row=%s level=%d", tostring(Water.setting:get()), Water.level())
|
||||
if not Water.enabled() then
|
||||
return say("STOP: WATER is OFF -- press 9, or set the row")
|
||||
end
|
||||
|
||||
-- 2. is there any water on this map to reflect in?
|
||||
local terrain, water = ChunkMesher.pair(map, false)
|
||||
if not terrain then terrain, water = ChunkMesher.pair(map, true) end
|
||||
say("terrain mesh=%s water mesh=%s", tostring(terrain ~= nil),
|
||||
tostring(water ~= nil))
|
||||
if not terrain then
|
||||
return say("STOP: no terrain mesh yet -- the build is still cooking")
|
||||
end
|
||||
if not water then
|
||||
say("STOP: this map has no water surface. That is not a fault unless")
|
||||
say(" you can see a lake: check the tileset's water tiles reach")
|
||||
say(" TileShape (run voxel_survey.lua for the shape breakdown).")
|
||||
return
|
||||
end
|
||||
|
||||
-- 3. did the driver give us a depth texture to read? This is the one
|
||||
-- hardware requirement the rest of the mode does not already have.
|
||||
say("depth canvas readable: %s", tostring(Voxel3D.depthReadable()))
|
||||
if not Voxel3D.depthReadable() then
|
||||
say("STOP: no readable depth canvas on this driver (depth24/readable).")
|
||||
say(" The water falls back to the flat scene shader, which is")
|
||||
say(" exactly what it looked like before this feature existed.")
|
||||
return
|
||||
end
|
||||
|
||||
-- 4. did the shader build? Both variants -- the wireframe one needs
|
||||
-- derivatives, which a driver may refuse on its own.
|
||||
say("shader plain=%s grid=%s",
|
||||
tostring(Water.shader(false) ~= nil), tostring(Water.shader(true) ~= nil))
|
||||
if not Water.shader(false) then
|
||||
return say("STOP: the water shader did not compile -- the mod log has "
|
||||
.. "the driver's own message")
|
||||
end
|
||||
|
||||
-- 5. is there a sky to reflect, and something hanging in it?
|
||||
local ramp, count = Sky.ramp()
|
||||
say("sky ramp=%s bands=%s edge=%s", tostring(ramp ~= nil), tostring(count),
|
||||
tostring(Voxel3D.skyEdge))
|
||||
local body = DayNight.body()
|
||||
if body then
|
||||
local amt = DayNight.glow()
|
||||
local w, h = Voxel3D.size()
|
||||
local rpx = Sky.discRadius(h, Voxel3D.cell or 1,
|
||||
{ moon = body.moon, glowAmt = amt })
|
||||
local ang = rpx / ((h or 1) / math.max(1e-4, Voxel3D.fovY or 1))
|
||||
say("body=%s dir=(%.2f, %.2f, %.2f) disc=%.1fpx (%.2f deg)",
|
||||
body.moon and "moon" or "sun", body.dx, body.dy, body.dz, rpx,
|
||||
math.deg(ang))
|
||||
else
|
||||
say("body: none in the sky right now (set REFL_TIME=day or =night)")
|
||||
end
|
||||
if not ramp then
|
||||
say("NOTE: no band ramp -- indoors, or the ramp could not be built. The")
|
||||
say(" sky half of the reflection is off; the ray march still runs.")
|
||||
end
|
||||
|
||||
-- 6. how much reflection this camera is actually asking for. Both numbers
|
||||
-- fall out of the rung, and between them they explain every "it only
|
||||
-- works at 75" report: Fresnel decides how much shows, and the lean
|
||||
-- decides whether what shows has anything in it.
|
||||
local f = Water.FRESNEL_FLOOR + (Water.FRESNEL_CEIL - Water.FRESNEL_FLOOR)
|
||||
* (1 - Voxel3D.descent) ^ Water.FRESNEL_POWER
|
||||
say("camera: descent %.3f -> fresnel about %.2f, horizon lean %.2f",
|
||||
Voxel3D.descent, f, Water.lean(Voxel3D.descent))
|
||||
say("waves: %d px columns, phase %.2f", Water.WAVE_HEIGHT, Water._waveTime())
|
||||
|
||||
say("OK: every link is live. The effect is strongest at REFL_LEVEL=5 (the")
|
||||
say(" 75-degree rung, where Fresnel is highest and the reflection is")
|
||||
say(" exact) and with a low sun (REFL_TIME=dusk).")
|
||||
end
|
||||
Reference in New Issue
Block a user