A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
RailInterpolator.cc
1#include "RailInterpolator.hh"
2
3#include "game/field/RailManager.hh"
4
5namespace Kinoko::Field {
6
8RailInterpolator::RailInterpolator(f32 speed, u32 idx)
9 : m_points(RailManager::Instance()->rail(idx)->points()), m_currVel(0.0f) {
10 m_railIdx = idx;
11 auto *rail = RailManager::Instance()->rail(idx);
12 m_pointCount = rail->pointCount();
13 m_isOscillating = rail->isOscillating();
14 m_speed = speed;
15}
16
18RailInterpolator::~RailInterpolator() = default;
19
21const EGG::Vector3f &RailInterpolator::floorNrm(size_t idx) const {
22 return RailManager::Instance()->rail(m_railIdx)->floorNrm(idx);
23}
24
26f32 RailInterpolator::railLength() const {
27 return RailManager::Instance()->rail(m_railIdx)->getPathLength();
28}
29
31void RailInterpolator::updateVel() {
32 f32 t = m_segmentT;
33 setCurrVel((1.0f - t) * m_prevPointVel + t * m_nextPointVel);
34}
35
37void RailInterpolator::calcVelocities() {
38 m_prevPointVel = static_cast<f32>(m_points[m_currPointIdx].setting[0]);
39 m_nextPointVel =
40 shouldChangeDirection() ? 0.0f : static_cast<f32>(m_points[m_nextPointIdx].setting[0]);
41
42 if (m_prevPointVel == 0.0f) {
43 m_prevPointVel = m_speed;
44 }
45
46 if (m_nextPointVel == 0.0f) {
47 m_nextPointVel = m_speed;
48 }
49
50 // @bug This is callable with an invalid m_nextPointIdx, so we need a safeguard
51 // This invalid state is resolved very shortly after, but the vel propagates to the next frame
52 // If t is ever not 0, this leads to undefined behavior, so we guard against it here
53 if (shouldChangeDirection()) {
54 ASSERT(m_segmentT == 0.0f);
55 }
56}
57
59bool RailInterpolator::shouldChangeDirection() const {
60 if (!m_isOscillating) {
61 return m_pointCount == m_nextPointIdx;
62 }
63
64 return m_movementDirectionForward ? m_nextPointIdx == m_pointCount : m_nextPointIdx == -1;
65}
66
68void RailInterpolator::calcDirectionChange() {
69 if (!m_isOscillating) {
70 return;
71 }
72
73 if (m_movementDirectionForward) {
74 m_nextPointIdx -= 2;
75 } else {
76 m_nextPointIdx += 2;
77 }
78
79 m_movementDirectionForward = !m_movementDirectionForward;
80}
81
82void RailInterpolator::calcNextIndices() {
83 if (m_movementDirectionForward) {
84 ++m_currPointIdx;
85 ++m_nextPointIdx;
86 } else {
87 --m_currPointIdx;
88 --m_nextPointIdx;
89 }
90
91 if (!m_isOscillating) {
92 if (m_nextPointIdx == m_pointCount) {
93 m_nextPointIdx = 0;
94 }
95
96 if (m_currPointIdx == m_pointCount) {
97 m_currPointIdx = 0;
98 }
99 }
100}
101
103RailLinearInterpolator::RailLinearInterpolator(f32 speed, u32 idx) : RailInterpolator(speed, idx) {
104 m_transitions = RailManager::Instance()->rail(m_railIdx)->getLinearTransitions();
105 init(0.0f, 0);
106}
107
109RailLinearInterpolator::~RailLinearInterpolator() = default;
110
112void RailLinearInterpolator::init(f32 t, u32 idx) {
113 m_segmentT = t;
114 m_currPointIdx = idx;
115
116 bool isLastPoint = (static_cast<u16>(idx) == m_pointCount - 1);
117 m_nextPointIdx = isLastPoint ? idx - 1 : idx + 1;
118 m_movementDirectionForward = isLastPoint ? !m_isOscillating : true;
119
120 m_curPos = m_points[m_currPointIdx].pos;
121 m_currentDirection = m_points[m_nextPointIdx].pos - m_curPos;
122 m_curTangentDir = m_currentDirection;
123 m_curTangentDir.normalise2();
124 m_currVel = m_speed;
125 m_prevPointVel = m_speed;
126 m_nextPointVel = m_speed;
127 m_4a = false;
128 m_usePerPointVelocities = false;
129 m_segmentLength = m_currentDirection.length();
130 m_currSegmentVel = m_speed / m_segmentLength;
131}
132
134RailInterpolator::Status RailLinearInterpolator::calc() {
135 if (m_4a) {
136 m_curPos = m_points[m_pointCount - 1].pos;
137
138 return Status::ChangingDirection;
139 }
140
141 if (m_usePerPointVelocities) {
142 updateVel();
143 }
144
145 m_segmentT += m_currSegmentVel;
146 m_curPos = lerp(m_segmentT, m_currPointIdx, m_nextPointIdx);
147
148 if (m_segmentT <= 1.0f) {
149 return Status::InProgress;
150 }
151
152 Status status = Status::SegmentEnd;
153
154 calcNextSegment();
155
156 if (shouldChangeDirection()) {
157 status = Status::ChangingDirection;
158
159 calcDirectionChange();
160 }
161
162 m_currentDirection = m_points[m_nextPointIdx].pos - m_points[m_currPointIdx].pos;
163 m_segmentLength = m_currentDirection.length();
164 m_curTangentDir = m_currentDirection;
165 m_currSegmentVel = m_currVel / m_segmentLength;
166 m_curTangentDir.normalise2();
167
168 return status;
169}
170
172void RailLinearInterpolator::setCurrVel(f32 speed) {
173 m_currVel = speed;
174 m_currSegmentVel = m_currVel / m_segmentLength;
175}
176
178void RailLinearInterpolator::evalCubicBezierOnPath(f32 t, EGG::Vector3f &currDir,
179 EGG::Vector3f &curTangentDir) {
180 s16 currIdx = 0;
181 f32 len = 0.0f;
182
183 getPathLocation(t, currIdx, len);
184
185 s16 nextIdx = currIdx + 1;
186 if (nextIdx == m_pointCount) {
187 nextIdx = 0;
188 }
189
190 currDir = m_points[currIdx].pos * (1.0f - len) + m_points[nextIdx].pos * len;
191 curTangentDir = m_transitions[currIdx].m_dir;
192}
193
195void RailLinearInterpolator::getPathLocation(f32 t, s16 &idx, f32 &len) {
196 if (!m_movementDirectionForward) {
197 return;
198 }
199
200 f32 dist = m_segmentT * m_transitions[m_currPointIdx].m_length;
201 if (t <= dist) {
202 idx = m_currPointIdx;
203 len = (dist - t) * m_transitions[m_currPointIdx].m_lengthInv;
204 return;
205 }
206
207 for (s32 i = 0; i < m_pointCount; ++i) {
208 s32 currIdx = m_currPointIdx - 1;
209 if (currIdx == -1) {
210 currIdx = m_pointCount - 1;
211 }
212
213 currIdx -= i;
214 if (currIdx < 0) {
215 currIdx += m_pointCount;
216 }
217
218 dist += m_transitions[currIdx].m_length;
219 if (t <= dist) {
220 idx = currIdx;
221 len = (dist - t) * m_transitions[currIdx].m_lengthInv;
222 return;
223 }
224 }
225}
226
228void RailLinearInterpolator::calcNextSegment() {
229 calcNextIndices();
230
231 // @bug The game accesses POTI points out-of-bounds, but then course-corrects by
232 // setting m_segmentT to 0.0f once it detects an invalid nextPointIdx, but after
233 // it already read from undefined memory.
234 if (shouldChangeDirection()) {
235 m_segmentT = 0.0f;
236 } else {
237 f32 prevDirLength = m_segmentLength;
238 m_currentDirection = m_points[m_nextPointIdx].pos - m_points[m_currPointIdx].pos;
239 m_segmentLength = m_currentDirection.length();
240 m_segmentT = ((m_segmentT - 1.0f) * prevDirLength) / m_segmentLength;
241
242 if (m_segmentT > 1.0f) {
243 m_segmentT = 0.99f;
244 }
245 }
246
247 if (m_usePerPointVelocities) {
248 calcVelocities();
249 }
250}
251
253EGG::Vector3f RailLinearInterpolator::lerp(f32 t, u32 currIdx, u32 nextIdx) const {
254 return m_points[currIdx].pos * (1.0f - t) + m_points[nextIdx].pos * t;
255}
256
258RailSmoothInterpolator::RailSmoothInterpolator(f32 speed, u32 idx) : RailInterpolator(speed, idx) {
259 auto *rail = RailManager::Instance()->rail(m_railIdx);
260 m_transitions = rail->getSplineTransitions();
261 m_pathPercentages = rail->getPathPercentages();
262
263 init(0.0f, 0);
264}
265
267RailSmoothInterpolator::~RailSmoothInterpolator() = default;
268
270void RailSmoothInterpolator::init(f32 t, u32 idx) {
271 m_segmentT = t;
272
273 m_currPointIdx = idx;
274
275 if (idx == static_cast<u32>(m_pointCount - 1) && m_isOscillating) {
276 m_nextPointIdx = m_currPointIdx - 1;
277 m_movementDirectionForward = false;
278 m_curTangentDir = calcCubicBezierTangentDir(m_segmentT, m_transitions[m_currPointIdx]);
279 m_currSegmentVel = m_currVel * m_transitions[m_nextPointIdx].m_lengthInv;
280 } else {
281 m_nextPointIdx = idx + 1 == m_pointCount ? 0 : idx + 1;
282 m_movementDirectionForward = true;
283 m_curTangentDir = calcCubicBezierTangentDir(m_segmentT, m_transitions[m_currPointIdx]);
284 m_currSegmentVel = m_currVel * m_transitions[m_currPointIdx].m_lengthInv;
285 }
286
287 m_curPos = m_points[m_currPointIdx].pos;
288 m_prevPos = m_points[m_currPointIdx].pos;
289 m_currVel = m_speed;
290 m_prevPointVel = m_speed;
291 m_nextPointVel = m_speed;
292 m_velocity = 0.0f;
293 m_4a = false;
294 m_usePerPointVelocities = false;
295 m_velocityPending = false;
296}
297
299RailInterpolator::Status RailSmoothInterpolator::calc() {
300 if (m_4a) {
301 m_curPos = m_transitions[m_pointCount - 2].m_p3;
302
303 return Status::ChangingDirection;
304 }
305
306 if (m_usePerPointVelocities) {
307 updateVel();
308 }
309
310 m_prevPos = m_curPos;
311
312 f32 t = m_movementDirectionForward ? calcT(m_segmentT) : calcT(1.0f - m_segmentT);
313
314 calcCubicBezier(t, m_currPointIdx, m_nextPointIdx, m_curPos, m_curTangentDir);
315
316 m_velocityPending = true;
317 m_segmentT += m_currSegmentVel;
318
319 if (m_segmentT <= 1.0f) {
320 return Status::InProgress;
321 }
322
323 Status status = Status::SegmentEnd;
324
325 calcNextSegment();
326
327 if (shouldChangeDirection()) {
328 status = Status::ChangingDirection;
329
330 calcDirectionChange();
331 }
332
333 if (m_movementDirectionForward) {
334 m_currSegmentVel = m_currVel * m_transitions[m_currPointIdx].m_lengthInv;
335 } else {
336 m_currSegmentVel = m_currVel * m_transitions[m_nextPointIdx].m_lengthInv;
337 }
338
339 return status;
340}
341
343void RailSmoothInterpolator::setCurrVel(f32 speed) {
344 m_currVel = speed;
345
346 if (m_movementDirectionForward) {
347 m_currSegmentVel = speed * m_transitions[m_currPointIdx].m_lengthInv;
348 } else {
349 m_currSegmentVel = speed * m_transitions[m_nextPointIdx].m_lengthInv;
350 }
351}
352
354void RailSmoothInterpolator::evalCubicBezierOnPath(f32 t, EGG::Vector3f &currDir,
355 EGG::Vector3f &curTangentDir) {
356 s16 currIdx = 0;
357 f32 len = 0.0f;
358
359 getPathLocation(t, currIdx, len);
360
361 s16 nextIdx = currIdx + 1;
362 if (nextIdx == m_pointCount) {
363 nextIdx = 0;
364 }
365
366 len = m_movementDirectionForward ? calcT(len) : calcT(1.0f - len);
367
368 calcCubicBezier(len, currIdx, nextIdx, currDir, curTangentDir);
369}
370
372void RailSmoothInterpolator::getPathLocation(f32 t, s16 &idx, f32 &len) {
373 if (!m_movementDirectionForward) {
374 return;
375 }
376
377 f32 dist = m_segmentT * m_transitions[m_currPointIdx].m_length;
378 if (t <= dist) {
379 idx = m_currPointIdx;
380 len = (dist - t) * m_transitions[m_currPointIdx].m_lengthInv;
381 return;
382 }
383
384 for (s32 i = 0; i < m_pointCount; ++i) {
385 s32 currIdx = m_currPointIdx - 1;
386 if (currIdx == -1) {
387 currIdx = m_pointCount - 1;
388 }
389
390 currIdx -= i;
391 if (currIdx < 0) {
392 currIdx += m_pointCount;
393 }
394
395 dist += m_transitions[currIdx].m_length;
396 if (t <= dist) {
397 idx = currIdx;
398 len = (dist - t) * m_transitions[currIdx].m_lengthInv;
399 return;
400 }
401 }
402}
403
405void RailSmoothInterpolator::calcCubicBezier(f32 t, u32 currIdx, u32 nextIdx, EGG::Vector3f &pos,
406 EGG::Vector3f &dir) const {
407 const auto &transition =
408 m_movementDirectionForward ? m_transitions[currIdx] : m_transitions[nextIdx];
409
410 pos = calcCubicBezierPos(t, transition);
411 dir = calcCubicBezierTangentDir(t, transition);
412}
413
415EGG::Vector3f RailSmoothInterpolator::calcCubicBezierPos(f32 t,
416 const RailSplineTransition &trans) const {
417 f32 dt = 1.0f - t;
418
419 EGG::Vector3f res = trans.m_p0 * (dt * dt * dt);
420 res += trans.m_p1 * (3.0f * t * (dt * dt));
421 res += trans.m_p2 * (3.0f * (t * t) * dt);
422 res += trans.m_p3 * (t * t * t);
423
424 return res;
425}
426
428EGG::Vector3f RailSmoothInterpolator::calcCubicBezierTangentDir(f32 t,
429 const RailSplineTransition &trans) const {
430 EGG::Vector3f c1 = trans.m_p0 * -1.0f + trans.m_p1 * 3.0f - (trans.m_p2 * 3.0f) + trans.m_p3;
431 EGG::Vector3f c2 = trans.m_p0 * 3.0f - trans.m_p1 * 6.0f + trans.m_p2 * 3.0f;
432 EGG::Vector3f c3 = trans.m_p0 * -3.0f + trans.m_p1 * 3.0f;
433 EGG::Vector3f ret = c1 * 3.0f * (t * t) + c2 * 2.0f * t + c3;
434
435 ret.normalise2();
436
437 if (!m_movementDirectionForward) {
438 ret *= -1.0f;
439 }
440
441 return ret;
442}
443
448 constexpr u32 ESTIMATOR_SAMPLE_COUNT = RailSpline::getEstimatorSampleCount();
449 constexpr f32 ESTIMATOR_STEP = RailSpline::getEstimatorStep();
450
451 u16 sampleIdx = m_movementDirectionForward ? m_currPointIdx * ESTIMATOR_SAMPLE_COUNT :
452 m_nextPointIdx * ESTIMATOR_SAMPLE_COUNT;
453
454 for (u16 i = 0; i < ESTIMATOR_SAMPLE_COUNT - 1; ++i) {
455 f32 currPercent = m_pathPercentages[sampleIdx + i];
456 f32 nextPercent = m_pathPercentages[sampleIdx + i + 1];
457
458 if (currPercent <= t && nextPercent > t) {
459 f32 delta = (t - currPercent) / (nextPercent - currPercent);
460 return ESTIMATOR_STEP * static_cast<f32>(i) + ESTIMATOR_STEP * delta;
461 }
462 }
463
464 f32 lastPercent = m_pathPercentages[sampleIdx + ESTIMATOR_SAMPLE_COUNT - 1];
465
466 if (lastPercent <= t && 1.0f >= t) {
467 u16 idx = ESTIMATOR_SAMPLE_COUNT - 1;
468 f32 delta = (t - lastPercent) / (1.0f - lastPercent);
469 return ESTIMATOR_STEP * static_cast<f32>(idx) + ESTIMATOR_STEP * delta;
470 }
471
472 return 0.0f;
473}
474
476void RailSmoothInterpolator::calcNextSegment() {
477 f32 nextT = m_segmentT - 1.0f;
478
479 if (m_isOscillating) {
480 if (m_nextPointIdx == 0 || m_nextPointIdx == m_pointCount - 1) {
481 m_segmentT = 0.0f;
482 } else if (m_movementDirectionForward) {
483 m_segmentT = nextT * m_transitions[m_currPointIdx].m_length *
484 m_transitions[m_nextPointIdx].m_lengthInv;
485 } else {
486 m_segmentT = nextT * m_transitions[m_currPointIdx - 1].m_length *
487 m_transitions[m_nextPointIdx - 1].m_lengthInv;
488 }
489 } else {
490 m_segmentT = nextT * m_transitions[m_currPointIdx].m_length *
491 m_transitions[m_nextPointIdx].m_lengthInv;
492 }
493
494 if (m_segmentT > 1.0f) {
495 m_segmentT = 0.99f;
496 }
497
498 calcNextIndices();
499
500 if (m_usePerPointVelocities) {
501 calcVelocities();
502 }
503}
504
505} // namespace Kinoko::Field
Pertains to collision.