game.units.meshkit — the geometry kit the monks are carved with.
Every hero body in Verdigris Cloister is built here: robes are solids of
revolution with cloth wobble in them, tools are tapered tubes and hand-cut
blades, the blight is faceted crystal. Nothing is imported and nothing is a
grey box.
Two things make it look made rather than assembled:
* VERTEX COLOUR. The engine's pbr shader multiplies `input.vertex_color`
into base colour (verified live), so wear, dirt at a hem and the warm
bounce up a sleeve are baked into the geometry itself instead of costing
a material per shade.
* FACETS. Every quad emits its own four vertices with the face's own
normal, so a surface reads as carved planes — the illuminated-manuscript
register the game is drawn in — unless `smooth` is asked for.
Usage:
local Kit = require("game.units.meshkit")
local b = Kit.builder()
b:revolve({ { r = 0.4, y = 0, c = dirt }, { r = 0.3, y = 1.4, c = cloth } }, 10, {})
local handle = b:mesh() -- a MeshHandle for Model.model
hash(n: number) → number
Same seed, same shape, every boot — a body must not shimmer between
rebuilds.
--!desc A deterministic pseudo-random value in 0..1 from an integer seed.
--!arg n The seed.
--!return A number in 0..1.
--!example local v = Kit.hash(7)
| arg | type | description |
|---|
| n | number | |
sn(n: number) → number
--!desc A deterministic value in -1..1 from an integer seed.
--!arg n The seed.
--!return A number in -1..1.
--!example local v = Kit.sn(7)
| arg | type | description |
|---|
| n | number | |
mix(a: { number }, b: { number }, t: number) → void
--!desc Mix two colours.
--!arg a First colour, {r, g, b}.
--!arg b Second colour, {r, g, b}.
--!arg t Blend factor, 0 = a, 1 = b.
--!return A new {r, g, b}.
--!example local c = Kit.mix(green, brass, 0.3)
| arg | type | description |
|---|
| a | { number } | |
| b | { number } | |
| t | number | |
shade(a: { number }, k: number) → void
--!desc Scale a colour's brightness.
--!arg a The colour, {r, g, b}.
--!arg k The multiplier.
--!return A new {r, g, b}.
--!example local dark = Kit.shade(cloth, 0.6)
| arg | type | description |
|---|
| a | { number } | |
| k | number | |
grain(a: { number }, amt: number, seed: number) → void
--!desc A colour nudged by deterministic noise — the grain that keeps a
--! flat-shaded surface from reading as plastic.
--!arg a The colour, {r, g, b}.
--!arg amt How far it may drift, typically 0.02..0.10.
--!arg seed The noise seed.
--!return A new {r, g, b}.
--!example local c = Kit.grain(cloth, 0.06, i * 3 + 1)
| arg | type | description |
|---|
| a | { number } | |
| amt | number | |
| seed | number | |
builder( ) → any
--!desc A fresh geometry builder. Everything is appended into one mesh.
--!return A builder.
--!example local b = Kit.builder()
vert(self: any, p: { number }, nrm: { number }, uv: { number }, c: { number }) → number
--!desc Append one vertex.
--!arg p Position {x, y, z}.
--!arg nrm Normal {x, y, z}.
--!arg uv Texture coordinate {u, v}.
--!arg c Colour {r, g, b}.
--!return The vertex index (0-based, as the index buffer wants it).
--!example local i = b:vert({0,0,0}, {0,1,0}, {0,0}, {1,1,1})
| arg | type | description |
|---|
| self | any | |
| p | { number } | |
| nrm | { number } | |
| uv | { number } | |
| c | { number } | |
mark(self: any) → number
--!desc How many vertices exist so far — a mark to hand to `smooth` or
--! `warp` so the operation only touches what comes after it.
--!return The current vertex count.
--!example local m = b:mark()
| arg | type | description |
|---|
| self | any | |
warp(self: any, from: number, fn: (number, number, number) → void
--!desc Move every vertex added since a mark through a function of position.
--! Normals are left alone, so use it for gentle shaping (a hood pulled
--! forward, a robe leaned into a stoop), not for folding a surface over.
--!arg from A mark from `mark()`.
--!arg fn Called with x, y, z; returns the new x, y, z.
--!example b:warp(m, function(x, y, z) return x, y, z + y * 0.1 end)
| arg | type | description |
|---|
| self | any | |
| from | number | |
| fn | (number, number, number | |
smooth(self: any, from: number) → void
--!desc Average the normals of coincident vertices added since a mark, so a
--! faceted surface reads as a rounded one. Heads and orbs want this;
--! crystal and blades never do.
--!arg from A mark from `mark()`.
--!example b:smooth(m)
| arg | type | description |
|---|
| self | any | |
| from | number | |
bakeFill(self: any, from: number, dir: { number }, lo: number, hi: number) → void
--!desc Bake a soft fill light into the vertex colours added since a mark.
--! The greenhouse's key light falls almost straight DOWN, so a robe's sides
--! and a tool's shaft would otherwise sit in near-black. This lifts a
--! surface by how much it faces the fill direction — a painter's fill,
--! carried in the geometry, so a body reads at gameplay distance whatever
--! the room's lighting is doing.
--!arg from A mark from `mark()`.
--!arg dir The fill direction {x, y, z}; it is normalised for you.
--!arg lo The multiplier where a surface faces fully away.
--!arg hi The multiplier where it faces the fill head on.
--!example b:bakeFill(0, { -0.4, 0.5, -0.8 }, 0.78, 1.30)
| arg | type | description |
|---|
| self | any | |
| from | number | |
| dir | { number } | |
| lo | number | |
| hi | number | |
bakeGroundDirt(self: any, from: number, y0: number, y1: number, k: number) → void
--!desc Darken the vertex colours added since a mark toward a floor height —
--! the contact shadow and ground dirt a hem picks up, baked in.
--!arg from A mark from `mark()`.
--!arg y0 The height at which the darkening is strongest.
--!arg y1 The height at which it has faded out.
--!arg k How dark it goes at `y0` (0.5 is halfway to black).
--!example b:bakeGroundDirt(0, 0.0, 0.45, 0.55)
| arg | type | description |
|---|
| self | any | |
| from | number | |
| y0 | number | |
| y1 | number | |
| k | number | |
revolve(self: any, profile: { any }, segs: number, opts: any?) → void
--!desc A solid of revolution — the robe, the hood, the censer, the pipe.
--! `profile` is a bottom-to-top list of rings: `{ r = radius, y = height,
--! c = colour }`. `opts` may carry `segs` (default 10), `capTop`,
--! `capBottom`, `sx`/`sz` (squash the circle into an ellipse), `wobble`
--! (per-segment radius variation — this is what makes cloth read as cloth),
--! `seed`, `lean` (metres of forward tilt per unit of height), `smoothed`,
--! `inward` (flip the winding, for the dark lining inside a hood) and
--! `skip(ri, si, segs)` (leave a quad out — this is how a cowl gets a face
--! opening rather than being a closed cone).
--!arg profile The rings, bottom to top.
--!arg segs How many radial segments; 8-12 keeps the carved look.
--!arg opts The options table above.
--!example b:revolve({ { r = .4, y = 0, c = hem }, { r = .3, y = 1.3, c = cloth } }, 10, { wobble = .05 })
| arg | type | description |
|---|
| self | any | |
| profile | { any } | |
| segs | number | |
| opts | any? | |
pt(ri: ?, si: ?) → void
| arg | type | description |
|---|
| ri | ? | |
| si | ? | |
tube(self: any, path: { any }, radii: any, segs: number, c: any, opts: any?) → void
--!desc A swept tube along a path — arms, shafts, hoses, chains, pipes.
--!arg path A list of {x, y, z} points, at least two.
--!arg radii Either one number, or one radius per path point.
--!arg segs Radial segments; 6 reads as hand-turned, 10 as machined.
--!arg c The base colour, or a list of one colour per path point.
--!arg opts `capEnds`, `seed`, `smoothed`.
--!example b:tube({ {0,0,0}, {0,1.5,0} }, 0.05, 6, brass, { capEnds = true })
| arg | type | description |
|---|
| self | any | |
| path | { any } | |
| radii | any | |
| segs | number | |
| c | any | |
| opts | any? | |
frame(i: ?) → void
a stable frame along the path
p(ri: ?, si: ?) → void
| arg | type | description |
|---|
| ri | ? | |
| si | ? | |
blade(self: any, outline: { any }, thick: number, c: { number }, edge: { number }) → void
--!desc A flat blade with a spine — a billhook, a knife, a leaf. Built in the
--! XY plane and given thickness on Z, so a caller places it with the part's
--! own transform.
--!arg outline A list of {x, y} points running around one face, anticlockwise.
--!arg thick Half-thickness on Z.
--!arg c The colour of the flat.
--!arg edge The colour along the cut edge — a bright line here is what makes
--! a blade read as sharp.
--!example b:blade({ {0,0}, {0.34,0.5}, {0.1,1.0}, {-0.06,0.5} }, 0.02, steel, hot)
| arg | type | description |
|---|
| self | any | |
| outline | { any } | |
| thick | number | |
| c | { number } | |
| edge | { number } | |
ring(k: ?, rad: ?, y: ?) → void
| arg | type | description |
|---|
| k | ? | |
| rad | ? | |
| y | ? | |
mesh(self: any) → any
--!desc Hand the accumulated geometry to the GPU and return the MeshHandle a
--! Model component takes straight in its `model` field.
--!return A MeshHandle.
--!example ent.component.add("Model", { model = b:mesh(), material = mat })
| arg | type | description |
|---|
| self | any | |
tris(self: any) → number
--!desc How many triangles the builder holds — the budget check.
--!return The triangle count.
--!example log.info(b:tris())
| arg | type | description |
|---|
| self | any | |