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 Transform (its pose), a RigidBody (its dynamic state), and a Collider (its shape — covered in Colliders & shapes).
The split matters. Pose lives on the Transform — 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 Transform())
.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:
// Solid box, half-extents (hx, hy, hz):
const k = body.mass / 3;
body.inverseInertiaLocal.set(
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 (inverseInertiaLocal.set(0, y, 0)).
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, { x: 0, y: 40, z: 0 }); // continuous, this step
physics.applyImpulse(body, { x: 5, y: 0, z: 0 }); // instantaneous Δmomentum
physics.applyImpulseAt(body, transform, impulse, point); // off-centre → adds spin
physics.applyTorque(body, { x: 0, y: 2, z: 0 });
physics.setLinearVelocity(body, { x: 0, y: 0, z: 0 });
Forces and torques accumulate between fixed steps and are consumed by the integrator each tick.
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.