SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
DynamicAABBTree.cpp
Go to the documentation of this file.
2#include <stack>
3#include <algorithm>
4#include <cmath>
5
6namespace Sleak {
7namespace Physics {
8
10 m_nodeCapacity = 16;
11 m_nodes.resize(m_nodeCapacity);
12 // Build free list
13 for (int i = 0; i < m_nodeCapacity - 1; ++i) {
14 m_nodes[i].parent = i + 1; // next free
15 m_nodes[i].height = -1;
16 }
17 m_nodes[m_nodeCapacity - 1].parent = NULL_NODE;
18 m_nodes[m_nodeCapacity - 1].height = -1;
19 m_freeList = 0;
20}
21
22int DynamicAABBTree::AllocateNode() {
23 if (m_freeList == NULL_NODE) {
24 int oldCapacity = m_nodeCapacity;
25 m_nodeCapacity *= 2;
26 m_nodes.resize(m_nodeCapacity);
27
28 for (int i = oldCapacity; i < m_nodeCapacity - 1; ++i) {
29 m_nodes[i].parent = i + 1;
30 m_nodes[i].height = -1;
31 }
32 m_nodes[m_nodeCapacity - 1].parent = NULL_NODE;
33 m_nodes[m_nodeCapacity - 1].height = -1;
34 m_freeList = oldCapacity;
35 }
36
37 int nodeId = m_freeList;
38 m_freeList = m_nodes[nodeId].parent;
39
40 m_nodes[nodeId].parent = NULL_NODE;
41 m_nodes[nodeId].left = NULL_NODE;
42 m_nodes[nodeId].right = NULL_NODE;
43 m_nodes[nodeId].height = 0;
44 m_nodes[nodeId].userData = nullptr;
45 ++m_nodeCount;
46
47 return nodeId;
48}
49
50void DynamicAABBTree::FreeNode(int nodeId) {
51 m_nodes[nodeId].parent = m_freeList;
52 m_nodes[nodeId].height = -1;
53 m_nodes[nodeId].userData = nullptr;
54 m_freeList = nodeId;
55 --m_nodeCount;
56}
57
58int DynamicAABBTree::Insert(const AABB& aabb, void* userData) {
59 int proxyId = AllocateNode();
60 m_nodes[proxyId].fatAABB = aabb.Fatten(FAT_AABB_MARGIN);
61 m_nodes[proxyId].userData = userData;
62 m_nodes[proxyId].height = 0;
63
64 InsertLeaf(proxyId);
65 return proxyId;
66}
67
68void DynamicAABBTree::Remove(int proxyId) {
69 RemoveLeaf(proxyId);
70 FreeNode(proxyId);
71}
72
73bool DynamicAABBTree::MoveProxy(int proxyId, const AABB& newAABB, const Vector3D& displacement) {
74 // If the new AABB is still inside the fat AABB, no need to update
75 if (m_nodes[proxyId].fatAABB.Contains(newAABB.min) &&
76 m_nodes[proxyId].fatAABB.Contains(newAABB.max)) {
77 return false;
78 }
79
80 RemoveLeaf(proxyId);
81
82 // Extend in direction of movement
83 AABB fatAABB = newAABB.Fatten(FAT_AABB_MARGIN);
84 Vector3D d = displacement * 2.0f;
85
86 if (d.GetX() < 0.0f) {
87 fatAABB.min.SetX(fatAABB.min.GetX() + d.GetX());
88 } else {
89 fatAABB.max.SetX(fatAABB.max.GetX() + d.GetX());
90 }
91 if (d.GetY() < 0.0f) {
92 fatAABB.min.SetY(fatAABB.min.GetY() + d.GetY());
93 } else {
94 fatAABB.max.SetY(fatAABB.max.GetY() + d.GetY());
95 }
96 if (d.GetZ() < 0.0f) {
97 fatAABB.min.SetZ(fatAABB.min.GetZ() + d.GetZ());
98 } else {
99 fatAABB.max.SetZ(fatAABB.max.GetZ() + d.GetZ());
100 }
101
102 m_nodes[proxyId].fatAABB = fatAABB;
103 InsertLeaf(proxyId);
104 return true;
105}
106
107void DynamicAABBTree::InsertLeaf(int leaf) {
108 if (m_root == NULL_NODE) {
109 m_root = leaf;
110 m_nodes[m_root].parent = NULL_NODE;
111 return;
112 }
113
114 // Find best sibling using Surface Area Heuristic
115 AABB leafAABB = m_nodes[leaf].fatAABB;
116 int index = m_root;
117
118 while (!m_nodes[index].IsLeaf()) {
119 int leftChild = m_nodes[index].left;
120 int rightChild = m_nodes[index].right;
121
122 float area = m_nodes[index].fatAABB.GetSurfaceArea();
123 AABB combinedAABB = m_nodes[index].fatAABB.Merge(leafAABB);
124 float combinedArea = combinedAABB.GetSurfaceArea();
125
126 // Cost of creating a new parent
127 float cost = 2.0f * combinedArea;
128 float inheritanceCost = 2.0f * (combinedArea - area);
129
130 // Cost of descending into left child
131 float costLeft;
132 if (m_nodes[leftChild].IsLeaf()) {
133 costLeft = m_nodes[leftChild].fatAABB.Merge(leafAABB).GetSurfaceArea() + inheritanceCost;
134 } else {
135 float oldArea = m_nodes[leftChild].fatAABB.GetSurfaceArea();
136 float newArea = m_nodes[leftChild].fatAABB.Merge(leafAABB).GetSurfaceArea();
137 costLeft = (newArea - oldArea) + inheritanceCost;
138 }
139
140 // Cost of descending into right child
141 float costRight;
142 if (m_nodes[rightChild].IsLeaf()) {
143 costRight = m_nodes[rightChild].fatAABB.Merge(leafAABB).GetSurfaceArea() + inheritanceCost;
144 } else {
145 float oldArea = m_nodes[rightChild].fatAABB.GetSurfaceArea();
146 float newArea = m_nodes[rightChild].fatAABB.Merge(leafAABB).GetSurfaceArea();
147 costRight = (newArea - oldArea) + inheritanceCost;
148 }
149
150 if (cost < costLeft && cost < costRight) break;
151
152 index = (costLeft < costRight) ? leftChild : rightChild;
153 }
154
155 int sibling = index;
156
157 // Create new parent
158 int oldParent = m_nodes[sibling].parent;
159 int newParent = AllocateNode();
160 m_nodes[newParent].parent = oldParent;
161 m_nodes[newParent].fatAABB = leafAABB.Merge(m_nodes[sibling].fatAABB);
162 m_nodes[newParent].height = m_nodes[sibling].height + 1;
163
164 if (oldParent != NULL_NODE) {
165 if (m_nodes[oldParent].left == sibling) {
166 m_nodes[oldParent].left = newParent;
167 } else {
168 m_nodes[oldParent].right = newParent;
169 }
170 } else {
171 m_root = newParent;
172 }
173
174 m_nodes[newParent].left = sibling;
175 m_nodes[newParent].right = leaf;
176 m_nodes[sibling].parent = newParent;
177 m_nodes[leaf].parent = newParent;
178
179 // Walk back and fix heights and AABBs
180 int node = m_nodes[leaf].parent;
181 while (node != NULL_NODE) {
182 node = Balance(node);
183
184 int left = m_nodes[node].left;
185 int right = m_nodes[node].right;
186
187 m_nodes[node].height = 1 + std::max(m_nodes[left].height, m_nodes[right].height);
188 m_nodes[node].fatAABB = m_nodes[left].fatAABB.Merge(m_nodes[right].fatAABB);
189
190 node = m_nodes[node].parent;
191 }
192}
193
194void DynamicAABBTree::RemoveLeaf(int leaf) {
195 if (leaf == m_root) {
196 m_root = NULL_NODE;
197 return;
198 }
199
200 int parent = m_nodes[leaf].parent;
201 int grandParent = m_nodes[parent].parent;
202 int sibling = (m_nodes[parent].left == leaf) ? m_nodes[parent].right : m_nodes[parent].left;
203
204 if (grandParent != NULL_NODE) {
205 if (m_nodes[grandParent].left == parent) {
206 m_nodes[grandParent].left = sibling;
207 } else {
208 m_nodes[grandParent].right = sibling;
209 }
210 m_nodes[sibling].parent = grandParent;
211 FreeNode(parent);
212
213 int node = grandParent;
214 while (node != NULL_NODE) {
215 node = Balance(node);
216
217 int left = m_nodes[node].left;
218 int right = m_nodes[node].right;
219
220 m_nodes[node].fatAABB = m_nodes[left].fatAABB.Merge(m_nodes[right].fatAABB);
221 m_nodes[node].height = 1 + std::max(m_nodes[left].height, m_nodes[right].height);
222
223 node = m_nodes[node].parent;
224 }
225 } else {
226 m_root = sibling;
227 m_nodes[sibling].parent = NULL_NODE;
228 FreeNode(parent);
229 }
230}
231
232int DynamicAABBTree::Balance(int nodeId) {
233 if (m_nodes[nodeId].IsLeaf() || m_nodes[nodeId].height < 2) {
234 return nodeId;
235 }
236
237 int left = m_nodes[nodeId].left;
238 int right = m_nodes[nodeId].right;
239
240 int balance = m_nodes[right].height - m_nodes[left].height;
241
242 // Rotate right branch up
243 if (balance > 1) {
244 int rightLeft = m_nodes[right].left;
245 int rightRight = m_nodes[right].right;
246
247 // Swap node and right child
248 m_nodes[right].left = nodeId;
249 m_nodes[right].parent = m_nodes[nodeId].parent;
250 m_nodes[nodeId].parent = right;
251
252 if (m_nodes[right].parent != NULL_NODE) {
253 if (m_nodes[m_nodes[right].parent].left == nodeId) {
254 m_nodes[m_nodes[right].parent].left = right;
255 } else {
256 m_nodes[m_nodes[right].parent].right = right;
257 }
258 } else {
259 m_root = right;
260 }
261
262 // Rotate
263 if (m_nodes[rightLeft].height > m_nodes[rightRight].height) {
264 m_nodes[right].right = rightLeft;
265 m_nodes[nodeId].right = rightRight;
266 m_nodes[rightRight].parent = nodeId;
267 m_nodes[nodeId].fatAABB = m_nodes[left].fatAABB.Merge(m_nodes[rightRight].fatAABB);
268 m_nodes[right].fatAABB = m_nodes[nodeId].fatAABB.Merge(m_nodes[rightLeft].fatAABB);
269 m_nodes[nodeId].height = 1 + std::max(m_nodes[left].height, m_nodes[rightRight].height);
270 m_nodes[right].height = 1 + std::max(m_nodes[nodeId].height, m_nodes[rightLeft].height);
271 } else {
272 m_nodes[right].right = rightRight;
273 m_nodes[nodeId].right = rightLeft;
274 m_nodes[rightLeft].parent = nodeId;
275 m_nodes[nodeId].fatAABB = m_nodes[left].fatAABB.Merge(m_nodes[rightLeft].fatAABB);
276 m_nodes[right].fatAABB = m_nodes[nodeId].fatAABB.Merge(m_nodes[rightRight].fatAABB);
277 m_nodes[nodeId].height = 1 + std::max(m_nodes[left].height, m_nodes[rightLeft].height);
278 m_nodes[right].height = 1 + std::max(m_nodes[nodeId].height, m_nodes[rightRight].height);
279 }
280
281 return right;
282 }
283
284 // Rotate left branch up
285 if (balance < -1) {
286 int leftLeft = m_nodes[left].left;
287 int leftRight = m_nodes[left].right;
288
289 m_nodes[left].left = nodeId;
290 m_nodes[left].parent = m_nodes[nodeId].parent;
291 m_nodes[nodeId].parent = left;
292
293 if (m_nodes[left].parent != NULL_NODE) {
294 if (m_nodes[m_nodes[left].parent].left == nodeId) {
295 m_nodes[m_nodes[left].parent].left = left;
296 } else {
297 m_nodes[m_nodes[left].parent].right = left;
298 }
299 } else {
300 m_root = left;
301 }
302
303 if (m_nodes[leftLeft].height > m_nodes[leftRight].height) {
304 m_nodes[left].right = leftLeft;
305 m_nodes[nodeId].left = leftRight;
306 m_nodes[leftRight].parent = nodeId;
307 m_nodes[nodeId].fatAABB = m_nodes[right].fatAABB.Merge(m_nodes[leftRight].fatAABB);
308 m_nodes[left].fatAABB = m_nodes[nodeId].fatAABB.Merge(m_nodes[leftLeft].fatAABB);
309 m_nodes[nodeId].height = 1 + std::max(m_nodes[right].height, m_nodes[leftRight].height);
310 m_nodes[left].height = 1 + std::max(m_nodes[nodeId].height, m_nodes[leftLeft].height);
311 } else {
312 m_nodes[left].right = leftRight;
313 m_nodes[nodeId].left = leftLeft;
314 m_nodes[leftLeft].parent = nodeId;
315 m_nodes[nodeId].fatAABB = m_nodes[right].fatAABB.Merge(m_nodes[leftLeft].fatAABB);
316 m_nodes[left].fatAABB = m_nodes[nodeId].fatAABB.Merge(m_nodes[leftRight].fatAABB);
317 m_nodes[nodeId].height = 1 + std::max(m_nodes[right].height, m_nodes[leftLeft].height);
318 m_nodes[left].height = 1 + std::max(m_nodes[nodeId].height, m_nodes[leftRight].height);
319 }
320
321 return left;
322 }
323
324 return nodeId;
325}
326
327void DynamicAABBTree::Query(const AABB& queryAABB, const std::function<bool(int)>& callback) const {
328 if (m_root == NULL_NODE) return;
329
330 std::stack<int> stack;
331 stack.push(m_root);
332
333 while (!stack.empty()) {
334 int nodeId = stack.top();
335 stack.pop();
336
337 if (nodeId == NULL_NODE) continue;
338
339 if (m_nodes[nodeId].fatAABB.Overlaps(queryAABB)) {
340 if (m_nodes[nodeId].IsLeaf()) {
341 bool shouldContinue = callback(nodeId);
342 if (!shouldContinue) return;
343 } else {
344 stack.push(m_nodes[nodeId].left);
345 stack.push(m_nodes[nodeId].right);
346 }
347 }
348 }
349}
350
351void DynamicAABBTree::RayCast(const Vector3D& origin, const Vector3D& direction, float maxDist,
352 const std::function<bool(int)>& callback) const {
353 if (m_root == NULL_NODE) return;
354
355 Vector3D invDir(
356 std::abs(direction.GetX()) > 1e-8f ? 1.0f / direction.GetX() : 1e8f,
357 std::abs(direction.GetY()) > 1e-8f ? 1.0f / direction.GetY() : 1e8f,
358 std::abs(direction.GetZ()) > 1e-8f ? 1.0f / direction.GetZ() : 1e8f
359 );
360
361 std::stack<int> stack;
362 stack.push(m_root);
363
364 while (!stack.empty()) {
365 int nodeId = stack.top();
366 stack.pop();
367
368 if (nodeId == NULL_NODE) continue;
369
370 const AABB& aabb = m_nodes[nodeId].fatAABB;
371
372 // Ray-AABB slab test
373 float tmin = 0.0f;
374 float tmax = maxDist;
375
376 for (int i = 0; i < 3; ++i) {
377 float aabbMin, aabbMax, o, id;
378 if (i == 0) { aabbMin = aabb.min.GetX(); aabbMax = aabb.max.GetX(); o = origin.GetX(); id = invDir.GetX(); }
379 else if (i == 1) { aabbMin = aabb.min.GetY(); aabbMax = aabb.max.GetY(); o = origin.GetY(); id = invDir.GetY(); }
380 else { aabbMin = aabb.min.GetZ(); aabbMax = aabb.max.GetZ(); o = origin.GetZ(); id = invDir.GetZ(); }
381
382 float t1 = (aabbMin - o) * id;
383 float t2 = (aabbMax - o) * id;
384
385 if (t1 > t2) std::swap(t1, t2);
386 tmin = std::max(tmin, t1);
387 tmax = std::min(tmax, t2);
388
389 if (tmin > tmax) break;
390 }
391
392 if (tmin > tmax) continue;
393
394 if (m_nodes[nodeId].IsLeaf()) {
395 if (!callback(nodeId)) return;
396 } else {
397 stack.push(m_nodes[nodeId].left);
398 stack.push(m_nodes[nodeId].right);
399 }
400 }
401}
402
403} // namespace Physics
404} // namespace Sleak
int height
float GetY() const
Definition Vector.hpp:361
void SetY(float val)
Definition Vector.hpp:366
float GetX() const
Definition Vector.hpp:360
float GetZ() const
Definition Vector.hpp:362
void SetX(float val)
Definition Vector.hpp:365
void SetZ(float val)
Definition Vector.hpp:367
void Query(const AABB &queryAABB, const std::function< bool(int)> &callback) const
Visits every leaf whose fat AABB overlaps queryAABB; stop early by returning false from callback.
int Insert(const AABB &aabb, void *userData)
Adds a new proxy with a fattened AABB and returns its id.
bool MoveProxy(int proxyId, const AABB &newAABB, const Vector3D &displacement)
Refits a proxy's fat AABB to newAABB, re-inserting it only if it moved outside the fat margin.
void Remove(int proxyId)
Removes a proxy and rebalances the tree around it.
void RayCast(const Vector3D &origin, const Vector3D &direction, float maxDist, const std::function< bool(int)> &callback) const
Walks the tree along a ray, visiting candidate leaves within maxDist.
Collision shapes, the broadphase tree, and the world that steps them.
Definition SceneBase.hpp:29
static constexpr int NULL_NODE
static constexpr float FAT_AABB_MARGIN
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
void Merge(const AABB &o)
Grows this box to also cover o.
Definition AABB.hpp:36
AABB Fatten(float margin) const
Definition Colliders.hpp:64