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 if (m_solidOobTimer >= 3 && m_surfaceFlags.onBit(eSurfaceFlags::SolidOOB) &&
285 m_surfaceFlags.offBit(eSurfaceFlags::Wall)) {
286 if (mask & KCL_TYPE_BIT(COL_TYPE_SOLID_OOB)) {
287 Field::CollisionDirector::Instance()->findClosestCollisionEntry(&mask,
289 }
290
291 activateOob(true, &mask, false, false);
292 }
293
294 mask = KCL_NONE;
295 calcTriggers(&mask, pos(), true);
296
297 m_solidOobTimer =
298 m_surfaceFlags.onBit(eSurfaceFlags::SolidOOB) ? std::min(3, m_solidOobTimer + 1) : 0;
299}
300
302void KartCollide::calcTriggers(Field::KCLTypeMask *mask, const EGG::Vector3f &pos, bool twoPoint) {
303 EGG::Vector3f v1 = twoPoint ? physics()->pos() : EGG::Vector3f::inf;
305 f32 radius = twoPoint ? 80.0f : 100.0f * move()->totalScale();
306 f32 scalar = -bsp().initialYPos * move()->totalScale() * 0.3f;
307 EGG::Vector3f scaledPos = pos + scalar * componentYAxis();
308 EGG::Vector3f back = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
309
310 m_smoothedBack += (back.dot(move()->smoothedUp()) - m_smoothedBack) * 0.3f;
311
312 scalar = m_smoothedBack * -physics()->fc() * 1.8f * move()->totalScale();
313 scaledPos += scalar * back;
314
315 bool collide = Field::CollisionDirector::Instance()->checkSphereCachedPartialPush(radius,
316 scaledPos, v1, typeMask, nullptr, mask, 0);
317
318 if (!collide) {
319 return;
320 }
321
322 if (twoPoint) {
323 handleTriggers(mask);
324 } else {
325 if (*mask & KCL_TYPE_FLOOR) {
326 Field::CollisionDirector::Instance()->findClosestCollisionEntry(mask, KCL_TYPE_FLOOR);
327 }
328
329 if (*mask & KCL_TYPE_WALL) {
330 m_surfaceFlags.setBit(eSurfaceFlags::Wall);
331 }
332
333 if (*mask & KCL_TYPE_BIT(COL_TYPE_SOLID_OOB)) {
334 m_surfaceFlags.setBit(eSurfaceFlags::SolidOOB);
335 }
336 }
337}
338
340void KartCollide::handleTriggers(Field::KCLTypeMask *mask) {
341 calcFallBoundary(mask, false);
342 processCannon(mask);
343
345 auto *colDir = Field::CollisionDirector::Instance();
346 if (colDir->findClosestCollisionEntry(mask, KCL_TYPE_BIT(COL_TYPE_EFFECT_TRIGGER))) {
347 if (KCL_VARIANT_TYPE(colDir->closestCollisionEntry()->attribute) == 4) {
348 halfPipe()->end(true);
349 state()->setEndHalfPipe(true);
350 m_surfaceFlags.setBit(eSurfaceFlags::StopHalfPipeState);
351 }
352 }
353 }
354}
355
357void KartCollide::calcFallBoundary(Field::KCLTypeMask *mask, bool /*shortBoundary*/) {
358 if (!(*mask & KCL_TYPE_BIT(COL_TYPE_FALL_BOUNDARY))) {
359 return;
360 }
361
362 auto *colDir = Field::CollisionDirector::Instance();
363 if (!colDir->findClosestCollisionEntry(mask, KCL_TYPE_BIT(COL_TYPE_FALL_BOUNDARY))) {
364 return;
365 }
366
367 activateOob(false, mask, false, false);
368}
369
371void KartCollide::calcBeforeRespawn() {
372 if (pos().y < 0.0f) {
373 activateOob(true, nullptr, false, false);
374 }
375
376 if (!state()->isBeforeRespawn()) {
377 return;
378 }
379
380 if (--m_respawnTimer > 0) {
381 return;
382 }
383
384 state()->setBeforeRespawn(false);
385 m_respawnTimer = 0;
386 move()->triggerRespawn();
387}
388
390void KartCollide::activateOob(bool /*detachCamera*/, Field::KCLTypeMask * /*mask*/,
391 bool /*somethingCPU*/, bool /*somethingBullet*/) {
392 constexpr s16 RESPAWN_TIME = 130;
393
394 if (state()->isBeforeRespawn()) {
395 return;
396 }
397
398 move()->initOob();
399
400 m_respawnTimer = RESPAWN_TIME;
401 state()->setBeforeRespawn(true);
402}
403
412void KartCollide::calcWheelCollision(u16 /*wheelIdx*/, CollisionGroup *hitboxGroup,
413 const EGG::Vector3f &colVel, const EGG::Vector3f &center, f32 radius) {
414 Hitbox &firstHitbox = hitboxGroup->hitbox(0);
415 BSP::Hitbox *bspHitbox = const_cast<BSP::Hitbox *>(firstHitbox.bspHitbox());
416 bspHitbox->radius = radius;
417 hitboxGroup->resetCollision();
418 firstHitbox.setWorldPos(center);
419
420 Field::CollisionInfo colInfo;
421 colInfo.bbox.setZero();
422 Field::KCLTypeMask kclOut;
424 Field::CourseColMgr::Instance()->setNoBounceWallInfo(&noBounceWallInfo);
425
426 bool collided = Field::CollisionDirector::Instance()->checkSphereCachedFullPush(
427 firstHitbox.radius(), firstHitbox.worldPos(), firstHitbox.lastPos(),
428 KCL_TYPE_VEHICLE_COLLIDEABLE, &colInfo, &kclOut, 0);
429
430 CollisionData &collisionData = hitboxGroup->collisionData();
431
432 if (!collided) {
433 collisionData.speedFactor = 1.0f;
434 collisionData.rotFactor = 1.0f;
435 return;
436 }
437
438 collisionData.tangentOff = colInfo.tangentOff;
439
440 if (noBounceWallInfo.dist > std::numeric_limits<f32>::min()) {
441 collisionData.tangentOff += noBounceWallInfo.tangentOff;
442 collisionData.noBounceWallNrm = noBounceWallInfo.fnrm;
443 collisionData.bSoftWall = true;
444 }
445
446 if (kclOut & KCL_TYPE_FLOOR) {
447 collisionData.bFloor = true;
448 collisionData.floorNrm = colInfo.floorNrm;
449 }
450
451 collisionData.relPos = firstHitbox.worldPos() - pos();
452 collisionData.vel = colVel;
453
454 if (colInfo.movingFloorDist > -std::numeric_limits<f32>::min()) {
455 collisionData.bHasRoadVel = true;
456 collisionData.roadVelocity = colInfo.roadVelocity;
457 }
458
459 processWheel(collisionData, firstHitbox, &colInfo, &kclOut);
460
461 if (!(kclOut & KCL_TYPE_VEHICLE_COLLIDEABLE)) {
462 return;
463 }
464
465 Field::CollisionDirector::Instance()->findClosestCollisionEntry(&kclOut,
467}
468
472 Field::CollisionInfo *colInfo) {
473 if (colInfo->perpendicularity <= 0.0f) {
474 return;
475 }
476
477 m_colPerpendicularity = std::max(m_colPerpendicularity, colInfo->perpendicularity);
478
479 if (collisionData.bWallAtLeftCloser || collisionData.bWallAtRightCloser) {
480 return;
481 }
482
483 f32 bspPosX = hitbox.bspHitbox()->position.x;
484 if (EGG::Mathf::abs(bspPosX) > 10.0f) {
485 if (bspPosX > 0.0f) {
486 collisionData.bWallAtLeftCloser = true;
487 } else {
488 collisionData.bWallAtRightCloser = true;
489 }
490
491 collisionData.colPerpendicularity = colInfo->perpendicularity;
492
493 return;
494 }
495
496 EGG::Vector3f right = dynamics()->mainRot().rotateVector(EGG::Vector3f::ex);
497 std::array<f32, 2> tangents = {0.0f, 0.0f};
498
499 // The loop is just to do left/right wall
500 for (size_t i = 0; i < tangents.size(); ++i) {
501 f32 sign = i == 1 ? -1.0f : 1.0f;
502 f32 effectiveRadius = sign * hitbox.radius();
503 EGG::Vector3f effectivePos = hitbox.worldPos() + effectiveRadius * right;
504 Field::CollisionInfoPartial tempColInfo;
505
506 if (Field::CollisionDirector::Instance()->checkSphereCachedPartial(hitbox.radius(),
507 effectivePos, hitbox.lastPos(), KCL_TYPE_DRIVER_WALL, &tempColInfo, nullptr,
508 0)) {
509 tangents[i] = colInfo->tangentOff.squaredLength();
510 }
511 }
512
513 if (tangents[0] > tangents[1]) {
514 collisionData.bWallAtLeftCloser = true;
515 collisionData.colPerpendicularity = colInfo->perpendicularity;
516 } else if (tangents[1] > tangents[0]) {
517 collisionData.bWallAtRightCloser = true;
518 collisionData.colPerpendicularity = colInfo->perpendicularity;
519 }
520}
521
523void KartCollide::calcBoundingRadius() {
524 m_boundingRadius = collisionGroup()->boundingRadius() * move()->hitboxScale();
525}
526
528void KartCollide::calcObjectCollision() {
529 constexpr f32 COS_PI_OVER_4 = 0.707f;
530 constexpr s32 DUMMY_POLE_ANG_VEL_TIME = 3;
531 constexpr f32 DUMMY_POLE_ANG_VEL = 0.005f;
532
533 m_totalReactionWallNrm = EGG::Vector3f::zero;
534 m_surfaceFlags.resetBit(eSurfaceFlags::ObjectWall, eSurfaceFlags::ObjectWall3);
535
536 size_t collisionCount = objectCollisionKart()->checkCollision(pose(), velocity());
537
538 const auto *objectDirector = Field::ObjectDirector::Instance();
539
540 for (size_t i = 0; i < collisionCount; ++i) {
541 Reaction reaction = objectDirector->reaction(i);
542 if (reaction != Reaction::None && reaction != Reaction::UNK_7) {
543 size_t handlerIdx = static_cast<std::underlying_type_t<Reaction>>(reaction);
544 Action newAction = (this->*s_objectCollisionHandlers[handlerIdx])(i);
545 if (newAction != Action::None) {
546 action()->setHitDepth(objectDirector->hitDepth(i));
547 action()->start(newAction);
548 }
549
550 if (reaction != Reaction::SmallBump && reaction != Reaction::BigBump) {
551 const EGG::Vector3f &hitDepth = objectDirector->hitDepth(i);
552 m_tangentOff += hitDepth;
553 m_movement += hitDepth;
554 }
555 }
556
557 if (objectDirector->collidingObject(i)->id() == Field::ObjectId::DummyPole) {
558 EGG::Vector3f hitDirection = objectCollisionKart()->GetHitDirection(i);
559 const EGG::Vector3f &lastDir = move()->lastDir();
560
561 if (lastDir.dot(hitDirection) < -COS_PI_OVER_4) {
562 EGG::Vector3f angVel = hitDirection.cross(lastDir);
563 f32 sign = angVel.y > 0.0f ? -1.0f : 1.0f;
564
565 m_poleAngVelTimer = DUMMY_POLE_ANG_VEL_TIME;
566 m_poleYaw = DUMMY_POLE_ANG_VEL * sign;
567 }
568 }
569 }
570
571 calcPoleTimer();
572}
573
575void KartCollide::calcPoleTimer() {
576 if (m_poleAngVelTimer > 0 && (state()->isAccelerate() || state()->isBrake())) {
577 EGG::Vector3f angVel2 = dynamics()->angVel2();
578 angVel2.y += m_poleYaw;
579 dynamics()->setAngVel2(angVel2);
580 }
581
582 m_poleAngVelTimer = std::max(0, m_poleAngVelTimer - 1);
583}
584
588void KartCollide::processWheel(CollisionData &collisionData, Hitbox &hitbox,
589 Field::CollisionInfo *colInfo, Field::KCLTypeMask *maskOut) {
590 processFloor(collisionData, hitbox, colInfo, maskOut, true);
591}
592
594void KartCollide::processBody(CollisionData &collisionData, Hitbox &hitbox,
595 Field::CollisionInfo *colInfo, Field::KCLTypeMask *maskOut) {
596 bool hasWallCollision = processWall(collisionData, maskOut);
597
598 processFloor(collisionData, hitbox, colInfo, maskOut, false);
599
600 if (hasWallCollision) {
601 calcSideCollision(collisionData, hitbox, colInfo);
602 }
603
604 processCannon(maskOut);
605}
606
608bool KartCollide::processWall(CollisionData &collisionData, Field::KCLTypeMask *maskOut) {
609 if (!(*maskOut & KCL_TYPE_DRIVER_WALL_NO_INVISIBLE_WALL2)) {
610 return false;
611 }
612
613 auto *colDirector = Field::CollisionDirector::Instance();
614 if (!colDirector->findClosestCollisionEntry(maskOut, KCL_TYPE_DRIVER_WALL_NO_INVISIBLE_WALL2)) {
615 return false;
616 }
617
619 colDirector->findClosestCollisionEntry(maskOut,
621 auto *entry = colDirector->closestCollisionEntry();
622 if (entry->attribute & KCL_TYPE_BIT(COL_TYPE_WALL_2)) {
623 collisionData.bSoftWall = true;
624 }
625 }
626
627 return true;
628}
629
636void KartCollide::processFloor(CollisionData &collisionData, Hitbox &hitbox,
637 Field::CollisionInfo * /*colInfo*/, Field::KCLTypeMask *maskOut, bool wheel) {
638 constexpr Field::KCLTypeMask BOOST_RAMP_MASK = KCL_TYPE_BIT(COL_TYPE_BOOST_RAMP);
639
640 if (collisionData.bSoftWall) {
641 ++m_someSoftWallTimer;
642 m_suspBottomHeightSoftWall += hitbox.worldPos().y - hitbox.radius();
643 }
644
645 if (!(*maskOut & KCL_TYPE_VEHICLE_COLLIDEABLE)) {
646 return;
647 }
648
649 auto *colDirector = Field::CollisionDirector::Instance();
650
651 if (!colDirector->findClosestCollisionEntry(maskOut, KCL_TYPE_FLOOR)) {
652 return;
653 }
654
655 const auto *closestColEntry = colDirector->closestCollisionEntry();
656
657 u16 attribute = closestColEntry->attribute;
658 if (!(attribute & 0x2000)) {
659 m_surfaceFlags.setBit(eSurfaceFlags::NotTrickable);
660 } else {
661 collisionData.bTrickable = true;
662 m_surfaceFlags.setBit(eSurfaceFlags::Trickable);
663 }
664
665 collisionData.speedFactor = std::min(collisionData.speedFactor,
666 param()->stats().kclSpeed[KCL_ATTRIBUTE_TYPE(attribute)]);
667
668 collisionData.intensity = (attribute >> 0xb) & 3;
669 collisionData.rotFactor += param()->stats().kclRot[KCL_ATTRIBUTE_TYPE(attribute)];
670
671 if (attribute & 0x4000) {
672 state()->setRejectRoad(true);
673 }
674
675 collisionData.closestFloorFlags = closestColEntry->typeMask;
676 collisionData.closestFloorSettings = KCL_VARIANT_TYPE(attribute);
677
678 if (wheel && !!(*maskOut & KCL_TYPE_BIT(COL_TYPE_BOOST_PAD))) {
679 move()->padType().setBit(KartMove::ePadType::BoostPanel);
680 }
681
682 if (!!(*maskOut & BOOST_RAMP_MASK) &&
683 colDirector->findClosestCollisionEntry(maskOut, BOOST_RAMP_MASK)) {
684 closestColEntry = colDirector->closestCollisionEntry();
685 move()->padType().setBit(KartMove::ePadType::BoostRamp);
686 state()->setBoostRampType(KCL_VARIANT_TYPE(closestColEntry->attribute));
687 m_surfaceFlags.setBit(eSurfaceFlags::BoostRamp, eSurfaceFlags::Trickable);
688 } else {
689 state()->setBoostRampType(-1);
690 m_surfaceFlags.setBit(eSurfaceFlags::NotTrickable);
691 }
692
693 if (!collisionData.bSoftWall) {
694 ++m_someNonSoftWallTimer;
695 m_suspBottomHeightNonSoftWall += hitbox.worldPos().y - hitbox.radius();
696 }
697
698 if (*maskOut & KCL_TYPE_BIT(COL_TYPE_STICKY_ROAD)) {
699 state()->setStickyRoad(true);
700 }
701
703 if ((*maskOut & halfPipeRampMask) &&
704 colDirector->findClosestCollisionEntry(maskOut, halfPipeRampMask)) {
705 state()->setHalfPipeRamp(true);
706 state()->setHalfPipeInvisibilityTimer(2);
707 if (KCL_VARIANT_TYPE(colDirector->closestCollisionEntry()->attribute) == 1) {
708 move()->padType().setBit(KartMove::ePadType::BoostPanel);
709 }
710 }
711
713 if (*maskOut & jumpPadMask && colDirector->findClosestCollisionEntry(maskOut, jumpPadMask)) {
714 if ((!state()->isTouchingGround() || !state()->isJumpPad()) &&
715 !state()->isJumpPadMushroomVelYInc()) {
716 move()->padType().setBit(KartMove::ePadType::JumpPad);
717 closestColEntry = colDirector->closestCollisionEntry();
718 state()->setJumpPadVariant(KCL_VARIANT_TYPE(closestColEntry->attribute));
719 }
720 collisionData.bTrickable = true;
721 }
722}
723
727 auto *colDirector = Field::CollisionDirector::Instance();
728 if (colDirector->findClosestCollisionEntry(maskOut, KCL_TYPE_BIT(COL_TYPE_CANNON_TRIGGER))) {
729 state()->setCannonPointId(
730 KCL_VARIANT_TYPE(colDirector->closestCollisionEntry()->attribute));
731 state()->setCannonStart(true);
732 }
733}
734
744void KartCollide::applySomeFloorMoment(f32 down, f32 rate, CollisionGroup *hitboxGroup,
745 const EGG::Vector3f &forward, const EGG::Vector3f &nextDir, const EGG::Vector3f &speed,
746 bool b1, bool b2, bool b3) {
747 CollisionData &colData = hitboxGroup->collisionData();
748 if (!colData.bFloor) {
749 return;
750 }
751
752 f32 velDotFloorNrm = colData.vel.dot(colData.floorNrm);
753
754 if (velDotFloorNrm >= 0.0f) {
755 return;
756 }
757
758 EGG::Matrix34f rotMat;
759 rotMat.makeQ(dynamics()->mainRot());
760 EGG::Matrix34f tmp = rotMat.multiplyTo(dynamics()->invInertiaTensor());
761 EGG::Matrix34f rotMatTrans = rotMat.transpose();
762 tmp = tmp.multiplyTo(rotMatTrans);
763
764 EGG::Vector3f crossVec = colData.relPos.cross(colData.floorNrm);
765 crossVec = tmp.multVector(crossVec);
766 crossVec = crossVec.cross(colData.relPos);
767
768 f32 scalar = -velDotFloorNrm / (1.0f + colData.floorNrm.dot(crossVec));
769 EGG::Vector3f negSpeed = -speed;
770 crossVec = colData.floorNrm.cross(negSpeed);
771 crossVec = crossVec.cross(colData.floorNrm);
772
773 if (std::numeric_limits<f32>::epsilon() >= crossVec.squaredLength()) {
774 return;
775 }
776
777 crossVec.normalise();
778 f32 speedDot = std::min(0.0f, speed.dot(crossVec));
779 crossVec *= ((scalar * speedDot) / velDotFloorNrm);
780
781 auto [proj, rej] = crossVec.projAndRej(forward);
782
783 f32 projNorm = proj.length();
784 f32 rejNorm = rej.length();
785 f32 projNorm_ = projNorm;
786 f32 rejNorm_ = rejNorm;
787
788 f32 dVar7 = down * EGG::Mathf::abs(scalar);
789 if (dVar7 < EGG::Mathf::abs(projNorm)) {
790 projNorm_ = dVar7;
791 if (projNorm < 0.0f) {
792 projNorm_ = -down * EGG::Mathf::abs(scalar);
793 }
794 }
795
796 f32 dVar5 = rate * EGG::Mathf::abs(scalar);
797 if (dVar5 < EGG::Mathf::abs(rejNorm)) {
798 rejNorm_ = dVar5;
799 if (rejNorm < 0.0f) {
800 rejNorm_ = -rate * EGG::Mathf::abs(scalar);
801 }
802 }
803
804 proj.normalise();
805 rej.normalise();
806
807 proj *= projNorm_;
808 rej *= rejNorm_;
809
810 EGG::Vector3f projRejSum = proj + rej;
811 EGG::Vector3f projRejSumOrig = projRejSum;
812
813 if (!b1) {
814 projRejSum.x = 0.0f;
815 projRejSum.z = 0.0f;
816 }
817 if (!b2) {
818 projRejSum.y = 0.0f;
819 }
820
821 projRejSum = projRejSum.rej(nextDir);
822
823 dynamics()->setExtVel(dynamics()->extVel() + projRejSum);
824
825 if (b3) {
826 EGG::Vector3f rotation = colData.relPos.cross(projRejSumOrig);
827 EGG::Vector3f rotation2 = dynamics()->mainRot().rotateVectorInv(tmp.multVector(rotation));
828
829 EGG::Vector3f angVel = rotation2;
830 angVel.y = 0.0f;
831 if (!b1) {
832 angVel.x = 0.0f;
833 }
834 dynamics()->setAngVel0(dynamics()->angVel0() + angVel);
835 }
836}
837
842bool KartCollide::FUN_805B6A9C(CollisionData &collisionData, const Hitbox &hitbox,
843 EGG::BoundBox3f &minMax, EGG::Vector3f &relPos, s32 &count,
844 const Field::KCLTypeMask &maskOut, const Field::CollisionInfo &colInfo) {
845 if (maskOut & KCL_TYPE_WALL) {
846 if (!(maskOut & KCL_TYPE_FLOOR) && state()->isHWG() &&
847 state()->softWallSpeed().dot(colInfo.wallNrm) < 0.3f) {
848 return true;
849 }
850
851 bool skipWalls = false;
852
853 collisionData.wallNrm += colInfo.wallNrm;
854
855 if (maskOut & KCL_TYPE_ANY_INVISIBLE_WALL) {
856 collisionData.bInvisibleWall = true;
857
858 if (!(maskOut & KCL_TYPE_4010D000)) {
859 collisionData.bInvisibleWallOnly = true;
860
862 skipWalls = true;
863 }
864 }
865 }
866
867 if (!skipWalls) {
868 if (maskOut & KCL_TYPE_BIT(COL_TYPE_WALL_2)) {
869 collisionData.bWall3 = true;
870 } else {
871 collisionData.bWall = true;
872 }
873 }
874 }
875
876 if (maskOut & KCL_TYPE_FLOOR) {
877 collisionData.floorNrm += colInfo.floorNrm;
878 collisionData.bFloor = true;
879 }
880
881 EGG::Vector3f tangentOff = colInfo.tangentOff;
882 minMax.min = minMax.min.minimize(tangentOff);
883 minMax.max = minMax.max.maximize(tangentOff);
884 tangentOff.normalise();
885
886 relPos += hitbox.relPos();
887 relPos += -hitbox.radius() * tangentOff;
888 ++count;
889
890 return false;
891}
892
898 const EGG::Vector3f &posRel, s32 count) {
899 setPos(pos() + movement);
900
901 if (!collisionData.bFloor && (collisionData.bWall || collisionData.bWall3)) {
902 collisionData.movement = movement;
903 }
904
905 f32 rotFactor = 1.0f / static_cast<f32>(count);
906 EGG::Vector3f scaledRelPos = rotFactor * posRel;
907 collisionData.rotFactor *= rotFactor;
908
909 EGG::Vector3f scaledAngVel0 = dynamics()->angVel0Factor() * dynamics()->angVel0();
910 EGG::Vector3f local_48 = mainRot().rotateVectorInv(scaledRelPos);
911 EGG::Vector3f local_30 = scaledAngVel0.cross(local_48);
912 local_30 = mainRot().rotateVector(local_30);
913 local_30 += extVel();
914
915 collisionData.vel = local_30;
916 collisionData.relPos = scaledRelPos;
917
918 if (collisionData.bFloor) {
919 f32 intVelY = dynamics()->intVel().y;
920 if (intVelY > 0.0f) {
921 collisionData.vel.y += intVelY;
922 }
923 collisionData.floorNrm.normalise();
924 }
925}
926
928void KartCollide::startFloorMomentRate() {
929 m_floorMomentRate = 0.01f;
930}
931
933void KartCollide::calcFloorMomentRate() {
934 m_floorMomentRate = state()->isInAction() ? 0.01f : std::min(m_floorMomentRate + 0.01f, 0.8f);
935}
936
938Action KartCollide::handleReactNone(size_t /*idx*/) {
939 return Action::None;
940}
941
943Action KartCollide::handleReactWallAllSpeed(size_t idx) {
944 m_totalReactionWallNrm += Field::ObjectCollisionKart::GetHitDirection(idx);
945 m_surfaceFlags.setBit(eSurfaceFlags::ObjectWall);
946
947 return Action::None;
948}
949
951Action KartCollide::handleReactSpinAllSpeed(size_t /*idx*/) {
952 return Action::UNK_0;
953}
954
956Action KartCollide::handleReactSpinSomeSpeed(size_t /*idx*/) {
957 return Action::UNK_1;
958}
959
961Action KartCollide::handleReactFireSpin(size_t /*idx*/) {
962 return Action::UNK_9;
963}
964
966Action KartCollide::handleReactSmallLaunch(size_t /*idx*/) {
967 return Action::UNK_2;
968}
969
971Action KartCollide::handleReactKnockbackSomeSpeedLoseItem(size_t /*idx*/) {
972 return Action::UNK_3;
973}
974
976Action KartCollide::handleReactLaunchSpinLoseItem(size_t /*idx*/) {
977 return Action::UNK_6;
978}
979
981Action KartCollide::handleReactKnockbackBumpLoseItem(size_t /*idx*/) {
982 return Action::UNK_4;
983}
984
986Action KartCollide::handleReactLongCrushLoseItem(size_t /*idx*/) {
987 return Action::UNK_12;
988}
989
991Action KartCollide::handleReactHighLaunchLoseItem(size_t /*idx*/) {
992 return Action::UNK_8;
993}
994
996Action KartCollide::handleReactWeakWall(size_t /*idx*/) {
997 move()->setSpeed(move()->speed() * 0.82f);
998 return Action::None;
999}
1000
1002Action KartCollide::handleReactLaunchSpin(size_t /*idx*/) {
1003 return Action::UNK_5;
1004}
1005
1007Action KartCollide::handleReactWallSpark(size_t idx) {
1008 m_totalReactionWallNrm += Field::ObjectCollisionKart::GetHitDirection(idx);
1009 m_surfaceFlags.setBit(eSurfaceFlags::ObjectWall3);
1010
1011 return Action::None;
1012}
1013
1015Action KartCollide::handleReactShortCrushLoseItem(size_t /*idx*/) {
1016 return Action::UNK_14;
1017}
1018
1020Action KartCollide::handleReactCrushRespawn(size_t /*idx*/) {
1021 return Action::UNK_16;
1022}
1023
1025Action KartCollide::handleReactExplosionLoseItem(size_t /*idx*/) {
1026 return Action::UNK_7;
1027}
1028
1029std::array<KartCollide::ObjectCollisionHandler, 33> KartCollide::s_objectCollisionHandlers = {{
1038 &KartCollide::handleReactWallAllSpeed,
1039 &KartCollide::handleReactSpinAllSpeed,
1040 &KartCollide::handleReactSpinSomeSpeed,
1041 &KartCollide::handleReactFireSpin,
1043 &KartCollide::handleReactSmallLaunch,
1044 &KartCollide::handleReactKnockbackSomeSpeedLoseItem,
1045 &KartCollide::handleReactLaunchSpinLoseItem,
1046 &KartCollide::handleReactKnockbackBumpLoseItem,
1047 &KartCollide::handleReactLongCrushLoseItem,
1051 &KartCollide::handleReactHighLaunchLoseItem,
1053 &KartCollide::handleReactWeakWall,
1055 &KartCollide::handleReactLaunchSpin,
1056 &KartCollide::handleReactWallSpark,
1060 &KartCollide::handleReactShortCrushLoseItem,
1061 &KartCollide::handleReactCrushRespawn,
1062 &KartCollide::handleReactExplosionLoseItem,
1063}};
1064
1065} // 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_ATTRIBUTE_TYPE(x)
Computes the "Base Type" portion of the KCL flags. It's the lower 5 bits of the flag.
#define KCL_VARIANT_TYPE(x)
Extracts the "Variant" portion of the 2 byte KCL flag. It's the 3 bits before the "Bast Type".
#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:197
void makeQ(const Quatf &q)
Sets rotation matrix from quaternion.
Definition Matrix.cc:65
Vector3f multVector33(const Vector3f &vec) const
Multiplies a 3x3 matrix by a vector.
Definition Matrix.cc:243
Vector3f multVector(const Vector3f &vec) const
Multiplies a vector by a matrix.
Definition Matrix.cc:220
Matrix34f transpose() const
Transposes the 3x3 portion of the matrix.
Definition Matrix.cc:328
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:46
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:50
Vector3f rotateVectorInv(const Vector3f &vec) const
Rotates a vector on the inverse quat.
Definition Quat.cc:64
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:114
constexpr void makeAllZero()
Resets all the bits to zero.
Definition BitFlag.hh:185
constexpr TBitFlag< T, E > & setBit(Es... es)
Sets the corresponding bits for the provided enum values.
Definition BitFlag.hh:57
A 3D float vector.
Definition Vector.hh:83
f32 normalise()
Normalizes the vector and returns the original length.
Definition Vector.cc:44
f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
Definition Vector.hh:182
f32 squaredLength() const
The dot product between the vector and itself.
Definition Vector.hh:177
Vector3f rej(const Vector3f &rhs) const
The rejection of this vector onto rhs.
Definition Vector.hh:199
Vector3f maximize(const Vector3f &rhs) const
Returns a vector whose elements are the max of the elements of both vectors.
Definition Vector.cc:65
Vector3f minimize(const Vector3f &rhs) const
Returns a vector whose elements are the min of the elements of both vectors.
Definition Vector.cc:77
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 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 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