1#include "KartHalfPipe.hh"
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"
10#include "game/field/CourseColMgr.hh"
12#include <egg/math/Math.hh>
17KartHalfPipe::KartHalfPipe() =
default;
20KartHalfPipe::~KartHalfPipe() =
default;
23void KartHalfPipe::reset() {
24 m_stunt = StuntType::None;
25 m_touchingZipper =
false;
30void KartHalfPipe::calc() {
31 constexpr s16 LANDING_BOOST_DELAY = 3;
33 if (state()->airtime() > 15 && state()->isOverZipper()) {
34 m_timer = LANDING_BOOST_DELAY;
37 bool isLanding = state()->isHalfPipeRamp() && m_timer <= 0;
41 if (!state()->isInAction() &&
42 collide()->surfaceFlags().offBit(KartCollide::eSurfaceFlags::StopHalfPipeState) &&
43 m_touchingZipper && state()->isAirStart()) {
44 dynamics()->setExtVel(EGG::Vector3f::zero);
45 state()->setOverZipper(
true);
53 m_nextSign = local_64.
dot(EGG::Vector3f::ey) > 0.0f ? 1.0f : -1.0f;
60 m_prevPos = prevPos();
64 f32 scaledDir = std::min(65.0f, move()->dir().y * move()->speed());
67 if (state()->isOverZipper()) {
68 dynamics()->setGravity(-1.3f);
76 sideRot = sideRot.multSwap(mainRot()).multSwap(m_rot);
78 f32 t = move()->calcSlerpRate(DEG2RAD360, mainRot(), sideRot);
80 dynamics()->setFullRot(slerp);
81 dynamics()->setMainRot(slerp);
88 if (state()->isHalfPipeRamp()) {
94 m_timer = std::max(0, m_timer - 1);
95 m_touchingZipper = isLanding;
99void KartHalfPipe::calcTrick() {
100 constexpr s16 TRICK_COOLDOWN = 10;
102 auto &trick = inputs()->currentState().trick;
104 if (trick != System::Trick::None) {
105 m_nextTimer = TRICK_COOLDOWN;
109 if (state()->isOverZipper()) {
110 if (!state()->isZipperTrick() && m_nextTimer > 0 && state()->airtime() > 3 &&
111 state()->airtime() < 10) {
116 m_nextTimer = std::max(0, m_nextTimer - 1);
120void KartHalfPipe::calcRot() {
121 if (m_stunt == StuntType::None) {
125 m_stuntManager.calcAngle();
127 f32 angle = m_rotSign * (DEG2RAD * m_stuntManager.angle);
130 case StuntType::Side360:
131 case StuntType::Side720:
134 case StuntType::Backside: {
137 EGG::Vector3f(0.0f, DEG2RAD * (0.25f * -m_rotSign * m_stuntManager.angle), 0.0f));
141 case StuntType::Frontside: {
143 rpy.
setRPY(
EGG::Vector3f(0.0f, 0.0f, DEG2RAD * (0.2f * -m_rotSign * m_stuntManager.angle)));
147 case StuntType::Frontflip:
150 case StuntType::Backflip:
157 physics()->composeStuntRot(m_stuntRot);
161void KartHalfPipe::calcLanding(
bool) {
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;
167 constexpr f32 COS_PI_OVER_4 = 0.707f;
178 EGG::Vector3f prevPos = m_prevPos + EGG::Vector3f::ey * PREVIOUS_RADIUS;
180 bool hasDriverFloorCollision = move()->calcZipperCollision(LANDING_RADIUS, bsp().initialYPos,
183 prevPos = hasDriverFloorCollision ? EGG::Vector3f::inf : prevPos;
185 if (state()->isOverZipper()) {
186 if (!move()->calcZipperCollision(MIDAIR_RADIUS, bsp().initialYPos, pos, upLocal, prevPos,
187 &colInfo2, &maskOut, mask)) {
192 if (move()->calcZipperCollision(WALL_RADIUS, bsp().initialYPos, pos, upLocal, prevPos,
193 &colInfo2, &maskOut, mask)) {
195 move()->setUp(up + (colInfo2.wallNrm - up) * 0.2f);
196 move()->setSmoothedUp(move()->up());
198 f32 yScale = bsp().initialYPos * scale().y;
200 pos + colInfo2.tangentOff + -WALL_RADIUS * colInfo2.wallNrm + yScale * upLocal;
201 newPos.y += move()->hopPosY();
203 dynamics()->setPos(newPos);
204 move()->setDir(move()->dir().perpInPlane(move()->up(),
true));
205 move()->setVel1Dir(move()->dir());
207 if (state()->isOverZipper()) {
208 state()->setZipperStick(
true);
213 if (state()->isOverZipper()) {
214 state()->setZipperStick(
false);
218 if (!hasDriverFloorCollision || state()->airtime() <= 5) {
222 if (colInfo.floorNrm.
dot(EGG::Vector3f::ey) <= COS_PI_OVER_4) {
226 if (state()->isOverZipper()) {
227 state()->setZipperStick(
false);
232void KartHalfPipe::activateTrick(s32 duration, System::Trick trick) {
233 if (duration < 51 || trick == System::Trick::None) {
234 m_stunt = StuntType::None;
236 m_rotSign = m_nextSign;
237 bool timerThreshold = duration > 70;
240 case System::Trick::Up:
241 m_stunt = timerThreshold ? StuntType::Backside : StuntType::Backflip;
243 case System::Trick::Down:
244 m_stunt = timerThreshold ? StuntType::Frontside : StuntType::Frontflip;
246 case System::Trick::Left:
247 case System::Trick::Right:
248 m_stunt = timerThreshold ? StuntType::Side720 : StuntType::Side360;
249 m_rotSign = trick == System::Trick::Left ? 1.0f : -1.0f;
255 m_stuntManager.setProperties(
static_cast<size_t>(m_stunt));
257 state()->setZipperTrick(
true);
260 m_stuntRot = EGG::Quatf::ident;
264void KartHalfPipe::end(
bool boost) {
265 if (state()->isOverZipper() && state()->airtime() > 5 && boost) {
266 move()->activateZipperBoost();
269 if (state()->isZipperTrick()) {
270 physics()->composeDecayingStuntRot(m_stuntRot);
273 if (state()->isOverZipper()) {
274 move()->setDir(mainRot().rotateVector(EGG::Vector3f::ez));
275 move()->setVel1Dir(move()->dir());
278 state()->setOverZipper(
false);
279 state()->setZipperTrick(
false);
280 state()->setZipperStick(
false);
282 m_stunt = StuntType::None;
285void KartHalfPipe::StuntManager::calcAngle() {
286 if (finalAngle * properties.finalAngleScalar < angle) {
287 angleDelta = std::max(properties.angleDeltaMin, angleDelta * angleDeltaFactor);
288 angleDeltaFactor = std::max(properties.angleDeltaFactorMin,
289 angleDeltaFactor - properties.angleDeltaFactorDecr);
292 angle = std::min(finalAngle, angle + angleDelta);
295void KartHalfPipe::StuntManager::setProperties(
size_t idx) {
296 static constexpr std::array<StuntProperties, 6> STUNT_PROPERTIES = {{
297 {6.0f, 2.5f, 0.955f, 0.01f, 0.7f, 360.0f},
298 {7.0f, 3.0f, 0.955f, 0.01f, 0.7f, 360.0f},
299 {7.0f, 3.0f, 0.95f, 0.01f, 0.7f, 360.0f},
300 {12.0f, 2.5f, 0.955f, 0.01f, 0.0f, 360.0f},
301 {4.0f, 4.0f, 0.98f, 0.01f, 0.0f, 360.0f},
302 {9.0f, 3.0f, 0.92f, 0.01f, 0.8f, 720.0f},
305 ASSERT(idx < STUNT_PROPERTIES.size());
307 properties = STUNT_PROPERTIES[idx];
308 finalAngle = properties.finalAngle;
309 angleDelta = properties.angleDelta;
310 angleDeltaFactorDecr = properties.angleDeltaFactorDecr;
312 angleDeltaFactor = 1.0f;
#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_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.
Vector3f rotateVector(const Vector3f &vec) const
Rotates a vector based on the quat.
Quatf slerpTo(const Quatf &q2, f32 t) const
Performs spherical linear interpolation.
void setAxisRotation(f32 angle, const Vector3f &axis)
Set the quat given angle and axis.
Vector3f rotateVectorInv(const Vector3f &vec) const
Rotates a vector on the inverse quat.
void setRPY(const Vector3f &rpy)
Sets roll, pitch, and yaw.
void makeVectorRotation(const Vector3f &from, const Vector3f &to)
Captures rotation between two vectors.
f32 normalise()
Normalizes the vector and returns the original length.
f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
Vector3f perpInPlane(const EGG::Vector3f &rhs, bool normalise) const
Calculates the orthogonal vector, based on the plane defined by this vector and rhs.