Skip to main content

ANARI Animation and glTF Integration

@luma.gl/anari/gltf is an optional entry point that projects glTF-owned animation data onto retained ANARI objects. It reuses @luma.gl/gltf parsing and the shared @luma.gl/engine animation mixer; the ordinary @luma.gl/anari entry point does not import a glTF loader or own a second animation runtime.

Experimental optional adapter

@luma.gl/anari is a private, experimental workspace package. Import the /gltf subpath only when glTF animation integration is needed and @luma.gl/gltf is available.

Optional entry-point exports

import {
makeANARIAnimationClipsFromGLTF,
makeANARIAnimationDataFromGLTF,
makeANARIAnimationScene
} from '@luma.gl/anari/gltf';

import type {
ANARIAnimationBindings,
ANARIAnimationSceneHandle,
ANARIGLTFAnimationMappings
} from '@luma.gl/anari/gltf';
  • makeANARIAnimationClipsFromGLTF(animations, mappings?) converts already-decoded glTF clips into JSON-compatible retained tracks.
  • makeANARIAnimationDataFromGLTF(scenegraphs, mappings?) extracts retained source-node hierarchy, decoded clips, initial morph weights, and playback defaults from a GLTFScenegraphs bundle.
  • makeANARIAnimationScene(description, bindings) binds a retained scene description to existing objects and returns shared mixer/playback controls.

None of these functions reads a .gltf or .glb file. Load/postprocess the asset with @loaders.gl/gltf; decode source animation channels through @luma.gl/gltf.

Bind a retained animated scene

import {makeANARIAnimationDataFromGLTF, makeANARIAnimationScene} from '@luma.gl/anari/gltf';

const animationData = makeANARIAnimationDataFromGLTF(scenegraphs, {
instanceIdentifiers: {
CharacterNode: ['character-instance']
},
geometryIdentifiers: {
CharacterNode: ['character-geometry']
},
materialIdentifiers: ['character-material']
});

const animations = makeANARIAnimationScene(animationData, {
instances: new Map([['character-instance', instance]]),
geometries: new Map([['character-geometry', geometry]]),
materials: new Map([['character-material', material]]),
samplers: new Map([['base-color-sampler', sampler]])
});

function renderFrame(timeMilliseconds: number): void {
animations.update(timeMilliseconds / 1000);
frame.render();
graphicsDevice.submit();
requestAnimationFrame(renderFrame);
}

requestAnimationFrame(renderFrame);

animations.update(timeSeconds) takes an absolute, monotonically increasing time in seconds; it computes the elapsed mixer delta internally. This differs from GLTFAnimator.setTime(), which accepts absolute milliseconds, and direct AnimationMixer.update(), which accepts a delta in seconds.

Retained objects remain caller-owned. All changes targeting one retained object during a frame are staged together and committed once.

Playback controls

console.log(animations.clipNames);

animations.selectClip('Walk');
animations.play();
animations.setSpeed(1.5);
animations.pause();
animations.seek(0.75);

The ANARIAnimationSceneHandle exposes clipNames, activeClip, update(), selectClip(), play(), pause(), seek(), setSpeed(), and the existing engine mixer.

Loop behavior can be declared as once, repeat, or ping-pong; negative playback speed enables reverse traversal. Access animations.mixer for shared-engine action weights, blending, and crossfades. See the engine animation guide and AnimationMixer API reference.

Retained JSON hierarchy and clips

{
"nodes": {
"character": {
"translation": [0, 0, 0],
"weights": [0],
"instances": ["character-instance"],
"geometries": ["character-geometry"]
},
"hand": {
"parent": "character",
"translation": [0, 1, 0],
"instances": ["hand-instance"]
}
},
"clips": [
{
"name": "Expression",
"tracks": [
{
"target": {
"type": "node",
"identifier": "character",
"path": "weights"
},
"times": [0, 0.5, 1],
"values": [[0], [1], [0]],
"interpolation": "LINEAR"
}
]
}
],
"playback": {
"clip": "Expression",
"playing": true,
"speed": 1,
"loop": "repeat"
}
}

Node transforms preserve hierarchy, including meshless parent nodes. Instance placements inherit parent transforms; shared surface reuse and renderer instancing are preserved. Morph-weight tracks update only the retained geometries listed by their source node, keeping shared-mesh instances independent.

Supported animation targets

Target typeSupported uses
nodeTranslation, rotation, scale, and morph weights.
instanceRetained instance transform values.
materialSupported PBR scalar/vector factors and alpha cutoff.
samplerRetained UV transform offset, rotation, and scale.
lightCaller-declared retained light properties.
cameraCaller-declared retained camera properties.

STEP, LINEAR, and CUBICSPLINE interpolation use the shared engine implementation. glTF KHR_animation_pointer translates supported node/material/texture-transform channels; it does not automatically import arbitrary camera pointers, structural material switches, animated texCoord, or UV sets above TEXCOORD_1.

Morph targets and skins

The showcase importer preserves morph POSITION, NORMAL, and TANGENT deltas, initial node/mesh weights, and node-local geometry identities. Retained node weight animation updates the existing mesh vertex data without rebuilding its model.

Source JOINTS_0 and WEIGHTS_0 attributes are also preserved, and programmatic surfaces can use skin: {jointMatrices}. The showcase importer does not currently create or animate that joint palette automatically; complete imported skeletal playback requires application-provided palette integration. See ANARI arrays and geometry.

Validation

The optional @luma.gl/anari/schemas entry point validates animation nodes, targets, tracks, clips, playback settings, parent relationships, retained references, increasing keyframe times, and cubic tangent/value/tangent counts. See ANARI scene schemas.