Jiggle Transforms

Jiggle transforms add secondary motion to any Transform hierarchy: hair, tails, capes, antennae, foliage, chains and bones on a rig. You pick a root Transform and the components below it follow with springy, damped motion, driven by Verlet integration. There is a 3D form and a 2D form that run the same simulation, and an optional Entities form for DOTS projects.

Components #

  • VerletTransforms / VerletTransforms2D hold a list of trees and update them in LateUpdate, after animation.
  • VerletTransformTree / VerletTransformTree2D are one jiggle hierarchy: a root Transform, a settings asset and an ignoreList. The 2D tree derives from the 3D one and runs the same simulation in the XY plane.
  • VerletTransformTreeSettings is a ScriptableObject holding the curves and values that shape the motion. Create it from the Assets, Create menu and share it between trees.
  • VerletTransformNode is one bone: its Transform and simulation state. Only target is serialised, the rest is rebuilt when the tree registers.

Setting up #

  1. Put the bones in their rest pose.
  2. Add VerletTransforms (or VerletTransforms2D) to a GameObject, usually the rig or a parent of the chain.
  3. Add a tree to its list, set root to the first bone, and assign a VerletTransformTreeSettings asset.
  4. Press Play. Bones below the root now jiggle.

If you change the hierarchy at runtime, call RegisterTrees() again so the new rest pose is read.

How it behaves #

  • Rest pose is captured on registration. The trees read the hierarchy at Start. Bones move around that pose, so set it before the scene starts.
  • The root is not simulated. It follows its own animation and the bones below it are driven entirely by the simulation, so a tree should be the only thing animating those bones.
  • Fixed time step. The simulation runs on a fixed clock of scaled game time, simulationRate steps per second (60 by default). The root is sampled as timestamped poses and the bones are interpolated between steps, so the motion is the same at any frame rate. Frame spikes, a slow start or losing focus do not disturb it, and a time scale of zero moves nothing.
  • Substeps. Each step is divided into substeps smaller steps (4 by default) that solve the spring, the length and the contact constraints. More substeps make the bones stiffer and the motion truer, and cost more. The result converges as the count grows.
  • Real units. Mass is in kilograms, elasticity is a spring stiffness in newtons per metre, acceleration is in metres per second squared and maxSpeed is in metres per second. A node of mass m held against gravity g by a spring of stiffness k sags by m times g over k. Gravity is an acceleration and does not depend on mass.
  • Contact is solved inside the step. Collision planes are found once per step and held last in every substep, for each node and for three sample points along each bone, so there is no jitter and no visible penetration. Collision only runs where the collision radius is above zero and collisionLayers is not empty.
  • Pre-warm. By default a tree simulates with its root held still until the motion settles, before the first frame, so chains do not start out wagging. See below.
  • One shared simulation. The 2D and 3D forms use the same core, so tuning carries across. 2D trees keep Z on the plane of the root and turn bones about Z, and collide with 2D physics.

Tree settings #

Most settings are curves read along the tree. A node’s weight is 0 at the root and 1 at the furthest node, and the curve is evaluated at that weight and multiplied by the curve’s scale. A scale of 0 turns that effect off.

SettingDefault scaleWhat it does
constantAcceleration(0, -9.81, 0)Acceleration on every node in metres per second squared, gravity by default. In 2D the Z component is ignored.
mass0.01The mass of a node in kilograms. A heavier node sags further against the same spring, answers the spring and the root more slowly, and takes a smaller share of every correction.
damping0.02How much motion is lost, 0 to 1, per sixtieth of a second. Applied per substep.
elasticity1The stiffness of the spring that pulls a node back to its rest position, in newtons per metre. Higher is springier.
stiffness0.01How much a node resists being stretched away from its rest position, 0 to 1.
collision0 (off)The collision radius of each node in world units. Zero means no collision at that point of the tree.
collisionLayersNothingThe layers the nodes collide with. Nothing also skips the collision cost.
angle0 (no limit)The most a node may swing away from its rest direction, in degrees.
maxSpeed100The fastest a node may move, in metres per second. Zero for no limit.
Simulation settingDefaultWhat it does
simulationRate60Steps per second, 10 to 480. The step is fixed whatever the frame rate. Higher is smoother and costs more.
substeps4Smaller steps inside each step, 1 to 16.
maxStepsPerFrame120A cost limit for very long frames, not part of the physics. A frame that needs more steps skips the earliest ones and carries the tree along with its root over the skipped time, as a teleport would.
maxMillisecondsPerFrame6A budget shared by all trees in Play Mode, in milliseconds per frame. When it runs out the simulation slows down instead of the frame rate collapsing. Zero for no limit.

Curves are ScaledAnimationCurve values: an animation curve with a scale multiplier, so you shape the curve once and scale the strength separately. Settings are cached per tree. Inspector edits and scale changes are picked up automatically, but if you reshape a curve by code while trees are running, call settings.MarkChanged().

Forces #

Both components and each tree take forces as accelerations in metres per second squared.

MemberWhat it does
AddForce(force)Adds a force to every node of every tree, applied on the next step. Use it every frame for wind.
AddForce(treeIndex, force)The same for one tree.
AddForceAtPosition(forceMagnitude, position, radius = 1)Pushes the nodes near a point away from it, strongest at the point and fading to nothing at the radius. Use it for impacts and explosions.
RegisterTrees()Reads the hierarchy of every tree again, taking the current pose as the rest pose.
ResetTrees()Puts every tree back at its rest pose and clears its motion.
GetTree(index)Returns a tree by index, or null with a warning when the index is out of range.

The 2D forms take float2 forces. While showGizmo is on the bones and collision radii are drawn in the scene view.

Teleporting #

Moving the root instantly, such as a respawn or a level transition, reads as a very fast movement and would whip the bones along behind it. Call Teleport() right after the move, on the component or on a tree, and the shape and motion are kept. Teleport(position, rotation) on a tree moves the root and teleports in one call, and the 2D tree takes Teleport(Vector2 position, float degrees).

Pre-warm #

prewarm (on by default) and prewarmSeconds (10 by default) on VerletTransforms and VerletTransforms2D make each tree settle at Start: it simulates with the root held still until it has been below a small speed for a moment, or until the time limit passes. WarmTrees() on the component, or Warm(maxSeconds, settleSpeed) on a tree, settles on demand. It is a simulate-until-calm pass, run in chunks, not a one-shot static solve, so a limit that is too short can leave a long chain still moving.

Ignoring parts of a hierarchy #

Add Transforms to a tree’s ignoreList to leave them, and everything under them, out of the simulation. Use it for props held in a hand or bones that must stay animated.

Entities #

When the Entities package (1.0.0 or later) is installed, the Verlet Motion Toolkit also compiles an Entities layer in the Heathen.Verlet.Entities assembly. Each chain is one entity with a VerletChain component and VerletNode, VerletBonePose and VerletBone buffers. A solve system and an apply system run in the simulation group before the transform systems, with the same fixed clock, substeps, contact and pre-warm as the managed trees. Bakers convert existing VerletTransforms and VerletTransforms2D components, VerletChainBuilder.Create builds a chain from code, and VerletFieldAuthoring (shared wind) and VerletGroundAuthoring (a ground plane at the object’s world Y) bake the shared settings. Ropes are not on Entities yet.

Samples #

The Demo Scene shows hair, grass and links on many bones, the Skinned Mesh Sample shows jiggle on a skinned character, and the 2D Example shows two chains in 2D, one swinging into a 2D collider. See the Overview for the import path.

Rate This Article!