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 Kinoko::Field {
11
13ObjectEscalator::ObjectEscalator(const System::MapdataGeoObj &params, bool reverse /* = false */)
14 : ObjectKCL(params), m_initialPos(pos()), m_initialRot(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
36 EGG::Matrix34f mat;
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 setPos(m_initialPos + m_stepDims * m_stepFactor);
51}
52
54bool ObjectEscalator::checkPointPartial(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
55 KCLTypeMask mask, CollisionInfoPartial *info, KCLTypeMask *maskOut) {
56 return checkPointImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkPointPartial,
57 pos, prevPos, mask, info, maskOut);
58}
59
61bool ObjectEscalator::checkPointPartialPush(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
62 KCLTypeMask mask, CollisionInfoPartial *info, KCLTypeMask *maskOut) {
63 return checkPointImpl(&ObjectEscalator::shouldCheckColPush, &ObjColMgr::checkPointPartialPush,
64 pos, prevPos, mask, info, maskOut);
65}
66
68bool ObjectEscalator::checkPointFull(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
69 KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut) {
70 return checkPointImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkPointFull, pos,
71 prevPos, mask, info, maskOut);
72}
73
75bool ObjectEscalator::checkPointFullPush(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
76 KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut) {
77 return checkPointImpl(&ObjectEscalator::shouldCheckColPush, &ObjColMgr::checkPointFullPush, pos,
78 prevPos, mask, info, maskOut);
79}
80
82bool ObjectEscalator::checkSpherePartial(f32 radius, const EGG::Vector3f &pos,
83 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
84 KCLTypeMask *maskOut, u32 timeOffset) {
85 return checkSphereImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkSpherePartial,
86 radius, pos, prevPos, mask, info, maskOut, timeOffset);
87}
88
90bool ObjectEscalator::checkSpherePartialPush(f32 radius, const EGG::Vector3f &pos,
91 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
92 KCLTypeMask *maskOut, u32 timeOffset) {
93 return checkSphereImpl(&ObjectEscalator::shouldCheckColPush, &ObjColMgr::checkSpherePartialPush,
94 radius, pos, prevPos, mask, info, maskOut, timeOffset);
95}
96
98bool ObjectEscalator::checkSphereFull(f32 radius, const EGG::Vector3f &pos,
99 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
100 u32 timeOffset) {
101 return checkSphereImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkSphereFull,
102 radius, pos, prevPos, mask, info, maskOut, timeOffset);
103}
104
106bool ObjectEscalator::checkSphereFullPush(f32 radius, const EGG::Vector3f &pos,
107 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
108 u32 timeOffset) {
109 return checkCollision(radius, pos, prevPos, mask, info, maskOut, timeOffset);
110}
111
113void ObjectEscalator::narrScLocal(f32 radius, const EGG::Vector3f &pos, KCLTypeMask mask,
114 u32 /*timeOffset*/) {
115 m_objColMgr->narrScLocal(radius, pos, mask);
116}
117
119bool ObjectEscalator::checkPointCachedPartial(const EGG::Vector3f &pos,
120 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
121 KCLTypeMask *maskOut) {
122 return checkPointImpl(&ObjectEscalator::shouldCheckColNoPush,
123 &ObjColMgr::checkPointCachedPartial, pos, prevPos, mask, info, maskOut);
124}
125
127bool ObjectEscalator::checkPointCachedPartialPush(const EGG::Vector3f &pos,
128 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
129 KCLTypeMask *maskOut) {
130 return checkPointImpl(&ObjectEscalator::shouldCheckColPush,
131 &ObjColMgr::checkPointCachedPartialPush, pos, prevPos, mask, info, maskOut);
132}
133
135bool ObjectEscalator::checkPointCachedFull(const EGG::Vector3f &pos, const EGG::Vector3f &prevPos,
136 KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut) {
137 return checkPointImpl(&ObjectEscalator::shouldCheckColNoPush, &ObjColMgr::checkPointCachedFull,
138 pos, prevPos, mask, info, maskOut);
139}
140
142bool ObjectEscalator::checkPointCachedFullPush(const EGG::Vector3f &pos,
143 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut) {
144 return checkPointImpl(&ObjectEscalator::shouldCheckColPush,
145 &ObjColMgr::checkPointCachedFullPush, pos, prevPos, mask, info, maskOut);
146}
147
149bool ObjectEscalator::checkSphereCachedPartial(f32 radius, const EGG::Vector3f &pos,
150 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
151 KCLTypeMask *maskOut, u32 timeOffset) {
152 return checkSphereImpl(&ObjectEscalator::shouldCheckColNoPush,
153 &ObjColMgr::checkSphereCachedPartial, radius, pos, prevPos, mask, info, maskOut,
154 timeOffset);
155}
156
158bool ObjectEscalator::checkSphereCachedPartialPush(f32 radius, const EGG::Vector3f &pos,
159 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfoPartial *info,
160 KCLTypeMask *maskOut, u32 timeOffset) {
161 return checkSphereImpl(&ObjectEscalator::shouldCheckColPush,
162 &ObjColMgr::checkSphereCachedPartialPush, radius, pos, prevPos, mask, info, maskOut,
163 timeOffset);
164}
165
167bool ObjectEscalator::checkSphereCachedFull(f32 radius, const EGG::Vector3f &pos,
168 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
169 u32 timeOffset) {
170 return checkSphereImpl(&ObjectEscalator::shouldCheckColNoPush,
171 &ObjColMgr::checkSphereCachedFull, radius, pos, prevPos, mask, info, maskOut,
172 timeOffset);
173}
174
176bool ObjectEscalator::checkSphereCachedFullPush(f32 radius, const EGG::Vector3f &pos,
177 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
178 u32 timeOffset) {
179 return checkCollisionCached(radius, pos, prevPos, mask, info, maskOut, timeOffset);
180}
181
183const EGG::Matrix34f &ObjectEscalator::getUpdatedMatrix(u32 timeOffset) {
184 u32 t = System::RaceManager::Instance()->timer() - timeOffset;
185 m_workMatrix.makeRT(rot(), m_initialPos + m_stepDims * calcStepFactor(t));
186 return m_workMatrix;
187}
188
190bool ObjectEscalator::checkCollision(f32 radius, const EGG::Vector3f &pos,
191 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
192 u32 timeOffset) {
193 update(timeOffset);
194
195 if (m_checkColYPosMin >= pos.y || pos.y >= m_checkColYPosMax) {
196 return false;
197 }
198
199 if (!m_objColMgr->checkSphereFullPush(radius, pos, prevPos, mask, info, maskOut)) {
200 return false;
201 }
202
203 if ((*maskOut & KCL_TYPE_BIT(COL_TYPE_MOVING_ROAD)) == 0) {
204 return false;
205 }
206
207 auto *colDir = CollisionDirector::Instance();
208 if (!colDir->findClosestCollisionEntry(maskOut, KCL_TYPE_BIT(COL_TYPE_MOVING_ROAD))) {
209 return false;
210 }
211
212 auto *entry = colDir->closestCollisionEntry();
213 if (entry->dist > info->movingFloorDist) {
214 info->movingFloorDist = entry->dist;
215
216 u32 t = System::RaceManager::Instance()->timer() - timeOffset;
217 info->roadVelocity = m_stepDims * calcSpeed(t);
218 }
219
220 return true;
221}
222
224bool ObjectEscalator::checkCollisionCached(f32 radius, const EGG::Vector3f &pos,
225 const EGG::Vector3f &prevPos, KCLTypeMask mask, CollisionInfo *info, KCLTypeMask *maskOut,
226 u32 timeOffset) {
227 update(timeOffset);
228
229 if (m_checkColYPosMin >= pos.y || pos.y >= m_checkColYPosMax) {
230 return false;
231 }
232
233 if (!m_objColMgr->checkSphereCachedFullPush(radius, pos, prevPos, mask, info, maskOut)) {
234 return false;
235 }
236
237 if ((*maskOut & KCL_TYPE_BIT(COL_TYPE_MOVING_ROAD)) == 0) {
238 return false;
239 }
240
241 auto *colDir = CollisionDirector::Instance();
242 if (!colDir->findClosestCollisionEntry(maskOut, KCL_TYPE_BIT(COL_TYPE_MOVING_ROAD))) {
243 return false;
244 }
245
246 auto *entry = colDir->closestCollisionEntry();
247 if (entry->dist > info->movingFloorDist) {
248 info->movingFloorDist = entry->dist;
249
250 u32 t = System::RaceManager::Instance()->timer() - timeOffset;
251 info->roadVelocity = m_stepDims * calcSpeed(t);
252 }
253
254 return true;
255}
256
257template <typename T>
258 requires std::is_same_v<T, CollisionInfo> || std::is_same_v<T, CollisionInfoPartial>
259bool ObjectEscalator::checkPointImpl(ShouldCheckFunc shouldCheckFunc, CheckPointFunc<T> checkFunc,
260 const EGG::Vector3f &pos, const EGG::Vector3f &prevPos, KCLTypeMask mask, T *info,
261 KCLTypeMask *maskOut) {
262 if (m_checkColYPosMin > pos.y || pos.y >= m_checkColYPosMax) {
263 return false;
264 }
265
266 if (!(this->*shouldCheckFunc)()) {
267 return false;
268 }
269
270 return (m_objColMgr->*checkFunc)(pos, prevPos, mask, info, maskOut);
271}
272
273template <typename T>
274 requires std::is_same_v<T, CollisionInfo> || std::is_same_v<T, CollisionInfoPartial>
275bool ObjectEscalator::checkSphereImpl(ShouldCheckFunc shouldCheckFunc, CheckSphereFunc<T> checkFunc,
276 f32 radius, const EGG::Vector3f &pos, const EGG::Vector3f &prevPos, KCLTypeMask mask,
277 T *info, KCLTypeMask *maskOut, u32 timeOffset) {
278 if (m_checkColYPosMin > pos.y || pos.y >= m_checkColYPosMax) {
279 return false;
280 }
281
282 if (!(this->*shouldCheckFunc)()) {
283 return false;
284 }
285
286 calcScale(timeOffset);
287 update(timeOffset);
288
289 return (m_objColMgr->*checkFunc)(radius, pos, prevPos, mask, info, maskOut);
290}
291
293f32 ObjectEscalator::calcStepFactor(s32 t) {
294 constexpr s32 REVERSE_FRAMES_S32 = static_cast<s32>(REVERSE_FRAMES_F32);
295 constexpr s32 DISCRETE_STEP_OFFSETS = 200;
296
297 // Time since escalator began stopping
298 std::array<s32, 2> dtStop = {static_cast<s32>(static_cast<f32>(t) - m_stopFrames[0]),
299 static_cast<s32>(static_cast<f32>(t) - m_stopFrames[1])};
300
301 // Time since escalator started moving (second and third time)
302 std::array<s32, 2> dtStart = {static_cast<s32>(static_cast<f32>(t) - m_startFrames[0]),
303 static_cast<s32>(static_cast<f32>(t) - m_startFrames[1])};
304
305 // Time since escalator started moving at full speed
306 std::array<s32, 2> dtFullSpeed = {static_cast<s32>(static_cast<f32>(t) - m_fullSpeedFrames[0]),
307 static_cast<s32>(static_cast<f32>(t) - m_fullSpeedFrames[1])};
308
309 // Each dist lambda represents the displacement from each sub-function of the piecewise function
310
311 // Escalator has not stopped yet
312 auto dist0 = [this](s32 t) -> f64 {
313 t = std::clamp<s32>(t, 0, static_cast<s32>(m_stopFrames[0]));
314 return static_cast<f64>(static_cast<f32>(t) * m_speed[0]);
315 };
316
317 // Escalator is slowing down for the first time
318 auto dist1 = [=, this]() -> f64 {
319 f64 speed = static_cast<f64>(m_speed[0]);
320 f64 t = static_cast<f64>(std::clamp<s32>(dtStop[0], 0, REVERSE_FRAMES_S32));
321 return 0.5 * t * (speed + speed * (1.0 - t / static_cast<f64>(REVERSE_FRAMES_F32)));
322 };
323
324 // Escalator is speeding up after switching directions once
325 auto dist2 = [=, this]() -> f64 {
326 f64 t = static_cast<f64>(std::clamp<s32>(dtStart[0], 0, REVERSE_FRAMES_S32));
327 return t * (0.5 * t * static_cast<f64>(m_speed[1])) / static_cast<f64>(REVERSE_FRAMES_F32);
328 };
329
330 // Escalator is moving full speed after switching directions once
331 auto dist3 = [=, this](s32 t) -> f64 {
332 t = std::clamp<s32>(dtFullSpeed[0], 0, m_midDuration);
333 return static_cast<f64>(static_cast<f32>(t) * m_speed[1]);
334 };
335
336 // Escalator is slowing down for the second time
337 auto dist4 = [=, this]() -> f64 {
338 f64 speed = static_cast<f64>(m_speed[1]);
339 f64 t = static_cast<f32>(std::clamp<s32>(dtStop[1], 0, REVERSE_FRAMES_S32));
340 return 0.5 * t * (speed + speed * (1.0 - t / static_cast<f64>(REVERSE_FRAMES_F32)));
341 };
342
343 // Escalator is speeding up after switching directions twice
344 auto dist5 = [=, this]() -> f64 {
345 f64 speed = static_cast<f64>(m_speed[2]);
346 f64 t = static_cast<f64>(std::clamp<s32>(dtStart[1], 0, REVERSE_FRAMES_S32));
347 return (t * (0.5 * t * speed) / static_cast<f64>(REVERSE_FRAMES_F32));
348 };
349
350 // Escalator is moving full speed after switching directions twice
351 auto dist6 = [=, this]() -> f64 {
352 f64 t = static_cast<f64>(std::max(dtFullSpeed[1], 0));
353 return t * static_cast<f64>(m_speed[2]);
354 };
355
356 f64 totalDist = dist6() + dist5() + dist4() + dist3(t) + dist2() + dist0(t) + dist1();
357 s32 result = static_cast<s32>(STEP_HEIGHT * static_cast<f32>(totalDist));
358 f32 fin = static_cast<f32>(result % DISCRETE_STEP_OFFSETS);
359
360 if (fin < 0.0f) {
361 fin += static_cast<f32>(DISCRETE_STEP_OFFSETS);
362 }
363
364 return fin / STEP_HEIGHT;
365}
366
368f32 ObjectEscalator::calcSpeed(s32 t) {
369 // Escalator has not stopped yet
370 if (static_cast<f32>(t) < m_stopFrames[0]) {
371 return m_speed[0];
372 }
373
374 // Escalator is slowing down for the first time
375 if (t < m_stillFrames[0]) {
376 return m_speed[0] * static_cast<f32>(m_stillFrames[0] - t) / REVERSE_FRAMES_F32;
377 }
378
379 // Standstill right after stopping for the first time
380 if (static_cast<f32>(t) <= m_startFrames[0]) {
381 return 0.0f;
382 }
383
384 // Escalator is speeding up after switching directions once
385 if (static_cast<f32>(t) < REVERSE_FRAMES_F32 + m_startFrames[0]) {
386 return m_speed[1] * (static_cast<f32>(t) - m_startFrames[0]) / REVERSE_FRAMES_F32;
387 }
388
389 // Escalator is moving full speed after switching directions once
390 if (static_cast<f32>(t) < m_stopFrames[1]) {
391 return m_speed[1];
392 }
393
394 // Escalator is slowing down for the second time
395 if (t < m_stillFrames[1]) {
396 return m_speed[1] * static_cast<f32>(m_stillFrames[1] - t) / REVERSE_FRAMES_F32;
397 }
398
399 // Standstill right after stopping for the second/final time
400 if (static_cast<f32>(t) <= m_startFrames[1]) {
401 return 0.0f;
402 }
403
404 // Escalator is speeding up after switching directions twice
405 if (static_cast<f32>(t) < REVERSE_FRAMES_F32 + m_startFrames[1]) {
406 return m_speed[2] * (static_cast<f32>(t) - m_startFrames[1]) / REVERSE_FRAMES_F32;
407 }
408
409 // Escalator is moving full speed after switching directions twice
410 return m_speed[2];
411}
412
413} // namespace Kinoko::Field
@ COL_TYPE_MOVING_ROAD
TF conveyers and CM escalators.
#define KCL_TYPE_BIT(x)
Pertains to collision.