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 | Driven by direct pose writes; velocity is derived from the per-step delta. | Animation- or code-driven movers. |
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.
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.