Modules / Rendering & look

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.

SettingMeaning
.stillMetres per second. A body moving slower than this over the ground stands in its still role.
.shiftA gear is taken once the body moves at this fraction of that gear's own speed.
.leapMetres per second. Leaving the ground rising while moving faster than this over the ground plays leap; slower plays jump.
.landFallMetres per second. Touching down plays land only when the body was falling faster than this.
.moves, .turnsA one-shot clip carries travel when it ends more than moves metres from where it started, or turns turns degrees or more.
.kindsOne entry per kind drawn (see below).

.kinds.<kind> : { rig, model, fade, reads, aim?, props? }

KeyMeaning
rigThe loader name of the rig file, a json entry.
modelThe loader name of the model, a model entry.
fadeSeconds a crossfade between clips takes.
readsThe 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.

KeyMeaning
clipsClip name to its source file, or to the entry described below.
rolesThe clip each of anim's moments plays: still, jump, leap, fall and land. A rig whose bodies never leave the ground needs only still.
stancesStance name to { role: clip }. A role a stance leaves out plays the clip named after the role.
maskThe 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, showmeshes 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.
tableEach clip's measured length, travel and so on, measured by the bake.
endsPer 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.

SegmentFrom, to
headThe skull's base to its top.
torsoThe hips to the neck.
armL, armRThe shoulder to the elbow.
forearmL, forearmRThe elbow to the wrist.
handL, handRThe wrist to the middle fingertip.
uplegL, uplegRThe hip to the knee.
legL, legRThe knee to the ankle.
footL, footRThe ankle to the toe's tip.

Clip entries

KeyMeaning
fileThe 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.
loopLaps the clip. Without it the clip is a one-shot.
gearFor a loop, degrees from forward (0 forward, 90 left). Makes the loop a gear: the clip the body plays when moving in that direction.
markersName to a time: seconds, "peak", or "NN%" of the clip. A marker fires when the playhead crosses it.
scaleMultiplies the distance a one-shot carries the body. Default 1.
rateMultiplies the clip's playback speed. Default 1.
stanceThe stance this clip is a gear of. A gear with none is a gear of every stance.
mirrorThe 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.

FieldMeaning
rigThe stance the row stands in. A game changes the stance by writing this.
stateloco in its gears, act in a clip it was told to play, fall in the air.
clip, clipT, rateThe clip playing, seconds into it, and the row's speed multiplier.
from, fromT, blendThe previous clip frozen where it stood and the new clip's share, rising 0 to 1 over fade.
upper, upperT, upperW, was, wasT, wasWThe same for the upper-body channel and the gesture it is fading out.
holds, cancelWhether an act is committed, and the time before which it cannot be cancelled.
sinkThe 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
    model and pose: name them in world.ragdoll3d.mods as { model: "anim", pose: "anim" }, so limp bodies lie on the models anim draws. model(plugins, id) is model above, and pose(plugins, id, clip, t) is pose.
  • ik3d
    body: name it in world.ik3d.mods as { 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's props names as { model, root } in that order with root undefined until it is made, and the row's limp field. Undefined for a row of a kind anim does not draw, or whose model is not built yet.
  • interact
    body: name it in world.interact.mods as { 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's limp field. Undefined as for ik3d.

Used by

  • ragdoll3d
    Builds a dying body's bones from ends in its physics mode, and hands a shot a standing body's bones from them.
  • dismember3d
    Places a body's joints, and the limbs it cuts off, from ends.
  • interact
    Plays each step's clip as an act, stops it when the interaction ends, and moves the body along the clip's measured travel.

In the guide