Inverse kinematics
Meep provides three IK solvers — FABRIK for arbitrary chains, two-bone IK for limbs with a pole target, and one-bone surface alignment for foot placement on terrain.
Meep’s IK systems live in two places:
engine/physics/inverse_kinematics/— the solver algorithms, written as plain functions againstTransform/Vector3/Quaternion.engine/ecs/ik/— the ECS layer that binds those solvers to entities, skeletons, and terrain raycasts.
ECS components and system
An entity that needs IK adds an InverseKinematics component. The component
holds an array of IKConstraint objects — one per limb or surface-aligned
bone. InverseKinematicsSystem runs every frame after rendering, reads the
skeleton from the co-located Mesh component, and dispatches each constraint
to the appropriate solver.
import { InverseKinematics } from
"@woosh/meep-engine/src/engine/ecs/ik/InverseKinematics.js";
import { InverseKinematicsSystem } from
"@woosh/meep-engine/src/engine/ecs/ik/InverseKinematicsSystem.js";
em.addSystem(new InverseKinematicsSystem());
const ik = new InverseKinematics();
ik.add({
effector: "foot-left", // HumanoidBoneType string
solver: "2BIK", // "2BIK" or "1BSA"
offset: 0.05, // metres above contact point
distanceMin: 0,
distanceMax: 0.3, // influence fade-out range
strength: 1,
limit: Math.PI * 0.9 // max rotation delta in radians
});
IKConstraint fields
| Field | Type | Meaning |
|---|---|---|
effector | string | HumanoidBoneType value identifying the end bone |
solver | string | Solver key: "2BIK" or "1BSA" |
offset | number | Distance offset along the contact normal (positive = above surface) |
distance | NumericInterval | [distanceMin, distanceMax] — effector distances mapped to [full, no] influence |
strength | number | Overall influence multiplier [0, 1] |
limit | number | Maximum rotation angle (radians) before the constraint is clamped or skipped |
InverseKinematics.add({…}) is the factory method; it creates an IKConstraint
and pushes it onto constraints.
How the system dispatches
Each frame InverseKinematicsSystem.update(dt):
- Retrieves the terrain from the scene.
- Traverses all entities with both
InverseKinematicsandMesh. - Skips entities whose mesh has
MeshFlags.InViewcleared (not visible). - Creates an
IKProblem(pooled) per constraint and queues it under the solver key. - After traversal, calls
solver.solve(problem)for every queued problem.
The two registered solvers are keyed "2BIK" (TwoBoneInverseKinematicsSolver)
and "1BSA" (OneBoneSurfaceAlignmentSolver).
FABRIK — arbitrary chains
fabrik_solve in engine/physics/inverse_kinematics/fabrik/fabrik_solve.js
implements FABRIK (Forward And Backward Reaching Inverse Kinematics) for
chains of arbitrary length.
import { fabrik_solve } from
"@woosh/meep-engine/src/engine/physics/inverse_kinematics/fabrik/fabrik_solve.js";
fabrik_solve(
joints, // Transform[] — updated in place
lengths, // number[] — distance to the next joint
origin, // Vector3 — where the root must stay
target, // Vector3 — where the tip should reach
4, // max_iterations (default)
1e-7 // distance_tolerance squared (default)
);
Parameters
| Parameter | Type | Notes |
|---|---|---|
joints | Transform[] | Chain joints, root first. Positions and rotations are written. |
lengths | number[] | lengths[i] is the distance from joint i to joint i+1 |
origin | Vector3 | Root anchor — the first joint is pinned here |
target | Vector3 | Goal position for the tip joint |
max_iterations | number | Default 4 — solver exits early if tolerance is met |
distance_tolerance | number | Squared distance threshold for early exit; default 1e-7 |
The underlying primitive fabrik3d_solve_primitive works entirely on a flat
Float32Array of packed XYZ positions to avoid allocation in the inner loop.
Chains up to 64 joints use a pre-allocated scratch buffer; longer chains
allocate a temporary array.
When the target is unreachable (distance to target exceeds total chain length), the primitive stretches the chain in a straight line toward the target rather than iterating.
After the position pass, fabrik_solve computes the rotation delta for each
joint by comparing the before-and-after bone direction using
Quaternion.fromUnitVectors and applies it with multiplyQuaternions.
Two-bone IK
TwoBoneInverseKinematicsSolver (solver key "2BIK") handles limbs — the
canonical three-bone chain: upper-arm/thigh (A), forearm/shin (B), hand/foot
(C). It uses a terrain raycast to find the contact point, then calls
two_joint_ik to compute the local-space rotation deltas.
Algorithm
two_joint_ik in engine/physics/inverse_kinematics/two_joint_ik.js is
based on the analytic two-joint solution from Danny Green’s
“Simple Two-Joint IK”.
import { two_joint_ik } from
"@woosh/meep-engine/src/engine/physics/inverse_kinematics/two_joint_ik.js";
two_joint_ik(
a, b, c, // Vector3 world positions of root, mid, effector
t, // Vector3 target position
0.01, // epsilon for rounding compensation
a_gr, b_gr, // Quaternion global rotations of root and mid bone
a_lr, b_lr // Quaternion local rotations — updated in place
);
It computes the required interior angles using the cosine rule, then applies angular deltas via axis-angle rotations constructed from the cross product of current and target bone directions.
Terrain contact and influence
The ECS solver raycasts the terrain twice:
- From boneA toward boneC to find the surface intersection.
- From boneC back along the contact normal to refine the contact point on uneven geometry.
If the effector is already above the surface by more than constraint.distance.max
(normalised against limb length), influence is 0 and no rotation is applied.
If the effector is below the surface (penetration), influence is 1. Between
those extremes, influence fades linearly with the hover distance. The final
rotations are lerped between the current pose and the IK target pose by
influence × strength.
One-bone surface alignment
OneBoneSurfaceAlignmentSolver (solver key "1BSA") aligns a single bone’s
up-axis to the terrain normal — the primary use case is a foot bone conforming
to a sloped surface.
How it works
- Raycasts the terrain along the parent-to-child bone direction to find the contact point and normal.
- Raycasts again from the bone back along the normal to get the precise surface point.
- Computes an axis-angle rotation that rotates the bone’s local forward vector to align with the contact normal.
- Clamps the rotation against
constraint.limit(angle to the parent’s quaternion). If the required rotation exceeds the limit, influence is reduced to the limit boundary. - Lerps the bone’s current rotation toward the target by
strength × influenceand writes it back.
The offset field displaces the target position along the contact normal
before alignment, letting you keep the bone’s pivot a fixed distance above
the surface (e.g. a foot pivoting above the ground plane).
Related
- Skeletons & skinning —
HumanoidBoneType,BoneMapping, andfindSkeletonBoneByTypewhich IK solvers depend on - Animation graphs — clip-driven animation that IK post-processes
- Source:
engine/physics/inverse_kinematics/,engine/ecs/ik/