tecs.gfx.models

Resident glTF models and allocation-stable node animation.

tecs.gfx.models.load decodes a glTF or GLB file and returns a shared model. Each newInstance owns its joint palettes, morph weights, and primitive templates, so copies of one model can play different poses without duplicating geometry, materials, textures, morph deltas, or clips.

local model = assert(tecs.gfx.models.load("models/hero.gltf"))
local instance = model:newInstance()
local entities: {integer} = {}
for index, primitive in ipairs(instance.primitives) do
    entities[index] = world:spawn(
        primitive.transform,
        primitive.mesh,
        primitive.bounds,
        primitive.material,
        primitive.skin,
        primitive.morph,
        tecs.gfx.Tint(1, 1, 1, 1),
        tecs.gfx.Renderable3D
    )
end
world:commit()
for index, entity in ipairs(entities) do
    instance:bind(world, index, entity)
end
instance:play("Walk")

world:addSystem({
    name = "game.AnimateModel",
    phase = tecs.ecs.phases.Update,
    run = function(dt: number): nil
        instance:update(dt)
    end,
})

Sampling resets the reusable pose to the file's base TRS, applies one clip, builds node world matrices in parent order, updates bound Transform3D components, and stages each instance-owned skin palette and morph vector. No table or cdata is allocated per sample. Bounds remain caller-owned and must enclose every pose.

Module contents

Constructors

ConstructorDescription
newMaterialRegisters an independently authored material for any mesh.
newMeshBuilds resident indexed geometry from the original twelve-float mesh layout.

Types

TypeKindDescription
AnimationrecordHolds one authored clip and its reusable channel data.
BoundsInputrecordCarries a native value across a mutation boundary without losing its definition.
InstancerecordPlays one independently posed copy of a resident model.
LoadOptionstypeRuns once per imported material before the resident GPU upload is queued.
MaterialInfotypeAllows import-time material tuning without duplicating geometry or images.
MaterialInputrecordCarries a native value across a mutation boundary without losing its definition.
MaterialOptionstypeConfigures a shared mesh material.
MeshInputrecordCarries a native value across a mutation boundary without losing its definition.
MeshOptionstypeDescribes one indexed triangle mesh in local coordinates.
ModelrecordRetains shared geometry, materials and authored clips.
PlayOptionstypeControls clip playback, with speed one, looping and immediate playback by default.
PrimitiverecordHolds the spawnable components for one independently posed primitive.

Functions

FunctionKindDescription
loadfunctionLoads and registers a glTF or GLB scene for independent instances.
loadEnvironmentfunctionLoads the six original environment faces and registers their shared GPU mip chain.
nextUploadfunctionDrains one model upload for the native host; zero means the queue is empty.

Values

ValueKindDescription
MATERIAL_LAMBERTvariableSelects diffuse Lambert shading.
MATERIAL_METALLIC_ROUGHNESSvariableSelects Cook-Torrance metallic-roughness shading.
MATERIAL_UNLITvariableSelects unlit base color and emission.

Constructors#

newMaterialconstructor#

function newMaterial(options: MaterialOptions): MaterialInput

Registers an independently authored material for any mesh.

Arguments

NameTypeDescription
optionsMaterialOptions

The caller supplies an identity, shading model, factors and optional maps.

Returns

TypeDescription
MaterialInput

Returns a shared MeshMaterial component ready to spawn.

Raises

  • When a map cannot load or a material factor is invalid.

newMeshconstructor#

function newMesh(options: MeshOptions): MeshInput, BoundsInput

Builds resident indexed geometry from the original twelve-float mesh layout.

Arguments

NameTypeDescription
optionsMeshOptions

The caller supplies a stable name, position/normal/tangent/UV vertices and zero-based triangle indices.

Returns

TypeDescription
MeshInput

The mesh component and its computed local bounding sphere.

BoundsInput

Raises

  • When streams are empty, malformed or contain nonfinite values or invalid indices.

Types#

Animationrecord#

record Animation
    name: string
    duration: number
    channels: {Channel}
end

Holds one authored clip and its reusable channel data.

Fields

name#
name: string

Read-only. Names the authored clip.

duration#
duration: number

Read-only. Reports the clip duration in seconds.

channels#
channels: {Channel}

Engine-owned. Retains decoded channels for sampling; game code uses Instance.

BoundsInputrecord#

record BoundsInput<T, C>
    componentType: C
    value: T
end

Carries a native value across a mutation boundary without losing its definition.

Type parameters

NameDescription
T
C

Fields

componentType#
componentType: C

Read-only. Preserves the exact component or relationship definition.

value#
value: T

Caller-writable. Holds the struct supplied at the mutation boundary.

Instancerecord#

record Instance
    primitives: {Primitive}
    transform: mesh.Transform3D?
    animation: integer
    time: number
    speed: number
    loop: boolean
    playing: boolean
    bind: function(exclusive self: Instance, world: World, primitive: integer, entity: integer): nil
    unbind: function(exclusive self: Instance, primitive: integer): nil
    play: function(exclusive self: Instance, animation: string | integer, options: PlayOptions?): nil
    sample: function(exclusive self: Instance, animation: string | integer, time: number, loop: boolean?): nil
    update: function(exclusive self: Instance, dt: number): nil
end

Plays one independently posed copy of a resident model.

Methods

bind#
bind: function(exclusive self: Instance, world: World, primitive: integer, entity: integer): nil

Binds one primitive to an existing entity's Transform3D.

Arguments
NameTypeDescription
exclusive selfInstance
worldWorld

The caller supplies the entity's world. Every binding on one instance must use the same world.

primitiveinteger

The caller supplies a one-based primitive index.

entityinteger

The caller supplies a live entity carrying Transform3D.

Returns
TypeDescription
nil

The operation returns no value.

unbind#
unbind: function(exclusive self: Instance, primitive: integer): nil

Removes one primitive's entity binding.

Arguments
NameTypeDescription
exclusive selfInstance
primitiveinteger

The caller supplies a one-based primitive index.

Returns
TypeDescription
nil

The operation returns no value.

play#
play: function(exclusive self: Instance, animation: string | integer, options: PlayOptions?): nil

Selects and restarts a clip.

Arguments
NameTypeDescription
exclusive selfInstance
animationstring | integer

The caller supplies a one-based index or clip name.

optionsPlayOptions?

Omitted fields default to speed one, looping, and immediate playback.

Returns
TypeDescription
nil

The operation returns no value.

sample#
sample: function(exclusive self: Instance, animation: string | integer, time: number, loop: boolean?): nil

Samples a clip at an explicit time without allocating.

Arguments
NameTypeDescription
exclusive selfInstance
animationstring | integer

The caller supplies a one-based index or clip name.

timenumber

The caller supplies seconds. Negative values clamp to zero.

loopboolean?

Whether time wraps at the duration. Defaults to false.

Returns
TypeDescription
nil

The operation returns no value.

update#
update: function(exclusive self: Instance, dt: number): nil

Advances and samples the selected clip.

Arguments
NameTypeDescription
exclusive selfInstance
dtnumber

The caller supplies non-negative elapsed seconds.

Returns
TypeDescription
nil

The operation returns no value.

Fields

primitives#
primitives: {Primitive}

Read-only. Contains this instance's spawnable primitive bundles.

transform#
transform: mesh.Transform3D?

Caller-writable. Places the complete sampled model in world space, or nil to preserve the file's authored placement without placement work. Sampling composes this transform after the authored node hierarchy.

animation#
animation: integer

Read-only. Reports the selected one-based clip, or zero before play.

time#
time: number

Read-only. Reports the current clip time in seconds.

speed#
speed: number

Caller-writable. Multiplies elapsed time. Negative values raise on update.

loop#
loop: boolean

Caller-writable. Controls whether playback wraps at the duration.

playing#
playing: boolean

Caller-writable. Controls whether update advances playback.

LoadOptionstype#

type LoadOptions = {
    --- Caller-writable. Tunes each decoded material before its upload is queued.
    --- @param value The callback receives the material's authored values in place.
    --- @return The callback returns no value.
    material: function(exclusive value: MaterialInfo): nil
}

Runs once per imported material before the resident GPU upload is queued.

MaterialInfotype#

type MaterialInfo = {
    --- Caller-writable. Names the authored material.
    name: string,

    --- Caller-writable. Selects metallic-roughness (0), unlit (1), or Lambert (2).
    model: integer,

    --- Caller-writable. Sets the metallic factor from zero to one.
    metallic: number,

    --- Caller-writable. Sets the perceptual roughness from zero to one.
    roughness: number
}

Allows import-time material tuning without duplicating geometry or images.

MaterialInputrecord#

record MaterialInput<T, C>
    componentType: C
    value: T
end

Carries a native value across a mutation boundary without losing its definition.

Type parameters

NameDescription
T
C

Fields

componentType#
componentType: C

Read-only. Preserves the exact component or relationship definition.

value#
value: T

Caller-writable. Holds the struct supplied at the mutation boundary.

MaterialOptionstype#

type MaterialOptions = {
    --- Caller-writable. Names the shared material with a stable, non-empty identity.
    name: string,

    --- Caller-writable. Selects a MATERIAL_* model; metallic-roughness is the default.
    model: integer?,

    --- Caller-writable. Sets linear red reflectance and defaults to one.
    baseR: number?,

    --- Caller-writable. Sets linear green reflectance and defaults to one.
    baseG: number?,

    --- Caller-writable. Sets linear blue reflectance and defaults to one.
    baseB: number?,

    --- Caller-writable. Sets opacity from zero to one and defaults to one.
    baseA: number?,

    --- Caller-writable. Sets linear red emission and defaults to zero.
    emissiveR: number?,

    --- Caller-writable. Sets linear green emission and defaults to zero.
    emissiveG: number?,

    --- Caller-writable. Sets linear blue emission and defaults to zero.
    emissiveB: number?,

    --- Caller-writable. Sets the metallic factor from zero to one and defaults to zero.
    metallic: number?,

    --- Caller-writable. Sets perceptual roughness from zero to one and defaults to one.
    roughness: number?,

    --- Caller-writable. Scales the normal map XY channels and defaults to one.
    normalScale: number?,

    --- Caller-writable. Sets occlusion strength from zero to one and defaults to one.
    occlusionStrength: number?,

    --- Caller-writable. Selects opaque, mask, or blend and defaults to opaque.
    alphaMode: string?,

    --- Caller-writable. Sets the mask threshold from zero to one and defaults to 0.5.
    alphaCutoff: number?,

    --- Caller-writable. Disables back-face culling when true and defaults to false.
    doubleSided: boolean?,

    --- Caller-writable. Names the optional base-color texture, sampled as sRGB.
    baseMap: string?,

    --- Caller-writable. Names the optional tangent-space normal texture, sampled as
    --- linear data.
    normalMap: string?,

    --- Caller-writable. Names the optional linear texture with roughness in green and
    --- metallic in blue.
    metallicRoughnessMap: string?,

    --- Caller-writable. Names the optional linear texture with occlusion in red.
    occlusionMap: string?,

    --- Caller-writable. Names the optional emission texture, sampled as sRGB.
    emissionMap: string?
}

Configures a shared mesh material. Colors and emission are linear RGB texture paths load relative to the caller's content directory.

MeshInputrecord#

record MeshInput<T, C>
    componentType: C
    value: T
end

Carries a native value across a mutation boundary without losing its definition.

Type parameters

NameDescription
T
C

Fields

componentType#
componentType: C

Read-only. Preserves the exact component or relationship definition.

value#
value: T

Caller-writable. Holds the struct supplied at the mutation boundary.

MeshOptionstype#

type MeshOptions = {
    --- Caller-writable. Names shared geometry with a stable, non-empty identity.
    name: string,

    --- Caller-writable. Supplies twelve floats per vertex: position XYZ, normal XYZ,
    --- tangent XYZW and texture UV.
    vertices: {number},

    --- Caller-writable. Supplies zero-based triangle indices, three per triangle.
    indices: {integer},

    --- Caller-writable. Optionally supplies four linear RGBA values per vertex.
    colors: {number}?,

    --- Caller-writable. Optionally supplies four zero-based joint indices per vertex.
    joints: {integer}?,

    --- Caller-writable. Supplies four nonnegative weights with each joint quartet.
    --- Each quartet must have positive total; the constructor normalizes it.
    weights: {number}?
}

Describes one indexed triangle mesh in local coordinates.

Modelrecord#

record Model
    path: string
    id: integer
    animations: {Animation}
    animationCount: integer
    newInstance: function(self: Model): Instance
    animationIndex: function(borrows self: Model, name: string): integer?
end

Retains shared geometry, materials and authored clips.

Methods

newInstance#
newInstance: function(self: Model): Instance

Creates an independently animated instance.

Arguments
NameTypeDescription
selfModel
Returns
TypeDescription
Instance

Returns reusable primitive templates plus private joint palettes and morph-weight vectors.

animationIndex#
animationIndex: function(borrows self: Model, name: string): integer?

Returns a clip's one-based index.

Arguments
NameTypeDescription
borrows selfModel
namestring

The caller supplies an authored or generated clip name.

Returns
TypeDescription
integer?

Returns its index, or nil when absent. Duplicate authored names raise because selecting either by that name would be ambiguous.

Fields

path#
path: string

Read-only. Contains the source asset path.

id#
id: integer

Engine-owned. Identifies the resident upload; game code selects primitives instead.

animations#
animations: {Animation}

Read-only. Contains decoded clips in file order.

animationCount#
animationCount: integer

Read-only. Reports the number of decoded clips.

PlayOptionstype#

type PlayOptions = {
    --- Caller-writable. Multiplies elapsed time. Negative values raise on
    --- `update`.
    speed: number?,

    --- Caller-writable. Controls whether playback wraps at the duration.
    loop: boolean?,

    --- Caller-writable. Controls whether `update` advances playback.
    playing: boolean?
}

Controls clip playback, with speed one, looping and immediate playback by default.

Primitiverecord#

record Primitive
    transform: TransformInput
    mesh: MeshInput
    bounds: BoundsInput
    material: MaterialInput
    skin: SkinInput
    morph: MorphInput
    materialName: string = ""
end

Holds the spawnable components for one independently posed primitive.

Fields

transform#
transform: TransformInput

Caller-writable. Contains this primitive's sampled world transform.

mesh#
mesh: MeshInput

Caller-writable. Selects resident geometry.

bounds#
bounds: BoundsInput

Caller-writable. Supplies the local bound, which must enclose every animated pose.

material#
material: MaterialInput

Caller-writable. Selects resident PBR material data.

skin#
skin: SkinInput

Caller-writable. Selects this instance's joint palette; its empty palette denotes rigid geometry.

morph#
morph: MorphInput

Caller-writable. Selects this instance's morph weights; its empty palette denotes geometry without morph targets.

materialName#
materialName: string

Read-only. Reports the authored material name for fixture and game metadata.

Functions#

loadfunction#

function load(path: string, options: LoadOptions?): Model?, string?

Loads and registers a glTF or GLB scene for independent instances.

Arguments

NameTypeDescription
pathstring

The caller supplies a model path; external buffers and images resolve beside it.

optionsLoadOptions?

The caller may tune imported materials before upload.

Returns

TypeDescription
Model?

The shared model, or nil and the decoder's reason when loading fails.

string?

Raises

  • When the supplied path is empty.

loadEnvironmentfunction#

function loadEnvironment(name: string, faces: {string}): string?, string?

Loads the six original environment faces and registers their shared GPU mip chain.

Arguments

NameTypeDescription
namestring

The stable, non-empty environment identity used by View lighting and snapshots.

faces{string}

File paths in positive X, negative X, positive Y, negative Y, positive Z, negative Z order.

Returns

TypeDescription
string?

The environment name, or nil and a decoding failure. All faces must be equally sized squares.

string?

nextUploadfunction#

function nextUpload(): integer, string

Drains one model upload for the native host; zero means the queue is empty.

Returns

TypeDescription
integer

The model ID and packed geometry, or zero and an empty string.

string

Values#

MATERIAL_LAMBERTvariable#

const MATERIAL_LAMBERT: integer

Selects diffuse Lambert shading.

MATERIAL_METALLIC_ROUGHNESSvariable#

Selects Cook-Torrance metallic-roughness shading.

MATERIAL_UNLITvariable#

const MATERIAL_UNLIT: integer

Selects unlit base color and emission.