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() = default;
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 if (state()->airtime() > 15 && state()->isOverZipper()) {
32 m_timer = LANDING_BOOST_DELAY;
33 }
34
35 bool isLanding = state()->isHalfPipeRamp() && m_timer <= 0;
36
37 calcTrick();
38
39 if (!state()->isInAction() &&
40 collide()->surfaceFlags().offBit(KartCollide::eSurfaceFlags::StopHalfPipeState)) {
41 if (m_touchingZipper && state()->isAirStart()) {
42 dynamics()->setExtVel(EGG::Vector3f::zero);
43 state()->setOverZipper(true);
44
45 EGG::Vector3f upXZ = move()->up();
46 upXZ.y = 0.0f;
47 upXZ.normalise();
48 EGG::Vector3f up = move()->dir().perpInPlane(upXZ, true);
49
50 EGG::Vector3f local_64 = up.cross(bodyUp().perpInPlane(up, true));
51 m_nextSign = local_64.dot(EGG::Vector3f::ey) > 0.0f ? 1.0f : -1.0f;
52
53 EGG::Vector3f velNorm = velocity();
54 velNorm.normalise();
55 EGG::Vector3f rot = dynamics()->mainRot().rotateVectorInv(velNorm);
56
57 m_rot.makeVectorRotation(rot, EGG::Vector3f::ez);
58 m_prevPos = prevPos();
59
60 calcLanding(false);
61
62 f32 scaledDir = std::min(65.0f, move()->dir().y * move()->speed());
63 m_attemptedTrickTimer = std::max<s32>(0, scaledDir * 2.0f / 1.3f - 1.0f);
64 } else if (state()->isOverZipper()) {
65 dynamics()->setGravity(-1.3f);
66
67 EGG::Vector3f side = mainRot().rotateVector(EGG::Vector3f::ez);
68 EGG::Vector3f velNorm = velocity();
69 velNorm.normalise();
70
71 EGG::Quatf sideRot;
72 sideRot.makeVectorRotation(side, velNorm);
73 sideRot = sideRot.multSwap(mainRot()).multSwap(m_rot);
74
75 f32 t = move()->calcSlerpRate(DEG2RAD360, mainRot(), sideRot);
76 EGG::Quatf slerp = mainRot().slerpTo(sideRot, t);
77 dynamics()->setFullRot(slerp);
78 dynamics()->setMainRot(slerp);
79
81
82 calcRot();
83 calcLanding(false);
84 } else if (state()->isHalfPipeRamp()) {
85 calcLanding(true);
86 }
87 }
88
89 m_timer = std::max(0, m_timer - 1);
90 m_touchingZipper = isLanding;
91}
92
94void KartHalfPipe::calcTrick() {
95 constexpr s16 TRICK_COOLDOWN = 10;
96
97 auto &trick = inputs()->currentState().trick;
98
99 if (trick != System::Trick::None) {
100 m_nextTimer = TRICK_COOLDOWN;
101 m_trick = trick;
102 }
103
104 if (state()->isOverZipper()) {
105 if (!state()->isZipperTrick() && m_nextTimer > 0 && state()->airtime() > 3 &&
106 state()->airtime() < 10) {
107 activateTrick(m_attemptedTrickTimer, m_trick);
108 }
109 }
110
111 m_nextTimer = std::max(0, m_nextTimer - 1);
112}
113
115void KartHalfPipe::calcRot() {
116 if (m_stunt == StuntType::None) {
117 return;
118 }
119
120 m_stuntManager.calcAngle();
121
122 f32 angle = m_rotSign * (DEG2RAD * m_stuntManager.angle);
123
124 switch (m_stunt) {
125 case StuntType::Side360:
126 case StuntType::Side720:
127 m_stuntRot.setRPY(0.0f, angle, 0.0f);
128 break;
129 case StuntType::Backside: {
130 auto rpy = EGG::Quatf::FromRPY(0.0f, DEG2RAD * (0.25f * -m_rotSign * m_stuntManager.angle),
131 0.0f);
132 EGG::Vector3f rot = rpy.rotateVector(EGG::Vector3f::ez);
133 m_stuntRot.setAxisRotation(angle, rot);
134 } break;
135 case StuntType::Frontside: {
136 EGG::Quatf rpy = EGG::Quatf::FromRPY(0.0f, 0.0f,
137 DEG2RAD * (0.2f * -m_rotSign * m_stuntManager.angle));
138 EGG::Vector3f rot = rpy.rotateVector(EGG::Vector3f::ey);
139 m_stuntRot.setAxisRotation(angle, rot);
140 } break;
141 case StuntType::Frontflip:
142 m_stuntRot.setRPY(m_rotSign * angle, 0.0f, 0.0f);
143 break;
144 case StuntType::Backflip:
145 m_stuntRot.setRPY(-m_rotSign * angle, 0.0f, 0.0f);
146 break;
147 default:
148 break;
149 }
150
151 physics()->composeStuntRot(m_stuntRot);
152}
153
155void KartHalfPipe::calcLanding(bool) {
156 constexpr f32 LANDING_RADIUS = 150.0f;
157 constexpr f32 PREVIOUS_RADIUS = 200.0f;
158 constexpr f32 MIDAIR_RADIUS = 50.0f;
159 constexpr f32 WALL_RADIUS = 100.0f;
160
161 constexpr f32 COS_PI_OVER_4 = 0.707f;
162
163 Field::CollisionInfo colInfo;
164 Field::CollisionInfo colInfo2;
165 Field::KCLTypeMask maskOut;
166 EGG::Vector3f pos;
167 EGG::Vector3f upLocal;
168
169 Field::KCLTypeMask mask = state()->isOverZipper() ?
172 EGG::Vector3f prevPos = m_prevPos + EGG::Vector3f::ey * PREVIOUS_RADIUS;
173
174 bool hasDriverFloorCollision = move()->calcZipperCollision(LANDING_RADIUS, bsp().initialYPos,
175 pos, upLocal, prevPos, &colInfo, &maskOut, KCL_TYPE_DRIVER_FLOOR);
176
177 prevPos = hasDriverFloorCollision ? EGG::Vector3f::inf : prevPos;
178
179 if (state()->isOverZipper()) {
180 if (!move()->calcZipperCollision(MIDAIR_RADIUS, bsp().initialYPos, pos, upLocal, prevPos,
181 &colInfo2, &maskOut, mask)) {
182 mask |= KCL_TYPE_DRIVER_WALL;
183 }
184 }
185
186 if (move()->calcZipperCollision(WALL_RADIUS, bsp().initialYPos, pos, upLocal, prevPos,
187 &colInfo2, &maskOut, mask)) {
188 EGG::Vector3f up = move()->up();
189 move()->setUp(up + (colInfo2.wallNrm - up) * 0.2f);
190 move()->setSmoothedUp(move()->up());
191
192 f32 yScale = bsp().initialYPos * scale().y;
193 EGG::Vector3f newPos =
194 pos + colInfo2.tangentOff + -WALL_RADIUS * colInfo2.wallNrm + yScale * upLocal;
195 newPos.y += move()->hopPosY();
196
197 dynamics()->setPos(newPos);
198 move()->setDir(move()->dir().perpInPlane(move()->up(), true));
199 move()->setVel1Dir(move()->dir());
200
201 if (state()->isOverZipper()) {
202 state()->setZipperStick(true);
203 }
204
205 m_prevPos = newPos;
206 } else {
207 if (state()->isOverZipper()) {
208 state()->setZipperStick(false);
209 }
210 }
211
212 if (!hasDriverFloorCollision || state()->airtime() <= 5) {
213 return;
214 }
215
216 if (colInfo.floorNrm.dot(EGG::Vector3f::ey) <= COS_PI_OVER_4) {
217 return;
218 }
219
220 if (state()->isOverZipper()) {
221 state()->setZipperStick(false);
222 }
223}
224
226void KartHalfPipe::activateTrick(s32 duration, System::Trick trick) {
227 if (duration < 51 || trick == System::Trick::None) {
228 m_stunt = StuntType::None;
229 } else {
230 m_rotSign = m_nextSign;
231 bool timerThreshold = duration > 70;
232
233 switch (trick) {
234 case System::Trick::Up:
235 m_stunt = timerThreshold ? StuntType::Backside : StuntType::Backflip;
236 break;
237 case System::Trick::Down:
238 m_stunt = timerThreshold ? StuntType::Frontside : StuntType::Frontflip;
239 break;
240 case System::Trick::Left:
241 case System::Trick::Right:
242 m_stunt = timerThreshold ? StuntType::Side720 : StuntType::Side360;
243 m_rotSign = trick == System::Trick::Left ? 1.0f : -1.0f;
244 break;
245 default:
246 break;
247 }
248
249 m_stuntManager.setProperties(static_cast<size_t>(m_stunt));
250
251 state()->setZipperTrick(true);
252 }
253
254 m_stuntRot = EGG::Quatf::ident;
255}
256
258void KartHalfPipe::end(bool boost) {
259 if (state()->isOverZipper() && state()->airtime() > 5 && boost) {
260 move()->activateZipperBoost();
261 }
262
263 if (state()->isZipperTrick()) {
264 physics()->composeDecayingStuntRot(m_stuntRot);
265 }
266
267 if (state()->isOverZipper()) {
268 move()->setDir(mainRot().rotateVector(EGG::Vector3f::ez));
269 move()->setVel1Dir(move()->dir());
270 }
271
272 state()->setOverZipper(false);
273 state()->setZipperTrick(false);
274 state()->setZipperStick(false);
275
276 m_stunt = StuntType::None;
277}
278
279void KartHalfPipe::StuntManager::calcAngle() {
280 if (finalAngle * properties.finalAngleScalar < angle) {
281 angleDelta = std::max(properties.angleDeltaMin, angleDelta * angleDeltaFactor);
282 angleDeltaFactor = std::max(properties.angleDeltaFactorMin,
283 angleDeltaFactor - properties.angleDeltaFactorDecr);
284 }
285
286 angle = std::min(finalAngle, angle + angleDelta);
287}
288
289void KartHalfPipe::StuntManager::setProperties(size_t idx) {
290 static constexpr std::array<StuntProperties, 6> STUNT_PROPERTIES = {{
291 {6.0f, 2.5f, 0.955f, 0.01f, 0.7f, 360.0f},
292 {7.0f, 3.0f, 0.955f, 0.01f, 0.7f, 360.0f},
293 {7.0f, 3.0f, 0.95f, 0.01f, 0.7f, 360.0f},
294 {12.0f, 2.5f, 0.955f, 0.01f, 0.0f, 360.0f},
295 {4.0f, 4.0f, 0.98f, 0.01f, 0.0f, 360.0f},
296 {9.0f, 3.0f, 0.92f, 0.01f, 0.8f, 720.0f},
297 }};
298
299 ASSERT(idx < STUNT_PROPERTIES.size());
300
301 properties = STUNT_PROPERTIES[idx];
302 finalAngle = properties.finalAngle;
303 angleDelta = properties.angleDelta;
304 angleDeltaFactorDecr = properties.angleDeltaFactorDecr;
305 angle = 0.0f;
306 angleDeltaFactor = 1.0f;
307}
308
309} // 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
s32 m_attemptedTrickTimer
When attempting a trick, tracks how long the animation would be.
EGG::Vector3f bodyUp() const
Returns the second column of the rotation matrix, which is the "up" direction.
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 setAxisRotation(f32 angle, const Vector3f &axis)
Set the quat given angle and axis.
Definition Quat.cc:111
Vector3f rotateVectorInv(const Vector3f &vec) const
Rotates a vector on the inverse quat.
Definition Quat.cc:69
void setRPY(const Vector3f &rpy)
Sets roll, pitch, and yaw.
Definition Quat.cc:7
void makeVectorRotation(const Vector3f &from, const Vector3f &to)
Captures rotation between two vectors.
Definition Quat.cc:40
A 3D float vector.
Definition Vector.hh:87
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:186
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:96