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 Field {
6
8RailInterpolator::RailInterpolator(f32 speed, u32 idx) : m_currVel(0.0f) {
9 m_railIdx = idx;
10 auto *rail = RailManager::Instance()->rail(idx);
11 m_pointCount = rail->pointCount();
12 m_points = rail->points();
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_currSegmentVel = m_speed / m_currentDirection.length();
130}
131
133RailInterpolator::Status RailLinearInterpolator::calc() {
134 if (m_4a) {
135 m_curPos = m_points[m_pointCount - 1].pos;
136
137 return Status::ChangingDirection;
138 }
139
140 if (m_usePerPointVelocities) {
141 updateVel();
142 }
143
144 m_segmentT += m_currSegmentVel;
145 m_curPos = lerp(m_segmentT, m_currPointIdx, m_nextPointIdx);
146
147 if (m_segmentT <= 1.0f) {
148 return Status::InProgress;
149 }
150
151 Status status = Status::SegmentEnd;
152
153 calcNextSegment();
154
155 if (shouldChangeDirection()) {
156 status = Status::ChangingDirection;
157
158 calcDirectionChange();
159 }
160
161 m_currentDirection = m_points[m_nextPointIdx].pos - m_points[m_currPointIdx].pos;
162 m_curTangentDir = m_currentDirection;
163 m_currSegmentVel = m_currVel / m_currentDirection.length();
164 m_curTangentDir.normalise2();
165
166 return status;
167}
168
170void RailLinearInterpolator::setCurrVel(f32 speed) {
171 m_currVel = speed;
172 m_currSegmentVel = m_currVel / m_currentDirection.length();
173}
174
176void RailLinearInterpolator::evalCubicBezierOnPath(f32 t, EGG::Vector3f &currDir,
177 EGG::Vector3f &curTangentDir) {
178 s16 currIdx = 0;
179 f32 len = 0.0f;
180
181 getPathLocation(t, currIdx, len);
182
183 s16 nextIdx = currIdx + 1;
184 if (nextIdx == m_pointCount) {
185 nextIdx = 0;
186 }
187
188 currDir = m_points[currIdx].pos * (1.0f - len) + m_points[nextIdx].pos * len;
189 curTangentDir = m_transitions[currIdx].m_dir;
190}
191
193void RailLinearInterpolator::getPathLocation(f32 t, s16 &idx, f32 &len) {
194 if (!m_movementDirectionForward) {
195 return;
196 }
197
198 f32 dist = m_segmentT * m_transitions[m_currPointIdx].m_length;
199 if (t <= dist) {
200 idx = m_currPointIdx;
201 len = (dist - t) * m_transitions[m_currPointIdx].m_lengthInv;
202 return;
203 }
204
205 for (s32 i = 0; i < m_pointCount; ++i) {
206 s32 currIdx = m_currPointIdx - 1;
207 if (currIdx == -1) {
208 currIdx = m_pointCount - 1;
209 }
210
211 currIdx -= i;
212 if (currIdx < 0) {
213 currIdx += m_pointCount;
214 }
215
216 dist += m_transitions[currIdx].m_length;
217 if (t <= dist) {
218 idx = currIdx;
219 len = (dist - t) * m_transitions[currIdx].m_lengthInv;
220 return;
221 }
222 }
223}
224
226void RailLinearInterpolator::calcNextSegment() {
227 calcNextIndices();
228
229 // @bug The game accesses POTI points out-of-bounds, but then course-corrects by
230 // setting m_segmentT to 0.0f once it detects an invalid nextPointIdx, but after
231 // it already read from undefined memory.
232 if (shouldChangeDirection()) {
233 m_segmentT = 0.0f;
234 } else {
235 f32 prevDirLength = m_currentDirection.length();
236 m_currentDirection = m_points[m_nextPointIdx].pos - m_points[m_currPointIdx].pos;
237 m_segmentT = ((m_segmentT - 1.0f) * prevDirLength) / m_currentDirection.length();
238
239 if (m_segmentT > 1.0f) {
240 m_segmentT = 0.99f;
241 }
242 }
243
244 if (m_usePerPointVelocities) {
245 calcVelocities();
246 }
247}
248
250EGG::Vector3f RailLinearInterpolator::lerp(f32 t, u32 currIdx, u32 nextIdx) const {
251 return m_points[currIdx].pos * (1.0f - t) + m_points[nextIdx].pos * t;
252}
253
255RailSmoothInterpolator::RailSmoothInterpolator(f32 speed, u32 idx) : RailInterpolator(speed, idx) {
256 auto *rail = RailManager::Instance()->rail(m_railIdx);
257 m_transitions = rail->getSplineTransitions();
258 m_estimatorSampleCount = static_cast<u32>(rail->getEstimatorSampleCount());
259 m_estimatorStep = rail->getEstimatorStep();
260 m_pathPercentages = rail->getPathPercentages();
261
262 init(0.0f, 0);
263}
264
266RailSmoothInterpolator::~RailSmoothInterpolator() = default;
267
269void RailSmoothInterpolator::init(f32 t, u32 idx) {
270 m_segmentT = t;
271
272 m_currPointIdx = idx;
273
274 if (idx == static_cast<u32>(m_pointCount - 1) && m_isOscillating) {
275 m_nextPointIdx = m_currPointIdx - 1;
276 m_movementDirectionForward = false;
277 m_curTangentDir = calcCubicBezierTangentDir(m_segmentT, m_transitions[m_currPointIdx]);
278 m_currSegmentVel = m_currVel * m_transitions[m_nextPointIdx].m_lengthInv;
279 } else {
280 m_nextPointIdx = idx + 1 == m_pointCount ? 0 : idx + 1;
281 m_movementDirectionForward = true;
282 m_curTangentDir = calcCubicBezierTangentDir(m_segmentT, m_transitions[m_currPointIdx]);
283 m_currSegmentVel = m_currVel * m_transitions[m_currPointIdx].m_lengthInv;
284 }
285
286 m_curPos = m_points[m_currPointIdx].pos;
287 m_prevPos = m_points[m_currPointIdx].pos;
288 m_currVel = m_speed;
289 m_prevPointVel = m_speed;
290 m_nextPointVel = m_speed;
291 m_velocity = 0.0f;
292 m_4a = false;
293 m_usePerPointVelocities = false;
294}
295
297RailInterpolator::Status RailSmoothInterpolator::calc() {
298 if (m_4a) {
299 m_curPos = m_transitions[m_pointCount - 2].m_p3;
300
301 return Status::ChangingDirection;
302 }
303
304 if (m_usePerPointVelocities) {
305 updateVel();
306 }
307
308 m_prevPos = m_curPos;
309
310 f32 t = m_movementDirectionForward ? calcT(m_segmentT) : calcT(1.0f - m_segmentT);
311
312 calcCubicBezier(t, m_currPointIdx, m_nextPointIdx, m_curPos, m_curTangentDir);
313
314 EGG::Vector3f deltaPos = m_curPos - m_prevPos;
315 m_velocity = deltaPos.length();
316 m_segmentT += m_currSegmentVel;
317
318 if (m_segmentT <= 1.0f) {
319 return Status::InProgress;
320 }
321
322 Status status = Status::SegmentEnd;
323
324 calcNextSegment();
325
326 if (shouldChangeDirection()) {
327 status = Status::ChangingDirection;
328
329 calcDirectionChange();
330 }
331
332 if (m_movementDirectionForward) {
333 m_currSegmentVel = m_currVel * m_transitions[m_currPointIdx].m_lengthInv;
334 } else {
335 m_currSegmentVel = m_currVel * m_transitions[m_nextPointIdx].m_lengthInv;
336 }
337
338 return status;
339}
340
342void RailSmoothInterpolator::setCurrVel(f32 speed) {
343 m_currVel = speed;
344
345 if (m_movementDirectionForward) {
346 m_currSegmentVel = speed * m_transitions[m_currPointIdx].m_lengthInv;
347 } else {
348 m_currSegmentVel = speed * m_transitions[m_nextPointIdx].m_lengthInv;
349 }
350}
351
353void RailSmoothInterpolator::evalCubicBezierOnPath(f32 t, EGG::Vector3f &currDir,
354 EGG::Vector3f &curTangentDir) {
355 s16 currIdx = 0;
356 f32 len = 0.0f;
357
358 getPathLocation(t, currIdx, len);
359
360 s16 nextIdx = currIdx + 1;
361 if (nextIdx == m_pointCount) {
362 nextIdx = 0;
363 }
364
365 len = m_movementDirectionForward ? calcT(len) : calcT(1.0f - len);
366
367 calcCubicBezier(len, currIdx, nextIdx, currDir, curTangentDir);
368}
369
371void RailSmoothInterpolator::getPathLocation(f32 t, s16 &idx, f32 &len) {
372 if (!m_movementDirectionForward) {
373 return;
374 }
375
376 f32 dist = m_segmentT * m_transitions[m_currPointIdx].m_length;
377 if (t <= dist) {
378 idx = m_currPointIdx;
379 len = (dist - t) * m_transitions[m_currPointIdx].m_lengthInv;
380 return;
381 }
382
383 for (s32 i = 0; i < m_pointCount; ++i) {
384 s32 currIdx = m_currPointIdx - 1;
385 if (currIdx == -1) {
386 currIdx = m_pointCount - 1;
387 }
388
389 currIdx -= i;
390 if (currIdx < 0) {
391 currIdx += m_pointCount;
392 }
393
394 dist += m_transitions[currIdx].m_length;
395 if (t <= dist) {
396 idx = currIdx;
397 len = (dist - t) * m_transitions[currIdx].m_lengthInv;
398 return;
399 }
400 }
401}
402
404void RailSmoothInterpolator::calcCubicBezier(f32 t, u32 currIdx, u32 nextIdx, EGG::Vector3f &pos,
405 EGG::Vector3f &dir) const {
406 auto &transition = m_movementDirectionForward ? m_transitions[currIdx] : m_transitions[nextIdx];
407
408 pos = calcCubicBezierPos(t, transition);
409 dir = calcCubicBezierTangentDir(t, transition);
410}
411
413EGG::Vector3f RailSmoothInterpolator::calcCubicBezierPos(f32 t,
414 const RailSplineTransition &trans) const {
415 f32 dt = 1.0f - t;
416
417 EGG::Vector3f res = trans.m_p0 * (dt * dt * dt);
418 res += trans.m_p1 * (3.0f * t * (dt * dt));
419 res += trans.m_p2 * (3.0f * (t * t) * dt);
420 res += trans.m_p3 * (t * t * t);
421
422 return res;
423}
424
426EGG::Vector3f RailSmoothInterpolator::calcCubicBezierTangentDir(f32 t,
427 const RailSplineTransition &trans) const {
428 EGG::Vector3f c1 = trans.m_p0 * -1.0f + trans.m_p1 * 3.0f - (trans.m_p2 * 3.0f) + trans.m_p3;
429 EGG::Vector3f c2 = trans.m_p0 * 3.0f - trans.m_p1 * 6.0f + trans.m_p2 * 3.0f;
430 EGG::Vector3f c3 = trans.m_p0 * -3.0f + trans.m_p1 * 3.0f;
431 EGG::Vector3f ret = c1 * 3.0f * (t * t) + c2 * 2.0f * t + c3;
432
433 ret.normalise2();
434
435 if (!m_movementDirectionForward) {
436 ret *= -1.0f;
437 }
438
439 return ret;
440}
441
443f32 RailSmoothInterpolator::calcT(f32 t) const {
444 u16 sampleIdx = m_movementDirectionForward ? m_currPointIdx * m_estimatorSampleCount :
445 m_nextPointIdx * m_estimatorSampleCount;
446
447 f32 delta = 0.0f;
448 u16 idx = 0;
449
450 for (u16 i = 0; i < m_estimatorSampleCount - 1; ++i) {
451 f32 currPercent = m_pathPercentages[sampleIdx + i];
452 f32 nextPercent = m_pathPercentages[sampleIdx + i + 1];
453
454 if (currPercent <= t && nextPercent > t) {
455 delta = (t - currPercent) / (nextPercent - currPercent);
456 idx = i;
457 }
458 }
459
460 f32 lastPercent = m_pathPercentages[sampleIdx + m_estimatorSampleCount - 1];
461
462 if (lastPercent <= t && 1.0f >= t) {
463 idx = m_estimatorSampleCount - 1;
464 delta = (t - lastPercent) / (1.0f - lastPercent);
465 }
466
467 return m_estimatorStep * static_cast<f32>(idx) + m_estimatorStep * delta;
468}
469
471void RailSmoothInterpolator::calcNextSegment() {
472 f32 nextT = m_segmentT - 1.0f;
473
474 if (m_isOscillating) {
475 if (m_nextPointIdx == 0 || m_nextPointIdx == m_pointCount - 1) {
476 m_segmentT = 0.0f;
477 } else if (m_movementDirectionForward) {
478 m_segmentT = nextT * m_transitions[m_currPointIdx].m_length *
479 m_transitions[m_nextPointIdx].m_lengthInv;
480 } else {
481 m_segmentT = nextT * m_transitions[m_currPointIdx - 1].m_length *
482 m_transitions[m_nextPointIdx - 1].m_lengthInv;
483 }
484 } else {
485 m_segmentT = nextT * m_transitions[m_currPointIdx].m_length *
486 m_transitions[m_nextPointIdx].m_lengthInv;
487 }
488
489 if (m_segmentT > 1.0f) {
490 m_segmentT = 0.99f;
491 }
492
493 calcNextIndices();
494
495 if (m_usePerPointVelocities) {
496 calcVelocities();
497 }
498}
499
500} // namespace Field
Pertains to collision.
A 3D float vector.
Definition Vector.hh:88
f32 length() const
The square root of the vector's dot product.
Definition Vector.hh:192