Reference Updated July 2026

Cannon-es API Quick Reference

The core classes of cannon-es — the maintained fork of Cannon.js — for building rigid-body physics in the browser: World, Body, Shape, Material, Constraint, collision filtering, contact events, and the exact pattern for syncing bodies to Three.js meshes every frame.

🌍 World

CANNON.World is the top-level container: gravity, broadphase, solver, and the list of bodies/constraints/contact materials all live on it. Call world.step(dt) once per animation frame to advance the simulation.

Property / Method Type Purpose
gravity Vec3 Constant acceleration applied to all dynamic bodies
broadphase NaiveBroadphase | SAPBroadphase Pair culling strategy before narrow phase
solver.iterations number PGS solver iterations per step (default 10)
defaultContactMaterial ContactMaterial Friction/restitution used when no pair-specific material set
world.addBody(body) method Register a Body with the world
world.addConstraint(c) method Register a Constraint (joint)
world.step(dt, time, maxSubSteps) method Advance simulation by dt seconds (fixed timestep + interpolation)
import * as CANNON from 'cannon-es';

const world = new CANNON.World({
  gravity: new CANNON.Vec3(0, -9.82, 0),
});
world.broadphase = new CANNON.SAPBroadphase(world);
world.solver.iterations = 10;

// Fixed-timestep step call in the render loop
const fixedTimeStep = 1 / 60;
function animate() {
  requestAnimationFrame(animate);
  world.step(fixedTimeStep);
  renderer.render(scene, camera);
}

📦 Body

CANNON.Body represents a single rigid body: mass, position, quaternion (orientation), velocity, angular velocity, and one or more attached shapes. A body with mass: 0 is static — infinite mass, never moves, but participates in collisions.

Property Type Notes
mass number 0 = static, >0 = dynamic
position Vec3 World-space center of mass
quaternion Quaternion Orientation — copy directly into mesh.quaternion
velocity / angularVelocity Vec3 Linear / angular velocity in world space
type Body.DYNAMIC | STATIC | KINEMATIC KINEMATIC bodies are moved by script, unaffected by forces
linearDamping / angularDamping number 0–1 Per-step velocity decay (air resistance approximation)
fixedRotation boolean Locks orientation — useful for characters/props that shouldn't tip over
allowSleep / sleepSpeedLimit boolean / number Bodies at rest go to sleep — skipped by the solver until disturbed
ccdSpeedThreshold number Enables continuous collision detection above this speed (prevents tunneling)
const body = new CANNON.Body({
  mass: 1,
  position: new CANNON.Vec3(0, 5, 0),
  shape: new CANNON.Sphere(0.5),
  linearDamping: 0.01,
});
body.applyForce(new CANNON.Vec3(0, 0, 50), body.position);
body.applyImpulse(new CANNON.Vec3(5, 0, 0), body.position);
world.addBody(body);

🔷 Shapes

A Body can carry multiple shapes (compound bodies), each with an optional local offset and orientation via body.addShape(shape, offset, quaternion).

Shape Constructor Use case
Box new Box(new Vec3(hx,hy,hz)) Half-extents — crates, walls, platforms
Sphere new Sphere(radius) Cheapest narrow-phase test — balls, particles
Cylinder new Cylinder(rTop, rBottom, h, segments) Barrels, wheels (pair with HingeConstraint)
ConvexPolyhedron new ConvexPolyhedron({vertices, faces}) Custom convex meshes — import from a Three.js BufferGeometry
Trimesh new Trimesh(vertices, indices) Static concave geometry only — terrain, level meshes
Heightfield new Heightfield(matrix, {elementSize}) Grid-based terrain — cheaper than Trimesh for landscapes
Plane new Plane() Infinite static plane — ground, invisible walls
Particle new Particle() Zero-size point mass — cloth/rope nodes
Concave shapes are static-only

Trimesh and Heightfield can only be attached to mass: 0 bodies — cannon-es' SAT-based narrow phase requires convexity for dynamic bodies. For a dynamic concave object, decompose it into multiple convex shapes on one compound body (e.g. with a convex-hull decomposition tool).

🧪 Materials & ContactMaterial

A Material is just a named tag on a shape/body; the actual friction and restitution numbers live on a ContactMaterial that pairs two materials together.

const ice = new CANNON.Material('ice');
const rubber = new CANNON.Material('rubber');

const iceRubberContact = new CANNON.ContactMaterial(ice, rubber, {
  friction: 0.02,      // low — slides freely
  restitution: 0.3,    // 0 = no bounce, 1 = perfectly elastic
  contactEquationStiffness: 1e8,
  contactEquationRelaxation: 3,
});
world.addContactMaterial(iceRubberContact);

const puck = new CANNON.Body({ mass: 1, material: rubber, shape: new CANNON.Cylinder(0.4,0.4,0.1,16) });
const rink = new CANNON.Body({ mass: 0, material: ice, shape: new CANNON.Plane() });

🔗 Constraints

Constraints (joints) restrict the relative motion of two bodies. All constraints are added to the world with world.addConstraint(c).

Constraint Behaviour
PointToPointConstraint Pins a local point on A to a local point on B — ball-and-socket, ropes
HingeConstraint One rotational DOF around a shared axis — doors, wheels, pendulum arms
DistanceConstraint Fixed distance between two bodies — rigid rope segments
LockConstraint Fully welds two bodies together (no relative motion)
ConeTwistConstraint Ball joint with a cone angle limit — ragdoll shoulders/hips
// Pendulum: hinge a box to a fixed anchor point
const anchor = new CANNON.Body({ mass: 0 });
anchor.position.set(0, 5, 0);
world.addBody(anchor);

const hinge = new CANNON.HingeConstraint(anchor, bobBody, {
  pivotA: new CANNON.Vec3(0, 0, 0),
  pivotB: new CANNON.Vec3(0, 1.5, 0),
  axisA:  new CANNON.Vec3(0, 0, 1),
  axisB:  new CANNON.Vec3(0, 0, 1),
});
world.addConstraint(hinge);

🎯 Collision Groups & Masks

Every shape carries a collisionFilterGroup (which group it belongs to) and collisionFilterMask (which groups it should test against) — both bitmasks. Two shapes collide only if (A.group & B.mask) and (B.group & A.mask) are both non-zero.

const GROUP_PLAYER  = 1;   // 0b0001
const GROUP_ENEMY   = 2;   // 0b0010
const GROUP_TERRAIN = 4;   // 0b0100
const GROUP_TRIGGER = 8;   // 0b1000 — no physical response, events only

playerBody.collisionFilterGroup = GROUP_PLAYER;
playerBody.collisionFilterMask  = GROUP_ENEMY | GROUP_TERRAIN | GROUP_TRIGGER;

// Enemies collide with terrain but pass through each other
enemyBody.collisionFilterGroup = GROUP_ENEMY;
enemyBody.collisionFilterMask  = GROUP_PLAYER | GROUP_TERRAIN;

📡 Events

Event Fired on Payload
'collide' body { body, target, contact }
'sleep' / 'wakeup' body Body entered/left the sleeping state
'postStep' world Fired after every world.step() — good place to read updated positions
playerBody.addEventListener('collide', (e) => {
  const impactSpeed = e.contact.getImpactVelocityAlongNormal();
  if (impactSpeed > 5) playLandingSound(impactSpeed);
});

⚙️ Cannon-es ↔ Three.js Sync

Cannon-es has no rendering concept of its own — every frame, copy each body's position and quaternion directly into the matching Three.js mesh. Both libraries use the same axis convention and quaternion layout, so no conversion is needed.

const bodies = []; // { body: CANNON.Body, mesh: THREE.Mesh }

function addBox(size, mass, position) {
  const shape = new CANNON.Box(new CANNON.Vec3(size.x/2, size.y/2, size.z/2));
  const body  = new CANNON.Body({ mass, shape, position: new CANNON.Vec3(...position) });
  world.addBody(body);

  const mesh = new THREE.Mesh(
    new THREE.BoxGeometry(size.x, size.y, size.z),
    new THREE.MeshStandardMaterial({ color: 0x60a5fa }),
  );
  scene.add(mesh);

  bodies.push({ body, mesh });
  return body;
}

function animate() {
  requestAnimationFrame(animate);
  world.step(1/60);

  // Sync every mesh to its body's physics state
  for (const { body, mesh } of bodies) {
    mesh.position.copy(body.position);
    mesh.quaternion.copy(body.quaternion);
  }

  renderer.render(scene, camera);
}
Decouple physics step from render rate

world.step(dt, deltaTime, maxSubSteps) accepts the actual elapsed time as its second argument and will run extra fixed sub-steps internally to catch up — call it as world.step(1/60, realDeltaSeconds, 5) so physics stays deterministic even if the display refresh rate varies (60Hz vs 144Hz vs a stalled tab).

⚠️ Common Pitfalls