A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
BoxColManager.cc
1#include "BoxColManager.hh"
2
3#include "game/field/obj/ObjectCollidable.hh"
4#include "game/field/obj/ObjectDrivable.hh"
5
6#include <numeric>
7
8namespace Field {
9
11BoxColUnit::BoxColUnit() : m_pos(nullptr), m_radius(0.0f), m_range(0.0f), m_userData(nullptr) {}
12
14BoxColUnit::~BoxColUnit() = default;
15
17void BoxColUnit::init(f32 radius, f32 maxSpeed, const EGG::Vector3f *pos, const BoxColFlag &flag,
18 void *userData) {
19 m_pos = pos;
20 m_radius = radius;
21 m_range = radius + maxSpeed;
22 m_flag = flag;
23 m_flag.setBit(eBoxColFlag::Active);
24 m_userData = userData;
25 m_xMax = pos->x + m_range;
26 m_xMin = pos->x - m_range;
27}
28
30void BoxColUnit::makeInactive() {
31 m_flag.resetBit(eBoxColFlag::Active);
32}
33
35void BoxColUnit::resize(f32 radius, f32 maxSpeed) {
36 m_radius = radius;
37 m_range = radius + maxSpeed;
39}
40
42void BoxColUnit::reinsert() {
43 BoxColManager::Instance()->reinsertUnit(this);
44}
45
47void BoxColUnit::search(const BoxColFlag &flag) {
48 BoxColManager::Instance()->search(this, flag);
49}
50
54 constexpr f32 SPATIAL_BOUND = 999999.9f;
55
56 static const EGG::Vector3f upperBound(SPATIAL_BOUND, SPATIAL_BOUND, SPATIAL_BOUND);
57 static const EGG::Vector3f lowerBound(-SPATIAL_BOUND, -SPATIAL_BOUND, -SPATIAL_BOUND);
58
59 std::iota(m_unitIDs.begin(), m_unitIDs.end(), 1);
60
61 m_unitCount = 0;
62 m_nextUnitID = 0;
63
64 clear();
65
66 BoxColFlag flags;
67 insert(1.0f, 0.0f, &upperBound, flags, nullptr)->m_flag.setBit(eBoxColFlag::Intangible);
68 insert(1.0f, 0.0f, &lowerBound, flags, nullptr)->m_flag.setBit(eBoxColFlag::Intangible);
69}
70
72BoxColManager::~BoxColManager() {
73 if (s_instance) {
74 s_instance = nullptr;
75 WARN("BoxColManager instance not explicitly handled!");
76 }
77}
78
80void BoxColManager::clear() {
81 m_nextObjectID = MAX_UNIT_COUNT;
82 m_nextDrivableID = MAX_UNIT_COUNT;
83 m_maxID = 0;
84 m_cacheQueryUnit = nullptr;
85 m_cacheRadius = -1.0f;
86 m_cacheFlag.makeAllZero();
87}
88
93 clear();
94
95 // Update the AABB if necessary
96 int activeUnitIdx = 0;
97 for (auto &unit : m_unitPool) {
98 // Unit is not active, so it's not factored into mUnitCount, skip it
99 if (unit.m_flag.offBit(eBoxColFlag::Active)) {
100 continue;
101 }
102
103 // Unit is active, but we don't need to recalc its AABB, move to the next unit
105 unit.m_xMax = unit.m_pos->x + unit.m_range;
106 unit.m_xMin = unit.m_pos->x - unit.m_range;
107 m_highPoints[unit.m_highPointIdx].z = unit.m_pos->z + unit.m_range;
108 m_lowPoints[unit.m_lowPointIdx].z = unit.m_pos->z - unit.m_range;
109
110 unit.m_flag.resetBit(eBoxColFlag::TempRecalcAABB);
111 }
112
113 // We're done with all of the active units, so we avoid iterating over the remaining
114 // inactive units
115 if (++activeUnitIdx >= m_unitCount) {
116 break;
117 }
118 }
119
120 // The loops use insertion sort.
121 // We assume the units in the spatial index only change in small portions at a time per frame.
122 // The time complexity is closer to O(n) as a result rather than O(n^2).
123
124 // Reorganize the high points
125 for (size_t i = 1; i < static_cast<size_t>(m_unitCount); ++i) {
126 for (size_t j = i; m_highPoints[j - 1].z > m_highPoints[j].z && j >= 1; --j) {
127 BoxColHighPoint &upper = m_highPoints[j];
128 BoxColHighPoint &lower = m_highPoints[j - 1];
129
130 std::swap(upper, lower);
131
132 BoxColLowPoint &upperLow = m_lowPoints[upper.lowPoint];
133 BoxColLowPoint &lowerLow = m_lowPoints[lower.lowPoint];
134 ++upperLow.highPoint;
135 --lowerLow.highPoint;
136
137 ++m_unitPool[upperLow.unitID].m_highPointIdx;
138 --m_unitPool[lowerLow.unitID].m_highPointIdx;
139
140 u8 &nextMinLowPoint = upper.minLowPoint;
141 if (nextMinLowPoint == lower.lowPoint) {
142 do {
143 ++nextMinLowPoint;
144 } while (m_lowPoints[nextMinLowPoint].highPoint < j);
145 }
146
147 lower.minLowPoint = std::min(lower.minLowPoint, upper.lowPoint);
148 }
149 }
150
151 // Reorganize the low points
152 for (size_t i = 1; i < static_cast<size_t>(m_unitCount); ++i) {
153 for (size_t j = i; m_lowPoints[j - 1].z > m_lowPoints[j].z && j >= 1; --j) {
154 BoxColLowPoint &upper = m_lowPoints[j];
155 BoxColLowPoint &lower = m_lowPoints[j - 1];
156
157 std::swap(upper, lower);
158
159 ++m_highPoints[upper.highPoint].lowPoint;
160 --m_highPoints[lower.highPoint].lowPoint;
161
162 ++m_unitPool[upper.unitID].m_lowPointIdx;
163 --m_unitPool[lower.unitID].m_lowPointIdx;
164
165 if (upper.highPoint > lower.highPoint) {
166 int k = upper.highPoint;
167
168 while (k > lower.highPoint && m_highPoints[k].minLowPoint == j - 1) {
169 ++m_highPoints[k--].minLowPoint;
170 }
171 } else {
172 int k = lower.highPoint;
173
174 while (k > upper.highPoint && m_highPoints[k].minLowPoint == j) {
175 --m_highPoints[k--].minLowPoint;
176 }
177 }
178 }
179 }
180}
181
183ObjectCollidable *BoxColManager::getNextObject() {
184 return reinterpret_cast<ObjectCollidable *>(getNextImpl(m_nextObjectID, eBoxColFlag::Object));
185}
186
188ObjectDrivable *BoxColManager::getNextDrivable() {
189 return reinterpret_cast<ObjectDrivable *>(getNextImpl(m_nextDrivableID, eBoxColFlag::Drivable));
190}
191
193void BoxColManager::resetIterators() {
194 m_nextObjectID = -1;
195 iterate(m_nextObjectID, eBoxColFlag::Object);
196
197 m_nextDrivableID = -1;
198 iterate(m_nextDrivableID, eBoxColFlag::Drivable);
199}
200
202BoxColUnit *BoxColManager::insertDriver(f32 radius, f32 maxSpeed, const EGG::Vector3f *pos,
203 bool alwaysRecalc, Kart::KartObject *kartObject) {
204 BoxColFlag flag = BoxColFlag(eBoxColFlag::Driver);
205
206 if (alwaysRecalc) {
207 flag.setBit(eBoxColFlag::PermRecalcAABB);
208 }
209
210 return insert(radius, maxSpeed, pos, flag, kartObject);
211}
212
214BoxColUnit *BoxColManager::insertObject(f32 radius, f32 maxSpeed, const EGG::Vector3f *pos,
215 bool alwaysRecalc, void *userData) {
216 BoxColFlag flag = BoxColFlag(eBoxColFlag::Object);
217
218 if (alwaysRecalc) {
219 flag.setBit(eBoxColFlag::PermRecalcAABB);
220 }
221
222 return insert(radius, maxSpeed, pos, flag, userData);
223}
224
226BoxColUnit *BoxColManager::insertDrivable(f32 radius, f32 maxSpeed, const EGG::Vector3f *pos,
227 bool alwaysRecalc, void *userData) {
228 BoxColFlag flag = BoxColFlag(eBoxColFlag::Drivable);
229
230 if (alwaysRecalc) {
231 flag.setBit(eBoxColFlag::PermRecalcAABB);
232 }
233
234 return insert(radius, maxSpeed, pos, flag, userData);
235}
236
238void BoxColManager::reinsertUnit(BoxColUnit *unit) {
239 f32 radius = unit->m_radius;
240 f32 maxSpeed = unit->m_range - radius;
241 const EGG::Vector3f *pos = unit->m_pos;
242 BoxColFlag flag = unit->m_flag;
243 void *userData = unit->m_userData;
244
245 remove(unit);
246 insert(radius, maxSpeed, pos, BoxColFlag(), userData)->m_flag = flag;
247}
248
250void BoxColManager::search(BoxColUnit *unit, const BoxColFlag &flag) {
251 searchImpl(unit, flag);
252 resetIterators();
253}
254
256void BoxColManager::search(f32 radius, const EGG::Vector3f &pos, const BoxColFlag &flag) {
257 searchImpl(radius, pos, flag);
258 resetIterators();
259}
260
262bool BoxColManager::isSphereInSpatialCache(f32 radius, const EGG::Vector3f &pos,
263 const BoxColFlag &flag) const {
264 if (m_cacheRadius == -1.0f) {
265 return false;
266 }
267
268 if (!m_cacheFlag.onAll(flag)) {
269 return false;
270 }
271
272 f32 radiusDiff = m_cacheRadius - radius;
273 EGG::Vector3f posDiff = pos - m_cachePoint;
274
275 return EGG::Mathf::abs(posDiff.x) <= radiusDiff && EGG::Mathf::abs(posDiff.z) <= radiusDiff;
276}
277
279BoxColManager *BoxColManager::CreateInstance() {
280 ASSERT(!s_instance);
281 s_instance = new BoxColManager;
282 return s_instance;
283}
284
286void BoxColManager::DestroyInstance() {
287 ASSERT(s_instance);
288 auto *instance = s_instance;
289 s_instance = nullptr;
290 delete instance;
291}
292
293BoxColManager *BoxColManager::Instance() {
294 return s_instance;
295}
296
299 if (id == MAX_UNIT_COUNT) {
300 return nullptr;
301 }
302
303 BoxColUnit *unit = m_units[id];
304 iterate(id, flag);
305
306 return unit->m_userData;
307}
308
310void BoxColManager::iterate(s32 &iter, const BoxColFlag &flag) {
311 while (++iter < m_maxID) {
312 if (m_units[iter]->m_flag.on(flag)) {
313 return;
314 }
315 }
316
317 iter = MAX_UNIT_COUNT;
318}
319
321BoxColUnit *BoxColManager::insert(f32 radius, f32 maxSpeed, const EGG::Vector3f *pos,
322 const BoxColFlag &flag, void *userData) {
323 if (m_unitCount >= static_cast<s32>(MAX_UNIT_COUNT)) {
324 return nullptr;
325 }
326
327 s32 unitID = m_nextUnitID;
328 BoxColUnit &unit = m_unitPool[unitID];
329 unit.init(radius, maxSpeed, pos, flag, userData);
330 m_nextUnitID = m_unitIDs[unitID];
331 f32 range = radius + maxSpeed;
332 f32 zHigh = pos->z + range;
333 f32 zLow = pos->z - range;
334
335 if (m_unitCount == 0) {
336 m_highPoints[0].lowPoint = 0;
337 m_highPoints[0].minLowPoint = 0;
338 m_lowPoints[0].unitID = unitID;
339 m_highPoints[0].z = zHigh;
340 m_lowPoints[0].highPoint = 0;
341 m_lowPoints[0].unitID = unitID;
342 m_lowPoints[0].z = zLow;
343 m_unitPool[0].m_highPointIdx = 0;
344 m_unitPool[0].m_lowPointIdx = 0;
345 m_unitCount = 1;
346
347 return &unit;
348 }
349
350 // Binary search
351 int highPointIdx = 0;
352 int lowPointIdx = 0;
353 int i = m_unitCount;
354
355 while (true) {
356 int highSearch = highPointIdx + i;
357 int lowSearch = lowPointIdx + i;
358
359 if (highSearch <= m_unitCount && zHigh > m_highPoints[highSearch - 1].z) {
360 highPointIdx = highSearch;
361 }
362
363 if (lowSearch <= m_unitCount && zLow > m_lowPoints[lowSearch - 1].z) {
364 lowPointIdx = lowSearch;
365 }
366
367 if (i == 1) {
368 break;
369 }
370
371 i = (i + 1) / 2;
372 }
373
374 unit.m_highPointIdx = highPointIdx;
375 unit.m_lowPointIdx = lowPointIdx;
376
377 // Update high points
378 for (int i = m_unitCount; i > highPointIdx; --i) {
379 BoxColHighPoint &high = m_highPoints[i];
380 m_highPoints[i] = m_highPoints[i - 1];
381 BoxColLowPoint &low = m_lowPoints[high.lowPoint];
382
383 ++low.highPoint;
384 ++m_unitPool[low.unitID].m_highPointIdx;
385
386 if (high.minLowPoint >= lowPointIdx) {
387 ++high.minLowPoint;
388 }
389 }
390
391 m_highPoints[highPointIdx].lowPoint = lowPointIdx;
392 m_highPoints[highPointIdx].z = zHigh;
393
394 // Update min low point
395 if (highPointIdx == m_unitCount || m_highPoints[highPointIdx + 1].minLowPoint > lowPointIdx) {
396 m_highPoints[highPointIdx].minLowPoint = lowPointIdx;
397
398 for (int i = highPointIdx - 1; i >= 0 && m_highPoints[i].minLowPoint > lowPointIdx; --i) {
399 m_highPoints[i].minLowPoint = lowPointIdx;
400 }
401 } else {
402 m_highPoints[highPointIdx].minLowPoint = m_highPoints[highPointIdx + 1].minLowPoint;
403 }
404
405 // Update low points
406 for (int i = m_unitCount; i > lowPointIdx; --i) {
407 BoxColLowPoint &low = m_lowPoints[i];
408 m_lowPoints[i] = m_lowPoints[i - 1];
409 BoxColHighPoint &high = m_highPoints[low.highPoint];
410 ++high.lowPoint;
411 ++m_unitPool[low.unitID].m_lowPointIdx;
412 }
413
414 m_lowPoints[lowPointIdx].highPoint = highPointIdx;
415 m_lowPoints[lowPointIdx].unitID = unitID;
416 m_lowPoints[lowPointIdx].z = zLow;
417 ++m_unitCount;
418
419 return &unit;
420}
421
423void BoxColManager::remove(BoxColUnit *&pUnit) {
424 if (!pUnit || pUnit->m_flag.offBit(eBoxColFlag::Active)) {
425 return;
426 }
427
428 int highPointIdx = pUnit->m_highPointIdx;
429 int lowPointIdx = pUnit->m_lowPointIdx;
430
431 // Update high points
432 for (int i = highPointIdx; i < m_unitCount - 1; ++i) {
433 BoxColHighPoint &high = m_highPoints[i];
434 m_highPoints[i] = m_highPoints[i + 1];
435 BoxColLowPoint &low = m_lowPoints[high.lowPoint];
436 --low.highPoint;
437 --m_unitPool[low.unitID].m_highPointIdx;
438 if (high.minLowPoint > lowPointIdx) {
439 --high.minLowPoint;
440 }
441 }
442
443 // Update low points
444 for (int i = lowPointIdx; i < m_unitCount - 1; ++i) {
445 BoxColLowPoint &low = m_lowPoints[i];
446 m_lowPoints[i] = m_lowPoints[i + 1];
447 BoxColHighPoint &high = m_highPoints[low.highPoint];
448 --high.lowPoint;
449 --m_unitPool[low.unitID].m_lowPointIdx;
450
451 if (low.highPoint >= highPointIdx) {
452 continue;
453 }
454
455 int minLowPoint = m_highPoints[low.highPoint].minLowPoint;
456
457 if (minLowPoint != lowPointIdx) {
458 continue;
459 }
460
461 for (BoxColLowPoint *pLowPoint = &m_lowPoints[minLowPoint];
462 pLowPoint->highPoint < low.highPoint; ++minLowPoint) {
463 ++pLowPoint;
464 }
465
466 m_highPoints[low.highPoint].minLowPoint = minLowPoint;
467 }
468
469 pUnit->makeInactive();
470 int nextID = pUnit - m_unitPool.data();
471 m_unitIDs[nextID] = m_nextUnitID;
472 m_nextUnitID = nextID;
473 --m_unitCount;
474 pUnit = nullptr;
475}
476
478void BoxColManager::searchImpl(BoxColUnit *unit, const BoxColFlag &flag) {
479 if (unit->m_flag.offBit(eBoxColFlag::Active)) {
480 return;
481 }
482
483 int highPointIdx = unit->m_highPointIdx;
484 int lowPointIdx = unit->m_lowPointIdx;
485 int origLowPointIdx = unit->m_lowPointIdx;
486
487 f32 highZPos = m_highPoints[highPointIdx].z;
488 f32 lowZPos = m_lowPoints[origLowPointIdx].z;
489
490 f32 xMax = unit->m_xMax;
491 f32 xMin = unit->m_xMin;
492
493 const EGG::Vector3f *pos = unit->m_pos;
494 f32 radius = unit->m_radius;
495
496 f32 zHigh = pos->z + radius;
497 f32 zLow = pos->z - radius;
498 f32 xHigh = pos->x + radius;
499 f32 xLow = pos->x - radius;
500
501 int maxIdx = m_unitCount - 1;
502
503 m_maxID = 0;
504 m_cacheQueryUnit = unit;
505 m_cacheRadius = -1.0f;
506 m_cacheFlag = flag;
507
508 for (; highPointIdx > 7 && m_highPoints[highPointIdx - 8].z >= lowZPos;) {
509 highPointIdx -= 8;
510 }
511
512 for (; highPointIdx > 0 && m_highPoints[highPointIdx - 1].z >= lowZPos;) {
513 --highPointIdx;
514 }
515
516 for (; lowPointIdx < maxIdx - 7 && m_lowPoints[lowPointIdx + 8].z <= highZPos;) {
517 lowPointIdx += 8;
518 }
519
520 for (; lowPointIdx < maxIdx && m_lowPoints[lowPointIdx + 1].z <= highZPos;) {
521 ++lowPointIdx;
522 }
523
524 u8 minLowPoint = m_highPoints[highPointIdx].minLowPoint;
525
526 for (int i = lowPointIdx; i >= minLowPoint; --i, --lowPointIdx) {
527 BoxColLowPoint &low = m_lowPoints[i];
528
529 if (low.highPoint >= highPointIdx && lowPointIdx != origLowPointIdx) {
530 BoxColUnit &lowUnit = m_unitPool[low.unitID];
531
532 if (lowUnit.m_xMax < xMin || lowUnit.m_xMin > xMax) {
533 continue;
534 }
535
536 if (lowUnit.m_flag.off(flag) || lowUnit.m_flag.onBit(eBoxColFlag::Intangible)) {
537 continue;
538 }
539
540 f32 radius = lowUnit.m_radius;
541 if (lowUnit.m_pos->z + radius < zLow || lowUnit.m_pos->z - radius > zHigh) {
542 continue;
543 }
544
545 if (lowUnit.m_pos->x + radius < xLow || lowUnit.m_pos->x - radius > xHigh) {
546 continue;
547 }
548
549 m_units[m_maxID++] = &lowUnit;
550
551 if (m_maxID == MAX_UNIT_COUNT) {
552 break;
553 }
554 }
555
556 if (lowPointIdx == 0) {
557 break;
558 }
559 }
560}
561
563void BoxColManager::searchImpl(f32 radius, const EGG::Vector3f &pos, const BoxColFlag &flag) {
564 // Binary search
565 int highPointIdx = 0;
566 int lowPointIdx = 0;
567 f32 zHigh = pos.z + radius;
568 f32 zLow = pos.z - radius;
569 f32 xHigh = pos.x + radius;
570 f32 xLow = pos.x - radius;
571
572 m_maxID = 0;
573 m_cacheQueryUnit = nullptr;
574 m_cachePoint = pos;
575 m_cacheRadius = radius;
576 m_cacheFlag = flag;
577
578 int i = m_unitCount - 1;
579 while (true) {
580 int highSearch = highPointIdx + i;
581 int lowSearch = lowPointIdx + i;
582 if (highSearch <= m_unitCount && zLow > m_highPoints[highSearch - 1].z) {
583 highPointIdx = highSearch;
584 }
585
586 if (lowSearch <= m_unitCount && zHigh >= m_lowPoints[lowSearch].z) {
587 lowPointIdx = lowSearch;
588 }
589
590 if (i == 1) {
591 break;
592 }
593
594 i = (i + 1) / 2;
595 }
596
597 u8 minLowPoint = m_highPoints[highPointIdx].minLowPoint;
598
599 for (i = lowPointIdx; i >= minLowPoint; --i, --lowPointIdx) {
600 BoxColLowPoint &low = m_lowPoints[i];
601 if (low.highPoint >= highPointIdx) {
602 BoxColUnit &unit = m_unitPool[low.unitID];
603
604 if (unit.m_xMax < xLow || unit.m_xMin > xHigh) {
605 continue;
606 }
607
608 if (unit.m_flag.off(flag) || unit.m_flag.onBit(eBoxColFlag::Intangible)) {
609 continue;
610 }
611
612 m_units[m_maxID++] = &unit;
613
614 if (m_maxID == MAX_UNIT_COUNT) {
615 break;
616 }
617 }
618
619 if (lowPointIdx == 0) {
620 break;
621 }
622 }
623}
624
625BoxColManager *BoxColManager::s_instance = nullptr;
626
627} // namespace Field
std::array< BoxColUnit *, MAX_UNIT_COUNT > m_units
Units within our search bounds.
void * getNextImpl(s32 &id, const BoxColFlag &flag)
Helper function since the getters share all code except the flag.
std::array< BoxColHighPoint, MAX_UNIT_COUNT > m_highPoints
A unit's rightmost Z-axis point.
BoxColManager()
Creates two intangible units to represent the spatial bounds.
std::array< BoxColLowPoint, MAX_UNIT_COUNT > m_lowPoints
A unit's leftmost Z-axis point;.
std::array< u32, MAX_UNIT_COUNT > m_unitIDs
Specifies what unit to retrieve from the pool during allocation.
std::array< BoxColUnit, MAX_UNIT_COUNT > m_unitPool
Where all the units live.
void calc()
Recalculate the bounds of all active units having PermRecalcAABB or TempRecalcAABB flag,...
The highest level abstraction for a kart.
Definition KartObject.hh:11
Pertains to collision.
@ TempRecalcAABB
Only recalculate once.
@ PermRecalcAABB
Recalculate this unit's spatial indexing every frame.
@ Intangible
Ignore collision with the unit.
constexpr TBitFlag< T, E > & resetBit(Es... es)
Resets the corresponding bits for the provided enum values.
Definition BitFlag.hh:68
constexpr void makeAllZero()
Resets all the bits to zero.
Definition BitFlag.hh:185
constexpr bool onAll(T mask) const
Checks if all bits are on in the specified mask.
Definition BitFlag.hh:173
constexpr TBitFlag< T, E > & setBit(Es... es)
Sets the corresponding bits for the provided enum values.
Definition BitFlag.hh:57
A 3D float vector.
Definition Vector.hh:83
A representation of the boundaries of an entity that has dynamic collision.