mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-13 01:50:51 +02:00
138 lines
5.8 KiB
Lua
138 lines
5.8 KiB
Lua
-- Overworld battles: the two mons, as geometry standing on the map.
|
|
--
|
|
-- Not pics composited over a picture of the world -- quads INSIDE it, drawn
|
|
-- in the same 3D pass as the terrain, from the same camera, through the same
|
|
-- shader. Which means they get everything the world gets and nothing has to
|
|
-- be faked for them: the depth buffer decides what is in front of what, the
|
|
-- sun pass sees them and throws their real silhouettes across the ground,
|
|
-- and their size on screen is whatever standing on that tile at that
|
|
-- distance actually looks like.
|
|
--
|
|
-- One quad, standing upright with its feet on the cell and yawed to face the
|
|
-- camera. Upright rather than leaned back, unlike the free-roam mode's
|
|
-- character cards: those lean because that camera looks DOWN and a standing
|
|
-- card would foreshorten to nothing, and this one looks along the ground
|
|
-- from about a foot above it, where a card standing up is simply correct.
|
|
--
|
|
-- The shader's alpha discard cuts the mon's exact outline out of the quad,
|
|
-- so a Pokemon is its own silhouette against the world with no matte, no
|
|
-- billboard edge and no sorting to get wrong.
|
|
|
|
-- the mod namespace (see main.lua): V.require loads a sibling module
|
|
local V = ...
|
|
|
|
local Mat4 = V.require("Mat4")
|
|
local Voxel3D = V.require("Voxel3D")
|
|
|
|
local BattleBillboard = {}
|
|
|
|
-- How wide a full-size (7x7 tile) mon stands, in world pixels. One overworld
|
|
-- square, so a Pokemon covers the tile it is on and no more; a species whose
|
|
-- pic is smaller than the full buffer comes out proportionally smaller,
|
|
-- which is how the artwork's own size differences survive the trip.
|
|
BattleBillboard.FULL_W = 16
|
|
BattleBillboard.FULL_PIC = 56 -- the pic size FULL_W refers to
|
|
|
|
-- A hair of camera-ward bias, so a card standing ON the ground plane wins
|
|
-- the depth test against it instead of z-fighting the tile it is rooted to.
|
|
BattleBillboard.PULL = 1.5
|
|
|
|
local quad = nil -- nil = untried, false = unavailable
|
|
|
|
-- The unit card: x in -0.5..0.5, y in 0..1, z = 0, UV over the whole
|
|
-- texture. Feet on the model origin, so the model matrix only has to say
|
|
-- where the mon is standing and how big it is.
|
|
--
|
|
-- Which puts this card OFF the voxel grid, alone among the meshes in this
|
|
-- mode: the rest are built one unit per voxel in their own model space --
|
|
-- terrain in world pixels, a character's card in the sprite's own pixels --
|
|
-- and the wireframe is the integer planes of that space (see VoxelGrid).
|
|
-- One unit here is the whole card, so the only integer plane inside it is
|
|
-- x = 0, which is the pic's centre column: a single stray line straight
|
|
-- down the middle of every Pokemon and no seams anywhere else.
|
|
--
|
|
-- The card stays a unit card, because a mon's size on screen is decided by
|
|
-- the artwork's own dimensions and the distance it is standing at, and a
|
|
-- unit card is what lets one matrix say both. Whoever draws it turns the
|
|
-- wireframe off instead (Voxel3D.seams) -- a mesh that is not on the voxel
|
|
-- grid does not get a voxel grid drawn on it.
|
|
local function unitQuad()
|
|
if quad ~= nil then return quad or nil end
|
|
local verts = {
|
|
{ -0.5, 0, 0, 0, 1, 1 },
|
|
{ 0.5, 0, 0, 1, 1, 1 },
|
|
{ 0.5, 1, 0, 1, 0, 1 },
|
|
{ -0.5, 1, 0, 0, 0, 1 },
|
|
}
|
|
local indices = {}
|
|
Voxel3D.pushQuad(indices, 0)
|
|
local mesh = Voxel3D.newMesh(verts, indices)
|
|
quad = mesh or false
|
|
return quad or nil
|
|
end
|
|
|
|
BattleBillboard.mesh = unitQuad
|
|
|
|
-- The yaw that turns the card's face toward the eye. The quad's normal is
|
|
-- +Z before rotation, so this is just the bearing from the mon to the
|
|
-- camera -- flattened to the horizontal, because a card that also tipped to
|
|
-- face a camera above it would lift its feet off the floor.
|
|
function BattleBillboard.yawToward(x, z, eye)
|
|
if not eye then return 0 end
|
|
return math.atan2(eye[1] - x, eye[3] - z)
|
|
end
|
|
|
|
-- Stand a `w` x `h` card with its feet centred on world (x, y, z).
|
|
function BattleBillboard.matrix(x, y, z, w, h, yaw)
|
|
return Mat4.mul(Mat4.mul(Mat4.translate(x, y, z), Mat4.rotateY(yaw)),
|
|
Mat4.scale(w, h, 1))
|
|
end
|
|
|
|
-- The world size a pic of `pw` x `ph` texture pixels stands at.
|
|
function BattleBillboard.sizeFor(pw, ph)
|
|
if not (pw and ph and pw > 0 and ph > 0) then return 0, 0 end
|
|
local scale = BattleBillboard.FULL_W / BattleBillboard.FULL_PIC
|
|
return pw * scale, ph * scale
|
|
end
|
|
|
|
-- Draw one mon. `tex` is the pic already rendered to a texture (see
|
|
-- OverworldBattle, which lets the engine's own battler draw produce it, so
|
|
-- every faint slide, blink and squish comes along), `grow` the send-out
|
|
-- animation's scale or nil.
|
|
function BattleBillboard.draw(tex, x, y, z, grow)
|
|
local mesh = unitQuad()
|
|
if not (mesh and tex) then return false end
|
|
local pw, ph = tex:getDimensions()
|
|
local w, h = BattleBillboard.sizeFor(pw, ph)
|
|
if grow then w, h = w * grow, h * grow end
|
|
if w <= 0 or h <= 0 then return false end
|
|
local yaw = BattleBillboard.yawToward(x, z, Voxel3D.eye)
|
|
-- off the voxel grid, so no wireframe on it (see unitQuad)
|
|
Voxel3D.seams(false)
|
|
Voxel3D.draw(mesh, tex, BattleBillboard.matrix(x, y, z, w, h, yaw),
|
|
BattleBillboard.PULL)
|
|
Voxel3D.seams(true)
|
|
return true
|
|
end
|
|
|
|
-- The same card, as the SUN sees it: no camera-ward pull (that is a trick
|
|
-- for the view's own depth buffer and would drag the shadow off its owner)
|
|
-- and no draw call of its own, because the shadow pass has its own.
|
|
function BattleBillboard.caster(shadowMap, tex, x, y, z, grow)
|
|
local mesh = unitQuad()
|
|
if not (mesh and tex) then return false end
|
|
local pw, ph = tex:getDimensions()
|
|
local w, h = BattleBillboard.sizeFor(pw, ph)
|
|
if grow then w, h = w * grow, h * grow end
|
|
if w <= 0 or h <= 0 then return false end
|
|
local yaw = BattleBillboard.yawToward(x, z, Voxel3D.eye)
|
|
shadowMap.draw(mesh, tex, BattleBillboard.matrix(x, y, z, w, h, yaw))
|
|
return true
|
|
end
|
|
|
|
function BattleBillboard.invalidate()
|
|
quad = nil
|
|
end
|
|
|
|
return BattleBillboard
|