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.