Access Collision Manifold Points in Ammo.js
In physics simulations using ammo.js (the Emscripten port of Bullet Physics), detecting and processing collisions requires accessing the contact manifolds generated by the physics dispatcher. This article provides a direct, step-by-step guide on how to iterate through active collision manifold points in ammo.js to retrieve detailed contact data—such as impact points, surface normals, penetration depth, and applied impulse forces—for use in your game development and simulation workflows.
The Collision Iteration Process
Ammo.js processes collisions during the physics step and stores the results in “contact manifolds.” A manifold is a cache that contains contact points between pairs of overlapping objects. To inspect these collisions, you must query the physics world’s dispatcher after updating the simulation.
Here is the step-by-step implementation to access this data.
Step 1: Get the Dispatcher
To start, you need to access the collision dispatcher from your
btDiscreteDynamicsWorld instance.
// Assuming 'physicsWorld' is your Ammo.btDiscreteDynamicsWorld instance
const dispatcher = physicsWorld.getDispatcher();
const numManifolds = dispatcher.getNumManifolds();Step 2: Loop Through the Manifolds
The dispatcher holds a list of all active manifolds. You must iterate through this list to inspect each pair of colliding objects.
for (let i = 0; i < numManifolds; i++) {
const manifold = dispatcher.getManifoldByIndexInternal(i);
// Skip if there are no actual contact points in this manifold
const numContacts = manifold.getNumContacts();
if (numContacts === 0) continue;
// Retrieve the two colliding collision objects
const obj0 = manifold.getBody0();
const obj1 = manifold.getBody1();
// Cast them to btRigidBody if you need rigid body specific properties
const body0 = Ammo.castObject(obj0, Ammo.btRigidBody);
const body1 = Ammo.castObject(obj1, Ammo.btRigidBody);
// Step 3: Iterate through individual contact points
for (let j = 0; j < numContacts; j++) {
const pt = manifold.getContactPoint(j);
// Process contact point data here
}
}Step 3: Extract Contact Point Data
Once you have access to the btManifoldPoint (represented
by pt in the loop above), you can extract physical
properties of the collision:
- Distance/Penetration: Negative values indicate penetration, while positive values indicate a slight separation within the collision margin.
- World Positions: The exact coordinates where the collision occurred on both bodies.
- Normal Vector: The direction of the collision normal.
- Applied Impulse: The physical force applied to resolve the collision.
// Check the distance to ensure the objects are actually touching/penetrating
const distance = pt.getDistance();
if (distance < 0.0) {
// Collision point on Body A (World Space)
const localPtA = pt.getPositionWorldOnA();
const worldPtA = [localPtA.x(), localPtA.y(), localPtA.z()];
// Collision point on Body B (World Space)
const localPtB = pt.getPositionWorldOnB();
const worldPtB = [localPtB.x(), localPtB.y(), localPtB.z()];
// Normal vector on Body B (pointing towards Body A)
const normal = pt.m_normalWorldOnB;
const normalVec = [normal.x(), normal.y(), normal.z()];
// The force magnitude of the impact
const appliedImpulse = pt.getAppliedImpulse();
// Use this data for gameplay logic (e.g., playing sounds, spawning particles)
console.log(`Collision detected with impulse: ${appliedImpulse}`);
}Key Considerations for Performance
- Run Post-Step: Always run this iteration loop
directly after calling
physicsWorld.stepSimulation(deltaTime). Calling it before the step or mid-frame will result in outdated or inaccurate contact point data. - Memory Management: Because Ammo.js is a
WebAssembly/C++ wrapper, objects returned by methods like
getPositionWorldOnA()are pointers. Do not attempt to manually destroy/free these contact points or positions; they are managed internally by the Bullet physics pipeline. Copy their primitive coordinate values (x(),y(),z()) to native JavaScript objects if you need to store them across frames.