A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
KartHalfPipe.cc
1#include "KartHalfPipe.hh"
2
3#include "game/kart/KartCollide.hh"
4#include "game/kart/KartDynamics.hh"
5#include "game/kart/KartMove.hh"
6#include "game/kart/KartParam.hh"
7#include "game/kart/KartPhysics.hh"
8#include "game/kart/KartState.hh"
9
10#include <egg/math/Math.hh>
11
12namespace Kart {
13
15KartHalfPipe::KartHalfPipe() : m_prevPos(EGG::Vector3f::zero) {}
16
18KartHalfPipe::~KartHalfPipe() = default;
19
21void KartHalfPipe::reset() {
22 m_stunt = StuntType::None;
23 m_touchingZipper = false;
24 m_timer = 0;
25}
26
28void KartHalfPipe::calc() {
29 constexpr s16 LANDING_BOOST_DELAY = 3;
30
31 auto &status = KartObjectProxy::status();
32
33 if (state()->airtime() > 15 && status.onBit(eStatus::OverZipper)) {
34 m_timer = LANDING_BOOST_DELAY;
35 }
36
37 bool isLanding = status.onBit(eStatus::HalfPipeRamp) && m_timer <= 0;
38
39 calcTrick();
40
41 if (status.offBit(eStatus::InAction) &&
42 collide()->surfaceFlags().offBit(KartCollide::eSurfaceFlags::StopHalfPipeState)) {
43 if (m_touchingZipper && status.onBit(eStatus::AirStart)) {
44 dynamics()->setExtVel(EGG::Vector3f::zero);
45 status.setBit(eStatus::OverZipper);
46
47 EGG::Vector3f upXZ = move()->up();
48 upXZ.y = 0.0f;
49 upXZ.normalise();
50 EGG::Vector3f up = move()->dir().perpInPlane(upXZ, true);
51
52 EGG::Vector3f local_64 = up.cross(bodyUp().perpInPlane(up, true));
53 m_nextSign = local_64.dot(EGG::Vector3f::ey) > 0.0f ? 1.0f : -1.0f;
54
55 EGG::Vector3f velNorm = velocity();
56 velNorm.normalise();
57 EGG::Vector3f rot = dynamics()->mainRot().rotateVectorInv(velNorm);
58
59 m_rot.makeVectorRotation(rot, EGG::Vector3f::ez);
60 m_prevPos = prevPos();
61
62 calcLanding(false);
63
64 f32 scaledDir = std::min(65.0f, move()->dir().y * move()->speed());
65 m_attemptedTrickTimer = std::max<s32>(0, scaledDir * 2.0f / 1.3f - 1.0f);
66 } else if (status.onBit(eStatus::OverZipper)) {
67 dynamics()->setGravity(-1.3f);
68
69 EGG::Vector3f side = mainRot().rotateVector(EGG::Vector3f::ez);
70 EGG::Vector3f velNorm = velocity();
71 velNorm.normalise();
72
73 EGG::Quatf sideRot;
74 sideRot.makeVectorRotation(side, velNorm);
75 sideRot = sideRot.multSwap(mainRot()).multSwap(m_rot);
76
77 f32 t = move()->calcSlerpRate(DEG2RAD360, mainRot(), sideRot);
78 EGG::Quatf slerp = mainRot().slerpTo(sideRot, t);
79 dynamics()->setFullRot(slerp);
80 dynamics()->setMainRot(slerp);
81
82 --m_attemptedTrickTimer;
83
84 calcRot();
85 calcLanding(false);
86 } else if (status.onBit(eStatus::HalfPipeRamp)) {
87 calcLanding(true);
88 } else {
89 status.resetBit(eStatus::HalfpipeMidair);
90 }
91 }
92
93 m_timer = std::max(0, m_timer - 1);
94 m_touchingZipper = isLanding;
95}
96
98void KartHalfPipe::calcTrick() {
99 constexpr s16 TRICK_COOLDOWN = 10;
100
101 auto &trick = inputs()->currentState().trick;
102
103 if (trick != System::Trick::None) {
104 m_nextTimer = TRICK_COOLDOWN;
105 m_trick = trick;
106 }
107
108 const auto &status = KartObjectProxy::status();
109
110 if (status.onBit(eStatus::OverZipper)) {
111 if (status.offBit(eStatus::ZipperTrick) && m_nextTimer > 0 && state()->airtime() > 3 &&
112 state()->airtime() < 10) {
113 activateTrick(m_attemptedTrickTimer, m_trick);
114 }
115 }
116
117 m_nextTimer = std::max(0, m_nextTimer - 1);
118}
119
121void KartHalfPipe::calcRot() {
122 if (m_stunt == StuntType::None) {
123 return;
124 }
125
126 m_stuntManager.calcAngle();
127
128 f32 angle = m_rotSign * (DEG2RAD * m_stuntManager.angle);
129
130 switch (m_stunt) {
131 case StuntType::Side360:
132 case StuntType::Side720:
133 m_stuntRot.setRPY(0.0f, angle, 0.0f);
134 break;
135 case StuntType::Backside: {
136 auto rpy = EGG::Quatf::FromRPY(0.0f, DEG2RAD * (0.25f * -m_rotSign * m_stuntManager.angle),
137 0.0f);
138 EGG::Vector3f rot = rpy.rotateVector(EGG::Vector3f::ez);
139 m_stuntRot.setAxisRotation(angle, rot);
140 } break;
141 case StuntType::Frontside: {
142 EGG::Quatf rpy = EGG::Quatf::FromRPY(0.0f, 0.0f,
143 DEG2RAD * (0.2f * -m_rotSign * m_stuntManager.angle));
144 EGG::Vector3f rot = rpy.rotateVector(EGG::Vector3f::ey);
145 m_stuntRot.setAxisRotation(angle, rot);
146 } break;
147 case StuntType::Frontflip:
148 m_stuntRot.setRPY(m_rotSign * angle, 0.0f, 0.0f);
149 break;
150 case StuntType::Backflip:
151 m_stuntRot.setRPY(-m_rotSign * angle, 0.0f, 0.0f);
152 break;
153 default:
154 break;
155 }
156
157 physics()->composeStuntRot(m_stuntRot);
158}
159
161void KartHalfPipe::calcLanding(bool notAirborne) {
162 constexpr f32 LANDING_RADIUS = 150.0f;
163 constexpr f32 PREVIOUS_RADIUS = 200.0f;
164 constexpr f32 MIDAIR_RADIUS = 50.0f;
165 constexpr f32 WALL_RADIUS = 100.0f;
166
167 constexpr f32 COS_PI_OVER_4 = 0.707f;
168
169 Field::CollisionInfo colInfo;
170 Field::CollisionInfo colInfo2;
171 Field::KCLTypeMask maskOut;
172 EGG::Vector3f pos;
173 EGG::Vector3f upLocal;
174
175 auto &status = KartObjectProxy::status();
176 Field::KCLTypeMask mask = KCL_TYPE_ANY_INVISIBLE_WALL;
177 bool overZipper = status.onBit(eStatus::OverZipper);
178 if (!overZipper) {
179 if (notAirborne && velocity().y < 0.0f) {
180 mask = KCL_NONE;
181 } else {
183 }
184 }
185
186 status.resetBit(eStatus::HalfpipeMidair);
187
188 EGG::Vector3f prevPos = m_prevPos + EGG::Vector3f::ey * PREVIOUS_RADIUS;
189
190 bool hasDriverFloorCollision = move()->calcZipperCollision(LANDING_RADIUS, bsp().initialYPos,
191 pos, upLocal, prevPos, &colInfo, &maskOut, KCL_TYPE_DRIVER_FLOOR);
192
193 prevPos = hasDriverFloorCollision ? EGG::Vector3f::inf : prevPos;
194
195 if (overZipper) {
196 if (!move()->calcZipperCollision(MIDAIR_RADIUS, bsp().initialYPos, pos, upLocal, prevPos,
197 &colInfo2, &maskOut, mask)) {
198 mask |= KCL_TYPE_DRIVER_WALL;
199 }
200 }
201
202 if (move()->calcZipperCollision(WALL_RADIUS, bsp().initialYPos, pos, upLocal, prevPos,
203 &colInfo2, &maskOut, mask)) {
204 if ((maskOut & ~KCL_TYPE_BIT(COL_TYPE_HALFPIPE_INVISIBLE_WALL)) == 0) {
205 status.setBit(eStatus::HalfpipeMidair);
206 }
207
208 EGG::Vector3f up = move()->up();
209 move()->setUp(up + (colInfo2.wallNrm - up) * 0.2f);
210 move()->setSmoothedUp(move()->up());
211
212 f32 yScale = bsp().initialYPos * scale().y;
213 EGG::Vector3f newPos =
214 pos + colInfo2.tangentOff + -WALL_RADIUS * colInfo2.wallNrm + yScale * upLocal;
215 newPos.y += move()->hopPosY();
216
217 dynamics()->setPos(newPos);
218 move()->setDir(move()->dir().perpInPlane(move()->up(), true));
219 move()->setVel1Dir(move()->dir());
220
221 if (overZipper) {
222 status.setBit(eStatus::ZipperStick);
223 }
224
225 m_prevPos = newPos;
226 } else {
227 if (overZipper) {
228 status.resetBit(eStatus::ZipperStick);
229 }
230 }
231
232 if (!hasDriverFloorCollision || status.onBit(eStatus::HalfpipeMidair) ||
233 state()->airtime() <= 5) {
234 return;
235 }
236
237 if (colInfo.floorNrm.dot(EGG::Vector3f::ey) <= COS_PI_OVER_4) {
238 return;
239 }
240
241 if (status.onBit(eStatus::OverZipper)) {
242 status.resetBit(eStatus::ZipperStick);
243 }
244}
245
247void KartHalfPipe::activateTrick(s32 duration, System::Trick trick) {
248 if (duration < 51 || trick == System::Trick::None) {
249 m_stunt = StuntType::None;
250 } else {
251 m_rotSign = m_nextSign;
252 bool timerThreshold = duration > 70;
253
254 switch (trick) {
255 case System::Trick::Up:
256 m_stunt = timerThreshold ? StuntType::Backside : StuntType::Backflip;
257 break;
258 case System::Trick::Down:
259 m_stunt = timerThreshold ? StuntType::Frontside : StuntType::Frontflip;
260 break;
261 case System::Trick::Left:
262 case System::Trick::Right:
263 m_stunt = timerThreshold ? StuntType::Side720 : StuntType::Side360;
264 m_rotSign = trick == System::Trick::Left ? 1.0f : -1.0f;
265 break;
266 default:
267 break;
268 }
269
270 m_stuntManager.setProperties(static_cast<size_t>(m_stunt));
271
272 status().setBit(eStatus::ZipperTrick);
273 }
274
275 m_stuntRot = EGG::Quatf::ident;
276}
277
279void KartHalfPipe::end(bool boost) {
280 auto &status = KartObjectProxy::status();
281 bool overZipper = status.onBit(eStatus::OverZipper);
282
283 if (overZipper && state()->airtime() > 5 && boost) {
284 move()->activateZipperBoost();
285 }
286
287 if (status.onBit(eStatus::ZipperTrick)) {
288 physics()->composeDecayingStuntRot(m_stuntRot);
289 }
290
291 if (overZipper) {
292 move()->setDir(mainRot().rotateVector(EGG::Vector3f::ez));
293 move()->setVel1Dir(move()->dir());
294 }
295
296 status.resetBit(eStatus::OverZipper, eStatus::ZipperTrick, eStatus::ZipperStick,
297 eStatus::HalfpipeMidair);
298
299 m_stunt = StuntType::None;
300}
301
302void KartHalfPipe::StuntManager::calcAngle() {
303 if (finalAngle * properties.finalAngleScalar < angle) {
304 angleDelta = std::max(properties.angleDeltaMin, angleDelta * angleDeltaFactor);
305 angleDeltaFactor = std::max(properties.angleDeltaFactorMin,
306 angleDeltaFactor - properties.angleDeltaFactorDecr);
307 }
308
309 angle = std::min(finalAngle, angle + angleDelta);
310}
311
312void KartHalfPipe::StuntManager::setProperties(size_t idx) {
313 static constexpr std::array<StuntProperties, 6> STUNT_PROPERTIES = {{
314 {6.0f, 2.5f, 0.955f, 0.01f, 0.7f, 360.0f},
315 {7.0f, 3.0f, 0.955f, 0.01f, 0.7f, 360.0f},
316 {7.0f, 3.0f, 0.95f, 0.01f, 0.7f, 360.0f},
317 {12.0f, 2.5f, 0.955f, 0.01f, 0.0f, 360.0f},
318 {4.0f, 4.0f, 0.98f, 0.01f, 0.0f, 360.0f},
319 {9.0f, 3.0f, 0.92f, 0.01f, 0.8f, 720.0f},
320 }};
321
322 ASSERT(idx < STUNT_PROPERTIES.size());
323
324 properties = STUNT_PROPERTIES[idx];
325 finalAngle = properties.finalAngle;
326 angleDelta = properties.angleDelta;
327 angleDeltaFactorDecr = properties.angleDeltaFactorDecr;
328 angle = 0.0f;
329 angleDeltaFactor = 1.0f;
330}
331
332} // namespace Kart
#define KCL_TYPE_DRIVER_WALL
0xC010B000
@ COL_TYPE_HALFPIPE_INVISIBLE_WALL
Invisible wall after a half-pipe jump, like in BC.
#define KCL_TYPE_DRIVER_FLOOR
0x20E80DFF - Any KCL that the player can drive on.
#define KCL_TYPE_BIT(x)
#define KCL_TYPE_ANY_INVISIBLE_WALL
0x90002000
EGG core library.
Definition Archive.cc:6
Pertains to kart-related functionality.
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
Quatf slerpTo(const Quatf &q2, f32 t) const
Performs spherical linear interpolation.
Definition Quat.cc:84
void makeVectorRotation(const Vector3f &from, const Vector3f &to)
Captures rotation between two vectors.
Definition Quat.cc:40
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
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.cc:114