Physics

Part of Strut: it makes a model's parts move with the model: spring bones that jiggle, chains for hair and tails, a ragdoll, and cloth and flesh made from a mesh. It is visual and client-side, and a model with no physics data costs one cached lookup a frame.

What it is, and what it is not

The kinds of part

Spring boneOne bone with a mass on a lever that lags the model's motion and settles. Pouches, jowls, a belly. Rotates (and optionally translates) the bone.
ChainBones that each hang from the one before, simulated in world space with exact segment lengths. Hair, tails, ears, straps.
RagdollA skeleton of joints over named bones that the body can be let go into, with a muscle that pulls it back to the animation.
ClothThe vertices of a mesh on a bone: edges keep their length, folds resist, the top is sewn on. A skirt, a cape.
FleshA small cage fitted to the meshes on a bone that swings as one lump about its base and lags at its free end, with a dent of its own under every vertex that something presses on. It keeps its volume and bulges, and its cost does not depend on how dense the mesh is. Pouches, a bottom, a belly, a slime.

Giving a model physics

Three ways, which layer over each other bone by bone (the later wins):

  1. In Blockbench. The plugin has a Physics tab beside Edit, Paint and Animate (see below). The settings are saved with the project and written into the exported .strutmodel as an extension section named strut_physics:physics.
  2. The model file. The same JSON, in that section. A <model>.physics.json placed next to the model file is read as well.
  3. A sidecar in a resource pack, to retune a model without re-exporting it:
    assets/<namespace>/strut/physics/<model path without extension>.json
    mymod:strut/models/alt/ava.strutmodel  ->  assets/mymod/strut/physics/strut/models/alt/ava.json
    mymod:strut/models/alt/ava.strutmodel  ->  assets/mymod/strut/physics/models/alt/ava.json
    A model under strut/models takes its sidecar at its path without the leading strut/; the full strut/physics/strut/models/... is read too.

Unknown keys, bad values and bones the model does not have are logged as warnings, never thrown. A part in a sidecar replaces the same part in the model file whole; the rest stay.

Spring bones

{
  "bones": {
    "pouchL": { "preset": "jiggle-soft", "frequency": 1.7, "damping": 0.24, "length": 0.15,
               "limit": { "x": [-20, 20], "y": [-20, 20], "z": [-20, 20] } },
    "belly": { "preset": "jiggle-firm", "translate": true, "maxTranslate": 1.5, "weight": 0.8 }
  }
}
presetjiggle-soft, jiggle-firm, hair, tail, ear, soft-bone. Starting values; any key beside it overrides. jiggle-soft and jiggle-firm only rotate the bone about its pivot and nothing presses them. soft-bone is a rotating jiggle that is also pressed by other bodies (other, players, mobs, body) and flattens when pressed (squish 0.3); it does not slide the bone (translate is off, as sliding lifts a rigid part off its parent), which a bone can still turn on with "translate": true.
frequency, dampingHow fast it wobbles (hertz, 1.5 to 2.5 for jiggle) and the damping ratio (0 rings forever, 1 returns without overshoot, 0.15 to 0.35 for jiggle).
inertia0 to 1: how much of the entity's own motion reaches the bone.
gravityBlocks per second squared: 0 for jiggle, about 9.8 for hair.
tip, lengthThe lever: the direction from the pivot to the mass (default straight down) and its length in blocks. A shorter lever turns the same push into a bigger angle.
rotate, translateWhat the springs drive. Rotation is the default.
limit, maxTranslateMost rotation per axis in degrees, most translation in model units.
weight0 to 1: how much of the physics reaches the pose.
collidesWith, radius, squishThe layers it is pressed by, its size for collision (blocks) and how much it flattens (0 to 1). The soft-bone preset presses by default.

Chains

{ "chains": { "ponytail": { "bones": ["hair1", "hair2", "hair3"], "preset": "hair", "tipLength": 0.25, "maxAngle": 50 } } }

List the bones from the root down; each must be the child of the one before. Keys: preset (hair, tail, ear), frequency, damping, inertia (1 lags the entity fully), gravity, radius, maxAngle (most a segment may bend from its animated direction, degrees), tipLength, friction, weight, collidesWith (default ["body", "world"]).

Colliders, and what a part bumps into

A collider is a sphere, capsule or box on a bone, positions relative to the bone's pivot in model units. A part never ends a step inside a shape it collides with. A shape has a layer and a part lists the layers it feels in collidesWith:

bodyThe model's own shapes.
worldBlocks, by their real collision boxes (stairs, slabs, fences), with friction.
otherAnother Strut model's body shapes.
players, mobsA player's hitbox, any other living entity's hitbox.
limbShapes on arms and legs, for other models' parts to bump into.
{ "colliders": [
    { "bone": "head", "shape": "sphere", "center": [0, 8, 0], "radius": 5 },
    { "bone": "torso", "shape": "capsule", "from": [0, 0, 0], "to": [0, 12, 0], "radius": 4, "layer": "body" },
    { "bone": "torso", "shape": "box", "center": [0, 6, 0], "size": [8, 12, 4], "rotation": [0, 0, 10] } ] }

Made shapes. A part that lists body and has no authored shapes on a bone gets shapes made from the model itself: a box per cube for a bone with a few cubes, one low-poly capsule or box for a detailed bone or a mesh. "autoColliders": {"maxCubes": 8, "bones": {"hair": "off"}} tunes it (a bone can be auto, exact, simple or off); false turns it off. A part ignores made shapes on its own bone, its parents and children, and any shape it already overlaps at rest.

Allow and deny lists. "collideBones": ["torso"] (only these of its own model's bones) or "ignoreBones": ["head", "hair*"] (all but these) on a spring, chain, cloth or flesh. Names may start or end with *.

Cloth

The vertices of a mesh on a bone, welded into particles (a seam is stitched). Edges keep their length, a bend constraint across every fold resists crumpling, an edge that a fast-moving waistband stretches is brought back, and the particles are pushed out of shapes, blocks and other bodies.

{ "cloth": { "skirt": { "bone": "hip", "preset": "cloth", "pinHeight": 0.1 } } }
boneRequired. The bone whose mesh is the cloth. mesh picks one of its meshes by order (default all).
presetcloth, cloth-silk, cloth-stiff.
stiffness, bend0 to 1: how firmly edges keep their length; how much a fold resists (0 silk, 1 card).
pinHeightThe share of the mesh height, from the top, sewn to the bone (a waistband). 0 sews nothing.
anchorHow firmly the free cloth is pulled toward the animated shape, per second. 0 lets it hang free.
damping, inertia, gravity, radius, friction, weight, iterationsAs for chains. inertia defaults to 0.6 (a skirt is dragged along by the body).
maxParticlesDefault 600. A mesh with more welded vertices is left as modelled and the log says so: use a lower-poly mesh for the cloth.
collidesWithDefault ["body", "world"].

Flesh

{ "flesh": { "chest": { "preset": "pouch", "bones": ["pouchL", "pouchR"] } } }

Each bone named is a region: its meshes, and the meshes on the bones under it. One bone is a region on its own; two are a left and right pair that press on each other, flatten where they meet and bulge out at the rim. A small cage of 163 nodes is fitted to the region once, when the model is loaded, and the mesh is read into it. The old name of this section, "soft", still loads with a warning.

Every step the cage moves in two layers, both exact damped springs: the whole region swings as one lump, and each node lags on its own on top of that. The inertia is measured from how the animation itself moves the region, so an animation shakes it as much as walking does. Turning swings it behind the turn, and walking bounces it on each step unless the animation already bobs it. The lump swing is drawn as a turn about the base, like something hanging, with a stretch that keeps its volume.

Deformation. What presses into the flesh pushes the cage's surface in, and every vertex follows the cage, so the whole region deforms as one object: a tip or any other piece of the mesh goes in with the flesh it sits on, and nothing can tear apart or turn inside out. A hand halfway through is met by flesh pushed in under it, and as it moves across the dent moves with it and springs back behind it, with a wobble. The shapes count as one solid, so a hand of several cubes or a column of blocks has no seams to catch on, and each pressed node goes straight toward the middle of the flesh until it is clear of them all, never past most of the way there. What is pressed in swells out elsewhere: the cage is a closed shell, so the volume it has lost is given back along the free nodes whether or not the mesh itself is closed (bulge says how much of it). A press on the side also shoves the whole region, which swings back, and a dent that is let go fades slowly rather than snapping back (the longer the more meat), so a shape drawn across leaves a wake behind it. A block the middle of the flesh is in flattens it against its face; a body standing in it (a camera flown in) has no way round it, so the flesh is left as it is and clips into it like the rest of the model.

Everything is a field. Flesh collides with the signed distance field of the model's own geometry (cubes and meshes alike, see Colliders), never with the simple boxes and capsules a part may fall back on: the field is exact for a hand as for a chest, and says which way is out. The fields are made a moment after a model is first drawn, off the game thread, and are made for flesh whatever the mesh collider switch says; until they are there the flesh has no body of its own to rest against or be pressed by (other entities, whose shapes are their own, still press it).

Presets pouch (the default), seat (firmer, travels less), belly, thigh, jelly, flesh-soft and flesh-firm. Every key has a default from its preset:

bones or boneOne bone, or two for a pair. Required.
softness1 is the preset; up to 3 is very soft, 0.5 firm. Multiplied by the entity's own (below).
softCenter, softRadiusSoft inside the radius (blocks, in the mesh's own space), the rest held exactly as animated. This is how only the belly is soft while the ribs stay put.
carryOther bones whose meshes ride on the region as one rigid piece: a tip on a pouch. They follow the flesh around them, keep their own shape, and turn with the surface. When the root bone has no meshes of its own, every mesh under it is flesh.
limit, reachThe farthest a node may move from where the animation puts it, blocks (0.15625, two and a half pixels), and the share of that the region uses.
tip, up"auto", or an axis of the root bone ("+z", "-y"...) for the free end and for the side it hangs from, when the guess is wrong.
anchorfrom, hold, full, depth [from, to]: where and how firmly the top and the base are held.
lumpdamping, inertia, share (of the inertia the whole region takes), side (how much stiffer sideways), stiffen (how soon a big swing stiffens).
nodedamping of the free end, shape (the pull back to the cage form), link, gravity (the sag a step).
squeezein, out, k, rim: how a pair gives way toward each other, comes back, pushes apart, and bulges at the contact.
volume, bulge, rigid, firmness, turnHow much a closed mesh keeps its volume, the swelling around a press, how much the swing turns about the base rather than sliding, a frequency scale, and how strongly turning swings it.
pivot"bone" (default): the lump swings about the root bone's own pivot, where the animator put the joint. "auto": about the base of the fitted cage. Or [x, y, z], in the bone's space in blocks. A pivot too far from the flesh to swing about (more than four times its length) is ignored, with a warning.
meat0 to 2, default 0.5: how heavy and fleshy it feels. More of it damps the swing sooner, sags more, wobbles less after a press, and lets a released dent fade over a longer time. 0 is the bouncier feel of a balloon.
steprise and sway: the bounce and the side to side of a walk, blocks.
dressedstiff, limit, couple: dressed flesh is firmer, travels less, a pair moves together, and it does not swell past where it is modelled.
collideBones, ignoreBonesThe allow and deny lists, as on a spring bone: "collideBones": ["torso", "armL"] (only these of the model's own bones are felt, whether as body or as pressing) or "ignoreBones": ["head", "hair*"] (all but these). Names may start or end with *. The flesh's own bones are never felt.
collidesWith, pressesLayers it is pushed by (default body, other, players, mobs, world: blocks dent it too, a wall against a pouch), and the model's own bones that press into it rather than being body it rests on (default arm*, lowerArm*, forearm*, hand*, and steve* for a partner).
weight0 to 1, how much of the motion shows.

The grouped keys may also be written flat ("lumpDamping": 0.25). The keys of the older per-vertex flesh (stiffness, bend, damping, inertia, gravity, radius, friction, iterations, maxParticles, pinHeight, mesh) are ignored with one warning. A bone that is both a spring bone and flesh is flesh; the file warns.

From code. controls.softness(1.6) (a character's own softness), controls.dressed(true), and controls.weight("chest", w) for one region by its name.

One object. Separate pieces of one flesh (a tip modelled as its own piece, merged into the pouch mesh) are moved by the same cage as the piece they sit on, so they swing and dent together. A piece inside another's bounds is part of it; only pieces side by side become separate parts.

Cubes. A bone of cubes works as a bone of meshes does: when the flesh is made, the cubes of the bone and of what is under it are cut into meshes (each face into a grid of quads about a model unit and a quarter across, at most six along an edge) that look as the cubes did, and the cage moves those. The change is made once for the model and the model's GPU copy is made again from it. A flat cube (a plane) is left as it is. The cuts are what bend, so a cube much smaller than a unit stays a box that moves with the cage, and a large one bends smoothly.

One mesh, several parts. A mesh that merges separate parts (two pouches in one mesh) works: the parts are told apart by which vertices share a face, and each swings, dents and keeps its volume on its own.

Cost. A step costs the same whatever the mesh (the cage's nodes); the cage's few hundred numbers go to the GPU once a frame, and the vertex shader works each vertex's offset and shading out from them, so a mesh of any size costs the CPU what a region does. Where no GPU renderer reads them, the vertices are worked out on the CPU as before. A region that has been still for half a second rests: it keeps the shape it settled into and costs almost nothing until the body accelerates, turns or walks, something that presses comes within reach, or the body bends under it. In a crowd, the flesh nearest the camera (and the player's own) is worked out every frame, the rest takes turns within the frame's budget, and what it feels falls off with distance: within 8 blocks everything (other entities, blocks, its own body), to 12 blocks its own body, past that only the swing of the whole. Flesh has its own range, 24 blocks by default (fleshRange in the options file, or the options screen): farther away it rests and is drawn as modelled.

Limits. Meshes bound to an armature are drawn as modelled. A mesh turned on its own pivot is followed like any other. There is no flesh-against-cloth collision.

Ragdoll

{ "ragdoll": { "bones": ["upperBody", "torso", "head", "armL", "lowerArmL"], "parents": { "legL": "upperBody" } } }

One joint per named bone; keys radius, gravity, damping, friction, minJointAngle, tips. Blocks hold it up, its limbs keep their length and its joints do not fold through themselves. From code, below.

From code (client side)

StrutPhysics.of(creature).firmness(0.7f);                 // whole model: softer below 1, firmer above
StrutPhysics.of(creature).weight("pouchL", 0f);            // hold one part still; 1 releases it
StrutPhysics.of(creature).impulse("belly", 0, -2, 0);     // a kick, blocks per second
StrutPhysics.of(creature).wind(1f, 0f, 0f);               // a steady push
StrutPhysics.of(creature).enabled(false);                 // exactly the animation
StrutPhysics.of(creature).ragdoll(0f);                    // let the body go; 0 limp, 1 exactly the animation
StrutPhysics.of(creature).ragdollRecover(1.2f);           // the muscle returns, the body stands
StrutPhysics.of(creature).ragdollHit("torso", x, y, z);   // a shove
StrutPhysics.of(creature).ragdollOff();                   // fade out at once

An animation clip can kick a part on every viewer: "strut_physics:impulse": { "bone": "belly", "impulse": [0, -2, 0] }.

Authoring in Blockbench

Choose the Physics mode. Pick a bone in the panel's own list and make it a Spring, a Chain, or, if it has a mesh, Cloth or Flesh. A look sets sensible numbers; sliders refine them. Simulate moves the real model through a scripted motion or one of its animations with the same physics as the game (a TypeScript twin of the solvers, held to the Java ones by a recorded test), and draws colliders and the simulated cloth and flesh over it. Test shapes (ball, block, pole, wall) exist only on that screen. Collision shapes tags each bone's made shape and lets you fit your own with sliders. Everything is undoable. Tools, Physics as JSON (advanced) edits the text.

For cloth and flesh the preview hides the original mesh while it runs and draws the simulated one in a single plain colour, without its texture. A flesh is a cage fitted to the bone's mesh: the tab has a look for each preset and a slider for each of its main keys (softness, firmness, meat, reach, lag, settling, volume, swell), and the preview shows its swing, its lag and its volume, a pair pressing on each other, and a tip riding on it. What presses on it in the game (other models, blocks, the body) is not drawn. Its soft zone is set with a radius and a centre, or Zone from the mesh. The plugin's flesh is held to the game's by a recorded run (src/test/resources/golden/flesh.json), as the springs and chains are.

Options, quality and cost

Options live in config/strut-physics.json, a plain screen (the tool's Settings button) and, when Cloth Config is installed, a Cloth Config screen through Mod Menu. The master switch, collision with blocks, spring bones against blocks, cloth, flesh, other models, players, the range (models farther from the camera are drawn as animated), the flesh range and the quality.

LowConstraints relaxed half as often, fewer steps to catch up (3), at most 256 block shapes near a part, range scaled to 0.6.
MediumThe default.
HighHalf as many relaxation passes again, up to 1,024 block shapes.

Whatever the level, a per-frame budget (1.5, 3 and 6 ms) keeps a crowd from taking the frame: when the instances are more than the budget pays for, each takes its turn every few frames (at most every six; the turns are spread over the frames) and shows its last result in between, which costs nothing, and the F9 line says how many are waiting and how often they get their turn. One instance, or a few, never wait. A thrown ragdoll reads the blocks around itself in a volume it keeps until its joints leave it, and every solver tests only the shapes near its particles.

Commands

/strutphysics : on|off, debug on|off, info, impulse <bone> x y z, distance <blocks>, quality low|medium|high, collide others|players|springs|cloth <true|false>, ragdoll limp|weak|stand|off, reset, tool, test <scenario>.

Testing it in game

/strutphysics tool frees the cursor: left-drag a yellow dot to hold a chain particle, a spring mass, a cloth particle or a ragdoll joint; right-drag an entity to slide it (shift lifts it); scenario buttons load a ready-made setup for Strut's own test model (strut:strut/models/entity/test_dummy) and say what to do and what you should see. They are for developing Strut itself: they need an entity drawn with that model. The scenarios are springs, chains, colliders, world, bodies, springworld, selfcollide, ragdoll, cloth (a dress is put on the dummy), and flesh (two jelly balls). Every physics system ships one, and a test enforces it.

Water and lava

Chains, cloth, the ragdoll and spring bones react to the game's own liquids. A part that is in water floats a little (gravity is more than cancelled, so hair drifts up), is slowed, and is carried by a current; lava does the same more heavily. The liquid is read once a frame as the column of it at the entity, so a part reaching sideways out of that column is judged by the column's surface. It needs no setup in a model's physics file. It has its own switch: /strutphysics water <on|off>, the options screen, or the water key of the options file.

Known limits