A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
KartCollide.cc
1#include "KartCollide.hh"
2
3#include "game/kart/KartBody.hh"
4#include "game/kart/KartMove.hh"
5#include "game/kart/KartPhysics.hh"
6#include "game/kart/KartState.hh"
7
8#include "game/field/CollisionDirector.hh"
9#include "game/field/ObjectCollisionKart.hh"
10#include "game/field/ObjectDirector.hh"
11
12#include <egg/math/BoundBox.hh>
13#include <egg/math/Math.hh>
14
15namespace Kart {
16
18KartCollide::KartCollide() {
19 m_boundingRadius = 100.0f;
20 m_surfaceFlags.makeAllZero();
21}
22
24KartCollide::~KartCollide() = default;
25
27void KartCollide::init() {
28 calcBoundingRadius();
29 m_floorMomentRate = 0.8f;
30 m_surfaceFlags.makeAllZero();
31 m_respawnTimer = 0;
32 m_solidOobTimer = 0;
33 m_smoothedBack = 0.0f;
34 m_suspBottomHeightNonSoftWall = 0.0f;
35 m_suspBottomHeightSoftWall = 0.0f;
36 m_someNonSoftWallTimer = 0;
37 m_someSoftWallTimer = 0;
38 m_poleAngVelTimer = 0;
39 m_poleYaw = 0.0f;
40 m_colPerpendicularity = 0.0f;
41}
42
44void KartCollide::resetHitboxes() {
45 CollisionGroup *hitboxGroup = physics()->hitboxGroup();
46 for (u16 idx = 0; idx < hitboxGroup->hitboxCount(); ++idx) {
47 hitboxGroup->hitbox(idx).setLastPos(scale(), pose());
48 }
49}
50
55 CollisionGroup *hitboxGroup = physics()->hitboxGroup();
56 for (u16 idx = 0; idx < hitboxGroup->hitboxCount(); ++idx) {
57 hitboxGroup->hitbox(idx).calc(move()->totalScale(), body()->sinkDepth(), scale(), fullRot(),
58 pos());
59 }
60}
61
65 bool wasHalfPipe = state()->isEndHalfPipe() || state()->isActionMidZipper();
66 const EGG::Quatf &rot = wasHalfPipe ? mainRot() : fullRot();
67 calcBodyCollision(move()->totalScale(), body()->sinkDepth(), rot, scale());
68
69 auto &colData = collisionData();
70 bool existingWallCollision = colData.bWall || colData.bWall3;
71 bool newWallCollision =
72 m_surfaceFlags.onBit(eSurfaceFlags::ObjectWall, eSurfaceFlags::ObjectWall3);
73 if (existingWallCollision || newWallCollision) {
74 if (!existingWallCollision) {
75 colData.wallNrm = m_totalReactionWallNrm;
76 if (m_surfaceFlags.onBit(eSurfaceFlags::ObjectWall)) {
77 colData.bWall = true;
78 } else if (m_surfaceFlags.onBit(eSurfaceFlags::ObjectWall3)) {
79 colData.bWall3 = true;
80 }
81 } else if (newWallCollision) {
82 colData.wallNrm += m_totalReactionWallNrm;
83 if (m_surfaceFlags.onBit(eSurfaceFlags::ObjectWall)) {
84 colData.bWall = true;
85 } else if (m_surfaceFlags.onBit(eSurfaceFlags::ObjectWall3)) {
86 colData.bWall3 = true;
87 }
88 }
89
90 colData.wallNrm.normalise();
91 }
92
94}
95
100 f32 fVar1;
101
102 if (isInRespawn() || state()->isBoost() || state()->isOverZipper() ||
103 state()->isZipperInvisibleWall() || state()->isNoSparkInvisibleWall() ||
104 state()->isHalfPipeRamp()) {
105 fVar1 = 0.0f;
106 } else {
107 fVar1 = 0.05f;
108 }
109
110 bool resetXZ = fVar1 > 0.0f && state()->isAirtimeOver20() && dynamics()->velocity().y < -50.0f;
111
112 FUN_805B72B8(state()->isInAction() ? 0.3f : 0.01f, fVar1, resetXZ,
113 !state()->isJumpPadDisableYsusForce());
114}
115
123void KartCollide::FUN_805B72B8(f32 param_1, f32 param_2, bool lockXZ, bool addExtVelY) {
124 const auto &colData = collisionData();
125
126 if (!colData.bFloor && !colData.bWall && !colData.bWall3) {
127 return;
128 }
129
130 EGG::Vector3f collisionDir = colData.floorNrm + colData.wallNrm;
131 collisionDir.normalise();
132
133 f32 directionalVelocity = colData.vel.dot(collisionDir);
134 if (directionalVelocity >= 0.0f) {
135 return;
136 }
137
138 EGG::Matrix34f rotMat;
139 EGG::Matrix34f rotMatTrans;
140
141 rotMat.makeQ(dynamics()->mainRot());
142 rotMatTrans = rotMat.transpose();
143 rotMat = rotMat.multiplyTo(dynamics()->invInertiaTensor()).multiplyTo(rotMatTrans);
144
145 EGG::Vector3f relPos = colData.relPos;
146 if (lockXZ) {
147 relPos.x = 0.0f;
148 relPos.z = 0.0f;
149 }
150
151 EGG::Vector3f step1 = relPos.cross(collisionDir);
152 EGG::Vector3f step2 = rotMat.multVector33(step1);
153 EGG::Vector3f step3 = step2.cross(relPos);
154 f32 val = (-directionalVelocity * (1.0f + param_2)) / (1.0f + collisionDir.dot(step3));
155 EGG::Vector3f step4 = collisionDir.cross(-colData.vel);
156 EGG::Vector3f step5 = step4.cross(collisionDir);
157 step5.normalise();
158
159 f32 fVar1 = param_1 * EGG::Mathf::abs(val);
160 f32 otherVal = (val * colData.vel.dot(step5)) / directionalVelocity;
161
162 f32 fVar3 = otherVal;
163 if (fVar1 < EGG::Mathf::abs(otherVal)) {
164 fVar3 = fVar1;
165 if (otherVal < 0.0f) {
166 fVar3 = -param_1 * EGG::Mathf::abs(val);
167 }
168 }
169
170 EGG::Vector3f step6 = val * collisionDir + fVar3 * step5;
171
172 f32 local_1d0 = step6.y;
173 if (!addExtVelY) {
174 local_1d0 = 0.0f;
175 } else if (colData.bFloor) {
176 f32 velY = intVel().y;
177 if (velY > 0.0f) {
178 velY += extVel().y;
179 if (velY < 0.0f) {
180 EGG::Vector3f newExtVel = extVel();
181 newExtVel.y = velY;
182 dynamics()->setExtVel(newExtVel);
183 }
184 }
185 }
186
187 f32 prevExtVelY = extVel().y;
188 EGG::Vector3f extVelAdd = step6;
189 extVelAdd.y = local_1d0;
190 dynamics()->setExtVel(extVel() + extVelAdd);
191
192 if (prevExtVelY < 0.0f && extVel().y > 0.0f && extVel().y < 10.0f) {
193 EGG::Vector3f extVelNoY = extVel();
194 extVelNoY.y = 0.0f;
195 dynamics()->setExtVel(extVelNoY);
196 }
197
198 EGG::Vector3f step7 = relPos.cross(step6);
199 EGG::Vector3f step8 = rotMat.multVector33(step7);
200 EGG::Vector3f step9 = mainRot().rotateVectorInv(step8);
201 step9.y = 0.0f;
202 dynamics()->setAngVel0(dynamics()->angVel0() + step9);
203}
204
211void KartCollide::calcBodyCollision(f32 totalScale, f32 sinkDepth, const EGG::Quatf &rot,
212 const EGG::Vector3f &scale) {
213 CollisionGroup *hitboxGroup = physics()->hitboxGroup();
214 CollisionData &collisionData = hitboxGroup->collisionData();
215 collisionData.reset();
216
217 EGG::Vector3f posRel = EGG::Vector3f::zero;
218 s32 count = 0;
219 Field::CollisionInfo colInfo;
220 colInfo.bbox.setDirect(EGG::Vector3f::zero, EGG::Vector3f::zero);
221 Field::KCLTypeMask maskOut;
223 EGG::BoundBox3f minMax;
224 minMax.setZero();
225 bool bVar1 = false;
226
227 for (u16 hitboxIdx = 0; hitboxIdx < hitboxGroup->hitboxCount(); ++hitboxIdx) {
229 Hitbox &hitbox = hitboxGroup->hitbox(hitboxIdx);
230
231 if (hitbox.bspHitbox()->wallsOnly != 0) {
232 flags = 0x4A109000;
233 Field::CourseColMgr::Instance()->setNoBounceWallInfo(&noBounceWallInfo);
234 }
235
236 hitbox.calc(totalScale, sinkDepth, scale, rot, pos());
237
238 if (Field::CollisionDirector::Instance()->checkSphereCachedFullPush(hitbox.radius(),
239 hitbox.worldPos(), hitbox.lastPos(), flags, &colInfo, &maskOut, 0)) {
240 if (!!(maskOut & KCL_TYPE_VEHICLE_COLLIDEABLE)) {
241 Field::CollisionDirector::Instance()->findClosestCollisionEntry(&maskOut,
243 }
244
245 if (!FUN_805B6A9C(collisionData, hitbox, minMax, posRel, count, maskOut, colInfo)) {
246 bVar1 = true;
247
248 if (colInfo.movingFloorDist > -std::numeric_limits<f32>::min()) {
249 collisionData.bHasRoadVel = true;
250 collisionData.roadVelocity = colInfo.roadVelocity;
251 }
252
253 processBody(collisionData, hitbox, &colInfo, &maskOut);
254 }
255 }
256 }
257
258 if (bVar1) {
259 EGG::Vector3f movement = minMax.min + minMax.max;
260 applyBodyCollision(collisionData, movement, posRel, count);
261 } else {
262 collisionData.speedFactor = 1.0f;
263 collisionData.rotFactor = 1.0f;
264 }
265}
266
268void KartCollide::calcFloorEffect() {
269 if (state()->isTouchingGround()) {
270 m_surfaceFlags.setBit(eSurfaceFlags::Offroad, eSurfaceFlags::GroundBoostPanelOrRamp);
271 }
272
273 m_suspBottomHeightNonSoftWall = 0.0f;
274 m_surfaceFlags.resetBit(eSurfaceFlags::Wall, eSurfaceFlags::SolidOOB, eSurfaceFlags::BoostRamp,
275 eSurfaceFlags::Offroad, eSurfaceFlags::Trickable, eSurfaceFlags::NotTrickable,
276 eSurfaceFlags::StopHalfPipeState);
277 m_suspBottomHeightSoftWall = 0.0f;
278 m_someNonSoftWallTimer = 0;
279 m_someSoftWallTimer = 0;
280
281 Field::KCLTypeMask mask = KCL_NONE;
282 calcTriggers(&mask, pos(), false);
283
284 auto *colDir = Field::CollisionDirector::Instance();
285
286 if (m_solidOobTimer >= 3 && m_surfaceFlags.onBit(eSurfaceFlags::SolidOOB) &&
287 m_surfaceFlags.offBit(eSurfaceFlags::Wall)) {
288 if (mask & KCL_TYPE_BIT(COL_TYPE_SOLID_OOB)) {
289 colDir->findClosestCollisionEntry(&mask, KCL_TYPE_BIT(COL_TYPE_SOLID_OOB));
290 }
291
292 activateOob(true, &mask, false, false);
293 }
294
295 mask = KCL_NONE;
296 calcTriggers(&mask, pos(), true);
297
298 m_solidOobTimer =
299 m_surfaceFlags.onBit(eSurfaceFlags::SolidOOB) ? std::min(3, m_solidOobTimer + 1) : 0;
300
301 if (state()->isWall3Collision() || state()->isWallCollision()) {
302 Field::KCLTypeMask maskOut = KCL_NONE;
303
304 if (colDir->checkSphereCachedPartialPush(m_boundingRadius, pos(), EGG::Vector3f::inf,
305 KCL_TYPE_BIT(COL_TYPE_FALL_BOUNDARY), nullptr, &maskOut, 0)) {
306 calcFallBoundary(&maskOut, true);
307 }
308 }
309}
310
312void KartCollide::calcTriggers(Field::KCLTypeMask *mask, const EGG::Vector3f &pos, bool twoPoint) {
313 EGG::Vector3f v1 = twoPoint ? physics()->pos() : EGG::Vector3f::inf;
315 f32 radius = twoPoint ? 80.0f : 100.0f * move()->totalScale();
316 f32 scalar = -bsp().initialYPos * move()->totalScale() * 0.3f;
317 EGG::Vector3f scaledPos = pos + scalar * componentYAxis();
318 EGG::Vector3f back = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
319
320 m_smoothedBack += (back.dot(move()->smoothedUp()) - m_smoothedBack) * 0.3f;
321
322 scalar = m_smoothedBack * -physics()->fc() * 1.8f * move()->totalScale();
323 scaledPos += scalar * back;
324
325 bool collide = Field::CollisionDirector::Instance()->checkSphereCachedPartialPush(radius,
326 scaledPos, v1, typeMask, nullptr, mask, 0);
327
328 if (!collide) {
329 return;
330 }
331
332 if (twoPoint) {
333 handleTriggers(mask);
334 } else {
335 if (*mask & KCL_TYPE_FLOOR) {
336 Field::CollisionDirector::Instance()->findClosestCollisionEntry(mask, KCL_TYPE_FLOOR);
337 }
338
339 if (*mask & KCL_TYPE_WALL) {
340 m_surfaceFlags.setBit(eSurfaceFlags::Wall);
341 }
342
343 if (*mask & KCL_TYPE_BIT(COL_TYPE_SOLID_OOB)) {
344 m_surfaceFlags.setBit(eSurfaceFlags::SolidOOB);
345 }
346 }
347}
348
350void KartCollide::handleTriggers(Field::KCLTypeMask *mask) {
351 calcFallBoundary(mask, false);
352 processCannon(mask);
353
355 auto *colDir = Field::CollisionDirector::Instance();
356 if (colDir->findClosestCollisionEntry(mask, KCL_TYPE_BIT(COL_TYPE_EFFECT_TRIGGER))) {
357 if (colDir->closestCollisionEntry()->variant() == 4) {
358 halfPipe()->end(true);
359 state()->setEndHalfPipe(true);
360 m_surfaceFlags.setBit(eSurfaceFlags::StopHalfPipeState);
361 }
362 }
363 }
364}
365
367void KartCollide::calcFallBoundary(Field::KCLTypeMask *mask, bool shortBoundary) {
368 if (!(*mask & KCL_TYPE_BIT(COL_TYPE_FALL_BOUNDARY))) {
369 return;
370 }
371
372 auto *colDir = Field::CollisionDirector::Instance();
373 if (!colDir->findClosestCollisionEntry(mask, KCL_TYPE_BIT(COL_TYPE_FALL_BOUNDARY))) {
374 return;
375 }
376
377 bool safe = false;
378 const auto *entry = colDir->closestCollisionEntry();
379
380 if (shortBoundary) {
381 if (entry->variant() != 7) {
382 safe = true;
383 }
384 }
385
386 if (!safe) {
387 activateOob(false, mask, false, false);
388 }
389}
390
392void KartCollide::calcBeforeRespawn() {
393 if (pos().y < 0.0f) {
394 activateOob(true, nullptr, false, false);
395 }
396
397 if (!state()->isBeforeRespawn()) {
398 return;
399 }
400
401 if (--m_respawnTimer > 0) {
402 return;
403 }
404
405 state()->setBeforeRespawn(false);
406 m_respawnTimer = 0;
407 move()->triggerRespawn();
408}
409
411void KartCollide::activateOob(bool /*detachCamera*/, Field::KCLTypeMask * /*mask*/,
412 bool /*somethingCPU*/, bool /*somethingBullet*/) {
413 constexpr s16 RESPAWN_TIME = 130;
414
415 if (state()->isBeforeRespawn()) {
416 return;
417 }
418
419 move()->initOob();
420
421 m_respawnTimer = RESPAWN_TIME;
422 state()->setBeforeRespawn(true);
423}
424
433void KartCollide::calcWheelCollision(u16 /*wheelIdx*/, CollisionGroup *hitboxGroup,
434 const EGG::Vector3f &colVel, const EGG::Vector3f &center, f32 radius) {
435 Hitbox &firstHitbox = hitboxGroup->hitbox(0);
436 BSP::Hitbox *bspHitbox = const_cast<BSP::Hitbox *>(firstHitbox.bspHitbox());
437 bspHitbox->radius = radius;
438 hitboxGroup->resetCollision();
439 firstHitbox.setWorldPos(center);
440
441 Field::CollisionInfo colInfo;
442 colInfo.bbox.setZero();
443 Field::KCLTypeMask kclOut;
445 Field::CourseColMgr::Instance()->setNoBounceWallInfo(&noBounceWallInfo);
446
447 bool collided = Field::CollisionDirector::Instance()->checkSphereCachedFullPush(
448 firstHitbox.radius(), firstHitbox.worldPos(), firstHitbox.lastPos(),
449 KCL_TYPE_VEHICLE_COLLIDEABLE, &colInfo, &kclOut, 0);
450
451 CollisionData &collisionData = hitboxGroup->collisionData();
452
453 if (!collided) {
454 collisionData.speedFactor = 1.0f;
455 collisionData.rotFactor = 1.0f;
456 return;
457 }
458
459 collisionData.tangentOff = colInfo.tangentOff;
460
461 if (noBounceWallInfo.dist > std::numeric_limits<f32>::min()) {
462 collisionData.tangentOff += noBounceWallInfo.tangentOff;
463 collisionData.noBounceWallNrm = noBounceWallInfo.fnrm;
464 collisionData.bSoftWall = true;
465 }
466
467 if (kclOut & KCL_TYPE_FLOOR) {
468 collisionData.bFloor = true;
469 collisionData.floorNrm = colInfo.floorNrm;
470 }
471
472 collisionData.relPos = firstHitbox.worldPos() - pos();
473 collisionData.vel = colVel;
474
475 if (colInfo.movingFloorDist > -std::numeric_limits<f32>::min()) {
476 collisionData.bHasRoadVel = true;
477 collisionData.roadVelocity = colInfo.roadVelocity;
478 }
479
480 processWheel(collisionData, firstHitbox, &colInfo, &kclOut);
481
482 if (!(kclOut & KCL_TYPE_VEHICLE_COLLIDEABLE)) {
483 return;
484 }
485
486 Field::CollisionDirector::Instance()->findClosestCollisionEntry(&kclOut,
488}
489
493 Field::CollisionInfo *colInfo) {
494 if (colInfo->perpendicularity <= 0.0f) {
495 return;
496 }
497
498 m_colPerpendicularity = std::max(m_colPerpendicularity, colInfo->perpendicularity);
499
500 if (collisionData.bWallAtLeftCloser || collisionData.bWallAtRightCloser) {
501 return;
502 }
503
504 f32 bspPosX = hitbox.bspHitbox()->position.x;
505 if (EGG::Mathf::abs(bspPosX) > 10.0f) {
506 if (bspPosX > 0.0f) {
507 collisionData.bWallAtLeftCloser = true;
508 } else {
509 collisionData.bWallAtRightCloser = true;
510 }
511
512 collisionData.colPerpendicularity = colInfo->perpendicularity;
513
514 return;
515 }
516
517 EGG::Vector3f right = dynamics()->mainRot().rotateVector(EGG::Vector3f::ex);
518 std::array<f32, 2> tangents = {0.0f, 0.0f};
519
520 // The loop is just to do left/right wall
521 for (size_t i = 0; i < tangents.size(); ++i) {
522 f32 sign = i == 1 ? -1.0f : 1.0f;
523 f32 effectiveRadius = sign * hitbox.radius();
524 EGG::Vector3f effectivePos = hitbox.worldPos() + effectiveRadius * right;
525 Field::CollisionInfoPartial tempColInfo;
526
527 if (Field::CollisionDirector::Instance()->checkSphereCachedPartial(hitbox.radius(),
528 effectivePos, hitbox.lastPos(), KCL_TYPE_DRIVER_WALL, &tempColInfo, nullptr,
529 0)) {
530 tangents[i] = colInfo->tangentOff.squaredLength();
531 }
532 }
533
534 if (tangents[0] > tangents[1]) {
535 collisionData.bWallAtLeftCloser = true;
536 collisionData.colPerpendicularity = colInfo->perpendicularity;
537 } else if (tangents[1] > tangents[0]) {
538 collisionData.bWallAtRightCloser = true;
539 collisionData.colPerpendicularity = colInfo->perpendicularity;
540 }
541}
542
544void KartCollide::calcBoundingRadius() {
545 m_boundingRadius = collisionGroup()->boundingRadius() * move()->hitboxScale();
546}
547
549void KartCollide::calcObjectCollision() {
550 constexpr f32 COS_PI_OVER_4 = 0.707f;
551 constexpr s32 DUMMY_POLE_ANG_VEL_TIME = 3;
552 constexpr f32 DUMMY_POLE_ANG_VEL = 0.005f;
553
554 m_totalReactionWallNrm = EGG::Vector3f::zero;
555 m_surfaceFlags.resetBit(eSurfaceFlags::ObjectWall, eSurfaceFlags::ObjectWall3);
556
557 size_t collisionCount = objectCollisionKart()->checkCollision(pose(), velocity());
558
559 const auto *objectDirector = Field::ObjectDirector::Instance();
560
561 for (size_t i = 0; i < collisionCount; ++i) {
562 Reaction reaction = objectDirector->reaction(i);
563 if (reaction != Reaction::None && reaction != Reaction::UNK_7) {
564 size_t handlerIdx = static_cast<std::underlying_type_t<Reaction>>(reaction);
565 Action newAction = (this->*s_objectCollisionHandlers[handlerIdx])(i);
566 if (newAction != Action::None) {
567 action()->setHitDepth(objectDirector->hitDepth(i));
568 action()->start(newAction);
569 }
570
571 if (reaction != Reaction::SmallBump && reaction != Reaction::BigBump) {
572 const EGG::Vector3f &hitDepth = objectDirector->hitDepth(i);
573 m_tangentOff += hitDepth;
574 m_movement += hitDepth;
575 }
576 }
577
578 if (objectDirector->collidingObject(i)->id() == Field::ObjectId::DummyPole) {
579 EGG::Vector3f hitDirection = objectCollisionKart()->GetHitDirection(i);
580 const EGG::Vector3f &lastDir = move()->lastDir();
581
582 if (lastDir.dot(hitDirection) < -COS_PI_OVER_4) {
583 EGG::Vector3f angVel = hitDirection.cross(lastDir);
584 f32 sign = angVel.y > 0.0f ? -1.0f : 1.0f;
585
586 m_poleAngVelTimer = DUMMY_POLE_ANG_VEL_TIME;
587 m_poleYaw = DUMMY_POLE_ANG_VEL * sign;
588 }
589 }
590 }
591
592 calcPoleTimer();
593}
594
596void KartCollide::calcPoleTimer() {
597 if (m_poleAngVelTimer > 0 && (state()->isAccelerate() || state()->isBrake())) {
598 EGG::Vector3f angVel2 = dynamics()->angVel2();
599 angVel2.y += m_poleYaw;
600 dynamics()->setAngVel2(angVel2);
601 }
602
603 m_poleAngVelTimer = std::max(0, m_poleAngVelTimer - 1);
604}
605
609void KartCollide::processWheel(CollisionData &collisionData, Hitbox &hitbox,
610 Field::CollisionInfo *colInfo, Field::KCLTypeMask *maskOut) {
611 processFloor(collisionData, hitbox, colInfo, maskOut, true);
612}
613
615void KartCollide::processBody(CollisionData &collisionData, Hitbox &hitbox,
616 Field::CollisionInfo *colInfo, Field::KCLTypeMask *maskOut) {
617 bool hasWallCollision = processWall(collisionData, maskOut);
618
619 processFloor(collisionData, hitbox, colInfo, maskOut, false);
620
621 if (hasWallCollision) {
622 calcSideCollision(collisionData, hitbox, colInfo);
623 }
624
625 processCannon(maskOut);
626}
627
629bool KartCollide::processWall(CollisionData &collisionData, Field::KCLTypeMask *maskOut) {
630 if (!(*maskOut & KCL_TYPE_DRIVER_WALL_NO_INVISIBLE_WALL2)) {
631 return false;
632 }
633
634 auto *colDirector = Field::CollisionDirector::Instance();
635 if (!colDirector->findClosestCollisionEntry(maskOut, KCL_TYPE_DRIVER_WALL_NO_INVISIBLE_WALL2)) {
636 return false;
637 }
638
640 colDirector->findClosestCollisionEntry(maskOut,
642 auto *entry = colDirector->closestCollisionEntry();
643
644 collisionData.closestWallFlags = entry->baseType();
645 collisionData.closestWallSettings = entry->variant();
646
647 if (entry->attribute.onBit(Field::CollisionDirector::eCollisionAttribute::Soft)) {
648 collisionData.bSoftWall = true;
649 }
650 }
651
652 return true;
653}
654
661void KartCollide::processFloor(CollisionData &collisionData, Hitbox &hitbox,
662 Field::CollisionInfo * /*colInfo*/, Field::KCLTypeMask *maskOut, bool wheel) {
663 constexpr Field::KCLTypeMask BOOST_RAMP_MASK = KCL_TYPE_BIT(COL_TYPE_BOOST_RAMP);
664
665 if (collisionData.bSoftWall) {
666 ++m_someSoftWallTimer;
667 m_suspBottomHeightSoftWall += hitbox.worldPos().y - hitbox.radius();
668 }
669
670 if (!(*maskOut & KCL_TYPE_FLOOR)) {
671 return;
672 }
673
674 auto *colDirector = Field::CollisionDirector::Instance();
675
676 if (!colDirector->findClosestCollisionEntry(maskOut, KCL_TYPE_FLOOR)) {
677 return;
678 }
679
680 const auto *closestColEntry = colDirector->closestCollisionEntry();
681
682 if (closestColEntry->attribute.offBit(
683 Field::CollisionDirector::eCollisionAttribute::Trickable)) {
684 m_surfaceFlags.setBit(eSurfaceFlags::NotTrickable);
685 } else {
686 collisionData.bTrickable = true;
687 m_surfaceFlags.setBit(eSurfaceFlags::Trickable);
688 }
689
690 collisionData.speedFactor = std::min(collisionData.speedFactor,
691 param()->stats().kclSpeed[closestColEntry->baseType()]);
692
693 collisionData.intensity = closestColEntry->intensity();
694 collisionData.rotFactor += param()->stats().kclRot[closestColEntry->baseType()];
695
696 if (closestColEntry->attribute.onBit(
697 Field::CollisionDirector::eCollisionAttribute::RejectRoad)) {
698 state()->setRejectRoad(true);
699 }
700
701 collisionData.closestFloorFlags = closestColEntry->typeMask;
702 collisionData.closestFloorSettings = closestColEntry->variant();
703
704 if (wheel && !!(*maskOut & KCL_TYPE_BIT(COL_TYPE_BOOST_PAD))) {
705 move()->padType().setBit(KartMove::ePadType::BoostPanel);
706 }
707
708 if (!!(*maskOut & BOOST_RAMP_MASK) &&
709 colDirector->findClosestCollisionEntry(maskOut, BOOST_RAMP_MASK)) {
710 closestColEntry = colDirector->closestCollisionEntry();
711 move()->padType().setBit(KartMove::ePadType::BoostRamp);
712 state()->setBoostRampType(closestColEntry->variant());
713 m_surfaceFlags.setBit(eSurfaceFlags::BoostRamp, eSurfaceFlags::Trickable);
714 } else {
715 state()->setBoostRampType(-1);
716 m_surfaceFlags.setBit(eSurfaceFlags::NotTrickable);
717 }
718
719 if (!collisionData.bSoftWall) {
720 ++m_someNonSoftWallTimer;
721 m_suspBottomHeightNonSoftWall += hitbox.worldPos().y - hitbox.radius();
722 }
723
724 if (*maskOut & KCL_TYPE_BIT(COL_TYPE_STICKY_ROAD)) {
725 state()->setStickyRoad(true);
726 }
727
729 if ((*maskOut & halfPipeRampMask) &&
730 colDirector->findClosestCollisionEntry(maskOut, halfPipeRampMask)) {
731 state()->setHalfPipeRamp(true);
732 state()->setHalfPipeInvisibilityTimer(2);
733 if (colDirector->closestCollisionEntry()->variant() == 1) {
734 move()->padType().setBit(KartMove::ePadType::BoostPanel);
735 }
736 }
737
739 if (*maskOut & jumpPadMask && colDirector->findClosestCollisionEntry(maskOut, jumpPadMask)) {
740 if ((!state()->isTouchingGround() || !state()->isJumpPad()) &&
741 !state()->isJumpPadMushroomVelYInc()) {
742 move()->padType().setBit(KartMove::ePadType::JumpPad);
743 closestColEntry = colDirector->closestCollisionEntry();
744 state()->setJumpPadVariant(closestColEntry->variant());
745 }
746 collisionData.bTrickable = true;
747 }
748}
749
753 auto *colDirector = Field::CollisionDirector::Instance();
754 if (colDirector->findClosestCollisionEntry(maskOut, KCL_TYPE_BIT(COL_TYPE_CANNON_TRIGGER))) {
755 state()->setCannonPointId(colDirector->closestCollisionEntry()->variant());
756 state()->setCannonStart(true);
757 }
758}
759
769void KartCollide::applySomeFloorMoment(f32 down, f32 rate, CollisionGroup *hitboxGroup,
770 const EGG::Vector3f &forward, const EGG::Vector3f &nextDir, const EGG::Vector3f &speed,
771 bool b1, bool b2, bool b3) {
772 CollisionData &colData = hitboxGroup->collisionData();
773 if (!colData.bFloor) {
774 return;
775 }
776
777 f32 velDotFloorNrm = colData.vel.dot(colData.floorNrm);
778
779 if (velDotFloorNrm >= 0.0f) {
780 return;
781 }
782
783 EGG::Matrix34f rotMat;
784 rotMat.makeQ(dynamics()->mainRot());
785 EGG::Matrix34f tmp = rotMat.multiplyTo(dynamics()->invInertiaTensor());
786 EGG::Matrix34f rotMatTrans = rotMat.transpose();
787 tmp = tmp.multiplyTo(rotMatTrans);
788
789 EGG::Vector3f crossVec = colData.relPos.cross(colData.floorNrm);
790 crossVec = tmp.multVector(crossVec);
791 crossVec = crossVec.cross(colData.relPos);
792
793 f32 scalar = -velDotFloorNrm / (1.0f + colData.floorNrm.dot(crossVec));
794 EGG::Vector3f negSpeed = -speed;
795 crossVec = colData.floorNrm.cross(negSpeed);
796 crossVec = crossVec.cross(colData.floorNrm);
797
798 if (std::numeric_limits<f32>::epsilon() >= crossVec.squaredLength()) {
799 return;
800 }
801
802 crossVec.normalise();
803 f32 speedDot = std::min(0.0f, speed.dot(crossVec));
804 crossVec *= ((scalar * speedDot) / velDotFloorNrm);
805
806 auto [proj, rej] = crossVec.projAndRej(forward);
807
808 f32 projNorm = proj.length();
809 f32 rejNorm = rej.length();
810 f32 projNorm_ = projNorm;
811 f32 rejNorm_ = rejNorm;
812
813 f32 dVar7 = down * EGG::Mathf::abs(scalar);
814 if (dVar7 < EGG::Mathf::abs(projNorm)) {
815 projNorm_ = dVar7;
816 if (projNorm < 0.0f) {
817 projNorm_ = -down * EGG::Mathf::abs(scalar);
818 }
819 }
820
821 f32 dVar5 = rate * EGG::Mathf::abs(scalar);
822 if (dVar5 < EGG::Mathf::abs(rejNorm)) {
823 rejNorm_ = dVar5;
824 if (rejNorm < 0.0f) {
825 rejNorm_ = -rate * EGG::Mathf::abs(scalar);
826 }
827 }
828
829 proj.normalise();
830 rej.normalise();
831
832 proj *= projNorm_;
833 rej *= rejNorm_;
834
835 EGG::Vector3f projRejSum = proj + rej;
836 EGG::Vector3f projRejSumOrig = projRejSum;
837
838 if (!b1) {
839 projRejSum.x = 0.0f;
840 projRejSum.z = 0.0f;
841 }
842 if (!b2) {
843 projRejSum.y = 0.0f;
844 }
845
846 projRejSum = projRejSum.rej(nextDir);
847
848 dynamics()->setExtVel(dynamics()->extVel() + projRejSum);
849
850 if (b3) {
851 EGG::Vector3f rotation = colData.relPos.cross(projRejSumOrig);
852 EGG::Vector3f rotation2 = dynamics()->mainRot().rotateVectorInv(tmp.multVector(rotation));
853
854 EGG::Vector3f angVel = rotation2;
855 angVel.y = 0.0f;
856 if (!b1) {
857 angVel.x = 0.0f;
858 }
859 dynamics()->setAngVel0(dynamics()->angVel0() + angVel);
860 }
861}
862
867bool KartCollide::FUN_805B6A9C(CollisionData &collisionData, const Hitbox &hitbox,
868 EGG::BoundBox3f &minMax, EGG::Vector3f &relPos, s32 &count,
869 const Field::KCLTypeMask &maskOut, const Field::CollisionInfo &colInfo) {
870 if (maskOut & KCL_TYPE_WALL) {
871 if (!(maskOut & KCL_TYPE_FLOOR) && state()->isHWG() &&
872 state()->softWallSpeed().dot(colInfo.wallNrm) < 0.3f) {
873 return true;
874 }
875
876 bool skipWalls = false;
877
878 collisionData.wallNrm += colInfo.wallNrm;
879
880 if (maskOut & KCL_TYPE_ANY_INVISIBLE_WALL) {
881 collisionData.bInvisibleWall = true;
882
883 if (!(maskOut & KCL_TYPE_4010D000)) {
884 collisionData.bInvisibleWallOnly = true;
885
887 skipWalls = true;
888 }
889 }
890 }
891
892 if (!skipWalls) {
893 if (maskOut & KCL_TYPE_BIT(COL_TYPE_WALL_2)) {
894 collisionData.bWall3 = true;
895 } else {
896 collisionData.bWall = true;
897 }
898 }
899 }
900
901 if (maskOut & KCL_TYPE_FLOOR) {
902 collisionData.floorNrm += colInfo.floorNrm;
903 collisionData.bFloor = true;
904 }
905
906 EGG::Vector3f tangentOff = colInfo.tangentOff;
907 minMax.min = minMax.min.minimize(tangentOff);
908 minMax.max = minMax.max.maximize(tangentOff);
909 tangentOff.normalise();
910
911 relPos += hitbox.relPos();
912 relPos += -hitbox.radius() * tangentOff;
913 ++count;
914
915 return false;
916}
917
923 const EGG::Vector3f &posRel, s32 count) {
924 setPos(pos() + movement);
925
926 if (!collisionData.bFloor && (collisionData.bWall || collisionData.bWall3)) {
927 collisionData.movement = movement;
928 }
929
930 f32 rotFactor = 1.0f / static_cast<f32>(count);
931 EGG::Vector3f scaledRelPos = rotFactor * posRel;
932 collisionData.rotFactor *= rotFactor;
933
934 EGG::Vector3f scaledAngVel0 = dynamics()->angVel0Factor() * dynamics()->angVel0();
935 EGG::Vector3f local_48 = mainRot().rotateVectorInv(scaledRelPos);
936 EGG::Vector3f local_30 = scaledAngVel0.cross(local_48);
937 local_30 = mainRot().rotateVector(local_30);
938 local_30 += extVel();
939
940 collisionData.vel = local_30;
941 collisionData.relPos = scaledRelPos;
942
943 if (collisionData.bFloor) {
944 f32 intVelY = dynamics()->intVel().y;
945 if (intVelY > 0.0f) {
946 collisionData.vel.y += intVelY;
947 }
948 collisionData.floorNrm.normalise();
949 }
950}
951
953void KartCollide::startFloorMomentRate() {
954 m_floorMomentRate = 0.01f;
955}
956
958void KartCollide::calcFloorMomentRate() {
959 if (state()->isInAction() && action()->flags().onBit(KartAction::eFlags::Rotating)) {
960 m_floorMomentRate = 0.01f;
961 } else {
962 m_floorMomentRate = std::min(m_floorMomentRate + 0.01f, 0.8f);
963 }
964}
965
967Action KartCollide::handleReactNone(size_t /*idx*/) {
968 return Action::None;
969}
970
972Action KartCollide::handleReactWallAllSpeed(size_t idx) {
973 m_totalReactionWallNrm += Field::ObjectCollisionKart::GetHitDirection(idx);
974 m_surfaceFlags.setBit(eSurfaceFlags::ObjectWall);
975
976 return Action::None;
977}
978
980Action KartCollide::handleReactSpinAllSpeed(size_t /*idx*/) {
981 return Action::UNK_0;
982}
983
985Action KartCollide::handleReactSpinSomeSpeed(size_t /*idx*/) {
986 return Action::UNK_1;
987}
988
990Action KartCollide::handleReactFireSpin(size_t /*idx*/) {
991 return Action::UNK_9;
992}
993
995Action KartCollide::handleReactSmallLaunch(size_t /*idx*/) {
996 return Action::UNK_2;
997}
998
1000Action KartCollide::handleReactKnockbackSomeSpeedLoseItem(size_t /*idx*/) {
1001 return Action::UNK_3;
1002}
1003
1005Action KartCollide::handleReactLaunchSpinLoseItem(size_t /*idx*/) {
1006 return Action::UNK_6;
1007}
1008
1010Action KartCollide::handleReactKnockbackBumpLoseItem(size_t /*idx*/) {
1011 return Action::UNK_4;
1012}
1013
1015Action KartCollide::handleReactLongCrushLoseItem(size_t /*idx*/) {
1016 return Action::UNK_12;
1017}
1018
1020Action KartCollide::handleReactSmallBump(size_t idx) {
1021 move()->applyBumpForce(30.0f, objectCollisionKart()->GetHitDirection(idx), false);
1022 return Action::None;
1023}
1024
1026Action KartCollide::handleReactHighLaunchLoseItem(size_t /*idx*/) {
1027 return Action::UNK_8;
1028}
1029
1031Action KartCollide::handleReactWeakWall(size_t /*idx*/) {
1032 move()->setSpeed(move()->speed() * 0.82f);
1033 return Action::None;
1034}
1035
1037Action KartCollide::handleReactOffroad(size_t /*idx*/) {
1038 state()->setCollidingOffroad(true);
1039 m_surfaceFlags.setBit(eSurfaceFlags::Offroad);
1040 return Action::None;
1041}
1042
1044Action KartCollide::handleReactLaunchSpin(size_t idx) {
1045 action()->setTranslation(objectCollisionKart()->translation(idx));
1046 return Action::UNK_5;
1047}
1048
1050Action KartCollide::handleReactWallSpark(size_t idx) {
1051 m_totalReactionWallNrm += Field::ObjectCollisionKart::GetHitDirection(idx);
1052 m_surfaceFlags.setBit(eSurfaceFlags::ObjectWall3);
1053
1054 return Action::None;
1055}
1056
1058Action KartCollide::handleReactUntrickableJumpPad(size_t /*idx*/) {
1059 move()->setPadType(KartMove::PadType(KartMove::ePadType::JumpPad));
1060 state()->setJumpPadVariant(0);
1061
1062 return Action::None;
1063}
1064
1066Action KartCollide::handleReactShortCrushLoseItem(size_t /*idx*/) {
1067 return Action::UNK_14;
1068}
1069
1071Action KartCollide::handleReactCrushRespawn(size_t /*idx*/) {
1072 return Action::UNK_16;
1073}
1074
1076Action KartCollide::handleReactExplosionLoseItem(size_t /*idx*/) {
1077 return Action::UNK_7;
1078}
1079
1080std::array<KartCollide::ObjectCollisionHandler, 33> KartCollide::s_objectCollisionHandlers = {{
1089 &KartCollide::handleReactWallAllSpeed,
1090 &KartCollide::handleReactSpinAllSpeed,
1091 &KartCollide::handleReactSpinSomeSpeed,
1092 &KartCollide::handleReactFireSpin,
1094 &KartCollide::handleReactSmallLaunch,
1095 &KartCollide::handleReactKnockbackSomeSpeedLoseItem,
1096 &KartCollide::handleReactLaunchSpinLoseItem,
1097 &KartCollide::handleReactKnockbackBumpLoseItem,
1098 &KartCollide::handleReactLongCrushLoseItem,
1099 &KartCollide::handleReactSmallBump,
1102 &KartCollide::handleReactHighLaunchLoseItem,
1104 &KartCollide::handleReactWeakWall,
1105 &KartCollide::handleReactOffroad,
1106 &KartCollide::handleReactLaunchSpin,
1107 &KartCollide::handleReactWallSpark,
1110 &KartCollide::handleReactUntrickableJumpPad,
1111 &KartCollide::handleReactShortCrushLoseItem,
1112 &KartCollide::handleReactCrushRespawn,
1113 &KartCollide::handleReactExplosionLoseItem,
1114}};
1115
1116} // namespace Kart
#define KCL_TYPE_DRIVER_SOLID_SURFACE
0xEAFABDFF
#define KCL_TYPE_DRIVER_WALL_NO_INVISIBLE_WALL2
0x4010B000
#define KCL_TYPE_DRIVER_WALL
0xC010B000
@ COL_TYPE_STICKY_ROAD
Player sticks if within 200 units (rBC stairs).
@ COL_TYPE_BOOST_RAMP
Trickable. Variant affects boost duration.
@ COL_TYPE_WALL_2
Difference to COL_TYPE_WALL is unknown.
@ COL_TYPE_HALFPIPE_RAMP
Bowser's Castle half-pipe ramps.
@ COL_TYPE_HALFPIPE_INVISIBLE_WALL
Invisible wall after a half-pipe jump, like in BC.
@ COL_TYPE_FALL_BOUNDARY
Non-solid out-of-bounds trigger.
@ COL_TYPE_BOOST_PAD
Boost panel.
@ COL_TYPE_CANNON_TRIGGER
Launches player to a destination "cannon point".
@ COL_TYPE_SOLID_OOB
Solid out-of-bounds trigger.
@ COL_TYPE_JUMP_PAD
Like GBA Shy Guy Beach.
@ COL_TYPE_EFFECT_TRIGGER
Activates various effects based on variant.
#define KCL_TYPE_NON_DIRECTIONAL
0xE0F8BDFF
#define KCL_TYPE_BIT(x)
#define KCL_TYPE_4010D000
0x4010D000
#define KCL_TYPE_DRIVER_WALL_NO_INVISIBLE_WALL
0xC0109000
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
#define KCL_TYPE_ANY_INVISIBLE_WALL
0x90002000
#define KCL_TYPE_VEHICLE_COLLIDEABLE
0xEAF8BDFF
#define KCL_TYPE_DIRECTIONAL
0x05070000
#define KCL_TYPE_WALL
0xD010F000
A 3 x 4 matrix.
Definition Matrix.hh:8
Matrix34f multiplyTo(const Matrix34f &rhs) const
Multiplies two matrices.
Definition Matrix.cc:189
void makeQ(const Quatf &q)
Sets rotation matrix from quaternion.
Definition Matrix.cc:41
Vector3f multVector33(const Vector3f &vec) const
Multiplies a 3x3 matrix by a vector.
Definition Matrix.cc:236
Vector3f multVector(const Vector3f &vec) const
Multiplies a vector by a matrix.
Definition Matrix.cc:212
Matrix34f transpose() const
Transposes the 3x3 portion of the matrix.
Definition Matrix.cc:321
bool findClosestCollisionEntry(KCLTypeMask *typeMask, KCLTypeMask type)
Finds the closest KCL triangle out of the list of tris we are colliding with.
Houses hitbox and collision info for an object (body or wheel).
Represents a hitbox for the kart body or a wheel.
void calc(f32 totalScale, f32 sinkDepth, const EGG::Vector3f &scale, const EGG::Quatf &rot, const EGG::Vector3f &pos)
Calculates the position of a given hitbox, both relative to the player and world.
bool start(Action action)
Starts an action.
Definition KartAction.cc:49
void applyBodyCollision(CollisionData &collisionData, const EGG::Vector3f &movement, const EGG::Vector3f &posRel, s32 count)
Saves collision info when vehicle body collision occurs.
void calcWheelCollision(u16 wheelIdx, CollisionGroup *hitboxGroup, const EGG::Vector3f &colVel, const EGG::Vector3f &center, f32 radius)
Checks wheel hitbox collision and stores position/velocity info.
void applySomeFloorMoment(f32 down, f32 rate, CollisionGroup *hitboxGroup, const EGG::Vector3f &forward, const EGG::Vector3f &nextDir, const EGG::Vector3f &speed, bool b1, bool b2, bool b3)
Applies external and angular velocity based on the collision with the floor.
void processCannon(Field::KCLTypeMask *maskOut)
Checks if we are colliding with a cannon trigger and sets the state flag if so.
Action handleReactNone(size_t idx)
Object collision functions.
bool FUN_805B6A9C(CollisionData &collisionData, const Hitbox &hitbox, EGG::BoundBox3f &minMax, EGG::Vector3f &relPos, s32 &count, const Field::KCLTypeMask &maskOut, const Field::CollisionInfo &colInfo)
Called on collision of a new KCL type??? This only happens after airtime so far.
void processWheel(CollisionData &collisionData, Hitbox &hitbox, Field::CollisionInfo *colInfo, Field::KCLTypeMask *maskOut)
Processes moving water and floor collision effects.
void calcHitboxes()
On each frame, calculates the positions for each hitbox.
void calcSideCollision(CollisionData &collisionData, Hitbox &hitbox, Field::CollisionInfo *colInfo)
void processFloor(CollisionData &collisionData, Hitbox &hitbox, Field::CollisionInfo *colInfo, Field::KCLTypeMask *maskOut, bool wheel)
Processes the floor triangles' attributes.
void FUN_805B72B8(f32 param_1, f32 param_2, bool lockXZ, bool addExtVelY)
Affects velocity when landing from airtime.
void calcBodyCollision(f32 totalScale, f32 sinkDepth, const EGG::Quatf &rot, const EGG::Vector3f &scale)
Checks and acts on collision for each kart hitbox.
Pertains to kart-related functionality.
A representation of a bounding cuboid.
Definition BoundBox.hh:30
A quaternion, used to represent 3D rotation.
Definition Quat.hh:12
Vector3f rotateVector(const Vector3f &vec) const
Rotates a vector based on the quat.
Definition Quat.cc:55
Vector3f rotateVectorInv(const Vector3f &vec) const
Rotates a vector on the inverse quat.
Definition Quat.cc:69
constexpr TBitFlag< T, E > & resetBit(Es... es)
Resets the corresponding bits for the provided enum values.
Definition BitFlag.hh:68
constexpr bool onBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:103
constexpr bool offBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:136
constexpr void makeAllZero()
Resets all the bits to zero.
Definition BitFlag.hh:229
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
f32 normalise()
Normalizes the vector and returns the original length.
Definition Vector.cc:52
f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
Definition Vector.hh:187
f32 squaredLength() const
The dot product between the vector and itself.
Definition Vector.hh:182
Vector3f rej(const Vector3f &rhs) const
The rejection of this vector onto rhs.
Definition Vector.hh:204
Vector3f maximize(const Vector3f &rhs) const
Returns a vector whose elements are the max of the elements of both vectors.
Definition Vector.cc:73
Vector3f minimize(const Vector3f &rhs) const
Returns a vector whose elements are the min of the elements of both vectors.
Definition Vector.cc:85
Represents one of the many hitboxes that make up a vehicle.
Definition KartParam.hh:10
EGG::Vector3f position
The relative position of the hitbox.
Definition KartParam.hh:12
Information about the current collision and its properties.
u32 closestFloorSettings
The colliding floor KCL flag's "variant".
bool bFloor
Set if colliding with KCL which satisfies KCL_TYPE_FLOOR.
bool bWall3
Set if colliding with COL_TYPE_WALL_2.
bool bWall
Set if colliding with KCL which satisfies KCL_TYPE_WALL.
Field::KCLTypeMask closestFloorFlags
The colliding floor KCL flag's KColType.
s32 intensity
The KCL flag's "wheel depth".
std::array< f32, 32 > kclRot
Rotation scalars, indexed using KCL attributes.
Definition KartParam.hh:113