java-topology/docs/tickets/bullet-0001-ghostobject-overlapping-linear-scan.md

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).