art/saint/saintgeo — the procedural geometry the Slag Saint is cast from.
Every part of the saint is built here and handed to the renderer as a runtime
mesh, so the figure ships as the code that makes it rather than as sixty
baked mesh folders. The builders are deliberately small: a lofted prism, a
lathe, a torus and a weathering pass. A bell, a pauldron, an anvil and a
ribcage are all the same three calls with different profiles.
THE VERTEX COLOUR IS A CONTROL MASK, not a tint. `saint_bronze.shader` reads
it as:
r = verdigris g = soot b = polish (wet bronze) a = seam (furnace light)
Build a shape with the seam channel it needs, then run `Geo.weather` over the
finished part to bake the corrosion and the polish from the form itself.
local Geo = require("art.saint.saintgeo")
local g = Geo.new()
Geo.box(g, Geo.xf({0, 0, 0}), 2, 1, 0.6, { bevel = 0.08, corner = 0.12 })
Geo.weather(g, { verdigris = 0.5 })
local handle = Geo.build(g)
hash3(x: number, y: number, z: number) → number
Noise — the same value noise the shader uses, so what is baked into the
vertices and what is evaluated per pixel agree about where the grime is.
| arg | type | description |
|---|
| x | number | |
| y | number | |
| z | number | |
noise(x: number, y: number, z: number) → number
| arg | type | description |
|---|
| x | number | |
| y | number | |
| z | number | |
corner(a: number, b: number, c: number) → number
| arg | type | description |
|---|
| a | number | |
| b | number | |
| c | number | |
fbm(x: number, y: number, z: number) → number
| arg | type | description |
|---|
| x | number | |
| y | number | |
| z | number | |
xf(pos: { number }?, rotDeg: { number }?, scale: (number | { number }) → void
Build a transform from a position, a YXZ euler rotation (degrees) and a
scale. Stored as nine rotation-scale terms plus a translation.
| arg | type | description |
|---|
| pos | { number }? | |
| rotDeg | { number }? | |
| scale | (number | { number } | |
apply(xf: Xf, x: number, y: number, z: number) → void
| arg | type | description |
|---|
| xf | Xf | |
| x | number | |
| y | number | |
| z | number | |
face(g: Geom, pts: { { number } }, cols: any) → void
Emit one convex polygon as a fan of triangles with a single face normal.
`pts` are already in the part's local space. `cols` is one control-mask
colour for the whole face, or one per point.
| arg | type | description |
|---|
| g | Geom | |
| pts | { { number } } | |
| cols | any | |
circle(r: number, seg: number, rz: number?) → void
Rings — the 2D cross-sections everything is lofted from
A circle in XZ, counter-clockwise seen from +Y.
| arg | type | description |
|---|
| r | number | |
| seg | number | |
| rz | number? | |
rect(w: number, d: number, corner: number?, cn: number?) → void
A rounded rectangle in XZ. `corner` is the fillet radius, `cn` the points
spent on each corner — the shape most of the saint's plate is cut from.
| arg | type | description |
|---|
| w | number | |
| d | number | |
| corner | number? | |
| cn | number? | |
poly(r: number, sides: number, phase: number?) → void
An N-sided polygon in XZ — for faceted castings that should read as forged.
| arg | type | description |
|---|
| r | number | |
| sides | number | |
| phase | number? | |
loft(g: Geom, xf: Xf, sections: { Section }, opts: any?) → void
Loft a stack of cross-sections into a closed solid.
Every section must carry the same number of points. `opts.capBottom` /
`opts.capTop` default true; `opts.col` is the fallback control mask.
| arg | type | description |
|---|
| g | Geom | |
| xf | Xf | |
| sections | { Section } | |
| opts | any? | |
box(g: Geom, xf: Xf, w: number, h: number, d: number, opts: any?) → void
Shapes
A chamfered, optionally round-cornered box centred on its own origin.
| arg | type | description |
|---|
| g | Geom | |
| xf | Xf | |
| w | number | |
| h | number | |
| d | number | |
| opts | any? | |
taper(g: Geom, xf: Xf, ring: { { number } }, h: number, s0: number, s1: number, opts: any?) → void
A tapered tube — the limb and column workhorse. Runs from y=0 to y=h.
| arg | type | description |
|---|
| g | Geom | |
| xf | Xf | |
| ring | { { number } } | |
| h | number | |
| s0 | number | |
| s1 | number | |
| opts | any? | |
lathe(g: Geom, xf: Xf, profile: { { number } }, seg: number, opts: any?) → void
Revolve a profile of { radius, y } pairs. The bell, the collars, the
pauldron domes and the anvil's waist all come out of this.
| arg | type | description |
|---|
| g | Geom | |
| xf | Xf | |
| profile | { { number } } | |
| seg | number | |
| opts | any? | |
torus(g: Geom, xf: Xf, R: number, r: number, su: number, sv: number, opts: any?) → void
A torus — chain links, rivets, anchor rings.
| arg | type | description |
|---|
| g | Geom | |
| xf | Xf | |
| R | number | |
| r | number | |
| su | number | |
| sv | number | |
| opts | any? | |
at(i: number, j: number) → void
| arg | type | description |
|---|
| i | number | |
| j | number | |
rivet(g: Geom, xf: Xf, r: number, h: number, opts: any?) → void
A rivet — a small domed stud. Sixty of these are most of what makes the
saint read as a made thing rather than a shape.
| arg | type | description |
|---|
| g | Geom | |
| xf | Xf | |
| r | number | |
| h | number | |
| opts | any? | |
weather(g: Geom, opts: any?) → void
Weathering — bake the control mask from the form itself
Walk the finished part and write the verdigris / soot / polish channels
from each vertex's normal, height and noise. The seam channel (alpha) is
left exactly as built, because a seam is authored, not weathered.
| arg | type | description |
|---|
| g | Geom | |
| opts | any? | |
seamBand(g: Geom, y0: number, y1: number, amount: number) → void
Force the seam channel on every vertex inside a local-space Y band — how a
glowing joint line is painted after the shape is built.
| arg | type | description |
|---|
| g | Geom | |
| y0 | number | |
| y1 | number | |
| amount | number | |
stats(g: Geom) → void
Handoff
build(g: Geom) → any
Upload the accumulated geometry as a runtime GPU mesh.