2.9 KiB
bullet-0001: btGhostObject — O(N²) findLinearSearch in broadphase per-frame callback
Severity: HIGH File: src/BulletCollision/CollisionDispatch/btGhostObject.cpp Lines: 37, 49, 75, 90 Status: PATCHED
Description
btGhostObject maintains a list of overlapping collision objects in m_overlappingObjects (btAlignedObjectArray<btCollisionObject*>). The add and remove callbacks — addOverlappingObjectInternal and removeOverlappingObjectInternal — use findLinearSearch (O(N) sequential scan) to check membership before insert/remove.
These callbacks are invoked by btGhostPairCallback::addOverlappingPair / removeOverlappingPair, which are called every broadphase frame for every AABB pair involving a ghost object. In a scene with a ghost region and P dynamic bodies overlapping it, every simulation step calls findLinearSearch once per pair: O(P²) total per step.
The comment in the source acknowledges the defect:
// btGhostObject.cpp:36
///if this linearSearch becomes too slow (too many overlapping objects)
///we should add a more appropriate data structure
int index = m_overlappingObjects.findLinearSearch(otherObject);
Root Cause
btAlignedObjectArray::findLinearSearch is a plain for loop over the array (btAlignedObjectArray.h:438-452). No hash structure is used for the m_overlappingObjects membership check.
int findLinearSearch(const T& key) const {
int index = size();
for (int i = 0; i < size(); i++) {
if (m_data[i] == key) { index = i; break; }
}
return index;
}
Fix
Replace m_overlappingObjects (array) with a pair of structures:
btAlignedObjectArray<btCollisionObject*>for ordered iteration (used inconvexSweepTest,rayTest)btHashMap<btHashPtr, int>(Bullet's own hash map) or astd::unordered_set<btCollisionObject*>for O(1) membership
Quick fix: use a parallel btHashMap<btHashPtr, bool> m_overlappingSet for the contain-check:
// add
if (!m_overlappingSet.find(btHashPtr(otherObject))) {
m_overlappingObjects.push_back(otherObject);
m_overlappingSet.insert(btHashPtr(otherObject), true);
}
// remove
if (m_overlappingSet.find(btHashPtr(otherObject))) {
int index = ... // O(1) via reverse index or search once
m_overlappingObjects[index] = m_overlappingObjects.back();
m_overlappingObjects.pop_back();
m_overlappingSet.remove(btHashPtr(otherObject));
}
Speedup
| Overlapping objects (P) | Before (per step) | After (per step) |
|---|---|---|
| 10 | ~100 ops | ~10 ops |
| 100 | ~10 000 ops | ~100 ops |
| 500 | ~250 000 ops | ~500 ops |
Estimated ~100x speedup at P=100 overlapping objects with a ghost sensor region (character controller, trigger volume).