Create Complex Compound Collision Shapes in Ammo.js
Implementing complex collision detection in 3D web applications often requires shapes that standard primitives cannot represent. This article provides a step-by-step guide on how to successfully create and implement a complex compound collision shape in Ammo.js—the Emscripten port of the Bullet physics engine—by combining multiple simple shapes into a single rigid body for optimized performance and accurate physics simulation.
Why Use Compound Shapes?
In Ammo.js, dynamic rigid bodies cannot reliably use arbitrary
triangle meshes (btBvhTriangleMeshShape) for collisions due
to performance constraints and collision resolution limitations. To
simulate a complex dynamic object (such as a chair, a car, or an
L-shaped wall), you must use a btCompoundShape. This class
acts as a container that groups multiple primitive shapes (like boxes,
spheres, and cylinders) into a single physical entity, keeping
simulation performance high.
Step 1: Initialize the Compound Shape Container
To start, instantiate the compound shape container. This object will hold the child shapes and their relative offsets.
const compoundShape = new Ammo.btCompoundShape();Step 2: Define and Position Child Shapes
Each child shape added to the compound shape requires a local transform. This transform defines the child’s position and rotation relative to the center of the compound shape.
For every child shape you want to add: 1. Create the primitive shape.
2. Create a btTransform to define its relative position and
orientation. 3. Add the child shape to the compound shape.
// 1. Create a child shape (e.g., a box)
const boxShape = new Ammo.btBoxShape(new Ammo.btVector3(1, 0.5, 1));
// 2. Define the local transform for this child
const localTransform = new Ammo.btTransform();
localTransform.setIdentity();
// Offset the child 2 units up on the Y-axis
const position = new Ammo.btVector3(0, 2, 0);
localTransform.setOrigin(position);
// Rotate the child (optional)
const rotation = new Ammo.btQuaternion(0, 0, 0, 1); // Identity quaternion
localTransform.setRotation(rotation);
// 3. Add the child to the compound container
compoundShape.addChildShape(localTransform, boxShape);Repeat this process for as many child shapes as your complex geometry requires.
Step 3: Calculate Local Inertia
Ammo.js requires local inertia calculation for dynamic bodies. Calculate this based on the total mass and the final compound shape.
const mass = 10.0; // Mass in kg
const localInertia = new Ammo.btVector3(0, 0, 0);
compoundShape.calculateLocalInertia(mass, localInertia);Step 4: Construct and Add the Rigid Body
Once your compound shape is populated, construct the rigid body using a motion state and the construction info helper.
// Set the world transform (starting position of the entire compound object)
const startTransform = new Ammo.btTransform();
startTransform.setIdentity();
startTransform.setOrigin(new Ammo.btVector3(0, 5, 0)); // Start 5 units in the air
const motionState = new Ammo.btDefaultMotionState(startTransform);
// Create construction info
const rbInfo = new Ammo.btRigidBodyConstructionInfo(
mass,
motionState,
compoundShape,
localInertia
);
// Create the rigid body
const body = new Ammo.btRigidBody(rbInfo);
// Add the body to your physics world
physicsWorld.addRigidBody(body);Step 5: Memory Management
Because Ammo.js is written in C++ and compiled to
WebAssembly/JavaScript, you must manually manage memory for objects
created with the new keyword to avoid memory leaks.
Once your shapes and bodies are added to the world, free the temporary vector and transform objects that are no longer needed in your JavaScript scope:
Ammo.destroy(position);
Ammo.destroy(rotation);
Ammo.destroy(localTransform);
Ammo.destroy(localInertia);
Ammo.destroy(startTransform);
Ammo.destroy(rbInfo);Note that you must keep the compoundShape, its child
shapes (like boxShape), and the body in memory
as long as they exist in the physics simulation. Destroy them only when
removing the object from the game world.