A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
KartSub.cc
1#include "KartSub.hh"
2
3#include "game/kart/KartAction.hh"
4#include "game/kart/KartBody.hh"
5#include "game/kart/KartCollide.hh"
6#include "game/kart/KartMove.hh"
7#include "game/kart/KartObject.hh"
8#include "game/kart/KartPullPath.hh"
9#include "game/kart/KartState.hh"
10#include "game/kart/KartSuspensionPhysics.hh"
11
12#include "game/field/BoxColManager.hh"
13#include "game/field/CollisionDirector.hh"
14
15#include "game/system/RaceConfig.hh"
16#include "game/system/RaceManager.hh"
17
18#include <egg/math/Math.hh>
19
20namespace Kinoko::Kart {
21
22KartSub::KartSub() = default;
23
25KartSub::~KartSub() {
26 EGG::egg_delete(m_collide);
27 EGG::egg_delete(m_state);
28 EGG::egg_delete(m_move);
29 EGG::egg_delete(m_action);
30}
31
33void KartSub::createSubsystems(bool isBike, const KartParam::Stats &stats) {
34 m_move = isBike ? static_cast<KartMove *>(EGG::egg_new<KartMoveBike>()) :
35 EGG::egg_new<KartMove>();
36 m_action = EGG::egg_new<KartAction>();
37 m_move->createSubsystems(stats);
38 m_state = EGG::egg_new<KartState>();
39 m_collide = EGG::egg_new<KartCollide>();
40}
41
45 pointers.collide = m_collide;
46 pointers.state = m_state;
47 pointers.move = m_move;
48 pointers.action = m_action;
49}
50
52void KartSub::init() {
53 resetPhysics();
54 body()->reset();
55 m_state->init();
56 move()->setTurnParams();
57 action()->init();
58 m_collide->init();
59}
60
62void KartSub::initAABB(KartAccessor &accessor, KartObject *object) {
63 f32 radius = 25.0f + collide()->boundingRadius();
64 f32 hardSpeedLimit = move()->hardSpeedLimit();
65
66 accessor.boxColUnit = Field::BoxColManager::Instance()->insertDriver(radius, hardSpeedLimit,
67 &pos(), true, object);
68}
69
71void KartSub::initPhysicsValues() {
72 physics()->updatePose();
73 collide()->resetHitboxes();
74}
75
77void KartSub::resetPhysics() {
78 physics()->reset();
79 physics()->updatePose();
80 collide()->resetHitboxes();
81
82 for (u16 wheelIdx = 0; wheelIdx < suspCount(); ++wheelIdx) {
83 suspensionPhysics(wheelIdx)->reset();
84 }
85 for (u16 tireIdx = 0; tireIdx < tireCount(); ++tireIdx) {
86 tirePhysics(tireIdx)->reset();
87 }
88 m_move->setKartSpeedLimit();
89
90 resizeAABB(1.0f);
91
93 m_someScale = 1.0f;
94 m_maxSuspOvertravel.setZero();
95 m_minSuspOvertravel.setZero();
96}
97
104 auto &status = KartObjectProxy::status();
105
106 if (status.onBit(eStatus::CannonStart)) {
107 physics()->hitboxGroup()->reset();
108 for (size_t i = 0; i < tireCount(); ++i) {
109 tirePhysics(i)->hitboxGroup()->reset();
110 }
111 move()->enterCannon();
112 }
113
114 state()->calc();
115
116 if (status.onBit(eStatus::TriggerRespawn)) {
117 setInertiaScale(EGG::Vector3f(1.0f, 1.0f, 1.0f));
118 resetPhysics();
119 state()->reset();
120 move()->setTurnParams();
121 move()->calcRespawnStart();
122 }
123
124 physics()->setPos(dynamics()->pos());
125 physics()->setVelocity(dynamics()->velocity());
126 dynamics()->setGravity(-1.3f);
127 dynamics()->setAngVel0YFactor(0.9f);
128
129 state()->calcInput();
130 move()->calc();
131 action()->calc();
132 collide()->pullPath().calc();
133
134 if (status.onBit(eStatus::SkipWheelCalc)) {
135 for (size_t tireIdx = 0; tireIdx < tireCount(); ++tireIdx) {
136 tirePhysics(tireIdx)->setLastPos(pos());
137 }
138 return;
139 }
140
141 tryEndHWG();
142
143 dynamics()->setTop(move()->up());
144
145 // Pertains to startslides / leaning in stage 0 and 1
146 const auto *raceManager = System::RaceManager::Instance();
147 if (!raceManager->isStageReached(System::RaceManager::Stage::Race)) {
148 dynamics()->setIntVel(EGG::Vector3f::zero);
149
150 EGG::Vector3f killExtVel = dynamics()->extVel();
151 if (isBike()) {
152 killExtVel = killExtVel.rej(move()->smoothedUp());
153 } else {
154 killExtVel.x = 0.0f;
155 killExtVel.z = 0.0f;
156 }
157
158 dynamics()->setExtVel(killExtVel);
159 }
160
161 f32 maxSpeed = move()->hardSpeedLimit();
162 physics()->calc(DT, maxSpeed, scale(), status.offBit(eStatus::TouchingGround));
163
164 move()->calcRejectRoad();
165
166 if (status.offBit(eStatus::InCannon)) {
167 collide()->calcHitboxes();
168 collisionGroup()->setHitboxScale(move()->totalScale());
169 }
170}
171
178 constexpr s16 SIDE_COLLISION_TIME = 5;
179
180 state()->resetEjection();
181
182 m_movingWaterCollisionCount = 0;
183 m_movingObjCollisionCount = 0;
184 m_floorCollisionCount = 0;
185 m_objVel.setZero();
186 m_maxSuspOvertravel.setZero();
187 m_minSuspOvertravel.setZero();
188
189 // The flag is really 0x1f, but we only care about objects.
190 Field::BoxColFlag flags;
191 flags.setBit(Field::eBoxColFlag::Drivable, Field::eBoxColFlag::Object);
192 boxColUnit()->search(flags);
193
194 collide()->calcObjectCollision();
195 dynamics()->setPos(pos() + collide()->tangentOff());
196
197 auto &status = KartObjectProxy::status();
198
199 if (status.onBit(eStatus::SoftWallPush)) {
200 const EGG::Vector3f &softWallSpeed = state()->softWallSpeed();
201 f32 speedFactor = 5.0f;
202 EGG::Vector3f effectiveSpeed;
203
204 if (status.onBit(eStatus::HWG)) {
205 speedFactor = 10.0f;
206 effectiveSpeed = softWallSpeed;
207 } else {
208 effectiveSpeed = softWallSpeed.perpInPlane(move()->smoothedUp(), true);
209 f32 speedDotUp = softWallSpeed.dot(move()->smoothedUp());
210 if (speedDotUp < 0.0f) {
211 speedFactor += -speedDotUp * 10.0f;
212 }
213 }
214
215 effectiveSpeed *= speedFactor * scale().y;
216 setPos(pos() + effectiveSpeed);
217 collide()->setMovement(collide()->movement() + effectiveSpeed);
218 }
219
220 auto &colData = collisionData();
221 if (colData.bWallAtLeftCloser || colData.bWallAtRightCloser || m_sideCollisionTimer > 0) {
222 EGG::Vector3f right = dynamics()->mainRot().rotateVector(EGG::Vector3f::ex);
223
224 if (colData.bWallAtLeftCloser || colData.bWallAtRightCloser) {
225 f32 sign = colData.bWallAtRightCloser ? 1.0f : -1.0f;
226 m_colPerpendicularity = sign * colData.colPerpendicularity;
227 m_sideCollisionTimer = SIDE_COLLISION_TIME;
228 }
229
230 EGG::Vector3f colPerpBounceDir = 2.0f * m_colPerpendicularity * scale().x * right;
231 colPerpBounceDir.y = 0.0f;
232 setPos(pos() + colPerpBounceDir);
233 collide()->setMovement(collide()->movement() + colPerpBounceDir);
234 }
235
237
238 body()->calcSinkDepth();
239
240 Field::CollisionDirector::Instance()->checkCourseColNarrScLocal(250.0f, pos(),
242
243 if (status.offBit(eStatus::InCannon)) {
244 if (status.offBit(eStatus::ZipperStick)) {
245 collide()->findCollision();
246 body()->calcTargetSinkDepth();
247
248 if (colData.bWall || colData.bWall3) {
249 collide()->setMovement(collide()->movement() + colData.movement);
250 }
251 } else {
252 colData.reset();
253 }
254
255 collide()->calcFloorEffect();
256 collide()->calcFloorMomentRate();
257
258 if (colData.bFloor) {
259 // Update floor count
260 addFloor(colData, false);
261 }
262 }
263
264 EGG::Vector3f forward = fullRot().rotateVector(EGG::Vector3f::ez);
265 m_someScale = std::max(scale().y, param()->stats().shrinkScale);
266
267 const EGG::Vector3f gravity(0.0f, -1.3f, 0.0f);
268 f32 speedFactor = 1.0f;
269 f32 handlingFactor = 0.0f;
270 for (u16 i = 0; i < suspCount(); ++i) {
271 const EGG::Matrix34f wheelMatrix = body()->wheelMatrix(i);
272 suspensionPhysics(i)->calcCollision(DT, gravity, wheelMatrix);
273
274 const CollisionData &colData = tirePhysics(i)->hitboxGroup()->collisionData();
275
276 speedFactor = std::min(speedFactor, colData.speedFactor);
277
278 if (colData.bFloor) {
279 handlingFactor += colData.rotFactor;
280 addFloor(colData, false);
281 }
282 }
283
284 if (status.offBit(eStatus::SkipWheelCalc)) {
285 EGG::Vector3f vehicleCompensation = m_maxSuspOvertravel + m_minSuspOvertravel;
286 dynamics()->setPos(dynamics()->pos() + vehicleCompensation);
287
288 if (!collisionData().bFloor) {
289 EGG::Vector3f relPos = EGG::Vector3f::zero;
290 EGG::Vector3f vel = EGG::Vector3f::zero;
291 EGG::Vector3f floorNrm = EGG::Vector3f::zero;
292 u32 count = 0;
293
294 for (u16 wheelIdx = 0; wheelIdx < tireCount(); ++wheelIdx) {
295 const WheelPhysics *wheelPhysics = tirePhysics(wheelIdx);
296 if (wheelPhysics->_74() == 0.0f) {
297 continue;
298 }
299
300 const CollisionData &colData = wheelPhysics->hitboxGroup()->collisionData();
301 relPos += colData.relPos;
302 vel += colData.vel;
303 floorNrm += colData.floorNrm;
304 ++count;
305 }
306
307 if (count > 0) {
308 f32 scalar = (1.0f / static_cast<f32>(count));
309 floorNrm.normalise();
310
311 collide()->setFloorColInfo(collisionData(), relPos * scalar, vel * scalar,
312 floorNrm);
313
314 collide()->FUN_80572F4C();
315 }
316 }
317
318 for (u16 wheelIdx = 0; wheelIdx < suspCount(); ++wheelIdx) {
319 suspensionPhysics(wheelIdx)->calcSuspension(forward, vehicleCompensation);
320 }
321
322 move()->calcHopPhysics();
323 }
324
325 if (status.onBit(eStatus::CollidingOffroad)) {
326 const auto &stats = param()->stats();
327 speedFactor = stats.kclSpeed[3];
328 handlingFactor = stats.kclRot[3];
329 }
330
331 move()->setKCLWheelSpeedFactor(speedFactor);
332 move()->setKCLWheelRotFactor(handlingFactor);
333
334 move()->setFloorCollisionCount(m_floorCollisionCount);
335
336 calcMovingObj();
337 calcMovingWater();
338
339 physics()->updatePose();
340
341 collide()->resetHitboxes();
342
343 // calcRotation() is only ever used for gfx rendering, so skip
344}
345
347void KartSub::resizeAABB(f32 radiusScale) {
348 f32 radius = radiusScale * collisionGroup()->boundingRadius();
349 boxColUnit()->resize(radius + 25.0f, move()->hardSpeedLimit());
350}
351
353void KartSub::addFloor(const CollisionData &colData, bool) {
354 ++m_floorCollisionCount;
355
356 if (colData.bHasRoadVel) {
357 ++m_movingObjCollisionCount;
358 m_objVel += colData.roadVelocity;
359 }
360
361 auto &status = KartObjectProxy::status();
362 if (colData.bMovingWaterMomentum || colData.bMovingWaterDecaySpeed) {
363 ++m_movingWaterCollisionCount;
364
365 status.changeBit(colData.bMovingWaterDecaySpeed, eStatus::MovingWaterDecaySpeed);
366 status.changeBit(colData.bMovingWaterDisableAccel, eStatus::DisableAcceleration);
367 status.changeBit(colData.bMovingWaterVertical, eStatus::MovingWaterVertical);
368 } else {
369 status.resetBit(eStatus::MovingWaterDecaySpeed, eStatus::DisableAcceleration,
370 eStatus::MovingWaterVertical);
371 }
372
373 status.changeBit(colData.bMovingWaterStickyRoad, eStatus::MovingWaterStickyRoad);
374}
375
377void KartSub::updateSuspOvertravel(const EGG::Vector3f &suspOvertravel) {
378 m_maxSuspOvertravel = m_maxSuspOvertravel.minimize(suspOvertravel);
379 m_minSuspOvertravel = m_minSuspOvertravel.maximize(suspOvertravel);
380}
381
383void KartSub::tryEndHWG() {
384 auto &status = KartObjectProxy::status();
385
386 if (status.onBit(eStatus::SoftWallUnlockRotation)) {
387 if (EGG::Mathf::abs(move()->speed()) > 15.0f ||
389 status.resetBit(eStatus::SoftWallUnlockRotation);
390 } else if (status.onBit(eStatus::TouchingGround)) {
391 if (EGG::Mathf::abs(componentXAxis().dot(EGG::Vector3f::ey)) > 0.8f) {
392 status.resetBit(eStatus::SoftWallUnlockRotation);
393 }
394 }
395 }
396
397 if (status.onBit(eStatus::HWG) && status.offBit(eStatus::SoftWallPush)) {
399 status.onBit(eStatus::AllWheelsCollision)) {
400 status.resetBit(eStatus::HWG);
401 }
402 }
403
404 if (status.offBit(eStatus::InAction)) {
405 dynamics()->setForceUpright(status.offBit(eStatus::SoftWallUnlockRotation));
406 }
407}
408
410void KartSub::calcMovingObj() {
411 if (m_movingObjCollisionCount == 0) {
412 f32 scalar = state()->airtime() < 20 ? 1.0f : 0.9f;
413 physics()->composeDecayingMovingObjVel(0.7f, scalar, m_floorCollisionCount != 0);
414 } else {
415 m_objVel *= 1.0f / static_cast<f32>(m_floorCollisionCount);
416 physics()->composeMovingObjVel(m_objVel, 0.2f);
417 }
418}
419
421void KartSub::calcMovingWater() {
422 constexpr f32 DECAY_FLOOR_SCALAR = 0.7f;
423 constexpr f32 DECAY_AIR_SCALAR = 0.5f;
424 constexpr f32 DECAY_KC_AIR_SCALAR = 0.3f;
425
426 auto &status = KartObjectProxy::status();
427 const auto &pullPath = collide()->pullPath();
428
429 if (m_movingWaterCollisionCount > 0) {
430 f32 ratio = static_cast<f32>(m_movingWaterCollisionCount) /
431 static_cast<f32>(m_floorCollisionCount);
432 EGG::Vector3f dir = pullPath.pullDirection().perpInPlane(move()->smoothedUp(), true);
433
434 if (status.offBit(eStatus::MovingWaterDecaySpeed)) {
435 EGG::Vector3f vel = pullPath.pullSpeed() * dir * ratio;
436 physics()->composeMovingRoadVel(vel, 0.2f);
437 } else {
438 EGG::Vector3f vel = pullPath.pullSpeed() * dir;
439 physics()->shiftDecayMovingRoadVel(vel, pullPath.maxPullSpeed());
440 }
441 } else {
442 f32 airScalar = status.onBit(eStatus::MovingWaterStickyRoad) ? DECAY_AIR_SCALAR : 1.0f;
443 if (System::RaceConfig::Instance()->raceScenario().course == Course::Koopa_Cape &&
444 status.onBit(eStatus::MovingWaterDecaySpeed)) {
445 airScalar = DECAY_KC_AIR_SCALAR;
446 }
447
448 physics()->decayMovingRoadVel(DECAY_FLOOR_SCALAR, airScalar, m_floorCollisionCount > 0);
449 }
450
451 if (status.onBit(eStatus::MovingWaterStickyRoad)) {
452 EGG::Vector3f dir = pullPath.pullDirection().perpInPlane(move()->smoothedUp(), true);
453 dir.y *= 50.0f;
454 const EGG::Vector3f &vel = dynamics()->movingRoadVel();
455 dynamics()->setMovingRoadVel(EGG::Vector3f(vel.x, dir.y, vel.z));
456 }
457}
458
459} // namespace Kinoko::Kart
#define KCL_TYPE_VEHICLE_INTERACTABLE
0xEFFFBDFF
A 3 x 4 matrix.
Definition Matrix.hh:10
constexpr TBitFlagExt< N, E > & changeBit(bool on, Es... es)
Changes the state of the corresponding bits for the provided enum values.
Definition BitFlag.hh:369
void updatePose()
Constructs a transformation matrix from rotation and position.
void calcPass0()
The first phase of physics computations on each frame.
Definition KartSub.cc:103
s16 m_sideCollisionTimer
Number of frames to apply movement from wall collision.
Definition KartSub.hh:50
void copyPointers(KartAccessor &pointers)
Called during static construction of KartObject to synchronize the pointers.
Definition KartSub.cc:44
f32 m_colPerpendicularity
Dot product between floor and colliding wall normals.
Definition KartSub.hh:51
void calcPass1()
The second phase of physics computations on each frame.Handles the second-half of physics calculation...
Definition KartSub.cc:177
static constexpr f32 DT
Delta time.
Definition KartSub.hh:54
Manages wheel physics and collision checks.
Pertains to kart-related functionality.
@ SoftWallPush
Tracks whether we are being pushed by a soft wall or HWG.
Definition Status.hh:84
@ HWG
Set when "Horizontal Wall Glitch" is active.
Definition Status.hh:86
@ SoftWallUnlockRotation
Set when we are in "Barrel Roll" state.
Definition Status.hh:85
@ ZipperStick
Set while mid-air and still influenced by the zipper.
Definition Status.hh:56
@ AllWheelsCollision
Set when all wheels are touching floor collision.
Definition Status.hh:25
@ AirtimeOver20
Set after 20 frames of airtime, resets on landing.
Definition Status.hh:28
@ Wall3Collision
Set when colliding with wall KCL COL_TYPE_WALL_2.
Definition Status.hh:18
@ TouchingGround
Set when any part of the vehicle is colliding with floor KCL.
Definition Status.hh:30
@ WallCollision
Set if we are colliding with a wall.
Definition Status.hh:19
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
constexpr Vector3f rej(const Vector3f &rhs) const
The rejection of this vector onto rhs.
Definition Vector.hh:229
constexpr f32 normalise()
Normalizes the vector and returns the original length.
Definition Vector.hh:278
constexpr f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
Definition Vector.hh:206
constexpr Vector3f perpInPlane(const EGG::Vector3f &rhs, bool normalise) const
Calculates the orthogonal vector, based on the plane defined by this vector and rhs.
Definition Vector.hh:346
Information about the current collision and its properties.
bool bFloor
Set if colliding with KCL which satisfies KCL_TYPE_FLOOR.
Shared between classes who inherit KartObjectProxy so they can access one another.
Various character/vehicle-related handling and speed stats.
Definition KartParam.hh:79