InstancedGltfModelMeshDesc
The InstancedGltfModelMeshDesc class is a mesh descriptor that loads a single GLTF/GLB model and renders multiple transformed copies (instances) of it. It loads the GLTF once, then fans out every Mesh node into a sibling InstancedMesh sharing one per-instance matrix slot per model instance.
In addition to the properties below, the common properties from the base class (position, rotation, scale, matrix, matrixWorld, visible) are available. See MeshDesc for details.
The descriptor selects one of two internal rendering paths automatically based on the GLTF contents:
- Non-skinned path (instanced): every
Meshnode becomes anInstancedMesh. All instances share oneAnimationMixerrunning on the source scene; per-frame, each source mesh’smatrixWorldis re-sampled and every instance’s matrix is rewritten asT_i * sourceLocal_s. Node-TRS and morph-target animations both play back correctly. - Skinned path (per-instance clone fallback): because three.js’s
InstancedMeshcannot apply skinning, skinned GLTFs fall back to oneSkeletonUtils.cloneper instance, each with its ownAnimationMixer. This gives up instanced rendering for the skinned parts, but keeps the same desc API and plays shared clips in lockstep across clones.
The mesh uses Relative-To-Eye (RTE) precision, so it can be anchored anywhere on the globe without floating-point precision artifacts.
Picking is only supported on the non-skinned path. On the skinned path, batchIds is empty.
Shared Properties (InstancedModelsDescription)
Section titled “Shared Properties (InstancedModelsDescription)”Specified in the gltfModels config object.
Type: string
Description: URL of the GLTF/GLB model to load.
Example:
{ gltfModels: { url: "/models/tree.glb", }}castShadow
Section titled “castShadow”Type: boolean
Description: Specifies whether the instances cast shadows.
Default: false
receiveShadow
Section titled “receiveShadow”Type: boolean
Description: Specifies whether the instances receive shadows.
Default: false
animationActiveClip
Section titled “animationActiveClip”Type: string (optional)
Description: Name of the animation clip to play. The clip is shared across all instances. The list of available clips is exposed via handle.ref.animationClips after the model loads.
animationSpeed
Section titled “animationSpeed”Type: number
Description: Animation playback speed multiplier. Shared across all instances.
Default: 1
animationLoop
Section titled “animationLoop”Type: boolean
Description: Whether the active animation clip loops.
Default: true
animationAutoPlay
Section titled “animationAutoPlay”Type: boolean
Description: Auto-start the configured clip on load.
Default: false
Per-Instance Properties (ModelChildConfig)
Section titled “Per-Instance Properties (ModelChildConfig)”Properties for each individual model instance, specified in the children array. The transform is applied to every sub-mesh of that instance.
position
Section titled “position”Type: { x: number, y: number, z: number } | undefined
Description: Specifies the local position relative to the parent group.
Default: { x: 0, y: 0, z: 0 }
rotation
Section titled “rotation”Type: { x: number, y: number, z: number } | undefined
Description: Specifies the local rotation (Euler angles in radians).
Default: undefined
Type: { x: number, y: number, z: number } | undefined
Description: Specifies the local scale.
Default: { x: 1, y: 1, z: 1 }
matrix
Section titled “matrix”Type: Matrix4 | undefined
Description: Specifies a pre-computed transform matrix. When set, position, rotation, and scale are ignored.
Default: undefined
Config
Section titled “Config”pickable
Section titled “pickable”Type: boolean (optional)
Description: Enables picking for individual instances. Only effective on the non-skinned path.
Default: false
Events
Section titled “Events”Subscribe via handle.on(event, handler).
Description: Emitted when the GLTF has finished loading and instances are initialized.
Handler Type:
() => voidneedsUpdate
Section titled “needsUpdate”Description: Emitted when the descriptor’s instance state changes (add/remove/update/replace/clear).
Handler Type:
() => voidInstance Management
Section titled “Instance Management”handle.ref.add(config)
Section titled “handle.ref.add(config)”Adds a new instance. Returns the index of the added instance. Capacity grows automatically on the non-skinned path.
const index = handle.ref.add({ position: { x: 100, y: 0, z: 0 }, scale: { x: 2, y: 2, z: 2 },});handle.ref.removeAt(index)
Section titled “handle.ref.removeAt(index)”Removes an instance by index. Uses swap-with-last for O(1) removal; instance order is not preserved.
handle.ref.removeAt(1);handle.ref.updateAt(index, config)
Section titled “handle.ref.updateAt(index, config)”Updates an instance at the given index with partial config. Unset fields are preserved.
handle.ref.updateAt(0, { position: { x: 50, y: 0, z: 0 },});handle.ref.clear()
Section titled “handle.ref.clear()”Removes all instances.
handle.ref.replaceAll(configs)
Section titled “handle.ref.replaceAll(configs)”Batch replaces all instances. More efficient than clear() + repeated add() since it emits a single update notification.
handle.ref.count
Section titled “handle.ref.count”Gets the number of active instances.
handle.ref.animationClips
Section titled “handle.ref.animationClips”Read-only list of animation clip names found in the loaded GLTF. Empty until the load event fires.
handle.on("load", () => { console.log("Available clips:", handle.ref.animationClips);});handle.ref.playAnimation(name)
Section titled “handle.ref.playAnimation(name)”Plays the named animation clip on all instances. On the non-skinned path, all instances play in lockstep from a single mixer; on the skinned path, each clone plays its own copy of the same clip.
handle.ref.playAnimation("Fly");handle.ref.stopAnimation()
Section titled “handle.ref.stopAnimation()”Stops the currently playing animation on all instances.
Usage Examples
Section titled “Usage Examples”Basic Usage
Section titled “Basic Usage”import ThreeView from "@navaramap/three";import { InstancedGltfModelMeshDesc } from "@navaramap/three_default_descs";
const view = new ThreeView();view.registerMesh("gltfModels", InstancedGltfModelMeshDesc);await view.init();
const handle = view.addMesh<InstancedGltfModelMeshDesc>({ gltfModels: { url: "/models/tree.glb", castShadow: true, children: [ { position: { x: 0, y: 0, z: 0 } }, { position: { x: 50, y: 0, z: 0 }, scale: { x: 1.5, y: 1.5, z: 1.5 } }, { position: { x: 100, y: 0, z: 0 }, rotation: { x: 0, y: Math.PI / 4, z: 0 } }, ], }, position: { x: 0, y: 0, z: 6378137 },});Animated Skinned Model
Section titled “Animated Skinned Model”const handle = view.addMesh<InstancedGltfModelMeshDesc>({ gltfModels: { url: "/glTF/animated_bird_pigeon/scene.gltf", animationActiveClip: "Fly", animationSpeed: 1.5, animationLoop: true, animationAutoPlay: true, children: [ { position: { x: 0, y: 0, z: 0 } }, { position: { x: 20, y: 0, z: 0 } }, { position: { x: 40, y: 0, z: 0 } }, ], }, position: { x: 0, y: 0, z: 6378137 },});
handle.on("load", () => { console.log("Clips:", handle.ref.animationClips);});Dynamic Animation Control
Section titled “Dynamic Animation Control”// Switch clips at runtimehandle.ref.playAnimation("Walk");
// Stop all animationhandle.ref.stopAnimation();
// Add a new instance while animation is playinghandle.ref.add({ position: { x: 60, y: 0, z: 0 } });