Model
Loads and displays a 3D mesh on an entity. Disabling the component hides the mesh. Supports procedural meshes (`cube`, `sphere`, ...), model files, and remote URLs. Tint and outline are shader effects applied to the mesh.
Model
Loads and displays a 3D mesh on an entity. Disabling the component hides the mesh. Supports procedural meshes (cube, sphere, ...), model files, and remote URLs. Tint and outline are shader effects applied to the mesh.
Material lives on this component. There is no standalone Material component — a material only renders where there is a mesh to render it on, so the material field is part of Model (and SkinnedModel). Set it at add time or assign the field later; properties are registry-wide.
Public fields: model, material (AssetRef<material>), tintR/G/B, tintBlend, outlineR/G/B, outlineIntensity.
Methods: :setTint(color, blend?) (color: {r, g, b} array or {r=, g=, b=} map), :clearTint(), :setOutline(color, intensity?), :clearOutline(), :setMaterialProperty(prop, value), :getMaterialProperty(prop), :getMaterialPropertyNames().
entity(id).component.add("Model", { model = "cube", material = "gold" })
entity(id).component.get("Model").material = "checkerboard" -- swap material
entity(id).component.get("Model"):setTint({1, 0, 0}, 0.5)
entity(id).component.get("Model"):setMaterialProperty("roughness", 0.2)
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2Model Component Loads and displays a 3D mesh on the entity. Disabling hides the mesh. Tint recolours the mesh; outline draws a coloured silhouette rim around it. Usage: entity.find("box1").component.add("Model", { model = "cube" }) entity.find("box1").component.add("Model", { model = "cube", material = "gold" }) entity.find("box1").component.get("Model"):setTint({1, 0, 0}, 0.5) The `model` field accepts a builtin primitive name (cube, sphere, ...), a library identity (@publisher.path), or an AssetRef handle — the framework resolves all forms to an AssetRef before awake() runs. The `material` field references a named material definition in the registry (library or scene-created). Material lives ON the Model component — not as a standalone component — because a material only renders where there is a mesh to render it on. There is no per-instance material override: material properties are registry-wide (edits via `setMaterialProperty` below affect every entity sharing that material). This component drives the renderer entirely through the public `ecs.*` API (ecs.Mesh -> MeshInstance, ecs.Material -> MaterialRef). The async ecs.insert is correct here: a mesh/material ref may need to load, and that load must not stall the main thread. The typed ecs.Mesh / ecs.Material constructors own the camelCase -> snake_case mapping and the assetRef -> guid coercion, so this file carries no hand-maintained field map.
toHandle(ref: ?) → void
Bridge a resource to its live GPU HANDLE for the ecs layer. An AssetRef materialises its GPU resource via `:handle()` (the Disk→CPU→GPU upload — a mesh uploads its geometry, a material uploads the material and its textures) and the handle is returned; a value that already is a handle is returned as-is. The component owns this asset→GPU step and hands the ecs layer a resident handle.
| arg | type | description |
|---|---|---|
| ref | ? |
materialHandle(ref: ?) → void
Resolve the material AssetRef to a live GPU handle, robust to a stale guid. A material is registry-keyed by its identity; deleting and recreating one mints a fresh guid and orphans the old, so a saved-scene ref that still carries the old guid resolves to nothing and the mesh renders the white fallback. When the ref's guid is no longer a live material, resolve through the stable identity — which tracks the current material — so the mesh binds the material the author named. The guid stays the preferred lookup; the identity is consulted only when the guid has gone dead.
| arg | type | description |
|---|---|---|
| ref | ? |
meshFields( ) → void
Collect the public fields into the ecs.Mesh constructor's input shape. The constructor maps these to MeshInstance's rust fields (tint expands to the per-channel scalars, camelCase -> snake_case for the rest). `mesh` is bridged to its GPU handle (toHandle) so the ecs layer receives a resident GPU resource — the ecs layer just reads the handle's guid.
applyOutline( ) → void
Drive the canonical outline (a jump-flood silhouette rim) from the outline fields. `outlineIntensity` scales the rim's pixel thickness; 0 clears it. The fields and the `setOutline`/`clearOutline` methods share this one path, so authoring the outline as component data renders identically to calling the method.
renderBroken(reason: string) → void
| arg | type | description |
|---|---|---|
| reason | string |
applyMesh( ) → void
Apply `public.model` to the entity's MeshInstance. Absent model → remove the component; present → insert (async; the renderable appears once the mesh load resolves). insert is an upsert, so re-applying re-syncs every renderer-facing field at once.
applyMaterial( ) → void
Apply the entity's rendered material to its MaterialRef: the session material when one is active, else the authored `material`. The renderer pairs MaterialRef with the MeshInstance on the same entity automatically.
awake( ) → void
onPropertyChanged(key: ?, value: ?, _oldValue: ?) → void
| arg | type | description |
|---|---|---|
| key | ? | |
| value | ? | |
| _oldValue | ? |
onEnable( ) → void
onDisable( ) → void
onError(error: ?) → void
| arg | type | description |
|---|---|---|
| error | ? |
onAssetReload(field: ?) → void
The mesh or material this model draws with was edited in place — same ref, new content. Baked lighting reads a surface's albedo and emissive out of its material and its shape out of its mesh, so tell the bake: nothing about this entity changed, but the light it bounces did.
| arg | type | description |
|---|---|---|
| field | ? |
onDestroy( ) → void
setTint(color: table, blend: number?) → void
Set the mesh tint colour and blend amount. are 0-1.
| arg | type | description |
|---|---|---|
| color | table | Tint color as {r, g, b} array or {r=, g=, b=} map. Channel values |
| blend | number? | Blend amount (0 = no tint, 1 = full tint). Defaults to 1. |
examples
model:setTint({1, 0, 0})model:setTint({r = 1, g = 0, b = 0}, 0.5)clearTint( ) → void
Reset the tint so the mesh renders with its original material colour.
examples
model:clearTint()
setOutline(color: table, intensity: number?) → void
Set the outline colour and intensity for this mesh. values are 0-1.
| arg | type | description |
|---|---|---|
| color | table | Outline color as {r, g, b} array or {r=, g=, b=} map. Channel |
| intensity | number? | Outline intensity (0 = none, 1 = full). Defaults to 1. |
examples
model:setOutline({0, 1, 0})model:setOutline({r = 1, g = 1, b = 0}, 0.8)clearOutline( ) → void
Remove the outline from this mesh.
examples
model:clearOutline()
sessionSchema( ) → any
The session copy's vocabulary — the properties its record carries and the texture slots it binds — in the shape `MaterialSchema.route` reads, held by the renderer module per record and answered as a table lookup. A write resolves its key here and asks the renderer nothing: what the renderer holds for the material is a different document, and reading it is a cost no write pays. nil when the record carries neither, which writes every key as the caller spelled it: the permissive stance the authored path takes when a shader's schema can't be resolved.
routeSessionWrite(schema: any?, name: string, value: any) → void
Where a written key lands on the session copy — the surface its shader declares the name under, and the name it declares. With no vocabulary reported the key is taken as the caller spelled it.
| arg | type | description |
|---|---|---|
| schema | any? | |
| name | string | |
| value | any |
setMaterialProperty(property: string, value: any) → void
Set a property on this Model's material. With a session material active (see `applySessionMaterial`), the write lands on this entity's session copy alone; otherwise it changes the shared material at runtime, which every entity sharing it takes since material properties are registry-wide. Either way the change reaches the GPU and not the material's `mat.yaml` — call `material:saveDefinition()` to write the current values into the asset. The key resolves against the material's declared vocabulary on both targets, so the same name reaches the same uniform.
| arg | type | description |
|---|---|---|
| property | string | Property name (string). |
| value | any | New value for the property. Type depends on the property. |
examples
model:setMaterialProperty("baseColor", {1, 0, 0, 1})model:setMaterialProperty("roughness", 0.5)setMaterialProperties(props: { [string]: any }) → number
Set many properties on this Model's material in one call, the plural of `setMaterialProperty` and with the same target: this entity's session copy when one is active, else the authored material every entity sharing it takes. Each key resolves against the material's declared vocabulary the way the singular resolves it; a property the material's shader does not expose is skipped, so one patch table serves models on different shaders.
| arg | type | description |
|---|---|---|
| props | { [string]: any } | Table of `{ [propertyName] = value }` pairs. |
examples
model:setMaterialProperties({ roughness = 0.2, metallic = 0.9 })getMaterialProperty(property: string) →
Read a property from this Model's material — the session material when one is active, else the authored material.
| arg | type | description |
|---|---|---|
| property | string | Property name (string). |
examples
local color = model:getMaterialProperty("baseColor")getMaterialPropertyNames( ) →
List all property names available on this Model's material — the session material when one is active, else the authored material.
examples
for _, name in ipairs(model:getMaterialPropertyNames()) do print(name) end
resolveMobility( ) → string
Resolve this entity's effective mobility: `"static"` (stands still — receives lightmaps, occludes baked light) or `"movable"` (moves — samples probe volumes for indirect light). An explicit `mobility` field value wins; `"auto"` derives it from the entity AND everything carrying it: anything that animates, simulates, or is driven (a non-static `Physics` body, a `SkinnedModel`, a `ClipPlayer`, a `Mover`/`FreeMover` — on this entity or any ancestor — or a non-world runtime participation) is movable, everything else is static.
examples
if model:resolveMobility() == "static" then Lightmap.bake(id) end
applySessionMaterial(handle: any) → void
Show a session-created runtime material on this Model in place of its authored material. The authored `material` field is never touched — persisted state always carries the authored ref, so nothing broken can be saved. The handle is kept in the session store under `renderer.material.sessionKeyFor(entityId)`, so awake re-adopts it across VM reloads; on a fresh boot (runtime materials gone) the Model renders its authored material again automatically. Called with no handle, it copies this Model's current material into a fresh session material keyed to this entity, so later `setMaterialProperty` edits land on the copy instead of the shared authored material. A copy per entity is what a DIFFERENT LOOK per entity costs — its own pipeline binding and its own row in the material table. When entities want the same look and differ only in a VALUE, `renderer.instanceData.set` writes one of the four `vec4` lanes every drawn object already carries, which the shader reads as `input.shader_data[lane]`, and one material serves them all. and `renderer.material.animatedTexture` return it. Its `guid` field is the material's registry key, which is what `renderer.material.setProperty` / `describe` / `destroy` take; this call takes the handle itself. Omit to copy this Model's current material into a new session material.
| arg | type | description |
|---|---|---|
| handle | any | The runtime material handle OBJECT, as `renderer.material.create` |
examples
model:applySessionMaterial(handle)
model:applySessionMaterial() -- copy the current material for per-entity edits
restoreSessionMaterial( ) → boolean
End the session-material swap: the Model renders its authored material again.
examples
model:restoreSessionMaterial()
Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.