@ferret/animation
Animation graphs: state machines, blend spaces, layers, notifies, retargeting and a stand-in puppet.
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: RootDelta | root 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 AnimGraphRuntime of one graph on one character.
readonly values: Record<string, number> | |
notifyThreshold: number | a 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): Pose | Advance by dt seconds (already multiplied by the entity's time dilation). |
packages/animation/src/graph.ts:235
ClipEditError class
class ClipEditError extends Errorpackages/animation/src/clip.ts:234
ClipEditor class
class ClipEditorreadonly 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 }): ClipEditor | A 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 }): void | Set 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' }): number | Remove 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 }): number | Copy 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 } = {}): number | Mirror 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][] }): void | Change 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 GroupBackendApplies 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): RootDelta | Apply 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 IkRigIK 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(): this | run 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): Vec3 | a 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 Montagetime: 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): QuatThe 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 }>): voidPut 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 = {}): BoundClipsPuts 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 = {}): Puppetpackages/animation/src/puppet.ts:48
chain2Pose function
function chain2Pose(c: Chain2): Chain2Posepackages/animation/src/ik.ts:39
clipFromJson function
function clipFromJson(scene: BABYLON.Scene, clip: ClipJson, bones: Record<string, Node>, rest: Rest, o: { name?: string } = {}): BuiltClipMake 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[] } = {}): ClipInfoThe 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[] }> = {}): ClipLibrarypackages/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): numberHow 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): HoldResultPut 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 | nullupperarm_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): Vec3packages/animation/src/clip.ts:227
mirrorRotation function
function mirrorRotation(q: Quat, from: BoneRest, to: BoneRest): QuatThe 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): numberthe angle of a rotation in degrees, 0 to 180
packages/animation/src/clip.ts:68
qDistance function
function qDistance: (a: Quat, b: Quat) => numberangle between two rotations in degrees
packages/animation/src/clip.ts:70
qFromEuler function
function qFromEuler(deg: Vec3): QuatEuler 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: () => Quatpackages/animation/src/clip.ts:55
qInv function
function qInv: (q: Quat) => Quatpackages/animation/src/clip.ts:64
qMul function
function qMul(a: Quat, b: Quat): Quatpackages/animation/src/clip.ts:56
qNorm function
function qNorm(q: Quat): Quatpackages/animation/src/clip.ts:65
qRotate function
function qRotate(q: Quat, v: Vec3): Vec3packages/animation/src/clip.ts:66
qSlerp function
function qSlerp(a: Quat, b: Quat, t: number): Quatpackages/animation/src/clip.ts:84
qToEuler function
function qToEuler(q: Quat): Vec3packages/animation/src/clip.ts:98
restOf function
function restOf(bones: Record<string, Node>): RestThe 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 = {}): RetargetResultCopy 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): Vec3packages/animation/src/clip.ts:124
sampleRotation function
function sampleRotation(keys: RotKey[], t: number): QuatThe 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 = {}): TwoBoneResultSolve 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): QuatThe 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): Vec3The 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 AnimatorParamsgraph: 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?: boolean | Feed 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 BindClipsOptionsretarget?: RetargetOptions | retargetGroup 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 BoneRestWhat 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?: Quat | the bone's rest rotation in the skeleton's frame (all parents multiplied in); needed to mirror left to right |
pwq?: Quat | the same for its parent (identity at the root) |
parent?: string | the 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 BoundClipsgroups: 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 BuiltClipgroup: BABYLON.AnimationGroup | |
info: ClipInfo | |
missing: string[] |
packages/animation/src/clip-babylon.ts:63
Chain2 interface
interface Chain2A 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: Quat | world (frame) rotation of the parent of a, and the frame position of a |
posA: Vec3 | |
qa: Quat | |
pb: Vec3 | local 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 Chain2Posepackages/animation/src/ik.ts:37
ClipBone interface
interface ClipBonepackages/animation/src/clip.ts:17
ClipEditorOptions interface
interface ClipEditorOptionsrest: Rest | |
positionBones?: (bone: string) => boolean | which bones may carry position keys (default: the root and the pelvis) |
library?: (name: string) => ClipJson | undefined | a 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 ClipInfoduration: number | |
loop?: boolean | |
notifies?: Notify[] |
packages/animation/src/graph.ts:7
ClipIssue interface
interface ClipIssuelevel: 'error' | 'warning' | |
path: string | |
message: string |
packages/animation/src/clip.ts:52
ClipJson interface
interface ClipJsonformat: 1 | |
name: string | |
skeleton: string | skeleton id of the library (gw-68, viking-73, dragon-62) |
duration: number | seconds |
loop?: boolean | |
fps?: number | frame rate the clip is played and sampled at when it becomes an animation group (default 30) |
base?: string | the library clip this one was made from (its keys were copied in; informational) |
additive?: boolean | an 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 ClipNotifyname: string | |
time: number |
packages/animation/src/clip.ts:18
FootPlacementSpec interface
interface FootPlacementSpecfeet: FootSpec[] | |
pelvis?: string | the bone that carries the body and is lowered when a foot finds the ground lower (default pelvis) |
isGround: (mesh: BABYLON.AbstractMesh) => boolean | which meshes are ground |
probeUp?: number | how far above the foot the probe starts and how far below it ends, in metres (default 0.6 and 1.0) |
probeDown?: number | |
smooth?: number | how quickly the offsets follow the ground, 1/s (default 14) |
maxDrop?: number | the largest the body is lowered, in metres (default 0.45) |
tilt?: number | tilt 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 FootSpecchain: [string, string, string] | [thigh, calf, foot] |
packages/animation/src/ik-babylon.ts:51
GraphDef interface
interface GraphDefparams?: Record<string, 'float' | 'bool' | 'trigger'> | |
states: Record<string, StateDef> | |
initial: string | |
transitions?: TransitionDef[] | |
seed?: number | seed of the random players, so a run can be repeated (default: not seeded) |
packages/animation/src/graph.ts:65
GraphEvents interface
interface GraphEventsnotify?(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 GripA 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: number | radius of the grip, metres |
spacing?: number | distance from the hand on the grip to the second hand, toward the pommel (metres; 0 or absent: one hand) |
edge?: Vec3 | for 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 HoldArgsrest: Rest | |
pose: Pose | the 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: Vec3 | where the middle of the grip (the main hand) goes, in the skeleton frame |
direction: Vec3 | where 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?: boolean | hold with both hands (needs grip.spacing; default: when the grip has a spacing) |
poleMain?: Vec3 | points 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?: boolean | keep the elbows hinges (default true): the forearm bends about one axis only and the hand carries the forearm's turn |
wrapToward?: Vec3 | a direction in the skeleton frame the fingers wrap toward before roll (default: to the left of the holder, -x for a right hand) |
edge?: Vec3 | for 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 HoldResultrotations: 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: number | the turn of the hands about the blade used, degrees (the one chosen with roll: 'auto') |
edgeOff?: number | with edge: how far the blade's edge (either one) is from leading, degrees |
packages/animation/src/hold.ts:80
IkRigOptions interface
interface IkRigOptionsframe?: Node | the node that holds the skeleton: the parent of the root bone (default found by name) |
rest?: Rest | the 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 LimbSpecname: string | |
chain: [string, string, string] | [root bone, middle bone, end bone] by name: shoulder, elbow, hand / thigh, calf, foot |
target: () => BABYLON.Vector3 | null | where the end goes, in the world; null: the limb is left as animated this frame |
pole?: () => BABYLON.Vector3 | null | the 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?: number | seconds to fade the weight in and out (default 0.1) |
maxStretch?: number | how 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 LookAtSpecbones: { 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: Vec3 | the 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 | null | what to look at, in the world; null: look as animated |
maxAngle?: number | how far the head may leave where the animation looks, degrees (default 70) |
weight?: Live<number> | |
smooth?: number | how quickly the look follows, 1/s (default 8) |
packages/animation/src/ik-babylon.ts:37
LookBone interface
interface LookBoneparentQ: Quat | world (frame) rotation of this bone's parent |
q: Quat | local rotation |
share: number | share of the turn this bone takes, 0 to 1 (shares are normalised) |
packages/animation/src/ik.ts:94
MontageDef interface
interface MontageDefclip: 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 MotionSourceAnything 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 Notifytime: number | |
name: string |
packages/animation/src/graph.ts:5
Pose interface
interface Poseitems: 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 PoseItemclip: string | |
time: number | |
weight: number | |
mask?: string[] | |
additive?: { reference: number } |
packages/animation/src/graph.ts:91
PosKey interface
interface PosKeypackages/animation/src/clip.ts:16
Puppet interface
interface Puppetroot: 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 PuppetOptionsA 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?: number | overall size factor (1 = 1.8 m tall) |
armRest?: number | angle of the arms from straight down at rest, radians (a T-pose or A-pose authoring difference) |
prefix?: string | bone name prefix, to tell two puppets in one scene apart |
color?: [number, number, number] |
packages/animation/src/puppet.ts:8
RandomEntry interface
interface RandomEntryclip: string | |
weight?: number | chance 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?: number | seconds to cross-fade from the previous entry (default 0.2) |
packages/animation/src/graph.ts:23
RetargetOptions interface
interface RetargetOptionsmap?: 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?: string | removed from the front of the source names (and addPrefix put on the target names) before the lookup |
addPrefix?: string | |
positionScale?: number | position tracks are multiplied by this (the target is taller or shorter than the source) |
restRotation?: boolean | rotation 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 RetargetResultgroup: BABYLON.AnimationGroup | |
missing: string[] | source bones that have an animation but no target bone |
packages/animation/src/retarget.ts:14
RootDelta interface
interface RootDeltaWhat 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 RootMotionOptionsRoot 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: string | name 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?: boolean | also take the bone's turn about the vertical axis (default true) |
packages/animation/src/backend.ts:46
RotKey interface
interface RotKeypackages/animation/src/clip.ts:15
StateDef interface
interface StateDefmotion: Motion | |
speed?: number | playback rate (1 = as authored) |
loop?: boolean | override 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 TransitionDeffrom: string | '*' | |
to: string | |
when?: Condition[] | all must hold |
duration?: number | cross-fade seconds |
interruptible?: boolean | may start while the state is still fading in (default true) |
packages/animation/src/graph.ts:54
TwoBoneOptions interface
interface TwoBoneOptionspole?: Vec3 | a point the knee or elbow bends toward (default: the way it bends now) |
weight?: number | 0 to 1: how much of the solved pose is used (default 1) |
endRotation?: Quat | a 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 TwoBoneResultqa: Quat | |
qb: Quat | |
qc: Quat | |
stretched: boolean | the chain was too short to reach: it is stretched straight toward the target |
error: number | distance 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