Skip to content

Rigid bodies & the physics world

vgai uses Rapier for physics — directly, with no wrapper. You reach the world through ctx.rapierWorld and create bodies/colliders with the Rapier API.

Physics setup (packages/engine/src/setup/setup-physics.ts) creates a RAPIER.World with a default gravity of { x: 0, y: -9.81, z: 0 } and a RAPIER.EventQueue(true) for collision events. A debug LineSegments mesh is created for the collider-wireframe view.

A physics body is declared on an entity via the physics schema (packages/engine/src/scene/schema/physics.ts). The bodyType is one of:

Body typeBehavior
dynamicFully simulated — gravity, forces, collisions move it.
fixedImmovable — static geometry (ground, walls).
kinematicScript-driven — you set its transform; it pushes dynamic bodies but isn’t pushed.

mass is optional: if omitted, Rapier computes it from the collider’s density and volume. The collider field attaches a collider shape.

prePhysics writes forces, physics steps the world, postPhysics drains events and writes transforms
Physics spans three phases each tick.
  1. prePhysics — your code writes forces/velocities onto rigid bodies.
  2. physicsrapierWorld.step(eventQueue) advances the simulation.
  3. postPhysics — collision events are drained, then the transform writer copies each body’s transform back onto its Object3D.

The transform writer (packages/engine/src/physics/transform-writer.ts) runs as a single postPhysics system. Rapier reports world-space transforms; for entities parented under something other than the scene root, it converts world → parent-local using the inverse parent matrix. Scale is left untouched (Rapier carries no scale).

vgai keeps a bidirectional map between Three.js objects and Rapier handles — the PhysicsRegistry (packages/engine/src/physics/physics-registry.ts):

  • add(object3D, body, collider) — register an entity’s physics.
  • get(object3D) — the { body, collider } for an entity.
  • getByColliderHandle(handle) — the reverse lookup, used to resolve a collision back to its entity.
  • remove(object3D), entries(), clear().

The reverse colliderHandle → Object3D index is what lets collisions and triggers name the entities involved.