This repository was archived by the owner on Aug 14, 2026. It is now read-only.
forked from TeJota1337/DramaticShapeVoxelMod
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSpriteBillboards.lua
More file actions
84 lines (74 loc) · 3.45 KB
/
Copy pathSpriteBillboards.lua
File metadata and controls
84 lines (74 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
-- Voxel world mode: characters as flat forward-facing sprite billboards.
--
-- Every character -- the player, NPCs, the ghosts standing on a neighbour
-- map -- is its CURRENT 2D sprite frame on a single flat quad. The sheets
-- carry real alpha and the shader discards it, so the quad cuts the
-- sprite's exact silhouette out of itself; no geometry is built from the
-- pixels and nothing about a sprite is voxelized.
--
-- That is deliberate. A sprite is a DRAWING, not an object seen from one
-- side: Gen 1's overworld figures are 16x16 icons with a fixed front-on
-- reading, and turning one into a solid -- whether a contoured slab or a
-- carved visual hull -- reconstructs a body the artist never drew and the
-- game never implied. It also had the mod ship a description of the ROM
-- art. One quad wearing the real frame is both more faithful and cheaper:
-- it needs no pixel access at all, only the sheet's dimensions.
--
-- The card always faces SOUTH -- the direction the 2D game implies -- and
-- only LEANS BACK, pivoting at its feet, by exactly the camera's pitch
-- (VoxelScene's billboardMatrix), so at every tilt level it reads face-on
-- like the flat game. Right-facing and the alternating walk step are
-- matrix mirrors, not extra meshes. UVs point into the live sheet image,
-- so RED++ OBP bakes, SGB palette bakes and sprite-replacing mods all
-- texture it with no rebuild.
-- the mod namespace (see main.lua): V.require loads a sibling module
local V = ...
local Assets = require("src.render.Assets")
local Voxel3D = V.require("Voxel3D")
local SpriteBillboards = {}
local meshes = {}
-- One flat 16x16 quad UV-mapped to a whole frame. A hair of inset keeps
-- the sampler inside this frame rather than picking up the neighbouring
-- one along the shared edge.
local function buildCard(def, frame)
local ok, img = pcall(Assets.image, def.image)
if not (ok and img) then return nil end
local iw, ih = img:getDimensions()
local fy = frame * 16
if fy + 16 > ih then fy = 0 end
local u0, u1 = 0.02 / iw, (16 - 0.02) / iw
local v0, v1 = (fy + 0.05) / ih, (fy + 15.95) / ih
local verts = {
{ 0, 0, 0, u0, v1, 1 }, { 16, 0, 0, u1, v1, 1 },
{ 16, 16, 0, u1, v0, 1 }, { 0, 16, 0, u0, v0, 1 },
}
local indices = {}
Voxel3D.pushQuad(indices, 0)
return Voxel3D.newMesh(verts, indices)
end
-- The card for one (sprite def, frame index), or nil (headless / no
-- image), cached like every other derived GPU object.
--
-- The solid draw, the sun pass and the player's occlusion silhouette all
-- take THIS mesh. That the three agree is load-bearing, not tidiness: the
-- silhouette is drawn with the depth test INVERTED, so any self-overlap in
-- the mesh would read as "behind something" and repaint the figure on open
-- ground whether or not anything hides it; and the sun must see the same
-- outline the camera does, or a shadow stops matching what casts it.
function SpriteBillboards.mesh(def, frame)
local key = def.image .. "#" .. frame
if meshes[key] == nil then
local ok, m = pcall(buildCard, def, frame)
meshes[key] = (ok and m) or false
end
return meshes[key] or nil
end
-- Kept as its own name because the shadow and ghost passes read as their
-- own thing at the call sites; it once carried a different mesh from the
-- solid draw, and now deliberately does not.
SpriteBillboards.shadowQuad = SpriteBillboards.mesh
function SpriteBillboards.invalidate()
meshes = {}
end
Assets.register(SpriteBillboards.invalidate)
return SpriteBillboards