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