mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 11:50:50 +02:00
129 lines
4.9 KiB
Lua
129 lines
4.9 KiB
Lua
-- Driver: prove out the voxel sun pass.
|
|
--
|
|
-- Reports whether the shadow map can run, dumps the map itself as a PNG
|
|
-- (the packed depth reads as a red/green gradient -- what matters is that
|
|
-- it is populated and the right way up), and screenshots a stand point at
|
|
-- every camera pitch so the cast shadows can be eyeballed against the flat
|
|
-- ground truth.
|
|
--
|
|
-- POKEPORT_DRIVER=mods/DRAMATIC_SHAPE/tests/voxel_shadow_probe.lua lovec .
|
|
--
|
|
-- knobs (env):
|
|
-- SHADOW_MAP map id (default PALLET_TOWN)
|
|
-- SHADOW_SPOT "x,y[,facing]" (default 5,6,down)
|
|
-- SHOT_DIR output directory, must exist (default "shots")
|
|
return function(game)
|
|
local U = dofile("tests/drivers/util.lua")
|
|
local Pipelines = require("src.render.Pipelines")
|
|
|
|
local SPEED = math.max(1,
|
|
math.floor(tonumber(os.getenv("POKEPORT_SPEED")) or 1))
|
|
local function wait(n) U.wait(n * SPEED) end
|
|
|
|
local DIR = os.getenv("SHOT_DIR") or "shots"
|
|
local mapId = os.getenv("SHADOW_MAP") or "PALLET_TOWN"
|
|
local sx, sy, facing = (os.getenv("SHADOW_SPOT") or "5,6,down")
|
|
:match("^%s*(%d+)%s*,%s*(%d+)%s*,?%s*(%a*)")
|
|
facing = (facing ~= "" and facing) or "down"
|
|
|
|
-- reach the mod's lib namespace the way a companion mod would
|
|
local handle = game.mods.exports["DRAMATIC_SHAPE"]
|
|
local V = handle and handle.lib
|
|
assert(V, "DRAMATIC_SHAPE exports not reachable")
|
|
local ShadowMap = V.require("ShadowMap")
|
|
local Voxel3D = V.require("Voxel3D")
|
|
local VoxelGrid = V.require("VoxelGrid")
|
|
|
|
-- SHADOW_GRID=1 forces the voxel wireframe on for the run, and
|
|
-- SHADOW_CURVE=n the curved-world rung -- neither touches the player's
|
|
-- persisted setting
|
|
if os.getenv("SHADOW_GRID") == "1" then
|
|
VoxelGrid.sync(true)
|
|
print("[shadow] voxel grid forced on; shader built="
|
|
.. tostring(Voxel3D.shader(true) ~= nil))
|
|
end
|
|
local WorldCurve = V.require("WorldCurve")
|
|
local curve = math.floor(tonumber(os.getenv("SHADOW_CURVE")) or 0)
|
|
if curve > 0 then
|
|
WorldCurve.sync(curve)
|
|
print(("[shadow] world curve rung %d (amount %.2f)")
|
|
:format(curve, WorldCurve.AMOUNTS[curve + 1] or 0))
|
|
end
|
|
|
|
-- SHADOW_SUN="kx,kz" retunes the bearing for one run, so a sun can be
|
|
-- compared against another without an edit-rebuild cycle
|
|
local skx, skz = (os.getenv("SHADOW_SUN") or ""):match("^(-?[%d.]+),(-?[%d.]+)$")
|
|
if skx then
|
|
ShadowMap.KX, ShadowMap.KZ = tonumber(skx), tonumber(skz)
|
|
Voxel3D.SHADOW_KX, Voxel3D.SHADOW_KZ = ShadowMap.KX, ShadowMap.KZ
|
|
end
|
|
|
|
print(("[shadow] sun shear kx=%.2f kz=%.2f alpha=%.2f res=%d")
|
|
:format(ShadowMap.KX, ShadowMap.KZ, Voxel3D.SHADOW_ALPHA, ShadowMap.res))
|
|
|
|
-- pin the zoom: the world view size drives the light frustum, and a run
|
|
-- that inherits whatever the player left in options.lua is not
|
|
-- comparable with the one before it
|
|
local Zoom = require("src.render.Zoom")
|
|
Zoom.reset()
|
|
local steps = math.floor(tonumber(os.getenv("SHADOW_ZOOM")) or 0)
|
|
for _ = 1, math.abs(steps) do
|
|
Zoom.step(steps > 0 and 1 or -1, game.renderer and game.renderer:fitScale())
|
|
end
|
|
|
|
Pipelines.setLevel("tiltshift", 0)
|
|
U.teleport(game, mapId, tonumber(sx), tonumber(sy), facing)
|
|
wait(20)
|
|
do
|
|
local vw, vh = game.renderer:worldViewSize()
|
|
print(("[shadow] world view %dx%d px, zoom offset %d")
|
|
:format(vw, vh, Zoom.offset))
|
|
end
|
|
|
|
Pipelines.setLevel("voxel", 0)
|
|
wait(25)
|
|
game.capturePath = ("%s/shadow_%s_flat.png"):format(DIR, mapId)
|
|
wait(3)
|
|
|
|
print("[shadow] available=" .. tostring(ShadowMap.available()))
|
|
|
|
for level = 1, Pipelines.maxLevel("voxel") do
|
|
Pipelines.setLevel("voxel", level)
|
|
wait(30) -- outlast the 0.25s camera tween
|
|
local label = Pipelines.levelLabel("voxel", level) or level
|
|
game.capturePath = ("%s/shadow_%s_v%s.png"):format(DIR, mapId, label)
|
|
wait(3)
|
|
local e = ShadowMap.extent or { 0, 0, 0 }
|
|
print(("[shadow] level %s active=%s bias=%.6f frustum=%.0fx%.0f deep %.0f"
|
|
.. " (%.2f world px/texel)")
|
|
:format(label, tostring(ShadowMap.active()), ShadowMap.bias,
|
|
e[1], e[2], e[3], e[1] / ShadowMap.res))
|
|
end
|
|
|
|
-- the map itself: packed depth, so red is the high byte of "how far the
|
|
-- sun got". A blank (all-white) dump means nothing was drawn into it.
|
|
local canvas = ShadowMap.texture()
|
|
if canvas and canvas.newImageData then
|
|
local ok, err = pcall(function()
|
|
local data = canvas:newImageData()
|
|
local w, h = data:getDimensions()
|
|
local hit = 0
|
|
for y = 0, h - 1, 8 do
|
|
for x = 0, w - 1, 8 do
|
|
local r = data:getPixel(x, y)
|
|
if r < 0.999 then hit = hit + 1 end
|
|
end
|
|
end
|
|
print(("[shadow] map %dx%d, %d/%d sampled texels written")
|
|
:format(w, h, hit, (w / 8) * (h / 8)))
|
|
data:encode("png", "shadowmap.png")
|
|
end)
|
|
print("[shadow] dump " .. (ok and "ok (save dir/shadowmap.png)"
|
|
or tostring(err)))
|
|
end
|
|
|
|
Pipelines.setLevel("voxel", 0)
|
|
wait(5)
|
|
print("[shadow] done")
|
|
end
|