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

  1. Step the Physics World: Run your dynamicsWorld.stepSimulation() in your render loop.
  2. Access the Dispatcher: Retrieve the collision dispatcher using dynamicsWorld.getDispatcher().
  3. Iterate Through Manifolds: Loop through all active collision manifolds.
  4. 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.
  5. 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: