68 lines
2.9 KiB
Markdown
68 lines
2.9 KiB
Markdown
# 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:
|
|
|
|
```cpp
|
|
// 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.
|
|
|
|
```cpp
|
|
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 in `convexSweepTest`, `rayTest`)
|
|
- `btHashMap<btHashPtr, int>` (Bullet's own hash map) or a `std::unordered_set<btCollisionObject*>` for O(1) membership
|
|
|
|
Quick fix: use a parallel `btHashMap<btHashPtr, bool> m_overlappingSet` for the contain-check:
|
|
|
|
```cpp
|
|
// 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).
|