Guide to btGhostPairCallback in Ammo.js
In physics-enabled web applications using ammo.js, detecting volumes
of space without physical collision—such as trigger zones or sensor
areas—is achieved using ghost objects. However, these objects cannot
track overlaps out of the box. This article explains what
btGhostPairCallback is, why the ammo.js broadphase requires
it, and how registering this callback is the essential step to unlocking
real-time overlap detection and kinematic character controllers.
What are Ghost Objects in Ammo.js?
A ghost object (represented by btGhostObject or
btPairCachingGhostObject in ammo.js) is a special collision
object that resides in the physics world but does not cause physical
reactions. It will not push other objects away or block their
movement.
Instead, it acts as a sensor, detecting when other rigid bodies enter, occupy, or exit its volume. Common use cases include area triggers (such as detecting a player entering a room or collecting an item) and character controllers.
The Default Broadphase Limitation
To maintain high performance, ammo.js uses a two-phase collision detection system: broadphase (which quickly identifies bounding boxes that overlap) and narrowphase (which performs precise geometry collision calculations).
By default, the broadphase algorithm ignores ghost objects when building its internal cache of overlapping pairs. Because ghost objects do not generate physical collision forces, the physics engine does not waste CPU cycles tracking their overlapping relationships unless explicitly instructed to do so. Without a helper to manage these interactions, querying a ghost object for its overlapping neighbors will always return an empty list.
The Role of btGhostPairCallback
This is where btGhostPairCallback becomes necessary. It
is a utility class that acts as a bridge between the broadphase
collision filter and your ghost objects.
When you register a btGhostPairCallback with the physics
world, you instruct the broadphase to actively notify and update the
overlapping pair cache of any btPairCachingGhostObject
whenever an overlap begins or ends. This enables the ghost object to
maintain an accurate, real-time list of all other rigid bodies currently
intersecting its volume.
Why is it Essential?
Without registering btGhostPairCallback, ghost objects
cannot function:
- Trigger Zones Fail: A player walking into a trigger volume will go unnoticed because the ghost object’s pair cache is never populated.
- Kinematic Character Controllers Break: The
btKinematicCharacterControllerrelies on an underlyingbtPairCachingGhostObjectto detect walls, floors, and obstacles. Without the callback, the character controller cannot “see” obstacles, causing it to fall through floors or pass through solid walls.
How to Implement btGhostPairCallback in Ammo.js
To enable ghost object tracking, you must instantiate the callback and assign it to your dynamics world’s broadphase pair cache during your physics initialization setup:
// Standard physics world setup
const collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
const overlappingPairCache = new Ammo.btDbvtBroadphase();
const solver = new Ammo.btSequentialImpulseConstraintSolver();
const dynamicsWorld = new Ammo.btDiscreteDynamicsWorld(
dispatcher,
overlappingPairCache,
solver,
collisionConfiguration
);
// Crucial step: Register the ghost pair callback
const ghostPairCallback = new Ammo.btGhostPairCallback();
dynamicsWorld.getPairCache().setInternalGhostPairCallback(ghostPairCallback);Once this callback is registered, you can create
btPairCachingGhostObject instances, add them to your world,
and successfully retrieve overlapping objects during your physics update
loop:
const numOverlappingObjects = ghostObject.getNumOverlappingObjects();
for (let i = 0; i < numOverlappingObjects; i++) {
const obj = ghostObject.getOverlappingObject(i);
const rigidBody = Ammo.castObject(obj, Ammo.btRigidBody);
// Process the overlapping body (e.g., apply damage, trigger event)
}