A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
ObjectHeyho.cc
1#include "ObjectHeyho.hh"
2
3#include "game/field/CollisionDirector.hh"
4#include "game/field/RailManager.hh"
5
6namespace Kinoko::Field {
7
9ObjectHeyho::ObjectHeyho(const System::MapdataGeoObj &params)
10 : ObjectCollidable(params), StateManager(this, STATE_ENTRIES), m_color(params.setting(1)) {
11 const auto *rail = RailManager::Instance()->rail(params.pathId());
12 ASSERT(rail);
13 const auto &railPts = rail->points();
14
15 // The two endpoints are candidates for the highest Y coordinate in the route
16 // The midpoint's Y coordinate should be the lowest in the route
17 m_apex = std::max(railPts.front().pos.y, railPts.back().pos.y);
18 m_midpoint = railPts[railPts.size() / 2].pos;
19
20 // The object should speed up as we approach the low point and slow down as we leave it
21 // We form an acceleration constant so multiplying with (pos - center) gives v^2
22 // This way, we can inversely correlate height difference with speed
23 ASSERT(m_apex - m_midpoint.y > std::numeric_limits<f32>::epsilon());
24 f32 maxVel = static_cast<f32>(static_cast<s16>(params.setting(0)));
25 m_maxVelSq = maxVel * maxVel;
26 m_accel = m_maxVelSq / (m_apex - m_midpoint.y);
27}
28
30ObjectHeyho::~ObjectHeyho() = default;
31
33void ObjectHeyho::init() {
34 ASSERT(m_railInterpolator);
35 m_railInterpolator->init(0.0f, m_railInterpolator->pointCount() / 2);
36 setPos(m_railInterpolator->curPos());
37 m_currentVel = EGG::Mathf::sqrt(
38 m_maxVelSq - m_accel * (m_railInterpolator->curPos().y - m_midpoint.y));
39
40 m_transformOffset = m_railInterpolator->curTangentDir() * m_currentVel;
41 m_floorCollision = false;
42 m_up = EGG::Vector3f::ey;
43 m_forward = EGG::Vector3f::ez;
44 m_freeFall = false;
45 m_spinFrame = 0;
46
47 changeAnimation(Animation::Move);
48}
49
51void ObjectHeyho::calc() {
52 calcStateTransition();
53 calcMotion();
54 StateManager::calc();
55 calcInterp();
56}
57
59void ObjectHeyho::loadAnims() {
60 std::array<const char *, 4> names = {{
61 "body_color",
62 "move",
63 "jump",
64 "jump_ed",
65 }};
66
67 std::array<Render::AnmType, 4> types = {{
68 Render::AnmType::Pat,
69 Render::AnmType::Chr,
70 Render::AnmType::Chr,
71 Render::AnmType::Chr,
72 }};
73
74 linkAnims(names, types);
75}
76
78void ObjectHeyho::calcCollisionTransform() {
79 auto *objCol = collision();
80 if (!objCol) {
81 return;
82 }
83
84 EGG::Matrix34f tm;
85 tm.makeT(EGG::Vector3f(0.0f, 100.0f, 0.0f));
86 calcTransform();
87 EGG::Matrix34f m = transform().multiplyTo(tm);
88 objCol->transform(m, scale(), m_transformOffset);
89}
90
92void ObjectHeyho::calcMove() {
93 m_forward = m_railInterpolator->nextPoint().pos - m_railInterpolator->curPoint().pos;
94 m_floorCollision = false;
95
96 CollisionInfo info;
97
98 if (CollisionDirector::Instance()->checkSphereFull(COLLISION_RADIUS, pos() + COLLISION_OFFSET,
99 EGG::Vector3f::inf, KCL_TYPE_FLOOR, &info, nullptr, 0)) {
100 m_floorCollision = true;
101 if (info.floorDist > -std::numeric_limits<f32>::min()) {
102 m_floorNrm = info.floorNrm;
103 }
104
105 setPos(pos() + info.tangentOff - m_floorNrm * 60.0f);
106
107 if (m_currentAnim == Animation::Jumped) {
108 const auto *anim = m_drawMdl->anmMgr()->activeAnim(Render::AnmType::Chr);
109 if (anim->frame() >= anim->frameCount()) {
110 changeAnimation(Animation::Move);
111 }
112 }
113 }
114}
115
117void ObjectHeyho::calcJump() {
118 constexpr s16 SPIN_DELAY_FRAMES = 5;
119 constexpr s16 SPIN_RATE = 12; // degrees per frame
120 constexpr s16 SPIN_DEGREES = 720;
121
122 m_floorCollision = false;
123
124 CollisionInfo info;
125 Field::KCLTypeMask flags = KCL_NONE;
126
127 if (CollisionDirector::Instance()->checkSphereFull(COLLISION_RADIUS, pos() + COLLISION_OFFSET,
128 EGG::Vector3f::inf, KCL_TYPE_VEHICLE_COLLIDEABLE, &info, &flags, 0)) {
129 m_floorCollision = true;
130 if (info.floorDist > -std::numeric_limits<f32>::min()) {
131 m_floorNrm = info.floorNrm;
132 }
133
134 // Not m_pos += info.tangentOff - m_floorNrm * 60.0f
135 // The former requires m_pos to be the last addition to occur
136 // TODO: 100.0f - 10.0f - 30.0f? Why is this the case?
137 f32 xPos = pos().x + info.tangentOff.x - m_floorNrm.x * 60.0f;
138 f32 zPos = pos().z + info.tangentOff.z - m_floorNrm.z * 60.0f;
139 setPos(EGG::Vector3f(xPos, pos().y, zPos));
140
141 if (!(flags & KCL_TYPE_BIT(COL_TYPE_INVISIBLE_WALL)) && m_currentAnim != Animation::Move) {
142 m_forward = m_railInterpolator->nextPoint().pos - m_railInterpolator->curPoint().pos;
143 if (m_currentAnim == Animation::Jump) {
144 m_currentAnim = Animation::Jumped;
145 }
146 } else {
147 m_floorCollision = false;
148 const auto &curPos = m_railInterpolator->curPoint().pos;
149 const auto &nextPos = m_railInterpolator->nextPoint().pos;
150
151 m_forward = (nextPos.y > curPos.y ? nextPos : curPos) - m_midpoint;
152
153 // Red shy guys do a spin. Yes, this is based on color, and no, it's not an animation
154 // We couldn't possibly use even one of the six unused settings for this
155 if (m_color == 0) {
156 s16 frame = m_spinFrame - SPIN_DELAY_FRAMES;
157 if (frame >= 0 && frame <= SPIN_DEGREES / SPIN_RATE) {
158 EGG::Matrix34f m;
159 m.setAxisRotation(static_cast<f32>(frame) *
160 (static_cast<f32>(-SPIN_RATE) * DEG2RAD),
161 m_up);
162 m.setBase(3, EGG::Vector3f::zero);
163 m_forward = m.ps_multVector(m_forward);
164 }
165 }
166
167 ++m_spinFrame;
168 }
169 }
170
171 if (m_railInterpolator->segmentT() > 0.6f && m_currentAnim == Animation::Move) {
172 changeAnimation(Animation::Jump);
173 }
174}
175
177void ObjectHeyho::calcStateTransition() {
178 if (m_railInterpolator->curPoint().setting[1] == 0 ||
179 m_railInterpolator->nextPoint().setting[1] == 0) {
180 if (m_currentStateId != 0) {
181 m_currentStateId = 0;
182 }
183 } else {
184 if (m_currentStateId != 1) {
185 m_currentStateId = 1;
186 }
187 }
188}
189
191void ObjectHeyho::calcMotion() {
192 if (!m_freeFall) {
193 f32 sqVel = m_maxVelSq - m_accel * (pos().y - m_midpoint.y);
194 if (sqVel <= 0.0f) {
195 sqVel = 0.001f;
196 }
197 m_currentVel = EGG::Mathf::sqrt(sqVel);
198 m_railInterpolator->setCurrVel(m_currentVel);
199
200 if (m_railInterpolator->calc() == RailInterpolator::Status::ChangingDirection) {
201 // We have an edgecase where the endpoint heights aren't the same
202 // While the current velocity reaches 0 at the apex, it's higher on the other side
203 // To counter this, the object goes off the rail and into free fall until we land
204 m_launchVel = m_currentVel;
205 if (m_currentVel > 1.0f) {
206 m_freeFall = true;
207 }
208 }
209
210 if (m_currentStateId == 0 && !m_floorCollision) {
211 const auto &curPos = m_railInterpolator->curPos();
212 setPos(EGG::Vector3f(curPos.x, pos().y, curPos.z));
213 } else {
214 setPos(m_railInterpolator->curPos());
215 }
216 } else {
217 m_currentVel -= 1.0f;
218 setPos(EGG::Vector3f(pos().x, m_currentVel + pos().y, pos().z));
219 }
220
221 // m_currentVel < 0.0f => either we're in free fall or we just snapped to the rail
222 // If we would land back on the rail on the next frame, just let it snap to the rail instead
223 if (m_currentVel < 0.0f &&
224 EGG::Mathf::abs(pos().y - m_railInterpolator->curPos().y) <= -m_currentVel) {
225 m_currentVel = m_launchVel;
226 m_freeFall = false;
227 }
228}
229
231void ObjectHeyho::calcInterp() {
232 m_up = Interpolate(0.2f, m_up, m_floorNrm);
233 m_up.normalise2();
234 m_forward.normalise2();
235 setMatrixTangentTo(m_up, m_forward);
236}
237
238} // namespace Kinoko::Field
@ COL_TYPE_INVISIBLE_WALL
Solid wall that is (typically) invisible.
#define KCL_TYPE_BIT(x)
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
#define KCL_TYPE_VEHICLE_COLLIDEABLE
0xEAF8BDFF
Base class that represents different "states" for an object.
Pertains to collision.