Log inGet started

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)? Read core/resource-model first.

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:

MethodReturns
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.

SettingTypeDefaultMeaning
keepCpubooleanfalseKeep 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

CallShows
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.

  • renderer.mesh.* — the GPU/CPU resource layer (create a runtime GPU mesh, encode/decode the ZMSH payload, readback GPU → CPU). :load/:handle wrap it; see man renderer.
  • .material + .shader — what shades the geometry.
  • .bundle — container assets that carry .mesh interiors.
  • asset-type
  • reference