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 Kinoko::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; j >= 1 && m_highPoints[j - 1].z > m_highPoints[j].z; --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; j >= 1 && m_lowPoints[j - 1].z > m_lowPoints[j].z; --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
354void *BoxColManager::getNextImpl(s32 &id, const BoxColFlag &flag) {
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 Kinoko::Field
void * getNextImpl(s32 &id, const BoxColFlag &flag)
Helper function since the getters share all code except the flag.
std::array< BoxColUnit *, MAX_UNIT_COUNT > m_units
Units within our search bounds.
std::array< BoxColUnit, MAX_UNIT_COUNT > m_unitPool
Where all the units live.
std::array< BoxColHighPoint, MAX_UNIT_COUNT > m_highPoints
A unit's rightmost Z-axis point.
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.
BoxColManager()
Creates two intangible units to represent the spatial bounds.
void calc()
Recalculate the bounds of all active units having PermRecalcAABB or TempRecalcAABB flag,...
Pertains to collision.
@ TempRecalcAABB
Only recalculate once.
@ PermRecalcAABB
Recalculate this unit's spatial indexing every frame.
@ Intangible
Ignore collision with the unit.
constexpr bool onAll(T mask) const
Checks if all bits are on in the specified mask.
Definition BitFlag.hh:224
constexpr void makeAllZero()
Resets all the bits to zero.
Definition BitFlag.hh:236
constexpr TBitFlag< T, E > & resetBit(Es... es)
Resets the corresponding bits for the provided enum values.
Definition BitFlag.hh:75
constexpr TBitFlag< T, E > & setBit(Es... es)
Sets the corresponding bits for the provided enum values.
Definition BitFlag.hh:64
A 3D float vector.
Definition Vector.hh:107
A representation of the boundaries of an entity that has dynamic collision.