Inverse kinematics
Entity-target IK goals with joint limits, FABRIK chains, and two-joint limb solvers.
meep’s IK lives in two places:
engine/physics/inverse_kinematics/- the solver algorithms, written as plain functions againstTransform64/Vector3/Quaternion.engine/ecs/ik/- the ECS layer that binds those solvers to entities, to the transform hierarchy, and to entity targets.
The algorithms are pure maths. In the ECS layer a bone is an entity, not an object inside a renderer’s skeleton. The goal system evaluates world poses from the hierarchy and live animation clips, then writes local corrections back.
Entity-target goals
IKGoal places an effector bone at a target entity’s world pose through the TransformAttachment hierarchy. Add the component to the effector and register IKGoalSystem; the chain root rotates but stays in place. Physics Joint components on the chain supply joint limits. This path does not require a terrain.
import { IKGoal } from "@woosh/meep-engine/src/engine/ecs/ik/IKGoal.js";
import { IKGoalSystem } from "@woosh/meep-engine/src/engine/ecs/ik/IKGoalSystem.js";
await em.addSystem(new IKGoalSystem());
ecd.addComponentToEntity(effectorEntity, IKGoal.from({
target: targetEntity,
chain_root: rootBoneEntity,
position_weight: 1,
rotation_weight: 0,
}));
All four fields passed to IKGoal.from are required. Both weights range from 0 to 1: zero position weight restores the pose without the goal, while rotation weight blends the effector’s chain rotation towards the target’s rotation. IKGoalSystem depends on [IKGoal, TransformAttachment]; max_iterations defaults to 16 and tolerance to 1e-6 world units.
Target and chain-root entity references stay fixed while the component is attached. Remove and re-add it to change them. Destroying the target or a bone in the chain removes the goal. IKGoal.serializable is false; recreate goals after spawning the model’s bones.
Physics contact targets
ContactProbe and ContactProbeSystem under src/engine/physics/ecs/ place a target entity through its TransformAttachment. A probe casts from a reference entity’s world pose plus a world-axis offset. radius: 0 uses a ray; a positive radius uses a sphere cast. Its unit-length direction, positive distance and non-negative radius define the query. A hit places the target on the surface and aligns its up axis with the contact normal; a miss places it at the reference pose. The hit field reports the last result.
Register ContactProbeSystem before IKGoalSystem so contact targets are placed before IK consumes them. A probe reference can be an animated bone, but must not be a bone held by a CPU writer such as IK or cloth: that would feed the previous correction back into the target. Probes skip the character’s own colliders by following ParentEntity lifetime roots. ContactProbe.serializable is false.
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. Use this low-level helper for chains you drive yourself;
IKGoalSystem owns its own goal-solving path.
import { fabrik_solve } from
"@woosh/meep-engine/src/engine/physics/inverse_kinematics/fabrik/fabrik_solve.js";
fabrik_solve(
joints, // Transform64[] - 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 (default)
);
Parameters
| Parameter | Type | Notes |
|---|---|---|
joints | Transform64[] | Chain joints, root first. Translations, rotations and matrices are written - the announcement is not: call t64_announce_change(ecd, entity) per joint afterwards, or the meshes built on those transforms stay where they were |
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 - the solver exits early once the 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 - the distance from origin to target exceeds the summed link lengths - the primitive stretches the chain in a straight line toward the target instead of iterating.
After the position pass, fabrik_solve computes each joint’s rotation delta by
comparing the before-and-after bone direction with Quaternion.fromUnitVectors
and applies it with multiplyQuaternions.
Two-bone IK
The low-level two-joint helper turns a three-joint limb towards a target.
Algorithm
two_joint_ik in engine/physics/inverse_kinematics/two_joint_ik.js is based on
the analytic two-joint solution described in
“Simple Two-Joint IK”, which
the source cites.
import { two_joint_ik } from
"@woosh/meep-engine/src/engine/physics/inverse_kinematics/two_joint_ik.js";
import Vector3 from "@woosh/meep-engine/src/core/geom/Vector3.js";
two_joint_ik(
a, b, c, // Vector3 world positions of root, mid, effector
t, // Vector3 target position
new Vector3(0, 0, 1), // unit hinge axis in the mid joint's local frame
a_gr, b_gr, // Quaternion global rotations of root and mid bone
a_lr, b_lr // Quaternion local rotations - updated in place
);
The hinge axis must be normalized. The solver transforms it by the mid joint’s global rotation, preserves each bone’s component along that hinge, clamps the target to the resulting reachable interval, and writes local rotation deltas.
Related
- Skeletons & skinning - how a bone is addressed, and the GPU skinning path IK writes into
- Animation graphs - clip-driven animation that IK post-processes
- Hierarchy -
TransformAttachment, and why a solver writes local transforms - Physics queries - casts used to produce contact targets
- Source:
engine/physics/inverse_kinematics/,engine/ecs/ik/