How to Perform Bounding Box Queries in Ammo.js

This article explains how to perform a bounding box query in the Ammo.js physics engine to detect all rigid bodies within a specific 3D area. You will learn how to define an Axis-Aligned Bounding Box (AABB), implement a contact result callback, execute the query using the dynamics world, and manage memory properly to prevent WebAssembly memory leaks.

To perform a bounding box query in Ammo.js, you use the aabbTest method available on the dynamics world object. This method tests a virtual axis-aligned bounding box against all collision shapes in the physics world and reports any intersections through a callback.

Here is the step-by-step implementation.

Step 1: Define the Query Bounds

First, define the minimum and maximum coordinates of the 3D space you want to query using btVector3.

// Define the minimum and maximum corners of the bounding box
const minBounds = new Ammo.btVector3(minX, minY, minZ);
const maxBounds = new Ammo.btVector3(maxX, maxY, maxZ);

Step 2: Create the Contact Callback

Ammo.js utilizes a callback system to report overlaps. You must instantiate a ConcreteContactResultCallback and override its addSingleResult method. This method executes whenever an object overlaps with the defined bounding box.

const detectedObjects = [];

// Instantiate the callback object
const callback = new Ammo.ConcreteContactResultCallback();

// Override the collision result function
callback.addSingleResult = function(
    manifoldPoint,
    collisionObjectWrap0,
    partId0,
    index0,
    collisionObjectWrap1,
    partId1,
    index1
) {
    // Extract the collision objects from the wrappers
    const colObj0 = Ammo.castObject(collisionObjectWrap0.getCollisionObject(), Ammo.btCollisionObject);
    const colObj1 = Ammo.castObject(collisionObjectWrap1.getCollisionObject(), Ammo.btCollisionObject);

    // Store unique objects detected within the bounding box
    if (colObj0 && !detectedObjects.includes(colObj0)) {
        detectedObjects.push(colObj0);
    }
    if (colObj1 && !detectedObjects.includes(colObj1)) {
        detectedObjects.push(colObj1);
    }

    return 0; // Return value is required by the C++ signature
};

Step 3: Run the Query

Invoke the aabbTest function on your Ammo.js dynamics world instance, passing in the boundary vectors and the callback.

// Execute the bounding box query against the physics world
dynamicsWorld.aabbTest(minBounds, maxBounds, callback);

After this call completes, the detectedObjects array will contain references to all the btCollisionObject instances that lie within or intersect the specified area. You can identify specific objects by checking their user pointer or user index:

detectedObjects.forEach(obj => {
    const userPointer = obj.getUserPointer();
    // Use the user pointer to map back to your graphics engine objects (e.g., Three.js meshes)
});

Step 4: Clean Up Memory

Because Ammo.js is a WebAssembly/asm.js port of C++, you must manually deallocate objects created with the new keyword to prevent memory leaks.

// Free WebAssembly memory heap allocations
Ammo.destroy(minBounds);
Ammo.destroy(maxBounds);
Ammo.destroy(callback);