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::remove(BoxColUnit *&unit) {
251 if (!unit || unit->m_flag.offBit(eBoxColFlag::Active)) {
252 return;
253 }
254
255 int highPointIdx = unit->m_highPointIdx;
256 int lowPointIdx = unit->m_lowPointIdx;
257
258 // Update high points
259 for (int i = highPointIdx; i < m_unitCount - 1; ++i) {
260 BoxColHighPoint &high = m_highPoints[i];
261 m_highPoints[i] = m_highPoints[i + 1];
262 BoxColLowPoint &low = m_lowPoints[high.lowPoint];
263 --low.highPoint;
264 --m_unitPool[low.unitID].m_highPointIdx;
265
266 if (high.minLowPoint > lowPointIdx) {
267 --high.minLowPoint;
268 }
269 }
270
271 // Update low points
272 for (int i = lowPointIdx; i < m_unitCount - 1; ++i) {
273 BoxColLowPoint &low = m_lowPoints[i];
274 m_lowPoints[i] = m_lowPoints[i + 1];
275 BoxColHighPoint &high = m_highPoints[low.highPoint];
276 --high.lowPoint;
277 --m_unitPool[low.unitID].m_lowPointIdx;
278
279 if (low.highPoint >= highPointIdx) {
280 continue;
281 }
282
283 int minLowPoint = m_highPoints[low.highPoint].minLowPoint;
284
285 if (minLowPoint != lowPointIdx) {
286 continue;
287 }
288
289 for (BoxColLowPoint *pLowPoint = &m_lowPoints[minLowPoint];
290 pLowPoint->highPoint < low.highPoint; ++minLowPoint) {
291 ++pLowPoint;
292 }
293
294 m_highPoints[low.highPoint].minLowPoint = minLowPoint;
295 }
296
297 unit->makeInactive();
298 int nextID = unit - m_unitPool.data();
299 m_unitIDs[nextID] = m_nextUnitID;
300 m_nextUnitID = nextID;
301 --m_unitCount;
302 unit = nullptr;
303}
304
306void BoxColManager::search(BoxColUnit *unit, const BoxColFlag &flag) {
307 searchImpl(unit, flag);
308 resetIterators();
309}
310
312void BoxColManager::search(f32 radius, const EGG::Vector3f &pos, const BoxColFlag &flag) {
313 searchImpl(radius, pos, flag);
314 resetIterators();
315}
316
318bool BoxColManager::isSphereInSpatialCache(f32 radius, const EGG::Vector3f &pos,
319 const BoxColFlag &flag) const {
320 if (m_cacheRadius == -1.0f) {
321 return false;
322 }
323
324 if (!m_cacheFlag.onAll(flag)) {
325 return false;
326 }
327
328 f32 radiusDiff = m_cacheRadius - radius;
329 EGG::Vector3f posDiff = pos - m_cachePoint;
330
331 return EGG::Mathf::abs(posDiff.x) <= radiusDiff && EGG::Mathf::abs(posDiff.z) <= radiusDiff;
332}
333
335BoxColManager *BoxColManager::CreateInstance() {
336 ASSERT(!s_instance);
337 s_instance = new BoxColManager;
338 return s_instance;
339}
340
342void BoxColManager::DestroyInstance() {
343 ASSERT(s_instance);
344 auto *instance = s_instance;
345 s_instance = nullptr;
346 delete instance;
347}
348
349BoxColManager *BoxColManager::Instance() {
350 return s_instance;
351}
352
355 if (id == MAX_UNIT_COUNT) {
356 return nullptr;
357 }
358
359 BoxColUnit *unit = m_units[id];
360 iterate(id, flag);
361
362 return unit->m_userData;
363}
364
366void BoxColManager::iterate(s32 &iter, const BoxColFlag &flag) {
367 while (++iter < m_maxID) {
368 if (m_units[iter]->m_flag.on(flag)) {
369 return;
370 }
371 }
372
373 iter = MAX_UNIT_COUNT;
374}
375
377BoxColUnit *BoxColManager::insert(f32 radius, f32 maxSpeed, const EGG::Vector3f *pos,
378 const BoxColFlag &flag, void *userData) {
379 if (m_unitCount >= static_cast<s32>(MAX_UNIT_COUNT)) {
380 return nullptr;
381 }
382
383 s32 unitID = m_nextUnitID;
384 BoxColUnit &unit = m_unitPool[unitID];
385 unit.init(radius, maxSpeed, pos, flag, userData);
386 m_nextUnitID = m_unitIDs[unitID];
387 f32 range = radius + maxSpeed;
388 f32 zHigh = pos->z + range;
389 f32 zLow = pos->z - range;
390
391 if (m_unitCount == 0) {
392 m_highPoints[0].lowPoint = 0;
393 m_highPoints[0].minLowPoint = 0;
394 m_lowPoints[0].unitID = unitID;
395 m_highPoints[0].z = zHigh;
396 m_lowPoints[0].highPoint = 0;
397 m_lowPoints[0].unitID = unitID;
398 m_lowPoints[0].z = zLow;
399 m_unitPool[0].m_highPointIdx = 0;
400 m_unitPool[0].m_lowPointIdx = 0;
401 m_unitCount = 1;
402
403 return &unit;
404 }
405
406 // Binary search
407 int highPointIdx = 0;
408 int lowPointIdx = 0;
409 int i = m_unitCount;
410
411 while (true) {
412 int highSearch = highPointIdx + i;
413 int lowSearch = lowPointIdx + i;
414
415 if (highSearch <= m_unitCount && zHigh > m_highPoints[highSearch - 1].z) {
416 highPointIdx = highSearch;
417 }
418
419 if (lowSearch <= m_unitCount && zLow > m_lowPoints[lowSearch - 1].z) {
420 lowPointIdx = lowSearch;
421 }
422
423 if (i == 1) {
424 break;
425 }
426
427 i = (i + 1) / 2;
428 }
429
430 unit.m_highPointIdx = highPointIdx;
431 unit.m_lowPointIdx = lowPointIdx;
432
433 // Update high points
434 for (int i = m_unitCount; i > highPointIdx; --i) {
435 BoxColHighPoint &high = m_highPoints[i];
436 m_highPoints[i] = m_highPoints[i - 1];
437 BoxColLowPoint &low = m_lowPoints[high.lowPoint];
438
439 ++low.highPoint;
440 ++m_unitPool[low.unitID].m_highPointIdx;
441
442 if (high.minLowPoint >= lowPointIdx) {
443 ++high.minLowPoint;
444 }
445 }
446
447 m_highPoints[highPointIdx].lowPoint = lowPointIdx;
448 m_highPoints[highPointIdx].z = zHigh;
449
450 // Update min low point
451 if (highPointIdx == m_unitCount || m_highPoints[highPointIdx + 1].minLowPoint > lowPointIdx) {
452 m_highPoints[highPointIdx].minLowPoint = lowPointIdx;
453
454 for (int i = highPointIdx - 1; i >= 0 && m_highPoints[i].minLowPoint > lowPointIdx; --i) {
455 m_highPoints[i].minLowPoint = lowPointIdx;
456 }
457 } else {
458 m_highPoints[highPointIdx].minLowPoint = m_highPoints[highPointIdx + 1].minLowPoint;
459 }
460
461 // Update low points
462 for (int i = m_unitCount; i > lowPointIdx; --i) {
463 BoxColLowPoint &low = m_lowPoints[i];
464 m_lowPoints[i] = m_lowPoints[i - 1];
465 BoxColHighPoint &high = m_highPoints[low.highPoint];
466 ++high.lowPoint;
467 ++m_unitPool[low.unitID].m_lowPointIdx;
468 }
469
470 m_lowPoints[lowPointIdx].highPoint = highPointIdx;
471 m_lowPoints[lowPointIdx].unitID = unitID;
472 m_lowPoints[lowPointIdx].z = zLow;
473 ++m_unitCount;
474
475 return &unit;
476}
477
479void BoxColManager::searchImpl(BoxColUnit *unit, const BoxColFlag &flag) {
480 if (unit->m_flag.offBit(eBoxColFlag::Active)) {
481 return;
482 }
483
484 int highPointIdx = unit->m_highPointIdx;
485 int lowPointIdx = unit->m_lowPointIdx;
486 int origLowPointIdx = unit->m_lowPointIdx;
487
488 f32 highZPos = m_highPoints[highPointIdx].z;
489 f32 lowZPos = m_lowPoints[origLowPointIdx].z;
490
491 f32 xMax = unit->m_xMax;
492 f32 xMin = unit->m_xMin;
493
494 const EGG::Vector3f *pos = unit->m_pos;
495 f32 radius = unit->m_radius;
496
497 f32 zHigh = pos->z + radius;
498 f32 zLow = pos->z - radius;
499 f32 xHigh = pos->x + radius;
500 f32 xLow = pos->x - radius;
501
502 int maxIdx = m_unitCount - 1;
503
504 m_maxID = 0;
505 m_cacheQueryUnit = unit;
506 m_cacheRadius = -1.0f;
507 m_cacheFlag = flag;
508
509 for (; highPointIdx > 7 && m_highPoints[highPointIdx - 8].z >= lowZPos;) {
510 highPointIdx -= 8;
511 }
512
513 for (; highPointIdx > 0 && m_highPoints[highPointIdx - 1].z >= lowZPos;) {
514 --highPointIdx;
515 }
516
517 for (; lowPointIdx < maxIdx - 7 && m_lowPoints[lowPointIdx + 8].z <= highZPos;) {
518 lowPointIdx += 8;
519 }
520
521 for (; lowPointIdx < maxIdx && m_lowPoints[lowPointIdx + 1].z <= highZPos;) {
522 ++lowPointIdx;
523 }
524
525 u8 minLowPoint = m_highPoints[highPointIdx].minLowPoint;
526
527 for (int i = lowPointIdx; i >= minLowPoint; --i, --lowPointIdx) {
528 BoxColLowPoint &low = m_lowPoints[i];
529
530 if (low.highPoint >= highPointIdx && lowPointIdx != origLowPointIdx) {
531 BoxColUnit &lowUnit = m_unitPool[low.unitID];
532
533 if (lowUnit.m_xMax < xMin || lowUnit.m_xMin > xMax) {
534 continue;
535 }
536
537 if (lowUnit.m_flag.off(flag) || lowUnit.m_flag.onBit(eBoxColFlag::Intangible)) {
538 continue;
539 }
540
541 f32 radius = lowUnit.m_radius;
542 if (lowUnit.m_pos->z + radius < zLow || lowUnit.m_pos->z - radius > zHigh) {
543 continue;
544 }
545
546 if (lowUnit.m_pos->x + radius < xLow || lowUnit.m_pos->x - radius > xHigh) {
547 continue;
548 }
549
550 m_units[m_maxID++] = &lowUnit;
551
552 if (m_maxID == MAX_UNIT_COUNT) {
553 break;
554 }
555 }
556
557 if (lowPointIdx == 0) {
558 break;
559 }
560 }
561}
562
564void BoxColManager::searchImpl(f32 radius, const EGG::Vector3f &pos, const BoxColFlag &flag) {
565 // Binary search
566 int highPointIdx = 0;
567 int lowPointIdx = 0;
568 f32 zHigh = pos.z + radius;
569 f32 zLow = pos.z - radius;
570 f32 xHigh = pos.x + radius;
571 f32 xLow = pos.x - radius;
572
573 m_maxID = 0;
574 m_cacheQueryUnit = nullptr;
575 m_cachePoint = pos;
576 m_cacheRadius = radius;
577 m_cacheFlag = flag;
578
579 int i = m_unitCount - 1;
580 while (true) {
581 int highSearch = highPointIdx + i;
582 int lowSearch = lowPointIdx + i;
583 if (highSearch <= m_unitCount && zLow > m_highPoints[highSearch - 1].z) {
584 highPointIdx = highSearch;
585 }
586
587 if (lowSearch <= m_unitCount && zHigh >= m_lowPoints[lowSearch].z) {
588 lowPointIdx = lowSearch;
589 }
590
591 if (i == 1) {
592 break;
593 }
594
595 i = (i + 1) / 2;
596 }
597
598 u8 minLowPoint = m_highPoints[highPointIdx].minLowPoint;
599
600 for (i = lowPointIdx; i >= minLowPoint; --i, --lowPointIdx) {
601 BoxColLowPoint &low = m_lowPoints[i];
602 if (low.highPoint >= highPointIdx) {
603 BoxColUnit &unit = m_unitPool[low.unitID];
604
605 if (unit.m_xMax < xLow || unit.m_xMin > xHigh) {
606 continue;
607 }
608
609 if (unit.m_flag.off(flag) || unit.m_flag.onBit(eBoxColFlag::Intangible)) {
610 continue;
611 }
612
613 m_units[m_maxID++] = &unit;
614
615 if (m_maxID == MAX_UNIT_COUNT) {
616 break;
617 }
618 }
619
620 if (lowPointIdx == 0) {
621 break;
622 }
623 }
624}
625
626BoxColManager *BoxColManager::s_instance = nullptr;
627
628} // 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:229
constexpr bool onAll(T mask) const
Checks if all bits are on in the specified mask.
Definition BitFlag.hh:217
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:88
A representation of the boundaries of an entity that has dynamic collision.