mesh
A mesh is renderable geometry — vertices and triangle indices, and, for a skinned mesh, the per-vertex skinning data that binds it to a skeleton. A component that draws geometry references a mesh,…
New to how a mesh exists as an asset, as CPU-side vertex data, and as a live GPU resource (and how
renderer.mesh.*moves between them)? Readcore/resource-modelfirst.
Geometry is stored in data.zmsh; the GPU resource is keyed by
the asset's guid.
Create
asset.create("mesh", name, opts) writes the geometry and registers the mesh.
opts is raw geometry — flat arrays; positions and indices are required,
normals and uvs optional:
asset.create("mesh", "tri", {
positions = { 0,0,0, 1,0,0, 0,1,0 }, -- [x,y,z, ...] (required)
indices = { 0, 1, 2 }, -- [i0,i1,i2, ...] (required)
normals = { 0,0,1, 0,0,1, 0,0,1 }, -- [x,y,z, ...] (optional)
uvs = { 0,0, 1,0, 0,1 }, -- [u,v, ...] (optional)
})
Put a mesh on an entity
A component draws a mesh through a resource("mesh") field, which accepts the mesh
by name, by ref, or as a MeshHandle — the component resolves whatever you give
it. The builtin Model and SkinnedModel both expose a model field:
entity.find("box").component.add("Model", { model = "tri" }) -- by name
entity.find("box").component.add("Model", { model = asset.ref("tri") }) -- by ref
entity.find("box").component.add("Model", { model = meshHandle }) -- a MeshHandle (below)
Runtime mesh — no asset
Build geometry straight onto the GPU and render it without creating an asset. The
resource("mesh") field takes the returned MeshHandle directly:
local h = renderer.mesh.create({ positions = {0,0,0, 1,0,0, 0,1,0}, indices = {0,1,2} })
entity.find("box").component.add("Model", { model = h })
Read geometry — meshRef:load()
:load() decodes data.zmsh into the CPU store and returns a CPU handle carrying
vertexCount / indexCount:
| Method | Returns |
|---|---|
cpu:getVertices() | array of { pos = {x,y,z}, normal = {x,y,z}, uv = {u,v} }, one per vertex |
cpu:getTriangles() | array of { v1, v2, v3 }, each a full vertex (the shape above) |
cpu:getBounds() | { min = {x,y,z}, max = {x,y,z} } — the local-space AABB |
cpu:encode() | the data.zmsh bytes |
cpu:unload() | drop this guid's CPU copy |
local cpu = meshRef:load()
local v = cpu:getVertices()[1] -- { pos = {x,y,z}, normal = {x,y,z}, uv = {u,v} }
local aabb = cpu:getBounds()
cpu:unload()
By default the CPU copy is transient (decode → read → unload). Set keepCpu to
keep it resident — see Settings.
GPU handle — meshRef:handle()
:handle() uploads the geometry (Disk → CPU → GPU) and returns a MeshHandle,
cached on the asset so every consumer shares ONE GPU entry. A component does this
for you when you pass it the mesh; call it directly only for the runtime/low-level
path.
Settings — meshRef:settings() / meshRef:setSettings(patch)
A mesh's serializable settings live in its .metadata. :settings() returns them
with defaults filled (cached on the asset's runtime, so repeat reads don't
re-decode .metadata). :setSettings(patch) writes any subset — sibling settings
are preserved, and an unknown key errors.
| Setting | Type | Default | Meaning |
|---|---|---|---|
keepCpu | boolean | false | Keep the CPU geometry copy resident — for an in-use mesh — so geometry can be read repeatedly without re-loading. |
local s = meshRef:settings() -- { keepCpu = false }
meshRef:setSettings({ keepCpu = true })
keepCpu is an active lifecycle control, and only ever holds geometry for a mesh
that is in use (GPU-resident):
- Setting it on while the mesh is in use loads the CPU copy immediately.
- Setting it on for a mesh that is not in use loads nothing — the copy is retained the next time the mesh is materialised, so an unused mesh is never pinned in memory.
- Setting it off drops a resident CPU copy (the renderer keeps drawing from the GPU).
Read vertices — meshRef:getVertices()
With keepCpu on for an in-use mesh, read its vertices straight from the resident
CPU copy:
meshRef:setSettings({ keepCpu = true }) -- on an in-use mesh
local verts = meshRef:getVertices() -- { { pos = {x,y,z}, normal = {x,y,z}, uv = {u,v} }, ... }
If the CPU copy isn't resident — keepCpu is off, or the mesh isn't in use — it
errors loudly, naming the reason and the remedy, rather than silently loading a
throwaway copy:
mesh:getVertices: cannot read vertices — keepCpu is off, so no CPU copy is kept.
Turn on CPU retention for an in-use mesh first: meshRef:setSettings({ keepCpu = true }).
Discovery
| Call | Shows |
|---|---|
asset.list("mesh") | every registered mesh |
asset.inspect("<name>") | a text report about the mesh (metadata, source, status) |
meshRef:settings() | the resolved settings |
For the on-disk shape of a mesh asset, run asset.inspect("<name>") or read the
type's type.yaml — those are the live source of truth.
Related
renderer.mesh.*— the GPU/CPU resource layer (createa runtime GPU mesh,encode/decodetheZMSHpayload,readbackGPU → CPU).:load/:handlewrap it; seeman renderer..material+.shader— what shades the geometry..bundle— container assets that carry.meshinteriors.