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:

  1. Trigger Zones Fail: A player walking into a trigger volume will go unnoticed because the ghost object’s pair cache is never populated.
  2. Kinematic Character Controllers Break: The btKinematicCharacterController relies on an underlying btPairCachingGhostObject to 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)
}