How to Create a Dynamic Capsule Collision Shape in Ammo.js

This article explains how to construct a dynamic capsule collision shape for a character in ammo.js, the JavaScript port of the Bullet physics engine. You will learn how to define the capsule geometry, compute its inertia, lock its rotation for upright character movement, and integrate it into your physics world.

Step 1: Define the Capsule Geometry

In ammo.js, a capsule is defined by its radius and the height of its middle cylindrical section. Note that the total height of the capsule is the cylinder height plus twice the radius (for the two hemispherical caps).

// Define dimensions
const radius = 0.5; // 1 meter total diameter
const totalHeight = 1.8; // 1.8 meters tall
const cylinderHeight = totalHeight - (radius * 2); // 0.8 meters

// Create the shape (Y-aligned by default)
const capsuleShape = new Ammo.btCapsuleShape(radius, cylinderHeight);

Step 2: Set Up the Motion State and Transform

The motion state synchronizes the physics body’s position and rotation with your rendering engine (such as Three.js). You must define a starting transform for the capsule.

const startTransform = new Ammo.btTransform();
startTransform.setIdentity();

// Set starting position (X, Y, Z)
const startPosition = new Ammo.btVector3(0, 5, 0);
startTransform.setOrigin(startPosition);

// Create the motion state
const motionState = new Ammo.btDefaultMotionState(startTransform);

Step 3: Calculate Local Inertia

Because a character is a dynamic object that reacts to gravity and collisions, it must have a mass greater than zero. You must calculate the local inertia tensor based on this mass so the physics engine can simulate realistic movement.

const mass = 75; // Mass in kilograms
const localInertia = new Ammo.btVector3(0, 0, 0);
capsuleShape.calculateLocalInertia(mass, localInertia);

Step 4: Build and Configure the Rigid Body

To prevent a character capsule from tipping over when it collides with obstacles, you must lock its angular rotation. Setting the angular factor to zero on all axes keeps the character perfectly upright.

// Create construction info
const rbInfo = new Ammo.btRigidBodyConstructionInfo(
    mass, 
    motionState, 
    capsuleShape, 
    localInertia
);

// Instantiate the rigid body
const body = new Ammo.btRigidBody(rbInfo);

// Lock rotation so the capsule stays upright
const angularFactor = new Ammo.btVector3(0, 0, 0);
body.setAngularFactor(angularFactor);

// Set friction and restitution (bounciness)
body.setFriction(0.5);
body.setRestitution(0.0);

Step 5: Add to the Physics World and Clean Up

Finally, add the newly constructed rigid body to your active btDiscreteDynamicsWorld instance. Be sure to clean up temporary WebAssembly vector objects from memory to prevent leaks.

// Add the body to your physics world
physicsWorld.addRigidBody(body);

// Clean up temporary Ammo.js allocations
Ammo.destroy(startTransform);
Ammo.destroy(startPosition);
Ammo.destroy(localInertia);
Ammo.destroy(angularFactor);