Ammo.js Custom Broadphase Collision Filtering
This article explains how to filter specific collision pairs from generating contact points in Ammo.js by implementing a custom broadphase overlap callback. While Ammo.js provides default collision groups and masks for basic filtering, complex projects often require dynamic, object-specific collision rules. By overriding the broadphase overlap filter callback, you can intercept colliding pairs before they reach the narrowphase stage, saving performance and preventing unwanted contact point generation.
Step 1: Implement the btOverlapFilterCallback
To intercept collision pairs during the broadphase, you must
instantiate and define a custom btOverlapFilterCallback.
This callback features a needBroadphaseCollision method
that determines whether a collision pair should proceed to narrowphase
processing.
// Create the broadphase filter callback instance
const filterCallback = new Ammo.btOverlapFilterCallback();
// Override the collision decision method
filterCallback.needBroadphaseCollision = function(proxy0, proxy1) {
// Cast broadphase proxies to collision objects
const colObj0 = Ammo.castObject(proxy0.get_m_clientObject(), Ammo.btCollisionObject);
const colObj1 = Ammo.castObject(proxy1.get_m_clientObject(), Ammo.btCollisionObject);
// Retrieve user-defined identifiers (e.g., custom user pointers or IDs)
const id0 = colObj0.getUserPointer();
const id1 = colObj1.getUserPointer();
// Define your custom filtering logic
if (shouldPreventCollision(id0, id1)) {
return false; // Reject the pair; no contact points will be generated
}
// Default bitmask-based group and mask filtering fallback
const collides = (proxy0.get_m_collisionFilterGroup() & proxy1.get_m_collisionFilterMask()) !== 0 &&
(proxy1.get_m_collisionFilterGroup() & proxy0.get_m_collisionFilterMask()) !== 0;
return collides; // Return true to allow narrowphase contact generation
};Step 2: Register the Callback with the Physics World
After defining the callback, you must assign it to the overlapping
pair cache of your btDiscreteDynamicsWorld. This ensures
that the physics engine runs your custom logic during its broadphase
update loop.
// Access the pair cache from your dynamics world and assign the callback
const pairCache = dynamicsWorld.getPairCache();
pairCache.setOverlapFilterCallback(filterCallback);Step 3: Accessing User Data Safely
To identify which JavaScript objects correspond to the
btCollisionObject instances in your callback, assign a
unique pointer or index to each rigid body when initializing them:
// Assigning an ID or pointer reference when creating a body
const body = new Ammo.btRigidBody(constructionInfo);
body.setUserPointer(customObjectId); // customObjectId can be an integer IDInside the needBroadphaseCollision function, retrieve
this identifier using getUserPointer() to run your
comparison logic. Returning false from this callback
immediately discards the pair, completely bypassing narrowphase manifold
generation and collision dispatch.