Returns a joint transform in parent space after clip playback and layers.
LayerOptionslayers in create · 2 fields
joints?
readonly string[]= every joint
Names the joints whose subtrees the layer moves, such as ['spine_02'] for the upper body.
add?
boolean= false
Adds how the layer's clip differs from its first key to the pose beneath, rather than replacing it.
records
animate→compute
animate(pass, time) once a frame in a compute pass, after the kernels that call the verbs and before the passes that draw the poses: it uploads what changed and finds every pose's joints.
owns
a table ·4 buffers ·a uniform ·a pipeline
The plays, the clips' keys and spans, the joints' parents, inverse binds, bind origins and masks, every pose's joints in two halves, the clock, and the kernel that finds them.
lifecycle
✓initAsync()✓destroy()✓[Symbol.dispose]
injects
definition
Every pose's joints, which the vertices of a kind with a pose read, and the plays the verbs write, for a pipeline piped figures.inject().
binds
1 storage · 2 uniform
A skinned vertex, every pose's joints, the poses' slots and the clock that says which half of the joints to read.
2 storage · 2 uniform
jointPosition, every pose's joints, the joints' bind origins, the poses' slots and the clock.
2 storage · 2 uniform
A kernel calling play, once, stop, mix, weigh or seconds, the plays, the clips, the clock and the poses' slots.
Change the clip, fade or speed; a negative speed plays backward. One pose moves both characters through rebind, with the same sword model attached to each right hand.
A skinned character binds vertices to joints such as hips, knees and hands. A pose plays a clip. Each frame, a kernel computes its joint transforms. An instance kind with a pose follows those joints in both the colour and shadow passes.
Reads a binary glTF 2.0 file into meshes, selected parts, a skin and animations. It applies
default-scene node transforms and groups meshes by material. Morph targets are omitted.
Skinned meshes use their bind pose with four joints and weights per vertex. An unskinned mesh
attached to a joint, such as a helmet, binds to that joint. An unskinned mesh without a joint
ancestor must be read as a part or this throws. A part holding a skinned mesh binds to the skin
as meshes do; other parts have no skin bindings.
It checks every index, count and range before reading. Invalid glTF, unsupported file versions
and unsupported required features throw with details. Use load for a model whose buffers or
images are separate files. For extensions that need conversion,
errors name the gltf-transform command.
@param ― source the file's bytes, from anywhere
@param ― options.parts names of nodes to keep apart from the rest, each posed about its own origin
or, holding a skinned mesh, by the skin
@param ― options.rotation a turn of the whole model, for one that doesn't face +z
@returns ― plain arrays and images, for meshPool.add
Reads a binary glTF 2.0 file into meshes, selected parts, a skin and animations. It applies
default-scene node transforms and groups meshes by material. Morph targets are omitted.
Skinned meshes use their bind pose with four joints and weights per vertex. An unskinned mesh
attached to a joint, such as a helmet, binds to that joint. An unskinned mesh without a joint
ancestor must be read as a part or this throws. A part holding a skinned mesh binds to the skin
as meshes do; other parts have no skin bindings.
It checks every index, count and range before reading. Invalid glTF, unsupported file versions
and unsupported required features throw with details. Use load for a model whose buffers or
images are separate files. For extensions that need conversion,
errors name the gltf-transform command.
@param ― source the file's bytes, from anywhere
@param ― options.parts names of nodes to keep apart from the rest, each posed about its own origin
or, holding a skinned mesh, by the skin
@param ― options.rotation a turn of the whole model, for one that doesn't face +z
@returns ― plain arrays and images, for meshPool.add
Creates poses of skin that play named clips, for example { idle: general.clips[6], walk }. An entry that is an array of clips is a line, its clips sampled at one phase and mixed by
mix. It bakes clips at hz keys per second and blends between adjacent keys during playback.
Call animate each frame to compute joints in model space before drawing instances. The skin
must have 1 to 256 joints in parent-first order, with 16-float inverse binds. Invalid joint
data, capacity, bake rate, clips, lines of fewer than two clips, more than three layers, a layer
named base or a layer joint the skin lacks throw.
@param ― skin the joints, as gltf.read returns them in a model's skin
@param ― clips the clips and lines its poses play, by the names add and play take
@returns ― poses you animate every frame and destroy
create(
constroot:TgpuRoot
root,
constknight:gltf.ModelData<never>
knight.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
skin!,
{
idle:poses.ClipData| readonly poses.ClipData[]
idle:
import poses
poses.
functionclip(file:{
readonly clips:readonlyposes.ClipData[];
},name:string):poses.ClipData
export clip
Returns the clip named name among a file's clips. Throws listing the names the file has.
@param ― file what gltf.read returns, or anything holding clips
@param ― name the clip's name in the file
@returns ― the clip, as the file holds it
clip(
constmoves:gltf.ModelData<never>
moves,'Idle'),
walk:poses.ClipData| readonly poses.ClipData[]
walk:
import poses
poses.
functionclip(file:{
readonly clips:readonlyposes.ClipData[];
},name:string):poses.ClipData
export clip
Returns the clip named name among a file's clips. Throws listing the names the file has.
@param ― file what gltf.read returns, or anything holding clips
@param ― name the clip's name in the file
@returns ― the clip, as the file holds it
clip(
constmoves:gltf.ModelData<never>
moves,'Walking')},
{
Options<L extends string = string>.capacity: number
Limits the pose count to an integer from 1 to 65 535.
Defines an instance kind with custom fields, transform ownership and callbacks for vertex
motion or materials. Each kind uses colour and shadow pipelines per renderer. name brands
set ids, as in Id<'Fish'>, and labels the table and WGSL. Throws if both material and
surface are supplied, or a custom field duplicates a built-in or reserved name.
@param ― name a label for ids and WGSL
@returns ― a kind to give instances.create as kind
define('Guard',{
KindOptions<{ pose:U32;},{},"host">.host?:({
pose:d.U32;
}&{
readonly rotation?:"is a field of every instance"|undefined;
readonly position?:"is a field of every instance"|undefined;
readonly scale?:"is a field of every instance"|undefined;
readonly color?:"is a field of every instance"|undefined;
readonly roughness?:"is a field of every instance"|undefined;
readonly emission?:"is a field of every instance"|undefined;
readonly metallic?:"is a field of every instance"|undefined;
readonly mesh?:"is a field of every instance"|undefined;
readonly opacity?:"is a field of every instance"|undefined;
}&{
...;
})|undefined
Adds fields written on the CPU, as in table.define. Default none.
host:{
pose:d.U32
pose:
import d
d.
constu32:d.U32
export u32
A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)
Can also be called to cast a value to an u32 in accordance with WGSL casting rules.
Selects the pose that moves the vertices by their joints. Return an id from the
poses.create instance injected into the renderer. Both passes apply the pose and rotate
normals before deform. Culling uses the mesh bounds in the bind pose, so supply bounds
if animation extends beyond them. Meshes must have joints. Default none, which uses the bind
pose.
Creates a set of mesh instances with transforms, materials and optional custom fields. Each
instance uses a row, and renderers draw one list of live rows per mesh.
@param ― pool the mesh pool whose meshes it draws
@param ― options.kind a kind from instances.define
@returns ― a set of instances you flush every frame and destroy
Creates a renderer for colour and shadow pass descriptions, lit by the lighting each
description gives through beneath unless options.light replaces it. Use pipe to fill
resources read by kind callbacks.
renderer().
Renderer.pipe(fill: Inject): instances.Renderer
Returns this renderer with fill applied to every pipeline and kernel it builds. The fill
runs after lighting and before the pass camera. Use it for resources read by kind callbacks,
as in school.renderer().pipe(water.inject()).
pipe(
constfigures:poses.Poses<"idle"|"walk",never>
figures.
Poses<"idle"|"walk", never>.inject(): Inject
Fills joint and play resources for pipelines created from the configuration you pipe it
into. Apply this to the renderer of a kind with pose, and to kernels calling the verbs.
Builds a pipeline for drawing that writes every described output and returns its draw
methods. If the drawing cannot supply the fields an output needs, type checking fails with
that output's name. Use without to leave it out or instead to replace it.
add(
constrenderer:instances.Renderer
renderer);
const
constcast:passes.Drawn
cast=
constlight:lighting.LightingOf<{
readonly shadow:{};
}>
light.
shadow: Shadow |(Cascades & Shadow)
Provides the sun shadow when enabled in the options. Throws after destroy.
shadow.
ShadowPasses.add<{
color:d.v4f;
},{
position:d.v4f;
world:d.v3f;
normal:d.v3f;
uv:d.v2f;
uv1:d.v2f;
row:number;
}>(drawing:passes.Compound<{
color:d.v4f;
},{
position:d.v4f;
world:d.v3f;
normal:d.v3f;
uv:d.v2f;
uv1:d.v2f;
row:number;
}>):passes.Drawn(+1overload)
Builds a caster's depth pipeline with the shadow's bias and returns its draw methods.
Adds a pose playing clip on its base from time on the clock passed to animate. Its
layers play nothing. Ignores layer, weight and fade. Throws if the set is full or the
clip was not supplied at creation.
Adds an instance of a mesh, model or group of meshes from the pool. Each mesh uses a row,
and all rows move and change together under one id. values may be a function of each mesh,
to give a model's meshes values of their own. Throws if a mesh belongs to another pool, the
group is empty, a posed kind receives a mesh without joints or the set is full.
Uploads each mesh into the next free ranges and decodes images in the background. Each image
decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer
for the full vertex capacity. Throws if the pool is full, the model has no meshes, its
arrays disagree or encoded images have no decoder.
Starts clip at time on the base or play.layer, and blends from the preceding clip over
fade seconds. An interrupted blend starts from the last selected clip. Throws for a clip or
layer the poses lack.
play(
constpose:poses.Id
pose,'walk',
constnow:number
now,{
Play<never>.fade?: number |undefined
Sets the blend duration from the preceding clip in seconds. A layer that shows only in part,
fading in or out, fades the clip in from the share it shows. add ignores it. Default 0.
fade:0.3});
// After guards.flush(), before shadow and scene passes
const
constpass:TgpuComputePass
pass=
constframe:Frame
frame.
Frame.encoder: TgpuCommandEncoder
Records commands for submission after the recorder returns. submit() replaces this encoder.
Read it at each call instead of keeping it, and let the frame submit it.
Uploads changes and computes every pose joint at time, with one workgroup per pose. Record
this once per frame in a compute pass before drawing, also while paused, so last frame's
joints stay one frame old.
animate(
constpass:TgpuComputePass
pass,
constnow:number
now);
constpass:TgpuComputePass
pass.
TgpuComputePass.end(): void
Completes the recording of this compute pass
end();
Record poses before the passes that draw them. You can share a compute pass with culling. Await figures.initAsync() alongside the drawing passes. Otherwise the first animate compiles the kernel and stalls on WebKit.
Pass the skin from gltf.read to poses.create(root, skin, clips, options). Key clips by the names you use in add and play.
poses.clip(file, name) finds a clip in what gltf.read returns. A name the file lacks throws with the file’s clip names.
Clips address joints by name, so clips for one rig can animate all characters using that rig.
The CPU bakes each clip at hz keys per second for every joint. A pose interpolates between the two surrounding keys. Linear, step and cubic source keys all use this bake.
Joints not animated by a clip stay at rest. Channels for missing nodes are skipped.
create throws if a clip moves no joints or addresses a name shared by two joints. It also throws if a skin exceeds 256 joints or lists children before parents.
figures.add(clip, time, { speed, from, loop, mix }) returns a poses.Id for an instance’s pose. It throws if capacity is full or the clip name is unknown. The error lists available clips.
figures.play(id, clip, time, { layer, speed, from, loop, fade, weight, mix }) starts a clip at time on the base or on layer, and blends from the previous clip over fade seconds. If a blend is already running, the replacement blend starts from the last played clip.
speed defaults to 1 for normal playback. Use 0 to hold or a negative value to play backward. loop defaults to true; false holds the clip at its end. from is the starting offset in seconds and defaults to 0. fade also defaults to 0, and add ignores it.
Use the same clock for time and animate.
figures.seconds(id, time, { layer }) returns how far into its clip or cycle the base or a layer is, for a footstep or a hit frame in your own logic.
figures.duration(clip, mix) returns how many seconds a clip’s cycle lasts at speed 1, or a line’s at mix, to time a hit or the end of a clip played once.
remove invalidates the id. has checks whether it is live.
Walking and running clips blended by speed need their feet in step. A line is an entry of two or more clips that play at one shared phase, mixed by a number:
Creates poses of skin that play named clips, for example { idle: general.clips[6], walk }. An entry that is an array of clips is a line, its clips sampled at one phase and mixed by
mix. It bakes clips at hz keys per second and blends between adjacent keys during playback.
Call animate each frame to compute joints in model space before drawing instances. The skin
must have 1 to 256 joints in parent-first order, with 16-float inverse binds. Invalid joint
data, capacity, bake rate, clips, lines of fewer than two clips, more than three layers, a layer
named base or a layer joint the skin lacks throw.
@param ― skin the joints, as gltf.read returns them in a model's skin
@param ― clips the clips and lines its poses play, by the names add and play take
@returns ― poses you animate every frame and destroy
create(
constroot:TgpuRoot
root,
constknight:gltf.ModelData<never>
knight.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
skin!,
{
idle:poses.ClipData| readonly poses.ClipData[]
idle:
constclip:(name:string)=>poses.ClipData
clip('Idle'),
moving:poses.ClipData| readonly poses.ClipData[]
moving:[
constclip:(name:string)=>poses.ClipData
clip('Walking'),
constclip:(name:string)=>poses.ClipData
clip('Running')]},
{
Options<L extends string = string>.capacity: number
Limits the pose count to an integer from 1 to 65 535.
Adds a pose playing clip on its base from time on the clock passed to animate. Its
layers play nothing. Ignores layer, weight and fade. Throws if the set is full or the
clip was not supplied at creation.
Starts clip at time on the base or play.layer, and blends from the preceding clip over
fade seconds. An interrupted blend starts from the last selected clip. Throws for a clip or
layer the poses lack.
play(
constpose:poses.Id
pose,'moving',
constnow:number
now,{
Play<never>.fade?: number |undefined
Sets the blend duration from the preceding clip in seconds. A layer that shows only in part,
fading in or out, fades the clip in from the share it shows. add ignores it. Default 0.
fade:0.25});
// Every frame, from its speed in metres per second
Places the line playing on the base or on.layer at value from time, keeping its phase. A
call with the value it has writes nothing.
mix(
constpose:poses.Id
pose,
var Math:Math
An intrinsic object that provides basic mathematics functionality and constants.
Math.
Math.min(...values: number[]): number
Returns the smaller of a set of supplied numeric expressions.
@param ― values Numeric expressions to be evaluated.
min(
var Math:Math
An intrinsic object that provides basic mathematics functionality and constants.
Math.
Math.max(...values: number[]): number
Returns the larger of a set of supplied numeric expressions.
@param ― values Numeric expressions to be evaluated.
max((
constspeed:number
speed-0.8)/2.2,0),1),
constnow:number
now);
mix 0 plays the first clip, 1 the second and 1.5 halfway between the second and the third. Only the two clips around it are sampled.
The cycle lasts the clips’ durations mixed by mix. A change of mix keeps the phase, so the feet don’t skip. A call with the value the line has writes nothing.
The clips must put the same foot down at the same share of their cycles. Shift one that starts on a different step before passing it.
from counts seconds of the cycle at the starting mix. speed and loop act on the cycle. A line fades to and from clips and other lines as a clip does.
A layer plays a clip over the base on some joints alone, such as an arm holding a lantern while the rest of the body stands or walks. Name up to three layers at creation. Each moves the subtrees of the joints it names:
night-marketA seller raises her lantern for a customer out of the rain; the potions are in focus and the market's lights blur into discs.
The seller holds her lantern on a layer masked to her right arm, and raises it while the customer is at her table. Tap either of them to wave on a layer masked to the spine. The lantern's layer comes after it, so the seller waves her free hand and keeps the lantern.
Creates poses of skin that play named clips, for example { idle: general.clips[6], walk }. An entry that is an array of clips is a line, its clips sampled at one phase and mixed by
mix. It bakes clips at hz keys per second and blends between adjacent keys during playback.
Call animate each frame to compute joints in model space before drawing instances. The skin
must have 1 to 256 joints in parent-first order, with 16-float inverse binds. Invalid joint
data, capacity, bake rate, clips, lines of fewer than two clips, more than three layers, a layer
named base or a layer joint the skin lacks throw.
@param ― skin the joints, as gltf.read returns them in a model's skin
@param ― clips the clips and lines its poses play, by the names add and play take
@returns ― poses you animate every frame and destroy
create(
constroot:TgpuRoot
root,
constmage:gltf.ModelData<never>
mage.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
skin!,
{
idle:poses.ClipData| readonly poses.ClipData[]
idle:
constclip:(name:string)=>poses.ClipData
clip('Idle_B'),
hold:poses.ClipData| readonly poses.ClipData[]
hold:
constclip:(name:string)=>poses.ClipData
clip('Holding_A'),
raise:poses.ClipData| readonly poses.ClipData[]
raise:
constraised:poses.ClipData
raised,
wave:poses.ClipData| readonly poses.ClipData[]
wave:
constwaveLeft:poses.ClipData
waveLeft},
{
Options<L extends string = string>.capacity: number
Limits the pose count to an integer from 1 to 65 535.
Adds a pose playing clip on its base from time on the clock passed to animate. Its
layers play nothing. Ignores layer, weight and fade. Throws if the set is full or the
clip was not supplied at creation.
Starts clip at time on the base or play.layer, and blends from the preceding clip over
fade seconds. An interrupted blend starts from the last selected clip. Throws for a clip or
layer the poses lack.
Starts clip at time on the base or play.layer, and blends from the preceding clip over
fade seconds. An interrupted blend starts from the last selected clip. Throws for a clip or
layer the poses lack.
Names the layer that plays the clip. Default 'base'.
layer:'lantern',
Play<"wave"|"lantern">.fade?: number |undefined
Sets the blend duration from the preceding clip in seconds. A layer that shows only in part,
fading in or out, fades the clip in from the share it shows. add ignores it. Default 0.
fade:0.6});
// When she is tapped, wave once; the lantern's layer still holds her right arm
Starts clip at time on the base or play.layer, and blends from the preceding clip over
fade seconds. An interrupted blend starts from the last selected clip. Throws for a clip or
layer the poses lack.
Names the layer that plays the clip. Default 'base'.
layer:'wave',
Play<"wave"|"lantern">.loop?: boolean |undefined
Controls whether playback repeats. A value of false holds the final key at the end.
Default true.
loop:false,
Play<"wave"|"lantern">.fade?: number |undefined
Sets the blend duration from the preceding clip in seconds. A layer that shows only in part,
fading in or out, fades the clip in from the share it shows. add ignores it. Default 0.
fade:0.35});
// When the wave ends, to give the upper body back to the base
Names the layer that stops. The base doesn't stop, so stopping it throws. Default 'base'.
layer:'wave',
Stop<"wave"|"lantern">.fade?: number |undefined
Sets the fade back to the pose beneath in seconds. A layer still fading in fades out from the
share it shows. Default 0.
fade:0.5});
A layer replaces the pose beneath it inside its mask, by its weight. Layers apply in the order of layers, after the base. A joint outside every mask keeps the base’s placement exactly.
A clip on a layer moves only the joints in its mask, so a lean the clip makes above the mask is lost. KayKit’s Waving leans from the hips; poses.lean turns the spine by that lean before the clip plays on the spine.
joints defaults to every joint. A name the skin lacks throws with the skin’s joint list. create also throws for more than three layers or a layer named base.
A layer that played nothing, or was stopped, fades in from the pose beneath. stop fades it back out. A fade cut short by play or stop goes on from the share the layer shows. The base can’t stop; play another clip on it.
weigh(id, weight, { layer }) sets how much of a layer shows, from 0 to 1, at once, for a weight you change continuously. play takes weight too; without it a layer keeps the weight it has, which starts at 1. The base always shows whole, so weighing it throws.
Every method takes the layer as layer in its last argument, the base when you leave it out.
An additive layer adds how its clip moves away from the clip’s first key to whatever lies beneath. Breathing, a flinch or a recoil plays the same over a walk, a stand or a wave. The night market’s seller and customer breathe this way:
Creates poses of skin that play named clips, for example { idle: general.clips[6], walk }. An entry that is an array of clips is a line, its clips sampled at one phase and mixed by
mix. It bakes clips at hz keys per second and blends between adjacent keys during playback.
Call animate each frame to compute joints in model space before drawing instances. The skin
must have 1 to 256 joints in parent-first order, with 16-float inverse binds. Invalid joint
data, capacity, bake rate, clips, lines of fewer than two clips, more than three layers, a layer
named base or a layer joint the skin lacks throw.
@param ― skin the joints, as gltf.read returns them in a model's skin
@param ― clips the clips and lines its poses play, by the names add and play take
@returns ― poses you animate every frame and destroy
create(
constroot:TgpuRoot
root,
constknight:gltf.ModelData<never>
knight.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
skin!,
{
idle:poses.ClipData| readonly poses.ClipData[]
idle:
constclip:(name:string)=>poses.ClipData
clip('Idle'),
breathe:poses.ClipData| readonly poses.ClipData[]
breathe:
constclip:(name:string)=>poses.ClipData
clip('Breathe')},
{
Options<L extends string = string>.capacity: number
Limits the pose count to an integer from 1 to 65 535.
Adds a pose playing clip on its base from time on the clock passed to animate. Its
layers play nothing. Ignores layer, weight and fade. Throws if the set is full or the
clip was not supplied at creation.
Starts clip at time on the base or play.layer, and blends from the preceding clip over
fade seconds. An interrupted blend starts from the last selected clip. Throws for a clip or
layer the poses lack.
play(
constpose:poses.Id
pose,'breathe',
constnow:number
now,{
Play<"chest">.layer?:"chest"|"base"|undefined
Names the layer that plays the clip. Default 'base'.
layer:'chest',
Play<"chest">.from?: number |undefined
Sets the starting offset within the clip or cycle in seconds. Default 0.
from:
var Math:Math
An intrinsic object that provides basic mathematics functionality and constants.
These functions return new clips on the CPU, to pass to poses.create. The night market lines up a run with a walk, leans a wave onto the spine and mirrors it for the other hand:
const
constskin:poses.SkinData
skin=
constmage:gltf.ModelData<never>
mage.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
skin!;
const
conststand:poses.ClipData
stand=
import poses
poses.
functionclip(file:{
readonly clips:readonlyposes.ClipData[];
},name:string):poses.ClipData
export clip
Returns the clip named name among a file's clips. Throws listing the names the file has.
@param ― file what gltf.read returns, or anything holding clips
@param ― name the clip's name in the file
@returns ― the clip, as the file holds it
clip(
constmoves:gltf.ModelData<never>
moves,'Idle_A');
// Start the run where its left foot lands, as the walk's does at its start
Returns a copy of a looping clip that starts by seconds in, resampled into linear keys. Use
it to line up the clips of a line, so feet land at the same phase.
@param ― clip the looping clip
@param ― by seconds into clip the copy starts; a negative value starts before its end
@returns ― a clip as long as clip, with every channel resampled
shift(
import poses
poses.
functionclip(file:{
readonly clips:readonlyposes.ClipData[];
},name:string):poses.ClipData
export clip
Returns the clip named name among a file's clips. Throws listing the names the file has.
@param ― file what gltf.read returns, or anything holding clips
@param ― name the clip's name in the file
@returns ― the clip, as the file holds it
clip(
constmoves:gltf.ModelData<never>
moves,'Running_A'),0.08);
// The wave's lean from the hips, kept on the spine for a layer masked to it
Returns the channels of clip that move top and the joints beneath it, for a layer masked
to top. The rotation of top takes on how the joints above it turn in clip, measured from
the first key of options.from, so the layer turns its joints as the whole clip does. That
rotation is resampled into linear keys.
@param ― clip the clip to keep a part of
@param ― skin the joints clip moves
@param ― top the joint at the top of the part, as the layer's joints names it
@returns ― a clip with the rotation of top first, then the channels beneath it
lean(
import poses
poses.
functionclip(file:{
readonly clips:readonlyposes.ClipData[];
},name:string):poses.ClipData
export clip
Returns the clip named name among a file's clips. Throws listing the names the file has.
@param ― file what gltf.read returns, or anything holding clips
@param ― name the clip's name in the file
@returns ― the clip, as the file holds it
clip(
constmoves:gltf.ModelData<never>
moves,'Waving'),
constskin:poses.SkinData
skin,'spine',{
Lean.from?:poses.ClipData|undefined
Gives the clip whose first key the lean is measured from. Default rest, the rest pose.
Returns a copy of clip mirrored to the rig's other side. Each channel moves the joint
opposite names, its positions flip along across, and its rotations turn the other way
about the two other axes. The rig's sides must be mirror images, as in most humanoid rigs.
@param ― clip the clip to mirror
@param ― opposite the joint on the other side, such as (joint) => joint.replace(/\.r$/, '.l'),
or the joint itself for one in the middle
@returns ― a clip with a channel for each of clip's, on the opposite joints
Samples channel at time seconds on the CPU as create bakes it: it holds a step, mixes
linear keys, slerps rotations and follows cubic splines, and holds the first and last keys
outside them. Returns three numbers, or four for a rotation, in out when given.
@param ― channel the keys, as gltf.read returns them
@param ― time seconds into the clip
@param ― out the array it fills
@returns ― the position, rotation [x, y, z, w] or scale
sample(
constwave:poses.ClipData
wave.
ClipData.channels: readonly poses.ChannelData[]
channels[0]!,0.5);// a rotation, [x, y, z, w]
shift(clip, by) starts a looping clip by seconds in.
lean(clip, skin, top, { from }) keeps the channels of top and the joints beneath it, and turns top as the joints above it turn in the clip, measured from the first key of from, the rest pose by default.
mirror(clip, opposite, { across }) moves each channel to the joint opposite names and reflects it across the plane across points out of, 'x' by default. The rig’s sides must be mirror images.
shift and lean resample what they change into linear keys at hz, 30 by default.
sample(channel, time) returns a channel’s value as create bakes it, with slerped rotations and cubic splines, for keys you author yourself.
A crowd’s kernels already know each member’s speed and mood. They can start, stop and mix clips themselves through GPU verbs that mirror the methods. Give them indices from figures.clips and figures.layers, and pipe figures.inject() into the pipeline:
Plays entry clip looping on layer of pose id from from seconds, at speed 1, blending
from the preceding clip over fade seconds. Call it from a kernel created from
root.pipe(figures.inject()) recorded before animate.
Fills joint and play resources for pipelines created from the configuration you pipe it
into. Apply this to the renderer of a kind with pose, and to kernels calling the verbs.
Uploads changes and computes every pose joint at time, with one workgroup per pose. Record
this once per frame in a compute pass before drawing, also while paused, so last frame's
joints stay one frame old.
animate(
constpass:TgpuComputePass
pass,
constnow:number
now);
poses.play(id, clip, layer, fade, from) loops a clip from from seconds, and poses.once(id, clip, layer, fade) plays it once from its start. poses.stop(id, layer, fade), poses.mix(id, value, layer) and poses.weigh(id, weight, layer) match the methods, and poses.seconds(id, layer) reads the time into the clip or cycle. Each verb takes the pose, the method’s arguments without the time, then the layer and the rest. Layer 0 is the base, which poses.stop leaves playing, and a verb on a layer past 3 changes nothing.
Kernels play at speed 1.
Record the kernels before animate. They start clips at the time that frame’s animate writes, and their changes show in the same frame.
A machine’s enter and exit hooks can call the verbs, as in the skeletons.
Give each pose its plays from the CPU or from kernels. The CPU doesn’t see what kernels play, so a later figures.play fades from the last clip the CPU played.
figures.animate(pass, time) runs one workgroup per pose and one thread per joint.
Each vertex blends four joint matrices.
A kind’s deform runs on the posed vertex. Normals rotate with the pose.
With cull: true, each instance row, including held items, uses the sphere around its mesh’s bind-pose box. If a pose extends beyond it, as a lying body can, supply bounds on the kind.
The pose kernel binds 5 storage buffers and 1 uniform, plus what local reads.
The joints buffer holds this frame’s joints and last frame’s. Code compiled with history.previously, as a TAA motion output’s is, reads last frame’s, so a swinging arm draws its own motion. Both halves sit in one buffer, so a motion pipeline binds no extra storage.
Call animate every frame a pose is drawn, also while paused. A frame without it keeps the last motion.
A pose added since the last animate starts with equal halves and shows no motion in its first frame.
An instance that moves needs its set created with history: true for its transform’s motion.
Use local to turn a head toward a target, reach with a hand or follow a simulation. It receives the clip’s placement, joint index and pose slot, then returns a placement relative to the parent joint. Index its input data by figures.slotOf(id). For joints driven only by local, play poses.rest to leave all clip placements at rest.
const
constHEAD:number
HEAD=
constknight:gltf.ModelData<never>
knight.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
skin!.
SkinData.joints: readonly poses.JointData[]
joints.
ReadonlyArray<JointData>.findIndex(predicate: (value:poses.JointData,index:number,obj:readonlyposes.JointData[])=> unknown, thisArg?: any): number
Returns the index of the first element in the array where predicate is true, and -1
otherwise.
@param ― predicate find calls predicate once for each element of the array, in ascending
order, until it finds one where predicate returns true. If such an element is found,
findIndex immediately returns that element index. Otherwise, findIndex returns -1.
@param ― thisArg If provided, it will be used as the this value for each invocation of
predicate. If it is not provided, undefined is used instead.
Creates poses of skin that play named clips, for example { idle: general.clips[6], walk }. An entry that is an array of clips is a line, its clips sampled at one phase and mixed by
mix. It bakes clips at hz keys per second and blends between adjacent keys during playback.
Call animate each frame to compute joints in model space before drawing instances. The skin
must have 1 to 256 joints in parent-first order, with 16-float inverse binds. Invalid joint
data, capacity, bake rate, clips, lines of fewer than two clips, more than three layers, a layer
named base or a layer joint the skin lacks throw.
@param ― skin the joints, as gltf.read returns them in a model's skin
@param ― clips the clips and lines its poses play, by the names add and play take
@returns ― poses you animate every frame and destroy
create(
constroot:TgpuRoot
root,
constknight:gltf.ModelData<never>
knight.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
skin!,
{
rest:poses.ClipData| readonly poses.ClipData[]
rest:
import poses
poses.
constrest:poses.ClipData
This clip has no keys and keeps every joint at rest. Use it for poses driven only by local.
rest},
{
Options<L extends string = string>.capacity: number
Limits the pose count to an integer from 1 to 65 535.
Returns a joint transform in parent space after clip playback and layers. The callback
receives the played transform, joint index and pose slot. Use it to aim a head, reach towards
a target or read a simulation buffer indexed by slotOf. Default the clip transform.
local:(
played:{
rotation:d.v4f;
position:d.v3f;
scale:d.v3f;
}
played,
joint: number
joint,
slot: number
slot)=>{
'use gpu';
if(
joint: number
joint!==
constHEAD:number
HEAD)return
import poses
poses.
functionPlacement(props?:{
rotation:d.v4f;
position:d.v3f;
scale:d.v3f;
}|undefined):{
rotation:d.v4f;
position:d.v3f;
scale:d.v3f;
}
export Placement
This schema holds a unit quaternion, position and scale per axis. Instance transforms use
world space; joint transforms use parent space.
Placement(
played:{
rotation:d.v4f;
position:d.v3f;
scale:d.v3f;
}
played);
const
consthalf:number
half=
constturn:TgpuReadonly<d.WgslArray<d.F32>>
turn.
TgpuReadonly<WgslArray<F32>>.$: number[]
$[
slot: number
slot]!/2;
return
import poses
poses.
functionPlacement(props?:{
rotation:d.v4f;
position:d.v3f;
scale:d.v3f;
}|undefined):{
rotation:d.v4f;
position:d.v3f;
scale:d.v3f;
}
export Placement
This schema holds a unit quaternion, position and scale per axis. Instance transforms use
world space; joint transforms use parent space.
Characters sharing a rig can list joints in different orders. Use poses.rebind to match a model to another skin by joint name. One set of poses can then animate all those characters:
const
constrig:poses.SkinData
rig=
constknight:gltf.ModelData<never>
knight.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
Uploads each mesh into the next free ranges and decodes images in the background. Each image
decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer
for the full vertex capacity. Throws if the pool is full, the model has no meshes, its
arrays disagree or encoded images have no decoder.
Rebinds a skinned model from from to to by joint name. Use this when files list the same
rig joints in different orders, so one set of poses can move them all. Meshes without joints
stay unchanged. Throws if to lacks a joint or has a different inverse bind.
@param ― model a model skinned to from
@param ― from the skin it was made for
@param ― to the skin whose poses move it
@returns ― the model, skinned to to
rebind(
constrogue:gltf.ModelData<never>
rogue,
constrogue:gltf.ModelData<never>
rogue.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
skin!,
constrig:poses.SkinData
rig));
It throws if the target skin lacks a joint or has a different bind placement.
Bind a sword, shield or lantern to one joint with poses.attach. Model the item in that joint’s space, then add it alongside the body so one pose moves both:
Uploads each mesh into the next free ranges and decodes images in the background. Each image
decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer
for the full vertex capacity. Throws if the pool is full, the model has no meshes, its
arrays disagree or encoded images have no decoder.
Uploads each mesh into the next free ranges and decodes images in the background. Each image
decodes once per pool and format. The first skinned mesh allocates a joint-and-weight buffer
for the full vertex capacity. Throws if the pool is full, the model has no meshes, its
arrays disagree or encoded images have no decoder.
Attaches model to one joint of skin, such as a sword to a hand. It binds every mesh vertex
to that joint so posed instances follow it. The model coordinates must be in joint space, or
offset takes them there.
@param ― model the model it holds
@param ― skin the skin whose joint holds it
@param ― joint the name of the joint that holds it
@param ― options.offset a column-major matrix from the model's space to the joint's, such as a turn
for an item modelled facing the other way
@returns ― the model, its meshes bound to the joint
attach(
constsword:gltf.ModelData<never>
sword,
constknight:gltf.ModelData<never>
knight.
ModelData<never>.skin?:poses.SkinData|undefined
Provides the skin used by meshes in the default scene. When present, every mesh has joints
and weights. An unskinned mesh follows the joint it is attached to.
Adds an instance of a mesh, model or group of meshes from the pool. Each mesh uses a row,
and all rows move and change together under one id. values may be a function of each mesh,
to give a model's meshes values of their own. Throws if a mesh belongs to another pool, the
group is empty, a posed kind receives a mesh without joints or the set is full.
add({
meshes: readonly meshPool.Mesh[]
meshes:[...
constbody:meshPool.Model
body.
Model.meshes: readonly meshPool.Mesh[]
meshes,...
constblade:meshPool.Model
blade.
Model.meshes: readonly meshPool.Mesh[]
meshes]},{
pose: number
pose});
An item modelled in another space takes an offset, a matrix from its space to the joint’s. This one turns an axe half a turn about its grip:
Returns where joint of pose id is at time, as animate places it: a column-major matrix
from the joint's space to the model's, with the joint's position in its last column. It plays
the clips, lines, layers and fades set through these methods on the CPU, and leaves out local
and plays from kernels. Throws for a joint the skin lacks or a name two joints share.
It returns a column-major matrix from the joint’s space to the model’s. Multiply it by the instance’s transform for world space.
It plays what the base and layers play from the CPU, with their fades, lines and weights.
A name the skin lacks, or one two joints share, throws.
On the GPU, poses.jointPosition(id, joint) returns joint joint’s position in model space, as the last animate placed it, including plays from kernels and local. Call it from a pipeline piped figures.inject(), such as a picker’s position or a kernel that sends sparks from a hand. Joint indices follow the skin’s joints.
In a shader created from root.pipe(pool.inject()).pipe(figures.inject()), poses.skinned(i, vertex, id) poses vertex i’s position and normal by pose id:
const
constwhich:TgpuUniform<d.U32>
which=
constroot:TgpuRoot
root.
TgpuRoot.createUniform<d.U32>(typeSchema: d.U32, initial?: number |((buffer:TgpuBuffer<NoInfer<d.U32>>)=>void)|undefined): TgpuUniform<d.U32>(+1 overload)
Allocates memory on the GPU, allows passing data between host and shader.
Read-only on the GPU, optimized for small data. For a general-purpose buffer,
use
TgpuRoot.createBuffer
.
@param ― typeSchema The type of data that this buffer will hold.
@param ― initial Either initial value of the buffer, or an initializer to execute on the mapped buffer. (optional)
createUniform(
import d
d.
constu32:d.U32
export u32
A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)
Can also be called to cast a value to an u32 in accordance with WGSL casting rules.
Transforms the position and normal of vertex i by the weighted joints in pose id.
skinned(
i: number
i,
import meshPool
meshPool.
constvertexAt:(i:number)=>{
position:d.v3f;
normal:d.v3f;
uv:d.v2f;
}
Decodes pool vertex i into an @typegpu/kit/geometry vertex. In a mesh draw, the vertex index
already includes baseVertex. Create calling shaders from root.pipe(pool.inject()).
vertexAt(
i: number
i),
constwhich:TgpuUniform<d.U32>
which.
TgpuUniform<U32>.$: number
$);
return{
position:d.v4f
position:
import cameras
cameras.
constcurrent:TgpuAccessor<d.WgslStruct<{
viewProjection:d.Mat4x4f;
position:d.Vec3f;
right:d.Vec3f;
up:d.Vec3f;
}>>
Provides the camera for a pipeline. A camera's inject() fills it with the camera uniform.
Fills joint and play resources for pipelines created from the configuration you pipe it
into. Apply this to the renderer of a kind with pose, and to kernels calling the verbs.
96 bytes per joint for this frame’s and last frame’s joints, plus 212 bytes per pose slot. A pose with 24 joints takes 2.5 KB.
the skin
96 bytes per joint for the parent, inverse bind matrix, bind origin and layer masks. A 24-joint skin takes 2.3 KB.
the pool
8 extra bytes per vertex of capacity after adding a skinned model. The default 65 536 vertices take 512 KB.
Each joint reads 2 keys for a clip, 4 for a crossfade or a line and 8 for a line fading from a line, then as many again for each layer playing. An additive layer reads 1 more for a clip and 2 more for a line.
Call figures.destroy() when no drawing uses the buffers. After destruction, every method but duration, has and remove throws The poses were destroyed. The has method returns false and remove does nothing.