A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
ObjectChoropu.cc
1#include "ObjectChoropu.hh"
2
3#include "game/field/ObjectDirector.hh"
4#include "game/field/RailManager.hh"
5
6#include "game/kart/Reaction.hh"
7
8#include "game/system/RaceManager.hh"
9
10namespace Kinoko::Field {
11
13ObjectChoropu::ObjectChoropu(const System::MapdataGeoObj &params)
14 : ObjectCollidable(params), StateManager(this, STATE_ENTRIES) {
15 constexpr f32 MAX_SPEED = 20.0f;
16
17 m_startFrameOffset = static_cast<s16>(params.setting(1));
18 m_idleDuration = params.setting(0);
19 m_isStationary = strcmp(getName(), "choropu") != 0;
20
21 s16 railIdx = params.pathId();
22 if (railIdx != -1) {
23 auto *rail = RailManager::Instance()->rail(railIdx);
24 rail->checkSphereFull();
25 }
26
27 // If the mole moves around, then we need to create the dirt trail.
28 if (!m_isStationary) {
29 const auto &flowTable = ObjectDirector::Instance()->flowTable();
30 const auto *collisionSet =
31 flowTable.set(flowTable.slot(flowTable.getIdFromName("choropu_ground")));
32 ASSERT(collisionSet);
33
34 s16 height = parse<s16>(collisionSet->params.cylinder.height);
35 size_t groundCount = static_cast<size_t>(MAX_GROUND_LEN / EGG::Mathf::abs(height * 2)) + 1;
36 m_groundObjs = owning_span<ObjectChoropuGround *>(groundCount);
37
38 for (auto *&obj : m_groundObjs) {
39 obj = EGG::egg_new<ObjectChoropuGround>(pos(), rot(), scale());
40 obj->load();
41 obj->resize(RADIUS, MAX_SPEED);
42 }
43
44 m_groundHeight = m_groundObjs.front()->height();
45 }
46
47 m_objHoll = EGG::egg_new<ObjectChoropuHoll>(params);
48 m_objHoll->load();
49}
50
52ObjectChoropu::~ObjectChoropu() = default;
53
55void ObjectChoropu::init() {
56 if (m_isStationary) {
57 disableCollision();
58
59 m_objHoll->setScale(EGG::Vector3f(1.0f, m_objHoll->scale().y, 1.0f));
60 m_nextStateId = 0;
61 m_groundLength = 0.0f;
62
63 calcTransform();
64 m_transMat = transform();
65 } else {
66 if (m_mapObj->pathId() == -1) {
67 return;
68 }
69
70 m_railInterpolator->init(0.0f, 0);
71 m_railInterpolator->setPerPointVelocities(true);
72
73 m_railPos = m_railInterpolator->curPos();
74 setPos(m_railPos);
75
76 disableCollision();
77 m_objHoll->disableCollision();
78
79 m_nextStateId = 0;
80 }
81}
82
84void ObjectChoropu::calc() {
85 constexpr u32 START_DELAY = 300;
86
87 // Nothing to do if the mole hasn't spawned yet
88 u32 t = System::RaceManager::Instance()->timer();
89 if (t < m_startFrameOffset + START_DELAY) {
90 return;
91 }
92
93 if (!m_isStationary) {
94 if (m_mapObj->pathId() == -1) {
95 return;
96 }
97
98 // Kinoko-specific: the base game computes RailOrthonormalBasis() here, but only its
99 // translation is ever used, so we skip computing the rotation.
100 m_railPos = m_railInterpolator->curPos();
101 }
102
103 StateManager::calc();
104
105 m_objHoll->setScale(EGG::Vector3f(1.0f, m_objHoll->scale().y, 1.0f));
106}
107
109Kart::Reaction ObjectChoropu::onCollision(Kart::KartObject * /*kartObj*/,
110 Kart::Reaction reactionOnKart, Kart::Reaction /*reactionOnObj*/,
111 EGG::Vector3f & /*hitDepth*/) {
112 return m_currentStateId == 1 ? Kart::Reaction::SmallBump : reactionOnKart;
113}
114
115void ObjectChoropu::enterStateStub() {}
116
118void ObjectChoropu::enterDigging() {
119 if (m_isStationary) {
120 disableCollision();
121 } else {
122 for (auto *&obj : m_groundObjs) {
123 obj->enableCollision();
124 }
125
126 disableCollision();
127 m_objHoll->disableCollision();
128 m_groundLength = 0.0f;
129 }
130}
131
133void ObjectChoropu::enterPeeking() {
134 if (m_isStationary) {
135 setPos(m_transMat.base(3));
136 setRot(EGG::Vector3f(rot().x, rot().y, 0.0f));
137
138 enableCollision();
139 } else {
140 setPos(m_railPos);
141 setRot(EGG::Vector3f(rot().x, 0.0f, 0.0f));
142 enableCollision();
143
144 const auto &curTanDir = m_railInterpolator->curTangentDir();
145 s16 curPointIdx = m_railInterpolator->curPointIdx();
146 EGG::Matrix34f mat;
147 SetRotTangentHorizontal(mat, m_railInterpolator->floorNrm(curPointIdx), curTanDir);
148 mat.setBase(3, m_railInterpolator->curPos());
149
150 m_objHoll->setTransform(mat);
151 m_objHoll->enableCollision();
152 }
153}
154
156void ObjectChoropu::enterJumping() {
157 enableCollision();
158
159 setPos(m_isStationary ? m_transMat.base(3) : m_railPos);
160 setRot(EGG::Vector3f(rot().x, rot().y, 0.0f));
161}
162
163void ObjectChoropu::calcStateStub() {}
164
166void ObjectChoropu::calcDigging() {
167 if (m_isStationary) {
168 if (m_currentFrame > m_idleDuration) {
169 m_nextStateId = 1;
170 }
171
172 return;
173 }
174
175 setPos(m_railInterpolator->curPos());
176
177 if (m_railInterpolator->calc() == RailInterpolator::Status::SegmentEnd) {
178 if (m_railInterpolator->curPoint().setting[1] == 1) {
179 m_nextStateId = 1;
180 } else {
181 calcGround();
182 }
183 } else {
184 bool skipGroundCalc = false;
185
186 if (m_railInterpolator->nextPoint().setting[1] == 1) {
187 f32 invT = 1.0f - m_railInterpolator->segmentT();
188 if (invT * m_railInterpolator->getCurrSegmentLength() < 250.0f) {
189 skipGroundCalc = true;
190 }
191 }
192
193 if (!skipGroundCalc) {
194 calcGround();
195 }
196 }
197}
198
200void ObjectChoropu::calcPeeking() {
201 constexpr s16 PEEK_DURATION = 40;
202 constexpr s16 STATE_DURATION = 100;
203
204 if (!m_isStationary) {
205 m_groundLength = std::max(0.0f, m_groundLength - m_railInterpolator->speed());
206
207 calcGroundObjs();
208 }
209
210 if (m_currentFrame > STATE_DURATION) {
211 m_nextStateId = 3;
212 }
213
214 if (m_currentFrame > PEEK_DURATION) {
215 disableCollision();
216 }
217}
218
220void ObjectChoropu::calcJumping() {
221 constexpr f32 JUMP_LINEAR_COEFFICIENT = 65.0f;
222 constexpr f32 JUMP_QUADRATIC_COEFFICIENT = 2.7f;
223
224 if (!m_isStationary) {
225 m_groundLength = std::max(0.0f, m_groundLength - m_railInterpolator->speed());
226 calcGroundObjs();
227 }
228
229 // y = -1.35t^2 + 65.0t
230 f32 posY = JUMP_LINEAR_COEFFICIENT * static_cast<f32>(m_currentFrame) -
231 static_cast<f32>(m_currentFrame) * 0.5f * JUMP_QUADRATIC_COEFFICIENT *
232 static_cast<f32>(m_currentFrame);
233
234 if (posY < 0.0f) {
235 m_nextStateId = 0;
236 }
237
238 posY += (m_isStationary ? m_transMat.base(3).y : m_railInterpolator->curPos().y);
239 setPos(EGG::Vector3f(pos().x, posY, pos().z));
240}
241
243void ObjectChoropu::calcGround() {
244 m_groundLength += m_railInterpolator->getCurrVel();
245 if (m_groundLength > MAX_GROUND_LEN) {
246 m_groundLength = MAX_GROUND_LEN - 1.0f;
247 }
248
249 calcGroundObjs();
250}
251
253void ObjectChoropu::calcGroundObjs() {
254 size_t idx =
255 std::min(static_cast<size_t>(m_groundLength / m_groundHeight) + 1, m_groundObjs.size());
256
257 for (auto *&obj : m_groundObjs) {
258 obj->enableCollision();
259 }
260
261 for (size_t i = idx; i < m_groundObjs.size(); ++i) {
262 m_groundObjs[i]->disableCollision();
263 }
264
265 if (m_groundLength > RADIUS) {
266 f32 height = std::min(m_groundHeight, m_groundLength) - RADIUS;
267 m_groundObjs[0]->calcPosAndMat(height, calcInterpolatedPose(RADIUS + 0.5f * height));
268 }
269
270 for (size_t i = 1; i < idx - 1; ++i) {
271 f32 height = 0.5f * m_groundHeight + m_groundHeight * static_cast<f32>(i);
272 m_groundObjs[i]->calcPosAndMat(m_groundHeight, calcInterpolatedPose(height));
273 }
274
275 f32 height = m_groundLength - m_groundHeight * static_cast<f32>(idx - 1);
276 EGG::Matrix34f mat =
277 calcInterpolatedPose(0.5f * height + m_groundHeight * static_cast<f32>(idx - 1));
278 m_groundObjs[idx - 1]->calcPosAndMat(height, mat);
279}
280
282EGG::Matrix34f ObjectChoropu::calcInterpolatedPose(f32 t) const {
283 EGG::Vector3f curDir;
284 EGG::Vector3f curTanDir;
285 m_railInterpolator->evalCubicBezierOnPath(t, curDir, curTanDir);
286 EGG::Matrix34f mat = OrthonormalBasis(curTanDir);
287 mat.setBase(3, curDir);
288 return mat;
289}
290
292ObjectChoropuGround::ObjectChoropuGround(const EGG::Vector3f &pos, const EGG::Vector3f &rot,
293 const EGG::Vector3f &scale)
294 : ObjectCollidable("choropu_ground", pos, rot, scale) {
295 const auto &flowTable = ObjectDirector::Instance()->flowTable();
296 const auto *collisionSet =
297 flowTable.set(flowTable.slot(flowTable.getIdFromName("choropu_ground")));
298 ASSERT(collisionSet);
299
300 s16 height = parse<s16>(collisionSet->params.cylinder.height);
301 m_height = 2.0f * EGG::Mathf::abs(static_cast<f32>(height));
302}
303
305ObjectChoropuGround::~ObjectChoropuGround() = default;
306
308void ObjectChoropuGround::calcPosAndMat(f32 height, const EGG::Matrix34f &mat) {
309 EGG::Matrix34f matTemp;
310 SetRotTangentHorizontal(matTemp, mat.base(2), EGG::Vector3f::ey);
311 matTemp.setBase(1, matTemp.base(1) * (height / m_height));
312 matTemp.setBase(3, mat.base(3));
313 setTransform(matTemp);
314}
315
317ObjectChoropuHoll::ObjectChoropuHoll(const System::MapdataGeoObj &params)
318 : ObjectCollidable(params) {}
319
321ObjectChoropuHoll::~ObjectChoropuHoll() = default;
322
323} // namespace Kinoko::Field
Base class that represents different "states" for an object.
Pertains to collision.