FERRETGame engine over Babylon.js
Ferret

@ferret/animation

Animation graphs: state machines, blend spaces, layers, notifies, retargeting and a stand-in puppet.

100 exports · source in packages/animation/src

Classes

Animator class

class Animator extends Component<AnimatorParams>

Plays an animation graph on an entity. Parameters come from the entity's motion source (or from game code through graph.set / graph.trigger), the pose is applied once per rendered frame (the world hands over dt scaled by the entity's time dilation). Notifies are emitted on the world's event bus as anim:notify { entity, name, clip }.

graph: AnimGraph
backend: GroupBackend
rootDelta: RootDeltaroot motion of the last frame, in the entity's own space (zero without rootMotion)
onAttach()
onFixedUpdate()
onUpdate(dt: number)
playMontage(def: MontageDef)Play a one-shot clip over the base animation (a gesture, an attack, a pick-up).
onDetach()

packages/animation/src/animator.ts:32

AnimGraph class

class AnimGraph

Runtime of one graph on one character.

readonly values: Record<string, number>
notifyThreshold: numbera clip whose weight in the output is at least this fires its notifies
constructor(readonly def: GraphDef, readonly clips: ClipLibrary, readonly events: GraphEvents = {})
get state(): string
get activeMontages(): readonly Montage[]
set(param: string, value: number | boolean)
get(param: string): number
trigger(name: string)
playMontage(def: MontageDef): Montage
stopMontage(m: Montage)
update(dt: number): PoseAdvance by dt seconds (already multiplied by the entity's time dilation).

packages/animation/src/graph.ts:235

ClipEditError class

class ClipEditError extends Error

packages/animation/src/clip.ts:234

ClipEditor class

class ClipEditor
readonly rest: Rest
constructor(clip: ClipJson, private readonly o: ClipEditorOptions)
static create(o: ClipEditorOptions & { skeleton: string; name: string; duration?: number; base?: string; loop?: boolean; fps?: number }): ClipEditorA new clip: empty (the rest pose), or a copy of a library clip.
get json(): ClipJson
get canUndo()
get canRedo()
history(): string[]
undo(): boolean
redo(): boolean
setKey(a: { bone: string; time: number; rotation?: Quat; euler?: Vec3; position?: Vec3; interp?: Interp; absolute?: boolean }): voidSet a key of a bone at a time (replaces the key there). rotation (quaternion) or euler (degrees) is the change from rest; with absolute it is the bone's own rotation (the change is worked out from the rest pose). position is the offset from the rest position (root and pelvis only).
removeKey(a: { bone: string; time: number; channel?: 'rotation' | 'position' }): numberRemove the key of a bone at a time (channel limits it to rotation or position). Returns how many were removed.
copyKey(a: { from: number; to: number; bone?: string }): numberCopy the keys at a time to another time: of one bone, or of every bone that has one (a pose). The values are what the clip is at from (held keys, or sampled between).
mirror(a: { time?: number } = {}): numberMirror left and right (partners are found by the _l / _r ending of the bone name; bones on the middle line are mirrored in place). With time, only the pose at that time is mirrored (the keys there); without, the whole clip.
ik(a: { chain: [string, string, string]; target: Vec3 | { bone: string; offset?: Vec3 }; pole?: Vec3 | { bone: string; offset?: Vec3 }; time: number; endRotation?: Quat; weight?: number; interp?: Interp }): { error: number; stretched: boolean }Set keys by solving a limb to a target at a time: chain is [root bone, middle bone, end bone] (shoulder, elbow, hand), target a point in the skeleton frame or a point on another bone ({bone, offset}, the offset in that bone's frame: a grip on the sword is an offset of the right hand), pole the same for the point the elbow or knee bends toward. The chain's three bones get rotation keys at time; the end bone keeps its rotation in the world unless endRotation (a rotation in the skeleton frame) is given. Returns how far the end is from the target afterwards (a limb too short stretches).
hold(a: { time: number; side?: 'r' | 'l'; grip: Grip; position: Vec3; direction: Vec3; roll?: number | 'auto'; both?: boolean; poleMain?: Vec3; poleOff?: Vec3; wrapToward?: Vec3; edge?: Vec3; edgeWeight?: number; interp?: Interp }): ReturnType<typeof holdWeapon>Put a weapon in the hands at a time (see holdWeapon): the grip's middle at position, the blade along direction, both hands if the grip has a spacing. The arms, the hands' turn and the fingers get rotation keys at time; returns the solve's errors.
holdRun(holds: Parameters<ClipEditor['hold']>[0][], o: { loop?: boolean; step?: number } = {}): ReturnType<typeof holdWeapon>[]Hold a weapon at many times at once (frames of a move, in any order): the turns of the hands about the blade left as 'auto' (or absent) are chosen for the whole run together (holdRolls: wrists least bent, hands moving least between the times; loop joins the last to the first), then each is set as hold does. The poses the run is solved on are the clip's at those times before the hands are set. One undo step per time. Returns one result per hold, in the order given.
setNotify(a: { name: string; time: number }): void
removeNotify(a: { name: string; time?: number }): number
retime(a: { scale?: number; curve?: [number, number][] }): voidChange the length: scale multiplies every time, or curve maps old times to new ones with straight lines between the pairs (first pair 0 -> 0 added if missing).
setInfo(a: { duration?: number; loop?: boolean; fps?: number; name?: string }): void
validate(): { ok: boolean; issues: ClipIssue[]; skipped: string[] }Checks of the clip: what a hand-made clip gets wrong. Errors are what cannot play; warnings are joints bent past their limit (limits), sudden jumps between keys (pops), a loop whose end does not meet its start, and tracks for bones the skeleton lacks. Foot sliding and ground penetration need the bodies' bones in space and are not checked here (skipped).

packages/animation/src/clip.ts:258

GroupBackend class

class GroupBackend

Applies a pose request to Babylon animation groups without Babylon's own animation clock: every targeted animation is sampled at the requested time (Animation.evaluate), the samples of one property are blended by weight (vectors and floats linearly, rotations by normalised lerp) and written to the target. A masked item (a montage on the upper body) counts only for targets under its bones, and takes its weight from the base clips there. Targets the pose does not mention keep their value, and a property whose weights sum to less than 1 keeps the rest of its current value. The groups are plain containers of animations: never started, so they cost nothing between frames.

constructor(readonly groups: Record<string, BABYLON.AnimationGroup>, readonly rootMotion?: RootMotionOptions)
apply(pose: Pose): RootDeltaApply a pose; returns the root motion the clips made this call (all zero without rootMotion).
dispose()

packages/animation/src/backend.ts:66

IkRig class

class IkRig

IK for one body. Add parts (limb, lookAt, footPlacement), then attach() to run it after each frame's animation (or call update(dt) yourself). Parts run in the order: feet, look-at, limbs (so a hand that follows a sword held by an animated hand sees the body as placed).

readonly frame: Node
readonly rest: Rest
readonly state: { pelvisDrop: number; footOffsets: number[]; limbError: Record<string, number> }what the last update did, for tests and the inspector
constructor(readonly scene: BABYLON.Scene, readonly bones: Record<string, Node>, o: IkRigOptions = {})
limb(spec: LimbSpec): this
lookAt(spec: LookAtSpec): this
footPlacement(spec: FootPlacementSpec): this
attach(): thisrun after the scene's animations every frame
dispose()
frameT(n: Node): { p: Vec3; q: Quat }frame position and rotation of a node, from the local values of it and its parents up to the frame
axisOf(bone: string, worldDir: BABYLON.Vector3): Vec3a world direction as an axis of a bone's own frame, with the skeleton as it is now (for lookAt.forward: build the rig with the body at rest, facing worldDir)
update(dt: number): void

packages/animation/src/ik-babylon.ts:83

Montage class

class Montage
time: number
weight: number
stopping: boolean
finished: boolean
constructor(readonly def: MontageDef, readonly id: number)

packages/animation/src/graph.ts:104

Functions

alignFrames function

function alignFrames(la: Vec3, lb: Vec3, wa: Vec3, wb: Vec3): Quat

The rotation that turns the orthonormal frame made of (la, lb) onto the one made of (wa, wb): la onto wa exactly, lb as near to wb as it can be.

packages/animation/src/hold.ts:30

applyClipPose function

function applyClipPose(bones: Record<string, Node>, rest: Rest, pose: Record<string, { q?: Quat; p?: Vec3 }>): void

Put the pose of a clip at a time on a body's nodes (rest where the clip has no key): for scrubbing in a panel, without an animation group.

packages/animation/src/clip-babylon.ts:128

bindClips function

function bindClips(scene: BABYLON.Scene, bodyRoot: BABYLON.TransformNode, source: Record<string, BABYLON.AnimationGroup>, map: Record<string, string>, o: BindClipsOptions = {}): BoundClips

Puts the clips of a skeleton on a body made of parts: source is the animation groups of a loaded clips file (by their names in the file), map says which of them each name of the animation graph is ({ idle: 'Idle', walk: 'Walk_Fwd' }), bodyRoot is the loaded character. Every bound clip is a copy of the source targeting the body's bones by name, so one clips file serves any number of bodies. A character made by tools/ue-export/characters.mjs has the bone names of its clips; a body with other names passes a bone map in retarget.

packages/animation/src/character.ts:25

bonesOf function

function bonesOf(root: Node, bones?: Iterable<string>): Record<string, Node>

The bones of a model by name: the transform nodes below a root (a body made by the library has them named <model>/<bone>).

packages/animation/src/clip-babylon.ts:11

buildPuppet function

function buildPuppet(scene: BABYLON.Scene, opts: PuppetOptions = {}): Puppet

packages/animation/src/puppet.ts:48

chain2Pose function

function chain2Pose(c: Chain2): Chain2Pose

packages/animation/src/ik.ts:39

clipFromJson function

function clipFromJson(scene: BABYLON.Scene, clip: ClipJson, bones: Record<string, Node>, rest: Rest, o: { name?: string } = {}): BuiltClip

Make an AnimationGroup of a clip for a body: one animation per bone that has keys, targeting that bone's node. bones are the body's nodes by bone name and rest its rest pose (restOf). Bones the body lacks are listed in missing and skipped. The group has the clip's name (or name) and is not started.

packages/animation/src/clip-babylon.ts:69

clipInfo function

function clipInfo(group: BABYLON.AnimationGroup, opts: { loop?: boolean; notifies?: Notify[] } = {}): ClipInfo

The length and frame rate of an AnimationGroup, as a graph clip.

packages/animation/src/backend.ts:4

clipLibrary function

function clipLibrary(groups: Record<string, BABYLON.AnimationGroup>, extra: Record<string, { loop?: boolean; notifies?: Notify[] }> = {}): ClipLibrary

packages/animation/src/backend.ts:8

clipToJson function

function clipToJson(group: BABYLON.AnimationGroup, rest: Rest, o: { name?: string; skeleton: string; loop?: boolean; fps?: number; positionBones?: (b: string) => boolean }): { clip: ClipJson; skipped: string[] }

The clip file of any AnimationGroup of a body, with the skeleton's rest (the library's clips are such groups): every animation of a bone's rotation and position becomes keys at the animation's own frames, as the change from rest (linear between keys, as Babylon plays them). Tracks of other properties and of nodes that are not bones are left out and named in skipped.

packages/animation/src/clip-babylon.ts:98

holdCost function

function holdCost(a: HoldArgs, r: HoldResult, near = false): number

How awkward a hold leaves the arms: the wrists' bend counts in full, the turn of the forearm they carry much less, the elbows and shoulders less; a hand off its place or a joint past its range costs much more. With near, also how far the hands moved from where a.pose has them.

packages/animation/src/hold.ts:121

holdRolls function

function holdRolls(holds: HoldArgs[], o: { step?: number; smooth?: number; loop?: boolean } = {}): number[]

The turns of the hands about the blade for a whole run of holds (one per frame of a clip, in time order): the run that is least awkward over all its frames and whose hands move least between frames (each frame tries every turn in step degrees, the best path through them is found at once, then each is refined by a degree). loop: the last frame joins the first. Returns one turn per hold, degrees, to pass as roll.

packages/animation/src/hold.ts:141

holdWeapon function

function holdWeapon(a: HoldArgs): HoldResult

Put a weapon in the hands: the grip's middle at position, the blade along direction, the main hand's frame turned so the weapon sits as its socket places it, the second hand mirrored on the grip spacing toward the pommel, the arms solved to them (two-bone IK on the pose, elbows toward the poles), and the fingers closed round the grip. The thumb is left as it is (it rests on the grip in the library's hands).

packages/animation/src/hold.ts:106

locomotionGraph function

function locomotionGraph(opts: { walkSpeed?: number; runSpeed?: number; points?: { at: number; clip: string }[]; crouch?: boolean; swim?: { at: number; clip: string }[]; swim2d?: { x: number; y: number; clip: string }[] } = {})

A ready-made locomotion graph for a puppet (or any character with the same clip names): a blend of idle and gaits by speed, jump, fall, and with crouch a crouch state (the clips crouch and crouchWalk) entered while the crouching parameter is true. swim (blend1d points on the speed, e.g. Swim_Idle and Swim_Fwd) adds a swim state entered while the swimming parameter is true (CharacterMovement in a dense medium) and left when it is false. swim2d (blend2d points on swimX = m/s to the right and swimY = m/s forward, e.g. Swim_Idle at 0,0, Swim_Fwd 0,3, Swim_Bwd 0,-3, Swim_Left -3,0, Swim_Right 3,0) replaces the speed blend of the swim state; the Animator feeds both from the motion source. points replaces the idle/walk/run blend: [{ at: 0, clip: 'idle' }, { at: 2, clip: 'walk' }, { at: 3.5, clip: 'jog' }, { at: 6, clip: 'sprint' }].

packages/animation/src/puppet.ts:165

mirrorName function

function mirrorName(name: string): string | null

upperarm_l -> upperarm_r and back (case kept: Wing_01_L -> Wing_01_R); null for a bone on the middle line

packages/animation/src/clip.ts:208

mirrorPosition function

function mirrorPosition(p: Vec3, from: BoneRest, to: BoneRest): Vec3

packages/animation/src/clip.ts:227

mirrorRotation function

function mirrorRotation(q: Quat, from: BoneRest, to: BoneRest): Quat

The mirror of a rotation change of a bone as the change of its partner, for a skeleton that is symmetric about the plane x = 0 in its own frame: the change is taken into the skeleton frame with the bone's rest rotation, reflected (the axis of a rotation keeps its component along the plane's normal and flips the others), and taken into the partner's frame. A bone on the middle line is its own partner.

packages/animation/src/clip.ts:220

motionClips function

function motionClips(m: Motion): string[]

packages/animation/src/graph.ts:149

poseFk function

function poseFk(rest: Rest, pose: Record<string, { q?: Quat; p?: Vec3 }>, bone: string): { p: Vec3; q: Quat }

The frame position and rotation of a bone in a pose of the clip (the change from rest per bone, as samplePose gives), the skeleton frame being the top bone's parent.

packages/animation/src/clip.ts:145

qAngle function

function qAngle(q: Quat): number

the angle of a rotation in degrees, 0 to 180

packages/animation/src/clip.ts:68

qDistance function

function qDistance: (a: Quat, b: Quat) => number

angle between two rotations in degrees

packages/animation/src/clip.ts:70

qFromEuler function

function qFromEuler(deg: Vec3): Quat

Euler angles in degrees, rotating about x, then y, then z of the parent frame's axes taken in the XYZ order of three.js (q = qx qy qz).

packages/animation/src/clip.ts:93

qIdentity function

function qIdentity: () => Quat

packages/animation/src/clip.ts:55

qInv function

function qInv: (q: Quat) => Quat

packages/animation/src/clip.ts:64

qMul function

function qMul(a: Quat, b: Quat): Quat

packages/animation/src/clip.ts:56

qNorm function

function qNorm(q: Quat): Quat

packages/animation/src/clip.ts:65

qRotate function

function qRotate(q: Quat, v: Vec3): Vec3

packages/animation/src/clip.ts:66

qSlerp function

function qSlerp(a: Quat, b: Quat, t: number): Quat

packages/animation/src/clip.ts:84

qToEuler function

function qToEuler(q: Quat): Vec3

packages/animation/src/clip.ts:98

restOf function

function restOf(bones: Record<string, Node>): Rest

The rest pose of a skeleton as the editor wants it: local position and rotation of every bone, and the rest rotations in the skeleton's frame (every parent multiplied in; the loader's mirror above the skeleton is not part of it). Read from the nodes as they are: call it before any clip has played, or with the skeleton returned to rest.

packages/animation/src/clip-babylon.ts:25

retargetGroup function

function retargetGroup(scene: BABYLON.Scene, source: BABYLON.AnimationGroup, targetRoot: BABYLON.TransformNode, opts: RetargetOptions = {}): RetargetResult

Copy an animation group from one skeleton onto another with different bone names, proportions and rest pose. Targets are found by name below targetRoot. Rotations keep their change from the rest pose (target = targetRest inverse(sourceRest) source); positions are scaled.

packages/animation/src/retarget.ts:25

samplePose function

function samplePose(clip: ClipJson, t: number): Record<string, { q?: Quat; p?: Vec3 }>

A pose of the clip at a time: per bone the change from rest. Bones without keys are not in it.

packages/animation/src/clip.ts:133

samplePosition function

function samplePosition(keys: PosKey[], t: number): Vec3

packages/animation/src/clip.ts:124

sampleRotation function

function sampleRotation(keys: RotKey[], t: number): Quat

The rotation key track at a time (held before the first and after the last key).

packages/animation/src/clip.ts:118

solveLookAt function

function solveLookAt(bones: LookBone[], forward: Vec3, toward: Vec3, o: { maxAngle?: number; weight?: number } = {}): Quat[]

Turn a chain of bones (root-ward first, the head last) so the axis forward of the last bone (in its own frame) points along toward, within maxAngle degrees of where it points now. Every bone takes its share of the turn; the last one takes what is left so the whole comes out exact. Parents' rotations are taken from parentQ of the first bone and the bones above each other.

packages/animation/src/ik.ts:107

solveTwoBone function

function solveTwoBone(c: Chain2, target: Vec3, o: TwoBoneOptions = {}): TwoBoneResult

Solve a two-bone chain so its end is at target (law of cosines), bending toward pole. The bone lengths are kept.

packages/animation/src/ik.ts:61

swing function

function swing(a: Vec3, b: Vec3): Quat

The shortest rotation taking direction a to direction b (any lengths).

packages/animation/src/ik.ts:15

swingTwist function

function swingTwist(q: Quat, axis: Vec3): { swing: number; twist: number; twistQ: Quat }

Split a rotation into a turn about axis (twist, signed degrees) and the rest (swing, degrees): q = swing * twist.

packages/animation/src/clip.ts:72

twistAxis function

function twistAxis(rest: Rest, bone: string): Vec3

The axis a bone twists about, in its own frame: the line from its parent's joint to its own (its rest offset seen from itself).

packages/animation/src/clip.ts:83

validateClipJson function

function validateClipJson(clip: unknown): ClipIssue[]

The problems of a clip file (JSON path and message). Empty when it can be played.

packages/animation/src/clip.ts:165

validateGraphDef function

function validateGraphDef(def: unknown): { path: string; message: string }[]

Check a graph definition without any clips: the initial state exists, transitions join existing states, every condition names a declared parameter of the right kind, motions have clips and sensible numbers. Returns the problems with their paths (relative to the graph); an empty list means new AnimGraph will accept it as long as the clips exist.

packages/animation/src/graph.ts:161

Interfaces

AnimatorParams interface

interface AnimatorParams
graph: GraphDef
groups: Record<string, BABYLON.AnimationGroup>one AnimationGroup per clip name used by the graph
notifies?: Record<string, Notify[]>notifies per clip, and clips that do not loop (default: all loop)
loops?: Record<string, boolean>
fromMovement?: booleanFeed speed, verticalSpeed, grounded, crouching from the entity's motion source (a component with motionState(): CharacterMovement, NavAgent) and the jump trigger from character:jump (default true)
rootMotion?: RootMotionOptions & { apply?: 'node' | 'none' }Take the movement the clips author on a bone off the skeleton (see RootMotionOptions). Each frame it is reported as Animator.rootDelta and the anim:rootmotion event { entity, x, y, z, yaw } in the entity's own space. apply: 'node' also moves and turns the entity's node by it (for entities nothing else drives: a prop, a cutscene actor); the default 'none' leaves that to the game, e.g. to feed a character controller.

packages/animation/src/animator.ts:10

BindClipsOptions interface

interface BindClipsOptions
retarget?: RetargetOptionsretargetGroup options (the bones are matched by name; restRotation re-bases the rotations when the two rest poses differ)
oneShot?: string[]clip names (the keys of map) that do not loop (default: all loop)

packages/animation/src/character.ts:3

BoneRest interface

interface BoneRest

What the editor must know about one bone of the skeleton: its rest pose (local), and the rest rotations in the skeleton frame for mirroring.

p: Vec3
q: Quat
wq?: Quatthe bone's rest rotation in the skeleton's frame (all parents multiplied in); needed to mirror left to right
pwq?: Quatthe same for its parent (identity at the root)
parent?: stringthe parent bone's name (absent at the top of the skeleton); needed to work in space (ik)

packages/animation/src/clip.ts:40

BoundClips interface

interface BoundClips
groups: Record<string, BABYLON.AnimationGroup>one AnimationGroup per graph clip name, targeting this body's bones: what Animator takes as groups
loops: Record<string, boolean>
missingClips: string[]graph clip names whose source clip does not exist, and the source bones without a bone on the body (per clip, once)
missingBones: string[]

packages/animation/src/character.ts:10

BuiltClip interface

interface BuiltClip
group: BABYLON.AnimationGroup
info: ClipInfo
missing: string[]

packages/animation/src/clip-babylon.ts:63

Chain2 interface

interface Chain2

A two-bone chain a -> b -> c: the local rotations and positions of the three bones, and the rotation of a's parent in the frame the target is given in.

pa: Quatworld (frame) rotation of the parent of a, and the frame position of a
posA: Vec3
qa: Quat
pb: Vec3local position of b in a, and of c in b
pc: Vec3
qb: Quat
qc: Quat

packages/animation/src/ik.ts:28

Chain2Pose interface

interface Chain2Pose
A: Vec3
B: Vec3
C: Vec3
wa: Quat
wb: Quat
wc: Quat

packages/animation/src/ik.ts:37

ClipBone interface

interface ClipBone
rotation?: RotKey[]
position?: PosKey[]

packages/animation/src/clip.ts:17

ClipEditorOptions interface

interface ClipEditorOptions
rest: Rest
positionBones?: (bone: string) => booleanwhich bones may carry position keys (default: the root and the pelvis)
library?: (name: string) => ClipJson | undefineda clip of the library by name, for base (its keys are copied)
limits?: [RegExp, number][]largest change from rest, in degrees, per bone matcher; the first match counts (default: a table for a human)
twistLimits?: [RegExp, number][]bones whose turn about their own length is checked apart (degrees): for these limits holds the bend (the swing) and this the twist; the default: the hands, which turn with the forearm (from a T-pose, palm down, a forearm turns some 170 degrees, a wrist bends no more than 90)

packages/animation/src/clip.ts:236

ClipInfo interface

interface ClipInfo
duration: number
loop?: boolean
notifies?: Notify[]

packages/animation/src/graph.ts:7

ClipIssue interface

interface ClipIssue
level: 'error' | 'warning'
path: string
message: string

packages/animation/src/clip.ts:52

ClipJson interface

interface ClipJson
format: 1
name: string
skeleton: stringskeleton id of the library (gw-68, viking-73, dragon-62)
duration: numberseconds
loop?: boolean
fps?: numberframe rate the clip is played and sampled at when it becomes an animation group (default 30)
base?: stringthe library clip this one was made from (its keys were copied in; informational)
additive?: booleanan additive clip (UE's additive animations): its keys are the change itself (rotation from identity, position from zero), to be put on top of another pose, not relative to rest
bones: Record<string, ClipBone>
notifies?: ClipNotify[]

packages/animation/src/clip.ts:20

ClipNotify interface

interface ClipNotify
name: string
time: number

packages/animation/src/clip.ts:18

FootPlacementSpec interface

interface FootPlacementSpec
feet: FootSpec[]
pelvis?: stringthe bone that carries the body and is lowered when a foot finds the ground lower (default pelvis)
isGround: (mesh: BABYLON.AbstractMesh) => booleanwhich meshes are ground
probeUp?: numberhow far above the foot the probe starts and how far below it ends, in metres (default 0.6 and 1.0)
probeDown?: number
smooth?: numberhow quickly the offsets follow the ground, 1/s (default 14)
maxDrop?: numberthe largest the body is lowered, in metres (default 0.45)
tilt?: numbertilt the foot to the ground normal, up to this many degrees (default 25; 0 leaves it)
weight?: Live<number>

packages/animation/src/ik-babylon.ts:55

FootSpec interface

interface FootSpec
chain: [string, string, string][thigh, calf, foot]

packages/animation/src/ik-babylon.ts:51

GraphDef interface

interface GraphDef
params?: Record<string, 'float' | 'bool' | 'trigger'>
states: Record<string, StateDef>
initial: string
transitions?: TransitionDef[]
seed?: numberseed of the random players, so a run can be repeated (default: not seeded)

packages/animation/src/graph.ts:65

GraphEvents interface

interface GraphEvents
notify?(name: string, clip: string): void
stateEnter?(state: string): void
stateExit?(state: string): void
montageEnd?(handle: Montage): void

packages/animation/src/graph.ts:97

Grip interface

interface Grip

A grip of a weapon in the frame of the bone that holds it (the socket's bone): point is the middle of the grip, axis runs along it toward the blade.

point: Vec3
axis: Vec3
radius: numberradius of the grip, metres
spacing?: numberdistance from the hand on the grip to the second hand, toward the pommel (metres; 0 or absent: one hand)
edge?: Vec3for a blade: the way its edge faces, across axis (a two-edged blade cuts along it either way)

packages/animation/src/hold.ts:18

HoldArgs interface

interface HoldArgs
rest: Rest
pose: Posethe pose the clip has at this time (the other bones; the held hands and fingers are replaced)
side: 'r' | 'l'the hand that holds the weapon: its bone is hand_<side>
grip: Grip
position: Vec3where the middle of the grip (the main hand) goes, in the skeleton frame
direction: Vec3where the blade points
roll?: number | 'auto'turn of the hands about the blade, degrees (0: the fingers wrap toward the holder's left for a right hand); 'auto': the turn that bends the wrists and forearms least and moves the hands least from where pose has them (searched all round; a clip made frame by frame has the previous frame's hands there, so it stays smooth)
both?: booleanhold with both hands (needs grip.spacing; default: when the grip has a spacing)
poleMain?: Vec3points the elbows bend toward, in the skeleton frame; default: down and out, a little back
poleOff?: Vec3
names?: { upper?: string; lower?: string; hand?: string; twist?: string; finger?: (finger: string, joint: number, side: string) => string }names, default upperarm_<side>, lowerarm_<side>, hand_<side>
elbows?: 'auto' | 'fixed'where the elbows go when no pole is given: 'auto' (default) round the shoulder-wrist line where the wrists and upper arms turn least; 'fixed': down, out to the side and a little back
hinge?: booleankeep the elbows hinges (default true): the forearm bends about one axis only and the hand carries the forearm's turn
wrapToward?: Vec3a direction in the skeleton frame the fingers wrap toward before roll (default: to the left of the holder, -x for a right hand)
edge?: Vec3for a blade with grip.edge: the way the edge should lead (the way the blade moves; skeleton frame), and how much that counts against the arms' comfort when the turn is chosen (roll: 'auto', holdRolls; default 1, 0: not at all). A cut gives the blade's motion with a weight that grows with its speed.
edgeWeight?: number

packages/animation/src/hold.ts:49

HoldResult interface

interface HoldResult
rotations: Record<string, Quat>keys to set: bone -> rotation change from rest (quaternion)
error: { main: number; off?: number }how far each hand's solve is from its place (metres), and whether the arm had to stretch
stretched: { main: boolean; off?: boolean }
roll: numberthe turn of the hands about the blade used, degrees (the one chosen with roll: 'auto')
edgeOff?: numberwith edge: how far the blade's edge (either one) is from leading, degrees

packages/animation/src/hold.ts:80

IkRigOptions interface

interface IkRigOptions
frame?: Nodethe node that holds the skeleton: the parent of the root bone (default found by name)
rest?: Restthe rest pose of the bodies skeleton (default: read from the nodes now, so build the rig before any clip has played)

packages/animation/src/ik-babylon.ts:72

LimbSpec interface

interface LimbSpec
name: string
chain: [string, string, string][root bone, middle bone, end bone] by name: shoulder, elbow, hand / thigh, calf, foot
target: () => BABYLON.Vector3 | nullwhere the end goes, in the world; null: the limb is left as animated this frame
pole?: () => BABYLON.Vector3 | nullthe point the elbow or knee bends toward, in the world (default: the way the animation bends it)
weight?: Live<number>0 to 1 (default 1)
fade?: numberseconds to fade the weight in and out (default 0.1)
maxStretch?: numberhow far past the limb's full length the target may be before the limb is let go back to the animation (a fraction of the length; the weight falls to 0 over the next 0.15). Default: never let go, the limb stretches toward the target.

packages/animation/src/ik-babylon.ts:21

LookAtSpec interface

interface LookAtSpec
bones: { bone: string; share: number }[]root-ward bone first, head last, with the share of the turn each takes (spine 0.2, neck 0.3, head 0.5)
forward: Vec3the head's axis that looks forward, in the head bone's own frame (the glTF/UE mannequins look along +Z... measure with forwardOf)
target: () => BABYLON.Vector3 | nullwhat to look at, in the world; null: look as animated
maxAngle?: numberhow far the head may leave where the animation looks, degrees (default 70)
weight?: Live<number>
smooth?: numberhow quickly the look follows, 1/s (default 8)

packages/animation/src/ik-babylon.ts:37

LookBone interface

interface LookBone
parentQ: Quatworld (frame) rotation of this bone's parent
q: Quatlocal rotation
share: numbershare of the turn this bone takes, 0 to 1 (shares are normalised)

packages/animation/src/ik.ts:94

MontageDef interface

interface MontageDef
clip: string
blendIn?: number
blendOut?: number
rate?: number
loop?: boolean
mask?: string[]only these bones (and everything below them) are animated; the rest keeps the base pose
additive?: boolean | { reference?: number }Add the clip's change from a reference pose on top of the result instead of replacing it (breathing, leaning, a flinch over any locomotion). reference is the time in seconds of the clip's own reference pose (default 0, its first frame). The weight scales the change; an additive montage never takes weight from the base. Combines with mask and loop.

packages/animation/src/graph.ts:74

MotionSource interface

interface MotionSource

Anything that moves an entity and can say how: CharacterMovement (players, NPCs on the capsule) and NavAgent (crowd).

motionState(): { velocity: { x: number; y: number; z: number }; grounded: boolean; crouching?: boolean; swimming?: boolean }

packages/animation/src/animator.ts:6

Notify interface

interface Notify
time: number
name: string

packages/animation/src/graph.ts:5

Pose interface

interface Pose
items: PoseItem[]the weights of the items without mask or additive sum to 1; a masked item blends over the base on its bones only; an additive item adds its change from the reference time on top of that

packages/animation/src/graph.ts:92

PoseItem interface

interface PoseItem
clip: string
time: number
weight: number
mask?: string[]
additive?: { reference: number }

packages/animation/src/graph.ts:91

PosKey interface

interface PosKey
t: number
p: Vec3
i?: Interp

packages/animation/src/clip.ts:16

Puppet interface

interface Puppet
root: BABYLON.TransformNode
mesh: BABYLON.Mesh
skeleton: BABYLON.Skeleton
bones: Record<string, BABYLON.TransformNode>
groups: Record<string, BABYLON.AnimationGroup>
notifies: Record<string, Notify[]>notifies authored with the clips (footsteps, take-off)
loops: Record<string, boolean>

packages/animation/src/puppet.ts:18

PuppetOptions interface

interface PuppetOptions

A procedural skinned character with a skeleton and a set of clips: the stand-in for a character exported from Unreal (a GLB with a skeleton and animations) in samples and tests. Bones are TransformNodes linked to a Babylon Skeleton, the way the glTF loader makes them, and the clips are AnimationGroups that target those nodes.

scale?: numberoverall size factor (1 = 1.8 m tall)
armRest?: numberangle of the arms from straight down at rest, radians (a T-pose or A-pose authoring difference)
prefix?: stringbone name prefix, to tell two puppets in one scene apart
color?: [number, number, number]

packages/animation/src/puppet.ts:8

RandomEntry interface

interface RandomEntry
clip: string
weight?: numberchance to be picked relative to the others (default 1)
loops?: [number, number]how many times the clip loops before the next pick: [min, max] (default [1, 1])
rate?: [number, number]playback rate range: [min, max] (default [1, 1])
blendIn?: numberseconds to cross-fade from the previous entry (default 0.2)

packages/animation/src/graph.ts:23

RetargetOptions interface

interface RetargetOptions
map?: Record<string, string> | ((source: string) => string | null)source bone name -> target bone name; a function, a table, or nothing when the names are equal after stripPrefix
stripPrefix?: stringremoved from the front of the source names (and addPrefix put on the target names) before the lookup
addPrefix?: string
positionScale?: numberposition tracks are multiplied by this (the target is taller or shorter than the source)
restRotation?: booleanrotation tracks are re-based from the source rest rotation to the target one (different T-/A-poses)
name?: string

packages/animation/src/retarget.ts:1

RetargetResult interface

interface RetargetResult
group: BABYLON.AnimationGroup
missing: string[]source bones that have an animation but no target bone

packages/animation/src/retarget.ts:14

RootDelta interface

interface RootDelta

What the clips moved the root bone by during one apply, in the bone's parent space (the entity's own space for a skeleton under it).

x: number
y: number
z: number
yaw: number

packages/animation/src/backend.ts:56

RootMotionOptions interface

interface RootMotionOptions

Root motion: the movement a clip authors on one bone (usually the root or hips) is taken off the skeleton and handed to the game, so the character's body moves by what the animation says (a dodge roll, a lunge, a climb) instead of the skeleton sliding away from its entity.

bone: stringname of the bone whose position track (and yaw) carries the motion
axes?: ('x' | 'y' | 'z')[]position axes taken off the bone and reported (default x and z: the bone keeps its own height bob)
yaw?: booleanalso take the bone's turn about the vertical axis (default true)

packages/animation/src/backend.ts:46

RotKey interface

interface RotKey
t: number
q: Quat
i?: Interp

packages/animation/src/clip.ts:15

StateDef interface

interface StateDef
motion: Motion
speed?: numberplayback rate (1 = as authored)
loop?: booleanoverride the loop flag of the clips: a state that plays once and holds the last pose

packages/animation/src/graph.ts:46

TransitionDef interface

interface TransitionDef
from: string | '*'
to: string
when?: Condition[]all must hold
duration?: numbercross-fade seconds
interruptible?: booleanmay start while the state is still fading in (default true)

packages/animation/src/graph.ts:54

TwoBoneOptions interface

interface TwoBoneOptions
pole?: Vec3a point the knee or elbow bends toward (default: the way it bends now)
weight?: number0 to 1: how much of the solved pose is used (default 1)
endRotation?: Quata world (frame) rotation for the end bone (the hand turned onto a grip, the foot onto the ground); default: it keeps its world rotation

packages/animation/src/ik.ts:44

TwoBoneResult interface

interface TwoBoneResult
qa: Quat
qb: Quat
qc: Quat
stretched: booleanthe chain was too short to reach: it is stretched straight toward the target
error: numberdistance of the end bone from the target after the solve

packages/animation/src/ik.ts:52

Types

ClipLibrary type

type ClipLibrary = Record<string, ClipInfo>

packages/animation/src/graph.ts:13

Condition type

type Condition = | { param: string; op: '>' | '<' | '>=' | '<=' | '==' | '!='; value: number } | { param: string; is: boolean } | { trigger: string } /** the state has played at least this many seconds */ | { stateTime: number } /** a non-looping state has reached its end */ | { finished: true } /** less than this fraction of the state's clip is left to play (Unreal's TimeRemaining (ratio) < value) */ | { remaining: number }

packages/animation/src/graph.ts:35

Interp type

type Interp = 'step' | 'linear' | 'smooth'

packages/animation/src/clip.ts:12

Motion type

type Motion = | { clip: string } | { blend1d: { param: string; points: { at: number; clip: string }[] } } | { blend2d: { x: string; y: string; points: { x: number; y: number; clip: string }[] } } /** plays one entry for a random number of loops, then cross-fades to another (Unreal's Random Player) */ | { random: { entries: RandomEntry[] } }

packages/animation/src/graph.ts:16

Quat type

type Quat = [number, number, number, number]

packages/animation/src/clip.ts:11

Rest type

type Rest = Record<string, BoneRest>

packages/animation/src/clip.ts:50

Vec3 type

type Vec3 = [number, number, number]

packages/animation/src/clip.ts:10

Constants

DEFAULT_LIMITS const

const DEFAULT_LIMITS: [RegExp, number][]

packages/animation/src/clip.ts:249

DEFAULT_TWIST_LIMITS const

const DEFAULT_TWIST_LIMITS: [RegExp, number][]

packages/animation/src/clip.ts:253

INTERPS const

const INTERPS: readonly Interp[]

packages/animation/src/clip.ts:13