Quick answer: Register a btGhostPairCallback on the broadphase’s overlapping pair cache. Without it, ghost objects never accumulate overlapping pairs.
A trigger volume uses a btGhostObject (or btPairCachingGhostObject). getNumOverlappingObjects() always returns 0 even when bodies are clearly inside.
Register the Ghost Pair Callback
btGhostPairCallback* ghostCallback = new btGhostPairCallback();
dynamicsWorld->getBroadphase()
->getOverlappingPairCache()
->setInternalGhostPairCallback(ghostCallback);
This is the step everyone misses. Without the callback installed on the broadphase, ghost objects are never told about their overlapping pairs.
Use btPairCachingGhostObject
For querying overlaps, use btPairCachingGhostObject — it caches the pairs so you can iterate them. Plain btGhostObject is more limited.
Add to the World With Flags
ghost->setCollisionFlags(ghost->getCollisionFlags()
| btCollisionObject::CF_NO_CONTACT_RESPONSE);
dynamicsWorld->addCollisionObject(ghost,
COL_TRIGGER, COL_PLAYER | COL_ENEMY);
CF_NO_CONTACT_RESPONSE makes it a sensor (no physical push). The group/mask args control what it detects.
Query Overlaps
for (int i = 0; i < ghost->getNumOverlappingObjects(); i++) {
btCollisionObject* obj = ghost->getOverlappingObject(i);
// note: AABB overlap — refine with a narrowphase test if needed
}
getOverlappingObjects gives broadphase (AABB) overlaps. For precise shape overlap, run a contactTest.
Verifying
Move a body into the trigger volume. getNumOverlappingObjects becomes non-zero. Leaving the volume drops it back. The ghost never pushes anything.
“Ghost objects need the ghost pair callback on the broadphase. That one registration makes overlaps work.”
getOverlappingObjects is AABB-level — for tight trigger shapes, always follow up with a contactTest or you’ll get false positives at the corners.