animplugin
Skeletal clips from a rig file, played on the rows that carry them.
Putting it in a scene
List it before render3d, and before ragdoll3d, interact and ik3d when the scene uses them. Each row of a kind in world.anim.kinds is drawn as its own copy of a rigged model, playing clips from a rig file. A row is drawn by the entry of the kind its as field names when it has one, so a seat's player row can be an xbot.
"plugins": ["loader", "scenemanager", "player", "anim", "render3d"],
"world": {
"anim": {
"still": 0.2, "shift": 0.6, "leap": 3, "landFall": 4, "moves": 0.5, "turns": 30,
"kinds": {
"xbot": {
"rig": "rig", "model": "xbot", "fade": 0.2,
"reads": { "pos": "pos", "yaw": "yaw", "vel": "vel", "grounded": "grounded", "limp": "limp", "aim": "look.pitch" },
"aim": { "bones": { "Spine1": 0.4, "Spine2": 0.6 } },
"props": [{ "model": "rifle", "bone": "RightHand", "at": [0, 0, 0], "turn": [0, 0, 0] }]
}
}
}
}
Properties
Read from world.anim.
| Setting | Meaning |
|---|---|
.still | Metres per second. A body moving slower than this over the ground stands in its still role. |
.shift | A gear is taken once the body moves at this fraction of that gear's own speed. |
.leap | Metres per second. Leaving the ground rising while moving faster than this over the ground plays leap; slower plays jump. |
.landFall | Metres per second. Touching down plays land only when the body was falling faster than this. |
.moves, .turns | A one-shot clip carries travel when it ends more than moves metres from where it started, or turns turns degrees or more. |
.kinds | One entry per kind drawn (see below). |
.kinds.<kind> : { rig, model, fade, reads, aim?, props? }
| Key | Meaning |
|---|---|
rig | The loader name of the rig file, a json entry. |
model | The loader name of the model, a model entry. |
fade | Seconds a crossfade between clips takes. |
reads | The names of the row fields anim reads: pos (x, y, z in metres), yaw (radians, positive left), vel (x, y, z in metres per second, y up), grounded (false in the air), limp (true while something else owns the body) and optionally aim. A row without one of these is at the origin, facing 0, standing, on the ground and not limp. aim is the pitch in radians, positive up, and may be a dotted path such as "look.pitch". |
aim | { bones: { suffix: share } }. The aim pitch is spread over the listed bones, each turned by share × pitch. Shares that sum to 1 turn everything above the last bone by the whole pitch. It needs reads.aim. |
props | [{ model, bone, at, turn }]. Hangs loader models on the body: on the first bone whose name ends with bone, moved by at in metres and turned by turn in degrees. |
The rig file
A loader json entry.
| Key | Meaning |
|---|---|
clips | Clip name to its source file, or to the entry described below. |
roles | The clip each of anim's moments plays: still, jump, leap, fall and land. A rig whose bodies never leave the ground needs only still. |
stances | Stance name to { role: clip }. A role a stance leaves out plays the clip named after the role. |
mask | The suffix of the bone the upper-body channel draws from, which takes in every bone below it. Without one there is no upper channel. |
meshes, show | meshes maps a role to a regular expression matched against mesh names. show maps a stance to the roles it draws. A mesh with no role is always drawn. |
table | Each clip's measured length, travel and so on, measured by the bake. |
ends | Per body, per clip, measured by the bake: fps, and for each segment its two ends on every frame (see below). Read through ends. Nothing anim draws reads it. |
bodies | { model: "fbx" }: the rigged characters the clips are baked onto, each a path under the library, written to the loader model entry of its name. The library is PATCHWORK_LIBRARY, else ~/patchwork-library, and a character is characters/<name>/<name>.fbx in it. Nothing plays by it. |
spread | { model: degrees }: how far the bake swings that body's upper arms out from its sides on every frame, for a body broader than the one the clips were made on. Nothing plays by it. |
The bake
The bake plays every clip of the rig onto each body in bodies in Blender and writes it as a glb, timed at 30 frames a second. Each glb is packed by the native gltfpack, 1.3 or later: meshopt compression, every node, bone and material name kept, and the clips' translations at 20 bits, which keeps a run's hips within 0.6 mm of the unpacked export. The loader decodes it with no setup. The same run measures table on the first body named and ends on every body, each with its own limb lengths. The library and gltfpack are read from PATCHWORK_LIBRARY and GLTFPACK in the environment, else from the repository's .env, and yarn health checks both.
Segment ends
Each row of a segment is [ax, ay, az, bx, by, bz], in metres in the body's frame as anim draws it: x the body's left, y up, z ahead. A loop's steady progress across the ground is taken off every point, as anim draws a loop's hips.
| Segment | From, to |
|---|---|
head | The skull's base to its top. |
torso | The hips to the neck. |
armL, armR | The shoulder to the elbow. |
forearmL, forearmR | The elbow to the wrist. |
handL, handR | The wrist to the middle fingertip. |
uplegL, uplegR | The hip to the knee. |
legL, legR | The knee to the ankle. |
footL, footR | The ankle to the toe's tip. |
Clip entries
| Key | Meaning |
|---|---|
file | The clip's FBX file, a path under ~/troupe-collection. |
rebase | { from, onto }: the bake moves the clip from the pose it was made around, the first frame of the FBX from, to the pose clip onto starts in. Nothing plays by it. |
loop | Laps the clip. Without it the clip is a one-shot. |
gear | For a loop, degrees from forward (0 forward, 90 left). Makes the loop a gear: the clip the body plays when moving in that direction. |
markers | Name to a time: seconds, "peak", or "NN%" of the clip. A marker fires when the playhead crosses it. |
scale | Multiplies the distance a one-shot carries the body. Default 1. |
rate | Multiplies the clip's playback speed. Default 1. |
stance | The stance this clip is a gear of. A gear with none is a gear of every stance. |
mirror | The name of another clip. This clip is that clip mirrored left to right, with its gear direction negated, so a left strafe can be a right strafe mirrored. |
The row
Fields of a row that plays clips. animate writes all of them except rig.
| Field | Meaning |
|---|---|
rig | The stance the row stands in. A game changes the stance by writing this. |
state | loco in its gears, act in a clip it was told to play, fall in the air. |
clip, clipT, rate | The clip playing, seconds into it, and the row's speed multiplier. |
from, fromT, blend | The previous clip frozen where it stood and the new clip's share, rising 0 to 1 over fade. |
upper, upperT, upperW, was, wasT, wasW | The same for the upper-body channel and the gesture it is fading out. |
holds, cancel | Whether an act is committed, and the time before which it cannot be cancelled. |
sink | The fall speed, written each step in the air. |
Crossfades are row fields, so a seat that arrives late draws the same fade.
Methods
Rules
From #plugins/anim/anim.sim.ts. me is the row.
animate( me, w, dt ) : { markers, travel }
Runs one step. In the air it plays jump, leap, fall and land. On the ground it plays the gear nearest the direction the body is moving against its facing, and the stance's still role when slower than still. It moves the playhead, fires markers and advances the crossfades. markers lists { name, clip, upper } in the order crossed. travel is { move: [x, z], yaw } in world metres and radians, which a one-shot clip carries the body by; the caller applies it. A limp row is not animated.
play( me, clip, { holds?, rate? } )
Starts a clip as an act, from 0. It is committed while holds is true or before the clip's cancel marker.
gesture( me, clip )
Plays a clip once on the upper-body channel.
committed( me ) : boolean
Whether the act playing can no longer be cancelled.
stop( me, w )
Ends the act playing. The body goes back to its gears at once, with the act fading out.
rest( me )
Resets every playing field, as for a respawn. The body stands in its stance's still role at full weight. rig is kept.
ends( w, me, segment, at? ) : number[]
The segment's two ends, [ax, ay, az, bx, by, bz] in the body's frame, at at, { clip, clipT }, the row's own clip and playhead when left out. It reads the rig's ends for the kind's model, on the frame the playhead falls on, held within the clip's frames. w is a rule's w or a page's plugins.wire. Throws when the rig has no ends for the model.
On the page
Through plugins.anim. These are posed for the frame once anim's own tick has run.
model( id ) : Object3D | undefined
The row's model, undefined before it is built, with its world matrices up to date.
props( id ) : Object3D[]
The row's props in the order the kind lists them, or an empty list before they are made, with their world matrices up to date once placed. Use it to find an empty inside one by name, such as a rifle's muzzle.
pose( id, clip, t ) : Object3D
Draws the row's model in clip alone, at t seconds, and returns it. The row must already have a model.
Mods
anim gives three mods.
- ragdoll3d
modelandpose: name them inworld.ragdoll3d.modsas{ model: "anim", pose: "anim" }, so limp bodies lie on the models anim draws.model(plugins, id)ismodelabove, andpose(plugins, id, clip, t)ispose. - ik3d
body: name it inworld.ik3d.modsas{ body: "anim" }, so ik3d bends the limbs of the bodies anim draws.body(plugins, row)is{ kind, root, props, limp }: the kind the row is drawn by, its model, every prop its kind'spropsnames as{ model, root }in that order withrootundefined until it is made, and the row'slimpfield. Undefined for a row of a kind anim does not draw, or whose model is not built yet. - interact
body: name it inworld.interact.modsas{ body: "anim" }, so a body in an interaction is drawn on its machine's point as the page draws it.body(plugins, row)is{ root, props, limp }: the model, the props made so far in its kind's order, and the row'slimpfield. Undefined as for ik3d.
Used by
- ragdoll3dBuilds a dying body's bones from
endsin its physics mode, and hands a shot a standing body's bones from them. - dismember3dPlaces a body's joints, and the limbs it cuts off, from
ends. - interactPlays each step's clip as an act, stops it when the interaction ends, and moves the body along the clip's measured travel.