Rigid bodies
The RigidBody component - body kinds, mass and inertia, forces and impulses, damping, and sleeping.
A rigid body is an entity carrying three components: a Transform64 (its pose), a RigidBody (its dynamic state), and a Collider (its shape - covered in Colliders & shapes).
The split matters. Pose lives on the Transform64 - position and rotation - and the PhysicsSystem writes it back every step, so the renderer reads the result for free. RigidBody owns everything that isn’t pose: velocity, mass, inertia, damping, gravity scale, and collision filtering.
import { RigidBody } from "@woosh/meep-engine/src/engine/physics/ecs/RigidBody.js";
import { BodyKind } from "@woosh/meep-engine/src/engine/physics/ecs/BodyKind.js";
const body = new RigidBody();
body.kind = BodyKind.Dynamic;
body.mass = 2;
new Entity()
.add(new Transform64())
.add(body)
.add(collider) // see Colliders & shapes
.build(ecd);
Body kinds
BodyKind decides how a body participates in the simulation.
| Kind | Moves? | Use for |
|---|---|---|
Static | Never. Infinite mass; stored as immovable level geometry. | Level geometry, ground, walls. |
Dynamic | Integrated under gravity, forces, and contact impulses. | Gameplay objects - the common case. |
KinematicVelocity | Driven by its velocity; pushes dynamic bodies but isn’t pushed back. | Moving platforms, elevators. |
KinematicPosition | setTargetPose derives the velocity needed to reach a target on the next fixed step. | Animation- or code-driven movers. |
Moving a position-driven body
Use physics.setTargetPose(body, position, rotation) to move a KinematicPosition body continuously. Position and rotation are indexed arrays, respectively [x, y, z] and [x, y, z, w]. The next fixed step derives linear and angular velocities from its dt, pushes and carries dynamic bodies along that motion, and finishes at the target pose.
physics.setTargetPose(platformBody, [5, 2, 0], [0, 0, 0, 1]);
Send a target every tick, including when standing still: the derived velocities remain on the body, so without another target it keeps moving at that rate. setTargetPose accepts only KinematicPosition bodies. Use setPose for teleports; direct Transform64 edits also do not derive motion velocity. The first-person controller uses target poses for movement and setPose for warps.
Mass and inertia
mass is in kilograms and must be positive for dynamic bodies. Static and kinematic bodies behave as if mass were infinite regardless.
Rotation is a separate matter. A body’s inverseInertiaLocal defaults to (0, 0, 0), which locks rotation - the body slides but never tumbles. This is deliberate: a missing inertia tensor gives you a locked body, never a NaN tumble. To let a body spin, set the inverse of its principal moments of inertia:
import { v3_set } from "@woosh/meep-engine/src/core/geom/vec3/v3_set.js";
// Solid box, half-extents (hx, hy, hz):
const k = body.mass / 3;
v3_set(body.inverseInertiaLocal, 0,
1 / (k * (hy*hy + hz*hz)),
1 / (k * (hx*hx + hz*hz)),
1 / (k * (hx*hx + hy*hy)),
);
A zero component locks that axis - handy for keeping a character capsule upright (v3_set(body.inverseInertiaLocal, 0, 0, y, 0)).
inverseInertiaLocal, linearVelocity, angularVelocity, accumulatedForce and accumulatedTorque are three-element Float64Arrays. Read them as [0], [1], [2] for x, y, z. Use v3_set(vector, 0, x, y, z) to write all three without allocating; these fields have no .x accessors or Vector3.set(x, y, z) method.
Velocity, damping, and gravity
linearVelocity/angularVelocity- world-space, m/s and rad/s. Read freely; set through the system (below) so sleeping bodies wake.linearDamping/angularDamping- per-second velocity decay. A little (0.02–0.05) helps piles settle instead of jittering.gravityScale- multiplier on world gravity.1is normal,0floats, negative inverts. World gravity defaults to(0, -9.81, 0); change it withphysics.setGravity([x, y, z]).
Forces and impulses
Don’t write the force accumulators directly - go through the PhysicsSystem, which also wakes a sleeping body for you:
physics.applyForce(body, [0, 40, 0]); // continuous, this step
physics.applyImpulse(body, [5, 0, 0]); // instantaneous Δmomentum
physics.applyImpulseAt(body, transform, impulse, point); // off-centre → adds spin
physics.applyTorque(body, [0, 2, 0]);
physics.setLinearVelocity(body, [0, 0, 0]);
physics.setAngularVelocity(body, [0, 3, 0]); // replace the spin
Forces and torques accumulate between fixed steps and are consumed by the integrator each tick.
Forces, impulses, torques, velocities and world points are read by index as [x, y, z]. setPose(body, position, rotation) takes an indexed position and quaternion [x, y, z, w]. Arrays, typed arrays, and the engine’s Vector3 / Quaternion classes all work; plain {x, y, z} objects do not. Reuse scratch arrays or a Transform64’s translation and rotation views in hot paths.
setPose updates the live transform matrix, announces TRANSFORM64_EVENT_CHANGE
and marks an interpolated body to snap instead of blending across the teleport.
It then sends TRANSFORM64_EVENT_PLACED; attachments forward that placement
intent to their children so motion consumers can reset their history.
Sleeping
A body that stays below a small velocity threshold for sleepTimeThreshold seconds (default 0.5) leaves the active set and stops costing solver time. Connected piles sleep together - a whole stack drops out and wakes as one.
physics.wake(body)/physics.sleep(body)- force the state.RigidBodyFlags.DisableSleep- keep a body always awake (e.g. one you drive externally).
import { RigidBodyFlags } from "@woosh/meep-engine/src/engine/physics/ecs/RigidBodyFlags.js";
body.setFlag(RigidBodyFlags.DisableSleep);
Where to go next
- Colliders & shapes - give the body a shape and a surface.
- Determinism - why the same forces produce the same result every time.