mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 12:11:03 +02:00
112 lines
4.1 KiB
Lua
112 lines
4.1 KiB
Lua
-- Voxel world mode: the camera angle and its tween.
|
|
--
|
|
-- Deliberately the same shape as src/render/Tilt.lua -- level 0 is off and
|
|
-- 1..3 are the same 15/35/50 degree ladder, eased the same way. What
|
|
-- differs is what the renderer does with the angle: tilt projects the flat
|
|
-- world canvas as one rigid plane, voxel mode drives a real 3D camera over
|
|
-- extruded terrain and voxel character models.
|
|
--
|
|
-- And because it is a real camera over real geometry, it has a rung tilt
|
|
-- could never have: 75 degrees, low enough to read as a diorama shot from
|
|
-- table height. Tilt's flat plane degenerates into a horizon line there,
|
|
-- but geometry only gets more of itself to show.
|
|
--
|
|
-- The LEVEL is not ours. The engine's render_pipelines plumbing owns it --
|
|
-- the options row, the hotkey, the ladder labels, persistence in
|
|
-- save.options.pipelines.voxel, and the mutual exclusion with tilt -- and
|
|
-- hands it to update() every frame. All this module keeps is the eased
|
|
-- ANGLE that level implies, because the tween is renderer state and only
|
|
-- the renderer knows what to do with a half-raised camera.
|
|
--
|
|
-- Purely presentational, like tilt and survey zoom: nothing here reaches
|
|
-- collision, movement, triggers or scripts.
|
|
|
|
local Voxel = {}
|
|
|
|
Voxel.ANGLES_DEG = { 0, 15, 35, 50, 75 }
|
|
Voxel.ANGLE_LABELS = { "OFF", "15", "35", "50", "75" }
|
|
Voxel.MAX_LEVEL = #Voxel.ANGLES_DEG - 1
|
|
|
|
Voxel.level = 0
|
|
Voxel.angle = 0
|
|
Voxel.from = 0
|
|
Voxel.goal = 0
|
|
Voxel.t = 1
|
|
|
|
-- Whether the scene has terrain to show for the current map. VoxelScene
|
|
-- maintains it every frame; while the first mesh of a fresh toggle is
|
|
-- still building, update() holds the camera tween at flat -- the 2D
|
|
-- fallback IS the flat pose, so the switch waits invisibly instead of
|
|
-- tilting an empty stage (or, before builds went asynchronous, freezing
|
|
-- the whole frame for seconds).
|
|
Voxel.ready = true
|
|
|
|
Voxel.TWEEN_TIME = 0.25
|
|
-- Camera distance as a multiple of the view height, and the matching field
|
|
-- of view. Kept equal to Tilt.FOCAL so a given angle frames the world the
|
|
-- same way in both modes; Voxel3D derives the FOV from it.
|
|
Voxel.FOCAL = 1.0
|
|
|
|
local function ease(t)
|
|
return t * t * (3 - 2 * t)
|
|
end
|
|
|
|
local function goalFor(level)
|
|
return math.rad(Voxel.ANGLES_DEG[level + 1] or 0)
|
|
end
|
|
|
|
function Voxel.setLevel(level)
|
|
level = math.floor(tonumber(level) or 0)
|
|
if level < 0 then level = 0 end
|
|
if level > Voxel.MAX_LEVEL then level = Voxel.MAX_LEVEL end
|
|
local goal = goalFor(level)
|
|
if goal ~= Voxel.goal or level ~= Voxel.level then
|
|
Voxel.from = Voxel.angle
|
|
Voxel.goal = goal
|
|
Voxel.t = 0
|
|
-- leaving flat: presume no terrain until the scene reports some, so
|
|
-- the tween's very first frame already waits instead of easing over
|
|
-- an empty stage (render() confirms readiness the same frame when
|
|
-- the meshes are already cached)
|
|
if Voxel.angle == 0 and goal > 0 then Voxel.ready = false end
|
|
end
|
|
Voxel.level = level
|
|
end
|
|
|
|
function Voxel.reset()
|
|
Voxel.level, Voxel.angle = 0, 0
|
|
Voxel.from, Voxel.goal, Voxel.t = 0, 0, 1
|
|
end
|
|
|
|
function Voxel.levelLabel(level)
|
|
return Voxel.ANGLE_LABELS[(level or Voxel.level) + 1] or "OFF"
|
|
end
|
|
|
|
-- The pipeline's per-frame tick. `level` is what the engine currently has
|
|
-- the mode set to, so a hotkey press or an options row lands here as a new
|
|
-- goal to ease toward rather than as a jump. dt is real frame time, so
|
|
-- fast-forward does not speed the camera up.
|
|
function Voxel.update(dt, level)
|
|
if level ~= nil and level ~= Voxel.level then Voxel.setLevel(level) end
|
|
-- hold at flat until there is geometry to tilt over; easing OUT (goal
|
|
-- below the current angle) never waits
|
|
if Voxel.angle == 0 and Voxel.goal > 0 and not Voxel.ready then
|
|
return
|
|
end
|
|
if Voxel.t < 1 then
|
|
Voxel.t = math.min(1, Voxel.t + dt / Voxel.TWEEN_TIME)
|
|
Voxel.angle = Voxel.from + (Voxel.goal - Voxel.from) * ease(Voxel.t)
|
|
else
|
|
Voxel.angle = Voxel.goal
|
|
end
|
|
end
|
|
|
|
-- True while voxel mode is on *or* still easing out -- i.e. whenever the
|
|
-- renderer must take the 3D path instead of the flat blit. Mirrors
|
|
-- Tilt.active so the two gate the same way.
|
|
function Voxel.active()
|
|
return Voxel.level > 0 or Voxel.angle > 0
|
|
end
|
|
|
|
return Voxel
|