Skip to content

@typegpu/kitposes

Poses

Animate joint-bound meshes with glTF clips, blending poses on the GPU each frame.

kit.poses

module

poses.create(root, skin, clips, { capacity, … }) → Poses

poses.sample(channel, time, out?)

poses.shift(clip, by, options?) → ClipData

poses.mirror(clip, opposite, options?) → ClipData

and poses.lean, poses.attach, poses.rebind, poses.clip

rootborrows
the root
skintakes
the joints, as gltf.read returns them in a model's skin
clipstakes
the clips and lines its poses play, by the names add and play take
Posesreturns
poses you animate every frame and destroy
channeltakes
the keys, as gltf.read returns them
timetakes
seconds into the clip
out?takes
the array it fills
cliptakes
the looping clip
bytakes
seconds into clip the copy starts; a negative value starts before its end
ClipDatareturns
a clip as long as clip, with every channel resampled
cliptakes
the clip to mirror
oppositetakes
the joint on the other side, such as (joint) => joint.replace(/\.r$/, '.l'), or the joint itself for one in the middle
ClipDatareturns
a clip with a channel for each of clip's, on the opposite joints
GPU functions
skinnedskinnedPositionplayoncestopmixweighsecondsjointPosition
schemas
Placement
constants
rest
types
JointDataSkinDataChannelDataClipDataPlayStopIdPosesResampleMirrorLean
Optionscreate · 4 fields
capacity

number

Limits the pose count to an integer from 1 to 65 535.

hz?

number= 30

Sets a finite, positive number of baked keys per second.

layers?

Readonly<Record<L, LayerOptions>>= none

Names up to three layers over the base, applied in this order.

local?

(played: Placement, joint: number, slot: number) => Placement= the clip transform

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
  • animatecompute

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.

import {
import poses
poses
} from '@typegpu/kit';
const
const knight: gltf.ModelData<never>
knight
=
import gltf
gltf
.
function read<never>(source: ArrayBuffer | ArrayBufferView, options?: gltf.ReadOptions<never, false> | undefined): gltf.ModelData<never> (+1 overload)

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

read
(
const knightFile: ArrayBuffer
knightFile
);
const
const moves: gltf.ModelData<never>
moves
=
import gltf
gltf
.
function read<never>(source: ArrayBuffer | ArrayBufferView, options?: gltf.ReadOptions<never, false> | undefined): gltf.ModelData<never> (+1 overload)

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

read
(
const clipFile: ArrayBuffer
clipFile
); // clips made for the same rig
const
const figures: poses.Poses<"idle" | "walk", never>
figures
=
import poses
poses
.
function create<"idle" | "walk", never>(root: TgpuRoot, skin: poses.SkinData, clips: Readonly<Record<"idle" | "walk", poses.ClipData | readonly poses.ClipData[]>>, options: poses.Options<never>): poses.Poses<"idle" | "walk", never>

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
(
const root: TgpuRoot
root
,
const knight: 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
.
function clip(file: {
readonly clips: readonly poses.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
(
const moves: gltf.ModelData<never>
moves
, 'Idle'),
walk: poses.ClipData | readonly poses.ClipData[]
walk
:
import poses
poses
.
function clip(file: {
readonly clips: readonly poses.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
(
const moves: gltf.ModelData<never>
moves
, 'Walking') },
{
Options<L extends string = string>.capacity: number

Limits the pose count to an integer from 1 to 65 535.

capacity
: 256 },
);
const
const Guard: instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">
Guard
=
import instances
instances
.
function define<"Guard", {
pose: d.U32;
}, {}, "host">(name: "Guard", options?: instances.KindOptions<{
pose: d.U32;
}, {}, "host">): instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">

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
.
const u32: 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.

@example const value = u32(); // 0

@example const value = u32(7); // 7

@example const value = u32(3.14); // 3

@example const value = u32(-1); // 4294967295

@example const value = u32(-3.1); // 0

u32
},
KindOptions<{ pose: U32; }, {}, "host">.pose?: ((instance: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
mesh: number;
opacity: number;
pose: number;
}) => number) | undefined

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.

pose
: (
guard: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
mesh: number;
opacity: number;
pose: number;
}
guard
) => {
'use gpu';
return
guard: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
mesh: number;
opacity: number;
pose: number;
}
guard
.
pose: number
pose
;
},
});
const
const guards: instances.Instances<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>
guards
=
import instances
instances
.
function create<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>(root: TgpuRoot, pool: meshPool.MeshPool, options: instances.Options<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>): instances.Instances<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">> (+1 overload)

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

create
(
const root: TgpuRoot
root
,
const pool: meshPool.MeshPool<{}>
pool
, {
Options<Kind<"Guard", { pose: U32; }, {}, "host">>.kind?: instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host"> | undefined

Selects a kind from instances.define. Default a plain kind with a transform and material.

kind
:
const Guard: instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">
Guard
,
Options<K extends AnyKind = Plain>.capacity: number

Limits the number of rows. A mesh instance uses one row; a model instance uses one row per mesh.

capacity
: 64 });
const
const renderer: instances.Renderer
renderer
=
const guards: instances.Instances<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>
guards
.
Instances<Kind<"Guard", { pose: U32; }, {}, "host">>.renderer(options?: instances.RendererOptions): instances.Renderer

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
(
const figures: 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.

inject
());
const
const drawn: passes.Drawn
drawn
=
const scene: passes.Passes<{
readonly hdr: typeof passes.color;
readonly depth: typeof passes.depth;
}>
scene
.
Passes<{ readonly hdr: typeof color; readonly depth: typeof depth; }>.add<{
color: d.v4f;
}, {
position: d.v4f;
world: d.v3f;
normal: d.v3f;
uv: d.v2f;
uv1: d.v2f;
row: number;
}>(this: passes.Passes<passes.Fed<{
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 (+1 overload)

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
(
const renderer: instances.Renderer
renderer
);
const
const cast: passes.Drawn
cast
=
const light: 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 (+1 overload)

Builds a caster's depth pipeline with the shadow's bias and returns its draw methods.

add
(
const renderer: instances.Renderer
renderer
);
const
const pose: poses.Id
pose
=
const figures: poses.Poses<"idle" | "walk", never>
figures
.
Poses<"idle" | "walk", never>.add(clip: "idle" | "walk", time: number, play?: poses.Play<never> | undefined): poses.Id

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.

add
('idle', 0);
const guards: instances.Instances<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>
guards
.
Instances<Kind<"Guard", { pose: U32; }, {}, "host">>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: instances.Values<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">> | ((mesh: meshPool.Mesh, index: number) => instances.Values<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>) | undefined): IdOf<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>

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
(
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

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.

add
(
const knight: gltf.ModelData<never>
knight
), {
position?: ArrayLike<number> | d.InferInput<d.Vec3f> | undefined
position
: [2, 0, 0],
pose: poses.Id
pose
});
// Blend from idle to walking over a third of a second
const figures: poses.Poses<"idle" | "walk", never>
figures
.
Poses<"idle" | "walk", never>.play(id: poses.Id, clip: "idle" | "walk", time: number, play?: poses.Play<never> | undefined): void

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
(
const pose: poses.Id
pose
, 'walk',
const now: 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
const pass: TgpuComputePass
pass
=
const frame: 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.

encoder
.
TgpuCommandEncoder.beginComputePass(descriptor?: TgpuComputePassDescriptor): TgpuComputePass

Begins recording a compute pass

beginComputePass
();
const figures: poses.Poses<"idle" | "walk", never>
figures
.
Poses<"idle" | "walk", never>.animate(pass: TgpuComputePass, time: number): void

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
(
const pass: TgpuComputePass
pass
,
const now: number
now
);
const pass: 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:

const
const figures: poses.Poses<"idle" | "moving", never>
figures
=
import poses
poses
.
function create<"idle" | "moving", never>(root: TgpuRoot, skin: poses.SkinData, clips: Readonly<Record<"idle" | "moving", poses.ClipData | readonly poses.ClipData[]>>, options: poses.Options<never>): poses.Poses<"idle" | "moving", never>

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
(
const root: TgpuRoot
root
,
const knight: 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
:
const clip: (name: string) => poses.ClipData
clip
('Idle'),
moving: poses.ClipData | readonly poses.ClipData[]
moving
: [
const clip: (name: string) => poses.ClipData
clip
('Walking'),
const clip: (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.

capacity
: 64 },
);
const
const pose: poses.Id
pose
=
const figures: poses.Poses<"idle" | "moving", never>
figures
.
Poses<"idle" | "moving", never>.add(clip: "idle" | "moving", time: number, play?: poses.Play<never> | undefined): poses.Id

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.

add
('idle',
const now: number
now
);
// When the character starts moving
const figures: poses.Poses<"idle" | "moving", never>
figures
.
Poses<"idle" | "moving", never>.play(id: poses.Id, clip: "idle" | "moving", time: number, play?: poses.Play<never> | undefined): void

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
(
const pose: poses.Id
pose
, 'moving',
const now: 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
const figures: poses.Poses<"idle" | "moving", never>
figures
.
Poses<"idle" | "moving", never>.mix(id: poses.Id, value: number, time: number, on?: Pick<poses.Play<never>, "layer"> | undefined): void

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
(
const pose: 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
((
const speed: number
speed
- 0.8) / 2.2, 0), 1),
const now: 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.
const
const figures: poses.Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">
figures
=
import poses
poses
.
function create<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">(root: TgpuRoot, skin: poses.SkinData, clips: Readonly<Record<"idle" | "hold" | "raise" | "wave", poses.ClipData | readonly poses.ClipData[]>>, options: poses.Options<"wave" | "lantern">): poses.Poses<"idle" | "hold" | "raise" | "wave", "wave" | "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
(
const root: TgpuRoot
root
,
const mage: 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
:
const clip: (name: string) => poses.ClipData
clip
('Idle_B'),
hold: poses.ClipData | readonly poses.ClipData[]
hold
:
const clip: (name: string) => poses.ClipData
clip
('Holding_A'),
raise: poses.ClipData | readonly poses.ClipData[]
raise
:
const raised: poses.ClipData
raised
,
wave: poses.ClipData | readonly poses.ClipData[]
wave
:
const waveLeft: poses.ClipData
waveLeft
},
{
Options<L extends string = string>.capacity: number

Limits the pose count to an integer from 1 to 65 535.

capacity
: 64,
Options<"wave" | "lantern">.layers?: Readonly<Record<"wave" | "lantern", poses.LayerOptions>> | undefined

Names up to three layers over the base, applied in this order. base names the base and can't name a layer. Default none.

layers
: {
wave: poses.LayerOptions
wave
: {
LayerOptions.joints?: readonly string[] | undefined

Names the joints whose subtrees the layer moves, such as ['spine_02'] for the upper body. Throws for a name the skin lacks. Default every joint.

joints
: ['spine'] },
lantern: poses.LayerOptions
lantern
: {
LayerOptions.joints?: readonly string[] | undefined

Names the joints whose subtrees the layer moves, such as ['spine_02'] for the upper body. Throws for a name the skin lacks. Default every joint.

joints
: ['upperarm.r'] } },
},
);
const
const seller: poses.Id
seller
=
const figures: poses.Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">
figures
.
Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">.add(clip: "idle" | "hold" | "raise" | "wave", time: number, play?: poses.Play<"wave" | "lantern"> | undefined): poses.Id

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.

add
('idle',
const now: number
now
);
const figures: poses.Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">
figures
.
Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">.play(id: poses.Id, clip: "idle" | "hold" | "raise" | "wave", time: number, play?: poses.Play<"wave" | "lantern"> | undefined): void

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
(
const seller: poses.Id
seller
, 'hold',
const now: number
now
, {
Play<"wave" | "lantern">.layer?: "wave" | "lantern" | "base" | undefined

Names the layer that plays the clip. Default 'base'.

layer
: 'lantern' });
// When a customer comes to the table
const figures: poses.Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">
figures
.
Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">.play(id: poses.Id, clip: "idle" | "hold" | "raise" | "wave", time: number, play?: poses.Play<"wave" | "lantern"> | undefined): void

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
(
const seller: poses.Id
seller
, 'raise',
const now: number
now
, {
Play<"wave" | "lantern">.layer?: "wave" | "lantern" | "base" | undefined

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
const figures: poses.Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">
figures
.
Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">.play(id: poses.Id, clip: "idle" | "hold" | "raise" | "wave", time: number, play?: poses.Play<"wave" | "lantern"> | undefined): void

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
(
const seller: poses.Id
seller
, 'wave',
const now: number
now
, {
Play<"wave" | "lantern">.layer?: "wave" | "lantern" | "base" | undefined

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
const figures: poses.Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">
figures
.
Poses<"idle" | "hold" | "raise" | "wave", "wave" | "lantern">.stop(id: poses.Id, time: number, stop?: poses.Stop<"wave" | "lantern"> | undefined): void

Fades stop.layer out over stop.fade seconds from time, back to the pose beneath.

stop
(
const seller: poses.Id
seller
,
const now: number
now
, {
Stop<"wave" | "lantern">.layer?: "wave" | "lantern" | "base" | undefined

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:

const
const figures: poses.Poses<"idle" | "breathe", "chest">
figures
=
import poses
poses
.
function create<"idle" | "breathe", "chest">(root: TgpuRoot, skin: poses.SkinData, clips: Readonly<Record<"idle" | "breathe", poses.ClipData | readonly poses.ClipData[]>>, options: poses.Options<"chest">): poses.Poses<"idle" | "breathe", "chest">

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
(
const root: TgpuRoot
root
,
const knight: 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
:
const clip: (name: string) => poses.ClipData
clip
('Idle'),
breathe: poses.ClipData | readonly poses.ClipData[]
breathe
:
const clip: (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.

capacity
: 64,
Options<"chest">.layers?: Readonly<Record<"chest", poses.LayerOptions>> | undefined

Names up to three layers over the base, applied in this order. base names the base and can't name a layer. Default none.

layers
: {
chest: poses.LayerOptions
chest
: {
LayerOptions.joints?: readonly string[] | undefined

Names the joints whose subtrees the layer moves, such as ['spine_02'] for the upper body. Throws for a name the skin lacks. Default every joint.

joints
: ['spine'],
LayerOptions.add?: boolean | undefined

Adds how the layer's clip differs from its first key to the pose beneath, rather than replacing it. Default false.

add
: true } } },
);
const
const pose: poses.Id
pose
=
const figures: poses.Poses<"idle" | "breathe", "chest">
figures
.
Poses<"idle" | "breathe", "chest">.add(clip: "idle" | "breathe", time: number, play?: poses.Play<"chest"> | undefined): poses.Id

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.

add
('idle',
const now: number
now
);
const figures: poses.Poses<"idle" | "breathe", "chest">
figures
.
Poses<"idle" | "breathe", "chest">.play(id: poses.Id, clip: "idle" | "breathe", time: number, play?: poses.Play<"chest"> | undefined): void

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
(
const pose: poses.Id
pose
, 'breathe',
const now: 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.

Math
.
Math.random(): number

Returns a pseudorandom number between 0 and 1.

random
() * 3 });
const figures: poses.Poses<"idle" | "breathe", "chest">
figures
.
Poses<"idle" | "breathe", "chest">.weigh(id: poses.Id, weight: number, on?: Pick<poses.Play<"chest">, "layer"> | undefined): void

Sets how much of on.layer shows, from 0 to 1, at once. The base always shows whole, so weighing it throws.

weigh
(
const pose: poses.Id
pose
, 0.7, {
layer?: "chest" | "base" | undefined

Names the layer that plays the clip. Default 'base'.

layer
: 'chest' });
  • A clip’s first key is its reference, so an additive clip needs no second bake, and one clip can play on both kinds of layer.
  • Rotations turn by the difference from the first key, positions add theirs and scales multiply by their ratio, all by the layer’s weight.
  • A clip played once holds its last key. One that doesn’t end where it starts keeps adding its last difference until you stop it.

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
const skin: poses.SkinData
skin
=
const mage: 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
const stand: poses.ClipData
stand
=
import poses
poses
.
function clip(file: {
readonly clips: readonly poses.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
(
const moves: gltf.ModelData<never>
moves
, 'Idle_A');
// Start the run where its left foot lands, as the walk's does at its start
const
const run: poses.ClipData
run
=
import poses
poses
.
function shift(clip: poses.ClipData, by: number, options?: poses.Resample): poses.ClipData

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
.
function clip(file: {
readonly clips: readonly poses.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
(
const moves: 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
const
const wave: poses.ClipData
wave
=
import poses
poses
.
function lean(clip: poses.ClipData, skin: poses.SkinData, top: string, options?: poses.Lean): poses.ClipData

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
.
function clip(file: {
readonly clips: readonly poses.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
(
const moves: gltf.ModelData<never>
moves
, 'Waving'),
const skin: 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.

from
:
const stand: poses.ClipData
stand
});
// The same wave with the left hand
const
const waveLeft: poses.ClipData
waveLeft
=
import poses
poses
.
function mirror(clip: poses.ClipData, opposite: (joint: string) => string, options?: poses.Mirror): poses.ClipData

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

mirror
(
const wave: poses.ClipData
wave
, (
joint: string
joint
) =>
joint: string
joint
.
String.replace(searchValue: {
[Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string;
}, replacer: (substring: string, ...args: any[]) => string): string (+3 overloads)

Replaces text in a string, using an object that supports replacement within a string.

@param ― searchValue A object can search for and replace matches within a string.

@param ― replacer A function that returns the replacement text.

replace
(/\.([rl])$/, (
_: string
_
,
side: string
side
: string) => (
side: string
side
=== 'r' ? '.l' : '.r')),
);
const
const hand: number[]
hand
=
import poses
poses
.
function sample(channel: poses.ChannelData, time: number, out?: number[]): number[]

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
(
const wave: 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:

const {
const base: number
base
,
const arm: number
arm
} =
const figures: poses.Poses<"moving" | "hold" | "raise", "arm">
figures
.
Poses<"moving" | "hold" | "raise", "arm">.layers: Readonly<Record<"arm" | "base", number>>

Gives each layer's index, with the base at 0, for kernels calling the verbs.

layers
;
const {
const raise: number
raise
} =
const figures: poses.Poses<"moving" | "hold" | "raise", "arm">
figures
.
Poses<"moving" | "hold" | "raise", "arm">.clips: Readonly<Record<"moving" | "hold" | "raise", number>>

Gives each clip's or line's index, for kernels calling poses.play and poses.once.

clips
;
const
const pace: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
pace
=
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
computeFn: <{
gid: d.BuiltinGlobalInvocationId;
}>(options: {
in: {
gid: d.BuiltinGlobalInvocationId;
};
workgroupSize: number[];
}) => TgpuComputeFnShell<{
gid: d.BuiltinGlobalInvocationId;
}> (+1 overload)
computeFn
({
workgroupSize: number[]
workgroupSize
: [64],
in: {
gid: d.BuiltinGlobalInvocationId;
}
in
: {
gid: d.BuiltinGlobalInvocationId
gid
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly clipDistances: d.BuiltinClipDistances;
readonly position: d.BuiltinPosition;
readonly frontFacing: d.BuiltinFrontFacing;
readonly fragDepth: d.BuiltinFragDepth;
readonly primitiveIndex: d.BuiltinPrimitiveIndex;
readonly sampleIndex: d.BuiltinSampleIndex;
... 11 more ...;
readonly numSubgroups: d.BuiltinNumSubgroups;
}
export builtin
builtin
.
globalInvocationId: d.BuiltinGlobalInvocationId
globalInvocationId
},
})(({
gid: d.v3u
gid
}) => {
'use gpu';
const
const i: number
i
=
gid: d.v3u
gid
.
v3u.x: number
x
;
if (
const i: number
i
>=
const COUNT: 300
COUNT
) return;
const
const pose: number
pose
=
const posesOf: TgpuReadonly<d.WgslArray<d.U32>>
posesOf
.
TgpuReadonly<WgslArray<U32>>.$: number[]
$
[
const i: number
i
]!;
// Stand at 0, walk at 1 and run at 2
import poses
poses
.
const mix: (id: number, value: number, layer: number) => void

Places the line on layer of pose id at value, keeping its phase.

mix
(
const pose: number
pose
,
import std
std
.
function clamp(value: number, low: number, high: number): number (+1 overload)
export clamp
clamp
(
const speeds: TgpuReadonly<d.WgslArray<d.F32>>
speeds
.
TgpuReadonly<WgslArray<F32>>.$: number[]
$
[
const i: number
i
]! / 0.8, 0, 2),
const base: number
base
);
// Once, the frame a member's flag goes up
if (
const raising: TgpuMutable<d.WgslArray<d.U32>>
raising
.
TgpuMutable<WgslArray<U32>>.$: number[]
$
[
const i: number
i
] === 1) {
const raising: TgpuMutable<d.WgslArray<d.U32>>
raising
.
TgpuMutable<WgslArray<U32>>.$: number[]
$
[
const i: number
i
] = 0;
import poses
poses
.
const play: (id: number, clip: number, layer: number, fade: number, from: number) => void

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.

play
(
const pose: number
pose
,
const raise: number
raise
,
const arm: number
arm
, 0.45, 0);
}
});
const
const pacing: TgpuComputePipeline
pacing
=
const root: TgpuRoot
root
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const figures: poses.Poses<"moving" | "hold" | "raise", "arm">
figures
.
Poses<"moving" | "hold" | "raise", "arm">.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.

inject
()).
WithBinding.createComputePipeline<{
gid: d.BuiltinGlobalInvocationId;
}>(descriptor: TgpuComputePipeline.Descriptor<{
gid: d.BuiltinGlobalInvocationId;
}>): TgpuComputePipeline
createComputePipeline
({
compute: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
compute
:
const pace: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
pace
});
// Every frame, before animate in the same pass
const pass: TgpuComputePass
pass
.
TgpuComputePass.setPipeline(pipeline: TgpuComputePipeline): void

Sets the current

TgpuComputePipeline

for subsequent dispatches

setPipeline
(
const pacing: TgpuComputePipeline
pacing
);
const pass: TgpuComputePass
pass
.
TgpuComputePass.dispatchWorkgroups(x: number, y?: number, z?: number): void
dispatchWorkgroups
(
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.ceil(x: number): number

Returns the smallest integer greater than or equal to its numeric argument.

@param ― x A numeric expression.

ceil
(
const COUNT: 300
COUNT
/ 64));
const figures: poses.Poses<"moving" | "hold" | "raise", "arm">
figures
.
Poses<"moving" | "hold" | "raise", "arm">.animate(pass: TgpuComputePass, time: number): void

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
(
const pass: TgpuComputePass
pass
,
const now: 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
const HEAD: number
HEAD
=
const knight: 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: readonly poses.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.

findIndex
(({
name: string

Names the joint targeted by clip channels.

name
}) =>
name: string

Names the joint targeted by clip channels.

name
=== 'head');
const
const turns: TgpuBuffer<d.WgslArray<d.F32>> & StorageFlag
turns
=
const root: TgpuRoot
root
.
TgpuRoot.createBuffer<d.WgslArray<d.F32>>(typeSchema: d.WgslArray<d.F32>, initial?: ((buffer: TgpuBuffer<NoInfer<d.WgslArray<d.F32>>>) => void) | d.InferInput<NoInfer<d.WgslArray<d.F32>>> | undefined): TgpuBuffer<d.WgslArray<d.F32>> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader.

@remarks ― Typed wrapper around a GPUBuffer.

@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)

createBuffer
(
import d
d
.
arrayOf<d.F32>(elementType: d.F32, elementCount: number): d.WgslArray<d.F32> (+2 overloads)
export arrayOf

@location ― . Wrap align/size in a struct instead, e.g. d.arrayOf(d.struct({ value: d.align(16, d.u32) }), n).

arrayOf
(
import d
d
.
const f32: d.F32
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
, 256)).
TgpuBuffer<WgslArray<F32>>.$usage<["storage"]>(usages_0: "storage"): TgpuBuffer<d.WgslArray<d.F32>> & StorageFlag
$usage
('storage');
const
const turn: TgpuReadonly<d.WgslArray<d.F32>>
turn
=
const turns: TgpuBuffer<d.WgslArray<d.F32>> & StorageFlag
turns
.
TgpuBuffer<WgslArray<F32>>.as<"readonly">(usage: "readonly"): TgpuReadonly<d.WgslArray<d.F32>>
as
('readonly');
const
const figures: poses.Poses<"rest", never>
figures
=
import poses
poses
.
function create<"rest", never>(root: TgpuRoot, skin: poses.SkinData, clips: Readonly<Record<"rest", poses.ClipData | readonly poses.ClipData[]>>, options: poses.Options<never>): poses.Poses<"rest", never>

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
(
const root: TgpuRoot
root
,
const knight: 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
.
const rest: 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.

capacity
: 256,
Options<never>.local?: ((played: poses.Placement, joint: number, slot: number) => poses.Placement) | undefined

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
!==
const HEAD: number
HEAD
) return
import poses
poses
.
function Placement(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
const half: number
half
=
const turn: TgpuReadonly<d.WgslArray<d.F32>>
turn
.
TgpuReadonly<WgslArray<F32>>.$: number[]
$
[
slot: number
slot
]! / 2;
return
import poses
poses
.
function Placement(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
({
rotation: d.v4f
rotation
:
import d
d
.
const vec4f: d.Vec4f
(x: number, y: number, z: number, w: number) => d.v4f (+9 overloads)
vec4f
(0,
import std
std
.
function sin(value: number): number (+1 overload)
export sin
sin
(
const half: number
half
), 0,
import std
std
.
function cos(value: number): number (+1 overload)
export cos
cos
(
const half: number
half
)),
position: d.v3f
position
:
played: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
}
played
.
position: d.v3f
position
,
scale: d.v3f
scale
:
played: {
rotation: d.v4f;
position: d.v3f;
scale: d.v3f;
}
played
.
scale: d.v3f
scale
,
});
},
},
);

Return a copy from local with poses.Placement(played). Do not return played itself.

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
const rig: poses.SkinData
rig
=
const knight: 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
!;
const
const thief: meshPool.Model
thief
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

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.

add
(
import poses
poses
.
function rebind(model: meshPool.ModelData, from: poses.SkinData, to: poses.SkinData): meshPool.ModelData

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
(
const rogue: gltf.ModelData<never>
rogue
,
const rogue: 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
!,
const rig: 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:

const
const body: meshPool.Model
body
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

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.

add
(
const knight: gltf.ModelData<never>
knight
);
const
const blade: meshPool.Model
blade
=
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.add(model: meshPool.ModelData<{}>): meshPool.Model (+1 overload)

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.

add
(
import poses
poses
.
function attach(model: meshPool.ModelData, skin: poses.SkinData, joint: string, options?: {
readonly offset?: ArrayLike<number>;
}): meshPool.ModelData

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
(
const sword: gltf.ModelData<never>
sword
,
const knight: 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
!, 'handslot.r'));
const guards: instances.Instances<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>
guards
.
Instances<Kind<"Guard", { pose: U32; }, {}, "host">>.add(mesh: meshPool.Mesh | Pick<meshPool.Model, "meshes">, values?: instances.Values<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">> | ((mesh: meshPool.Mesh, index: number) => instances.Values<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>) | undefined): IdOf<instances.Kind<"Guard", {
pose: d.U32;
}, {}, "host">>

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
: [...
const body: meshPool.Model
body
.
Model.meshes: readonly meshPool.Mesh[]
meshes
, ...
const blade: 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:

poses.attach(axe, knight.skin!, 'handslot.r', { offset: mat4.rotationY(Math.PI) });

Attachment transforms rotate normals and correct triangle winding for mirrored binds and offsets. A missing joint name throws with a list of joints.

Each mesh, including held items, needs an instance row. All meshes of one character share a pose.

figures.jointAt(id, joint, time) finds a joint on the CPU, where animate places it, for a muzzle flash, a thrown item or a camera on a hand:

const
const hand: m4x4f
hand
=
const figures: poses.Poses<"idle" | "walk", string>
figures
.
Poses<"idle" | "walk", string>.jointAt(id: poses.Id, joint: string, time: number): m4x4f

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.

jointAt
(
const pose: poses.Id
pose
, 'handslot.r',
const now: number
now
);
const
const grip: v3f
grip
=
const hand: m4x4f
hand
.
mat4x4<v4f>.columns: readonly [v4f, v4f, v4f, v4f]
columns
[3].
xyz: v3f
xyz
; // the hand's position in model space
  • 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
const which: TgpuUniform<d.U32>
which
=
const root: 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
.
const u32: 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.

@example const value = u32(); // 0

@example const value = u32(7); // 7

@example const value = u32(3.14); // 3

@example const value = u32(-1); // 4294967295

@example const value = u32(-3.1); // 0

u32
,
const pose: poses.Id
pose
);
const
const vertex: TgpuVertexFn<{}, {
normal: d.Vec3f;
}>
vertex
=
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
vertexFn: <{
i: d.BuiltinVertexIndex;
}, {
position: d.BuiltinPosition;
normal: d.Vec3f;
}>(options: {
in: {
i: d.BuiltinVertexIndex;
};
out: {
position: d.BuiltinPosition;
normal: d.Vec3f;
};
}) => TgpuVertexFnShell<{
i: d.BuiltinVertexIndex;
}, {
position: d.BuiltinPosition;
normal: d.Vec3f;
}> (+1 overload)
vertexFn
({
in: {
i: d.BuiltinVertexIndex;
}
in
: {
i: d.BuiltinVertexIndex
i
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly clipDistances: d.BuiltinClipDistances;
readonly position: d.BuiltinPosition;
readonly frontFacing: d.BuiltinFrontFacing;
readonly fragDepth: d.BuiltinFragDepth;
readonly primitiveIndex: d.BuiltinPrimitiveIndex;
readonly sampleIndex: d.BuiltinSampleIndex;
... 11 more ...;
readonly numSubgroups: d.BuiltinNumSubgroups;
}
export builtin
builtin
.
vertexIndex: d.BuiltinVertexIndex
vertexIndex
},
out: {
position: d.BuiltinPosition;
normal: d.Vec3f;
}
out
: {
position: d.BuiltinPosition
position
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly clipDistances: d.BuiltinClipDistances;
readonly position: d.BuiltinPosition;
readonly frontFacing: d.BuiltinFrontFacing;
readonly fragDepth: d.BuiltinFragDepth;
readonly primitiveIndex: d.BuiltinPrimitiveIndex;
readonly sampleIndex: d.BuiltinSampleIndex;
... 11 more ...;
readonly numSubgroups: d.BuiltinNumSubgroups;
}
export builtin
builtin
.
position: d.BuiltinPosition
position
,
normal: d.Vec3f
normal
:
import d
d
.
const vec3f: d.Vec3f
vec3f
},
})(({
i: number
i
}) => {
'use gpu';
const
const posed: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
posed
=
import poses
poses
.
function skinned(i: number, vertex: d.InferGPU<typeof Vertex>, id: number): {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
export skinned

Transforms the position and normal of vertex i by the weighted joints in pose id.

skinned
(
i: number
i
,
import meshPool
meshPool
.
const vertexAt: (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
),
const which: TgpuUniform<d.U32>
which
.
TgpuUniform<U32>.$: number
$
);
return {
position: d.v4f
position
:
import cameras
cameras
.
const current: 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.

current
.
TgpuAccessor<WgslStruct<{ viewProjection: Mat4x4f; position: Vec3f; right: Vec3f; up: Vec3f; }>>.$: {
viewProjection: d.m4x4f;
position: d.v3f;
right: d.v3f;
up: d.v3f;
}
$
.
viewProjection: d.m4x4f

Transforms world coordinates to clip space.

viewProjection
*
import d
d
.
const vec4f: d.Vec4f
(v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
vec4f
(
const posed: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
posed
.
position: d.v3f
position
, 1),
normal: d.v3f
normal
:
const posed: {
position: d.v3f;
normal: d.v3f;
uv: d.v2f;
}
posed
.
normal: d.v3f
normal
,
};
});
const
const pipeline: TgpuRenderPipeline<d.Vec4f>
pipeline
=
const root: TgpuRoot
root
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const pool: meshPool.MeshPool<{}>
pool
.
MeshPool<{}>.inject(): Inject

Fills mesh resources for pipelines created from the configuration you pipe it into.

inject
())
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const figures: poses.Poses<string, string>
figures
.
Poses<string, string>.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.

inject
())
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const view: cameras.OrbitCamera
view
.
CameraBase.inject(): Inject

Fills cameras.current for the pipelines created from the configuration it is piped into.

inject
())
.
WithBinding.createRenderPipeline<{}, {}, {
normal: d.Vec3f;
}, d.Vec4f>(descriptor: TgpuRenderPipeline<in Targets = never>.DescriptorBase & {
attribs?: {} | undefined;
vertex: TgpuVertexFn<{}, {
normal: d.Vec3f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>);
fragment: TgpuFragmentFn<{
normal: d.Vec3f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<...>) => d.v4f | (AnyAutoCustoms & Partial<...>));
targets?: TgpuColorTargetState;
}): TgpuRenderPipeline<...> (+2 overloads)
createRenderPipeline
({
vertex: TgpuVertexFn<{}, {
normal: d.Vec3f;
}> | ((input: AutoVertexIn<InferGPURecord<AttribRecordToDefaultDataTypes<{}>>>) => AutoVertexOut<AnyAutoCustoms>)
vertex
,
fragment: TgpuFragmentFn<{
normal: d.Vec3f;
} & Record<string, AnyFragmentInputBuiltin>, d.Vec4f> | ((input: AutoFragmentIn<InferGPURecord<{
normal: d.Vec3f;
}>>) => d.v4f | (AnyAutoCustoms & Partial<InferGPURecord<{
readonly $fragDepth: import("node_modules/typegpu/builtin").BuiltinFragDepth;
readonly $sampleMask: import("node_modules/typegpu/builtin").BuiltinSampleMask;
}>>))
fragment
,
targets?: TgpuColorTargetState
targets
: {
format?: GPUTextureFormat | undefined

The

GPUTextureFormat

of this color target. The pipeline will only be compatible with

GPURenderPassEncoder

s which use a

GPUTextureView

of this format in the corresponding color attachment.

@default ― navigator.gpu.getPreferredCanvasFormat()

format
: 'rgba16float' } });

A depth pass needs only poses.skinnedPosition(i, meshPool.positionAt(i), id).

What
the clips 48 bytes per key per joint.
the poses 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.

  • Each vertex supports at most four joints, and a skin supports 256. Weights use steps of 1/255.
  • A pose has a base and up to three layers, and each blends from one other clip. An interrupted blend starts from the last played clip, so it can pop.
  • Masks select whole subtrees. Additive layers refer to their clip’s first key. Lines mix along one number; there are no 2D blend spaces.
  • Kernels play at speed 1. A pose takes its plays from the CPU or from kernels, as above.
  • There is no inverse kinematics. Use local for joints of your own.
  • jointAt leaves out local and plays from kernels. Each call computes the joint and its parents on the CPU.
  • Moving the skin root moves meshes without moving the instance. Animate a walk in place and move instances at the planted foot’s pace.
  • All pose joints update each frame, including those used only by invisible instances.
  • Normals use joint matrices rather than inverse transposes. Uneven joint scale therefore causes slight shading errors.
  • Morph targets aren’t read.