Physics

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.

KindMoves?Use for
StaticNever. Infinite mass; stored as immovable level geometry.Level geometry, ground, walls.
DynamicIntegrated under gravity, forces, and contact impulses.Gameplay objects - the common case.
KinematicVelocityDriven by its velocity; pushes dynamic bodies but isn’t pushed back.Moving platforms, elevators.
KinematicPositionDriven 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.020.05) helps piles settle instead of jittering.
  • gravityScale - multiplier on world gravity. 1 is normal, 0 floats, negative inverts. World gravity defaults to (0, -9.81, 0); change it with physics.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