A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
ObjectEscalator.cc
1#include "ObjectEscalator.hh"
2
3#include "game/field/CollisionDirector.hh"
4#include "game/field/ObjectDirector.hh"
5
6#include "game/system/RaceManager.hh"
7
8#include <algorithm>
9
10namespace Field {
11
13ObjectEscalator::ObjectEscalator(const System::MapdataGeoObj &params, bool reverse /* = false */)
14 : ObjectKCL(params), m_initialPos(m_pos), m_initialRot(m_rot),
15 m_stillFrames(
16 {static_cast<s32>(params.setting(2)) * 60, static_cast<s32>(params.setting(4)) * 60}),
17 m_speed({(reverse ? -1.0f : 1.0f) *
18 (0.15f * static_cast<f32>(static_cast<s16>(params.setting(1))) / 100.0f),
19 (reverse ? -1.0f : 1.0f) *
20 (0.15f * static_cast<f32>(static_cast<s16>(params.setting(3))) / 100.0f),
21 (reverse ? -1.0f : 1.0f) *
22 (0.15f * static_cast<f32>(static_cast<s16>(params.setting(5))) / 100.0f)}),
23 m_checkColYPosMax(m_initialPos.y + MAX_HEIGHT_OFFSET),
24 m_checkColYPosMin(m_initialPos.y + MIN_HEIGHT_OFFSET),
25 m_stopFrames({static_cast<f32>(m_stillFrames[0]) - REVERSE_FRAMES_F32,
26 static_cast<f32>(m_stillFrames[1]) - REVERSE_FRAMES_F32}),
27 m_startFrames({static_cast<f32>(m_stillFrames[0]) + STANDSTILL_FRAMES,
28 static_cast<f32>(m_stillFrames[1]) + STANDSTILL_FRAMES}),
29 m_fullSpeedFrames(
30 {REVERSE_FRAMES_F32 + m_startFrames[0], REVERSE_FRAMES_F32 + m_startFrames[1]}),
31 m_midDuration(m_stopFrames[1] - m_fullSpeedFrames[0]) {
32 constexpr EGG::Vector3f STEP_DIMS = EGG::Vector3f(0.0f, STEP_HEIGHT, -30.0f);
33
34 m_stepFactor = 0.0f;
35
37 mat.makeR(m_initialRot);
38 m_stepDims = mat.ps_multVector(STEP_DIMS);
39}
40
42ObjectEscalator::~ObjectEscalator() = default;
43
45void ObjectEscalator::calc() {
46 s32 t = static_cast<s32>(System::RaceManager::Instance()->timer());
47 setMovingObjVel(m_stepDims * calcSpeed(t));
48
49 m_stepFactor = calcStepFactor(t);
50 m_pos = m_initialPos + m_stepDims * m_stepFactor;
51 m_flags.setBit(eFlags::Position);
52}
53
55bool ObjectEscalator::checkPointPartial(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
56 KCLTypeMask mask, CollisionInfoPartial *info, KCLTypeMask *maskOut) {
57 return checkPointImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkPointPartial,
58 pos, prevPos, mask, info, maskOut);
59}
60
62bool ObjectEscalator::checkPointPartialPush(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
63 KCLTypeMask mask, CollisionInfoPartial *info, KCLTypeMask *maskOut) {
64 return checkPointImpl(&ObjectEscalator::shouldCheckColPush, &ObjColMgr::checkPointPartialPush,
65 pos, prevPos, mask, info, maskOut);
66}
67
69bool ObjectEscalator::checkPointFull(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
70 KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut) {
71 return checkPointImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkPointFull, pos,
72 prevPos, mask, info, maskOut);
73}
74
76bool ObjectEscalator::checkPointFullPush(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
77 KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut) {
78 return checkPointImpl(&ObjectEscalator::shouldCheckColPush, &ObjColMgr::checkPointFullPush, pos,
79 prevPos, mask, info, maskOut);
80}
81
83bool ObjectEscalator::checkSpherePartial(f32 radius, const EGG::Vector3f &pos,
84 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
85 KCLTypeMask *maskOut, u32 timeOffset) {
86 return checkSphereImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkSpherePartial,
87 radius, pos, prevPos, mask, info, maskOut, timeOffset);
88}
89
91bool ObjectEscalator::checkSpherePartialPush(f32 radius, const EGG::Vector3f &pos,
92 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
93 KCLTypeMask *maskOut, u32 timeOffset) {
94 return checkSphereImpl(&ObjectEscalator::shouldCheckColPush, &ObjColMgr::checkSpherePartialPush,
95 radius, pos, prevPos, mask, info, maskOut, timeOffset);
96}
97
99bool ObjectEscalator::checkSphereFull(f32 radius, const EGG::Vector3f &pos,
100 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
101 u32 timeOffset) {
102 return checkSphereImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkSphereFull,
103 radius, pos, prevPos, mask, info, maskOut, timeOffset);
104}
105
107bool ObjectEscalator::checkSphereFullPush(f32 radius, const EGG::Vector3f &pos,
108 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
109 u32 timeOffset) {
110 return checkCollision(radius, pos, prevPos, mask, info, maskOut, timeOffset);
111}
112
114void ObjectEscalator::narrScLocal(f32 radius, const EGG::Vector3f &pos, KCLTypeMask mask,
115 u32 /*timeOffset*/) {
116 m_objColMgr->narrScLocal(radius, pos, mask);
117}
118
120bool ObjectEscalator::checkPointCachedPartial(const EGG::Vector3f &pos,
121 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
122 KCLTypeMask *maskOut) {
123 return checkPointImpl(&ObjectEscalator::shouldCheckColNoPush,
124 &ObjColMgr::checkPointCachedPartial, pos, prevPos, mask, info, maskOut);
125}
126
128bool ObjectEscalator::checkPointCachedPartialPush(const EGG::Vector3f &pos,
129 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
130 KCLTypeMask *maskOut) {
131 return checkPointImpl(&ObjectEscalator::shouldCheckColPush,
132 &ObjColMgr::checkPointCachedPartialPush, pos, prevPos, mask, info, maskOut);
133}
134
136bool ObjectEscalator::checkPointCachedFull(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
137 KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut) {
138 return checkPointImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkPointCachedFull,
139 pos, prevPos, mask, info, maskOut);
140}
141
143bool ObjectEscalator::checkPointCachedFullPush(const EGG::Vector3f &pos,
144 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut) {
145 return checkPointImpl(&ObjectEscalator::shouldCheckColPush,
146 &ObjColMgr::checkPointCachedFullPush, pos, prevPos, mask, info, maskOut);
147}
148
150bool ObjectEscalator::checkSphereCachedPartial(f32 radius, const EGG::Vector3f &pos,
151 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
152 KCLTypeMask *maskOut, u32 timeOffset) {
153 return checkSphereImpl(&ObjectEscalator::shouldCheckColNoPush,
154 &ObjColMgr::checkSphereCachedPartial, radius, pos, prevPos, mask, info, maskOut,
155 timeOffset);
156}
157
159bool ObjectEscalator::checkSphereCachedPartialPush(f32 radius, const EGG::Vector3f &pos,
160 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
161 KCLTypeMask *maskOut, u32 timeOffset) {
162 return checkSphereImpl(&ObjectEscalator::shouldCheckColPush,
163 &ObjColMgr::checkSphereCachedPartialPush, radius, pos, prevPos, mask, info, maskOut,
164 timeOffset);
165}
166
168bool ObjectEscalator::checkSphereCachedFull(f32 radius, const EGG::Vector3f &pos,
169 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
170 u32 timeOffset) {
171 return checkSphereImpl(&ObjectEscalator::shouldCheckColNoPush,
172 &ObjColMgr::checkSphereCachedFull, radius, pos, prevPos, mask, info, maskOut,
173 timeOffset);
174}
175
177bool ObjectEscalator::checkSphereCachedFullPush(f32 radius, const EGG::Vector3f &pos,
178 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
179 u32 timeOffset) {
180 return checkCollisionCached(radius, pos, prevPos, mask, info, maskOut, timeOffset);
181}
182
184const EGG::Matrix34f &ObjectEscalator::getUpdatedMatrix(u32 timeOffset) {
185 u32 t = System::RaceManager::Instance()->timer() - timeOffset;
186 m_workMatrix.makeRT(m_rot, m_initialPos + m_stepDims * calcStepFactor(t));
187 return m_workMatrix;
188}
189
191bool ObjectEscalator::checkCollision(f32 radius, const EGG::Vector3f &pos,
192 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
193 u32 timeOffset) {
194 update(timeOffset);
195
196 if (m_checkColYPosMin >= pos.y || pos.y >= m_checkColYPosMax) {
197 return false;
198 }
199
200 if (!m_objColMgr->checkSphereFullPush(radius, pos, prevPos, mask, info, maskOut)) {
201 return false;
202 }
203
204 if ((*maskOut & KCL_TYPE_BIT(COL_TYPE_MOVING_ROAD)) == 0) {
205 return false;
206 }
207
208 auto *colDir = CollisionDirector::Instance();
209 if (!colDir->findClosestCollisionEntry(maskOut, KCL_TYPE_BIT(COL_TYPE_MOVING_ROAD))) {
210 return false;
211 }
212
213 auto *entry = colDir->closestCollisionEntry();
214 if (entry->dist > info->movingFloorDist) {
215 info->movingFloorDist = entry->dist;
216
217 u32 t = System::RaceManager::Instance()->timer() - timeOffset;
218 info->roadVelocity = m_stepDims * calcSpeed(t);
219 }
220
221 return true;
222}
223
225bool ObjectEscalator::checkCollisionCached(f32 radius, const EGG::Vector3f &pos,
226 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
227 u32 timeOffset) {
228 update(timeOffset);
229
230 if (m_checkColYPosMin >= pos.y || pos.y >= m_checkColYPosMax) {
231 return false;
232 }
233
234 if (!m_objColMgr->checkSphereCachedFullPush(radius, pos, prevPos, mask, info, maskOut)) {
235 return false;
236 }
237
238 if ((*maskOut & KCL_TYPE_BIT(COL_TYPE_MOVING_ROAD)) == 0) {
239 return false;
240 }
241
242 auto *colDir = CollisionDirector::Instance();
243 if (!colDir->findClosestCollisionEntry(maskOut, KCL_TYPE_BIT(COL_TYPE_MOVING_ROAD))) {
244 return false;
245 }
246
247 auto *entry = colDir->closestCollisionEntry();
248 if (entry->dist > info->movingFloorDist) {
249 info->movingFloorDist = entry->dist;
250
251 u32 t = System::RaceManager::Instance()->timer() - timeOffset;
252 info->roadVelocity = m_stepDims * calcSpeed(t);
253 }
254
255 return true;
256}
257
258template <typename T>
259 requires std::is_same_v<T, CollisionInfo> || std::is_same_v<T, CollisionInfoPartial>
260bool ObjectEscalator::checkPointImpl(ShouldCheckFunc shouldCheckFunc, CheckPointFunc<T> checkFunc,
261 const EGG::Vector3f &pos, const EGG::Vector3f &prevPos, KCLTypeMask mask, T *info,
262 KCLTypeMask *maskOut) {
263 if (m_checkColYPosMin > pos.y || pos.y >= m_checkColYPosMax) {
264 return false;
265 }
266
267 if (!(this->*shouldCheckFunc)()) {
268 return false;
269 }
270
271 return (m_objColMgr->*checkFunc)(pos, prevPos, mask, info, maskOut);
272}
273
274template <typename T>
275 requires std::is_same_v<T, CollisionInfo> || std::is_same_v<T, CollisionInfoPartial>
276bool ObjectEscalator::checkSphereImpl(ShouldCheckFunc shouldCheckFunc, CheckSphereFunc<T> checkFunc,
277 f32 radius, const EGG::Vector3f &pos, const EGG::Vector3f &prevPos, KCLTypeMask mask,
278 T *info, KCLTypeMask *maskOut, u32 timeOffset) {
279 if (m_checkColYPosMin > pos.y || pos.y >= m_checkColYPosMax) {
280 return false;
281 }
282
283 if (!(this->*shouldCheckFunc)()) {
284 return false;
285 }
286
287 calcScale(timeOffset);
288 update(timeOffset);
289
290 return (m_objColMgr->*checkFunc)(radius, pos, prevPos, mask, info, maskOut);
291}
292
294f32 ObjectEscalator::calcStepFactor(s32 t) {
295 constexpr s32 REVERSE_FRAMES_S32 = static_cast<s32>(REVERSE_FRAMES_F32);
296 constexpr s32 DISCRETE_STEP_OFFSETS = 200;
297
298 // Time since escalator began stopping
299 std::array<s32, 2> dtStop = {static_cast<s32>(static_cast<f32>(t) - m_stopFrames[0]),
300 static_cast<s32>(static_cast<f32>(t) - m_stopFrames[1])};
301
302 // Time since escalator started moving (second and third time)
303 std::array<s32, 2> dtStart = {static_cast<s32>(static_cast<f32>(t) - m_startFrames[0]),
304 static_cast<s32>(static_cast<f32>(t) - m_startFrames[1])};
305
306 // Time since escalator started moving at full speed
307 std::array<s32, 2> dtFullSpeed = {static_cast<s32>(static_cast<f32>(t) - m_fullSpeedFrames[0]),
308 static_cast<s32>(static_cast<f32>(t) - m_fullSpeedFrames[1])};
309
310 // Each dist lambda represents the displacement from each sub-function of the piecewise function
311
312 // Escalator has not stopped yet
313 auto dist0 = [this](s32 t) -> f64 {
314 t = std::clamp<s32>(t, 0, static_cast<s32>(m_stopFrames[0]));
315 return static_cast<f64>(static_cast<f32>(t) * m_speed[0]);
316 };
317
318 // Escalator is slowing down for the first time
319 auto dist1 = [=, this]() -> f64 {
320 f64 speed = static_cast<f64>(m_speed[0]);
321 f64 t = static_cast<f64>(std::clamp<s32>(dtStop[0], 0, REVERSE_FRAMES_S32));
322 return 0.5 * t * (speed + speed * (1.0 - t / static_cast<f64>(REVERSE_FRAMES_F32)));
323 };
324
325 // Escalator is speeding up after switching directions once
326 auto dist2 = [=, this]() -> f64 {
327 f64 t = static_cast<f64>(std::clamp<s32>(dtStart[0], 0, REVERSE_FRAMES_S32));
328 return t * (0.5 * t * static_cast<f64>(m_speed[1])) / static_cast<f64>(REVERSE_FRAMES_F32);
329 };
330
331 // Escalator is moving full speed after switching directions once
332 auto dist3 = [=, this](s32 t) -> f64 {
333 t = std::clamp<s32>(dtFullSpeed[0], 0, m_midDuration);
334 return static_cast<f64>(static_cast<f32>(t) * m_speed[1]);
335 };
336
337 // Escalator is slowing down for the second time
338 auto dist4 = [=, this]() -> f64 {
339 f64 speed = static_cast<f64>(m_speed[1]);
340 f64 t = static_cast<f32>(std::clamp<s32>(dtStop[1], 0, REVERSE_FRAMES_S32));
341 return 0.5 * t * (speed + speed * (1.0 - t / static_cast<f64>(REVERSE_FRAMES_F32)));
342 };
343
344 // Escalator is speeding up after switching directions twice
345 auto dist5 = [=, this]() -> f64 {
346 f64 speed = static_cast<f64>(m_speed[2]);
347 f64 t = static_cast<f64>(std::clamp<s32>(dtStart[1], 0, REVERSE_FRAMES_S32));
348 return (t * (0.5 * t * speed) / static_cast<f64>(REVERSE_FRAMES_F32));
349 };
350
351 // Escalator is moving full speed after switching directions twice
352 auto dist6 = [=, this]() -> f64 {
353 f64 t = static_cast<f64>(std::max(dtFullSpeed[1], 0));
354 return t * static_cast<f64>(m_speed[2]);
355 };
356
357 f64 totalDist = dist6() + dist5() + dist4() + dist3(t) + dist2() + dist0(t) + dist1();
358 s32 result = static_cast<s32>(STEP_HEIGHT * static_cast<f32>(totalDist));
359 f32 fin = static_cast<f32>(result % DISCRETE_STEP_OFFSETS);
360
361 if (fin < 0.0f) {
362 fin += static_cast<f32>(DISCRETE_STEP_OFFSETS);
363 }
364
365 return fin / STEP_HEIGHT;
366}
367
369f32 ObjectEscalator::calcSpeed(s32 t) {
370 // Escalator has not stopped yet
371 if (static_cast<f32>(t) < m_stopFrames[0]) {
372 return m_speed[0];
373 }
374
375 // Escalator is slowing down for the first time
376 if (t < m_stillFrames[0]) {
377 return m_speed[0] * static_cast<f32>(m_stillFrames[0] - t) / REVERSE_FRAMES_F32;
378 }
379
380 // Standstill right after stopping for the first time
381 if (static_cast<f32>(t) <= m_startFrames[0]) {
382 return 0.0f;
383 }
384
385 // Escalator is speeding up after switching directions once
386 if (static_cast<f32>(t) < REVERSE_FRAMES_F32 + m_startFrames[0]) {
387 return m_speed[1] * (static_cast<f32>(t) - m_startFrames[0]) / REVERSE_FRAMES_F32;
388 }
389
390 // Escalator is moving full speed after switching directions once
391 if (static_cast<f32>(t) < m_stopFrames[1]) {
392 return m_speed[1];
393 }
394
395 // Escalator is slowing down for the second time
396 if (t < m_stillFrames[1]) {
397 return m_speed[1] * static_cast<f32>(m_stillFrames[1] - t) / REVERSE_FRAMES_F32;
398 }
399
400 // Standstill right after stopping for the second/final time
401 if (static_cast<f32>(t) <= m_startFrames[1]) {
402 return 0.0f;
403 }
404
405 // Escalator is speeding up after switching directions twice
406 if (static_cast<f32>(t) < REVERSE_FRAMES_F32 + m_startFrames[1]) {
407 return m_speed[2] * (static_cast<f32>(t) - m_startFrames[1]) / REVERSE_FRAMES_F32;
408 }
409
410 // Escalator is moving full speed after switching directions twice
411 return m_speed[2];
412}
413
414} // namespace Field
@ COL_TYPE_MOVING_ROAD
TF conveyers and CM escalators.
#define KCL_TYPE_BIT(x)
A 3 x 4 matrix.
Definition Matrix.hh:8
void makeR(const Vector3f &r)
Sets 3x3 rotation matrix from a vector of Euler angles.
Definition Matrix.cc:98
Vector3f ps_multVector(const Vector3f &vec) const
Paired-singles impl. of multVector.
Definition Matrix.cc:224
Pertains to collision.
A 3D float vector.
Definition Vector.hh:88