Detect Collisions Between Two Bodies in Ammo.js
Detecting collisions and retrieving exact contact points between specific physical bodies is a fundamental task in ammo.js game development. This article provides a straightforward guide on how to implement reliable collision detection using the physics dispatcher and contact manifolds, allowing you to identify when two specific 3D bodies collide and extract their exact contact point data.
The Contact Manifold Approach
Ammo.js (a port of the Bullet physics engine) does not use traditional event listeners for collisions. Instead, you must query the physics world’s dispatcher after each simulation step. The dispatcher maintains list of “contact manifolds”—structures that cache contact points between pairs of overlapping physical objects.
To reliably detect if two specific rigid bodies are colliding, you must iterate through these manifolds, compare the identities of the colliding bodies using their memory pointers, and verify that the contact distance indicates actual intersection.
Implementation Steps
- Step the Physics World: Run your
dynamicsWorld.stepSimulation()in your render loop. - Access the Dispatcher: Retrieve the collision
dispatcher using
dynamicsWorld.getDispatcher(). - Iterate Through Manifolds: Loop through all active collision manifolds.
- Compare Pointers: Use
Ammo.getPointer()to compare the manifold’s bodies with your target bodies. Comparing raw pointers is the most reliable method in WebAssembly-based environments. - Verify Contact Distance: Loop through the contact points of matching manifolds. A distance less than or equal to zero indicates a physical collision.
Code Example
The following JavaScript function demonstrates how to check for a
collision between two specific Ammo.btRigidBody objects and
retrieve the contact point in 3D space:
function checkCollisionBetween(bodyA, bodyB, dynamicsWorld) {
const dispatcher = dynamicsWorld.getDispatcher();
const numManifolds = dispatcher.getNumManifolds();
// Get memory pointers for reliable comparison
const ptrA = Ammo.getPointer(bodyA);
const ptrB = Ammo.getPointer(bodyB);
for (let i = 0; i < numManifolds; i++) {
const manifold = dispatcher.getManifoldByIndexInternal(i);
// Get the two colliding bodies
const ob0 = manifold.getBody0();
const ob1 = manifold.getBody1();
const ptr0 = Ammo.getPointer(ob0);
const ptr1 = Ammo.getPointer(ob1);
// Check if the manifold is for our two specific bodies
const isMatch = (ptr0 === ptrA && ptr1 === ptrB) || (ptr0 === ptrB && ptr1 === ptrA);
if (isMatch) {
const numContacts = manifold.getNumContacts();
for (let j = 0; j < numContacts; j++) {
const contactPoint = manifold.getContactPoint(j);
const distance = contactPoint.getDistance();
// A distance <= 0 means the objects are touching or penetrating
if (distance <= 0) {
// Extract the contact point coordinates on Body A
const localPtA = contactPoint.getPositionWorldOnA();
const contactX = localPtA.x();
const contactY = localPtA.y();
const contactZ = localPtA.z();
return {
colliding: true,
point: { x: contactX, y: contactY, z: contactZ },
distance: distance
};
}
}
}
}
return { colliding: false, point: null, distance: null };
}Retrieving Additional Contact Data
Once a collision is confirmed, the btManifoldPoint
object exposes several methods to help you handle the collision
response:
getPositionWorldOnA()/getPositionWorldOnB(): Returns thebtVector3position of the contact point in world coordinates relative to the respective body.m_normalWorldOnB: Returns the normal vector of the collision, which is crucial for calculating bounce directions or applying impact forces.getAppliedImpulse(): Returns a float representing the physical impulse applied at the contact point, allowing you to measure the intensity of the impact (e.g., to play sounds only during hard collisions).