A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
Rail.cc
1#include "Rail.hh"
2
3#include "game/field/CollisionDirector.hh"
4
5namespace Kinoko::Field {
6
8Rail::Rail(u16 idx, System::MapdataPointInfo *info) {
9 m_idx = idx;
10 m_pointCapacity = info->pointCount();
11 m_pointCount = info->pointCount();
12 m_isOscillating = info->setting(1) == 1;
13 m_points = info->points();
14 m_hasCheckedCol = false;
15 m_someScale = 0.1f;
16}
17
20Rail::~Rail() = default;
21
23void Rail::addPoint(f32 scale, const EGG::Vector3f &point) {
24 m_points.back().pos = point;
25 m_someScale = scale;
26 onPointAdded();
27}
28
30void Rail::checkSphereFull() {
31 if (m_hasCheckedCol) {
32 return;
33 }
34
35 m_floorNrms = owning_span<EGG::Vector3f>(m_pointCount);
36
37 for (size_t i = 0; i < m_pointCount; ++i) {
38 CollisionInfo info;
39 info.bbox.setZero();
40
41 bool hasCourseCol = CollisionDirector::Instance()->checkSphereFull(100.0f, m_points[i].pos,
42 EGG::Vector3f::inf, KCL_TYPE_FLOOR, &info, nullptr, 0);
43
44 if (hasCourseCol) {
45 if (info.floorDist > std::numeric_limits<f32>::min()) {
46 m_floorNrms[i] = info.floorNrm;
47 }
48 } else {
49 m_floorNrms[i] = EGG::Vector3f::ey;
50 }
51 }
52
53 m_hasCheckedCol = true;
54}
55
57RailLine::RailLine(u16 idx, System::MapdataPointInfo *info) : Rail(idx, info) {
58 m_dirCount = m_isOscillating ? m_pointCount - 1 : m_pointCount;
59 m_transitions = owning_span<RailLineTransition>(m_dirCount);
60 m_pathLength = 0.0f;
61
62 for (u16 i = 0; i < m_pointCount - 1; ++i) {
63 auto &transition = m_transitions[i];
64 transition.m_dir = m_points[i + 1].pos - m_points[i].pos;
65 transition.m_length = transition.m_dir.normalise();
66 transition.m_lengthInv = 1.0f / transition.m_length;
67 m_pathLength += transition.m_length;
68 }
69
70 if (!m_isOscillating) {
71 auto &transition = m_transitions.back();
72 transition.m_dir = m_points.front().pos - m_points.back().pos;
73 transition.m_length = transition.m_dir.normalise();
74 transition.m_lengthInv = 1.0f / transition.m_length;
75 m_pathLength += transition.m_length;
76 }
77}
78
80RailLine::~RailLine() = default;
81
83RailSpline::RailSpline(u16 idx, System::MapdataPointInfo *info) : Rail(idx, info) {
84 m_transitionCount = m_isOscillating ? m_pointCount - 1 : m_pointCount;
85 m_transitions = owning_span<RailSplineTransition>(m_transitionCount);
86
87 // This is normally not set until AFTER the call to invalidateTransitions,
88 // but the expected behavior requires that this is set to false.
89 // This misordering was probably never noticed since EGG::Heap zeroes memory.
90 m_doNotAllocatePathPercentages = false;
91
92 invalidateTransitions(false);
93
94 m_pathLength = 0.0f;
95
96 for (size_t i = 0; i < m_transitionCount; ++i) {
97 m_pathLength += m_transitions[i].m_length;
98 }
99}
100
102RailSpline::~RailSpline() = default;
103
105void RailSpline::onPointsChanged() {
106 m_doNotAllocatePathPercentages = true;
107 m_transitionCount = m_isOscillating ? m_pointCount - 1 : m_pointCount;
108
109 invalidateTransitions(false);
110
111 m_pathLength = 0.0f;
112
113 for (const auto &transition : m_transitions) {
114 m_pathLength += transition.m_length;
115 }
116}
117
119void RailSpline::onPointAdded() {
120 m_doNotAllocatePathPercentages = true;
121 m_transitionCount = m_isOscillating ? m_pointCount - 1 : m_pointCount;
122
123 invalidateTransitions(true);
124
125 m_pathLength = 0.0f;
126
127 for (const auto &transition : m_transitions) {
128 m_pathLength += transition.m_length;
129 }
130}
131
133void RailSpline::invalidateTransitions(bool lastOnly) {
134 if (!m_doNotAllocatePathPercentages) {
135 size_t count = ESTIMATOR_SAMPLE_COUNT * m_transitionCount + 1;
136 m_pathPercentages = owning_span<f32>(count);
137 }
138
139 m_segmentCount = 0;
140
141 if (m_isOscillating) {
142 if (!lastOnly) {
143 auto &firstTransition = m_transitions[0];
144 firstTransition.m_p0 = m_points[0].pos;
145 firstTransition.m_p1 =
146 (m_points[1].pos - m_points[0].pos).multInv(4.0f) + m_points[0].pos;
147 firstTransition.m_p2 =
148 calcCubicBezierP2(m_points[0].pos, m_points[1].pos, m_points[2].pos);
149 firstTransition.m_p3 = m_points[1].pos;
150 firstTransition.m_length = estimateLength(firstTransition, ESTIMATOR_SAMPLE_COUNT);
151 firstTransition.m_lengthInv = 1.0f / firstTransition.m_length;
152
153 for (u16 i = 1; i < m_transitionCount - 1; ++i) {
154 calcCubicBezierControlPoints(m_points[i - 1].pos, m_points[i].pos,
155 m_points[i + 1].pos, m_points[i + 2].pos, ESTIMATOR_SAMPLE_COUNT,
156 m_transitions[i]);
157 }
158 }
159
160 auto &lastTransition = m_transitions.back();
161 lastTransition.m_p0 = m_points[m_transitionCount - 1].pos;
162 lastTransition.m_p1 = calcCubicBezierP1(m_points[m_transitionCount - 2].pos,
163 m_points[m_transitionCount - 1].pos, m_points[m_transitionCount].pos);
164 lastTransition.m_p2 =
165 (m_points[m_transitionCount - 1].pos - m_points[m_transitionCount].pos)
166 .multInv(4.0f) +
167 m_points[m_transitionCount].pos;
168 lastTransition.m_p3 = m_points[m_transitionCount].pos;
169 lastTransition.m_length = estimateLength(lastTransition, ESTIMATOR_SAMPLE_COUNT);
170 lastTransition.m_lengthInv = 1.0f / lastTransition.m_length;
171 } else {
172 if (!lastOnly) {
173 auto &firstTransition = m_transitions[0];
174 firstTransition.m_p0 = m_points[0].pos;
175 firstTransition.m_p1 = calcCubicBezierP1(m_points[m_transitionCount - 1].pos,
176 m_points[0].pos, m_points[1].pos);
177 firstTransition.m_p2 =
178 calcCubicBezierP2(m_points[0].pos, m_points[1].pos, m_points[2].pos);
179 firstTransition.m_p3 = m_points[1].pos;
180 firstTransition.m_length = estimateLength(firstTransition, ESTIMATOR_SAMPLE_COUNT);
181 firstTransition.m_lengthInv = 1.0f / firstTransition.m_length;
182
183 for (u16 i = 1; i < m_transitionCount - 1; ++i) {
184 if (i + 2 != m_transitionCount) {
185 calcCubicBezierControlPoints(m_points[i - 1].pos, m_points[i].pos,
186 m_points[i + 1].pos, m_points[i + 2].pos, ESTIMATOR_SAMPLE_COUNT,
187 m_transitions[i]);
188 } else {
189 calcCubicBezierControlPoints(m_points[i - 1].pos, m_points[i].pos,
190 m_points[i + 1].pos, m_points[0].pos, ESTIMATOR_SAMPLE_COUNT,
191 m_transitions[i]);
192 }
193 }
194 }
195
196 auto &lastTransition = m_transitions.back();
197 lastTransition.m_p0 = m_points[m_transitionCount - 1].pos;
198 lastTransition.m_p1 = calcCubicBezierP1(m_points[m_transitionCount - 2].pos,
199 m_points[m_transitionCount - 1].pos, m_points[0].pos);
200 lastTransition.m_p2 = calcCubicBezierP2(m_points[m_transitionCount - 1].pos,
201 m_points[0].pos, m_points[1].pos);
202 lastTransition.m_p3 = m_points[0].pos;
203 lastTransition.m_length = estimateLength(lastTransition, ESTIMATOR_SAMPLE_COUNT);
204 lastTransition.m_lengthInv = 1.0f / lastTransition.m_length;
205 }
206}
207
209void RailSpline::calcCubicBezierControlPoints(const EGG::Vector3f &p0, const EGG::Vector3f &p1,
210 const EGG::Vector3f &p2, const EGG::Vector3f &p3, s32 count,
211 RailSplineTransition &transition) {
212 transition.m_p0 = p1;
213 transition.m_p1 = calcCubicBezierP1(p0, p1, p2);
214 transition.m_p2 = calcCubicBezierP2(p1, p2, p3);
215 transition.m_p3 = p2;
216 transition.m_length = estimateLength(transition, count);
217 transition.m_lengthInv = 1.0f / transition.m_length;
218}
219
221f32 RailSpline::estimateLength(const RailSplineTransition &transition, s32 count) {
222 std::array<EGG::Vector3f, 11> waypoints;
223
224 for (s32 i = 0; i < count + 1; ++i) {
225 waypoints[i] = cubicBezier(ESTIMATOR_STEP * static_cast<f32>(i), transition);
226 }
227 f32 length = 0.0f;
228
229 // Numerator loop
230 for (s32 i = 0; i < count; ++i) {
231 m_pathPercentages[m_segmentCount++] = length;
232 length += (waypoints[i] - waypoints[i + 1]).length();
233 }
234
235 // Denominator loop
236 for (s32 i = m_segmentCount - 1; i > m_segmentCount - count - 1; --i) {
237 m_pathPercentages[i] /= length;
238 }
239
240 return length;
241}
242
244EGG::Vector3f RailSpline::calcCubicBezierP1(const EGG::Vector3f &p0, const EGG::Vector3f &p1,
245 const EGG::Vector3f &p2) const {
246 EGG::Vector3f res = p2 - p0;
247 f32 len = res.length();
248 res.normalise2();
249 return p1 + res * (len * m_someScale);
250}
251
253EGG::Vector3f RailSpline::calcCubicBezierP2(const EGG::Vector3f &p0, const EGG::Vector3f &p1,
254 const EGG::Vector3f &p2) const {
255 EGG::Vector3f res = p0 - p2;
256 f32 len = res.length();
257 res.normalise2();
258 return p1 + res * (len * m_someScale);
259}
260
262EGG::Vector3f RailSpline::cubicBezier(f32 t, const RailSplineTransition &transition) const {
263 f32 dt = 1.0f - t;
264
265 EGG::Vector3f res = transition.m_p0 * (dt * dt * dt);
266 res += transition.m_p1 * (3.0f * t * (dt * dt));
267 res += transition.m_p2 * (3.0f * (t * t) * dt);
268 res += transition.m_p3 * (t * t * t);
269
270 return res;
271}
272
273} // namespace Kinoko::Field
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
Pertains to collision.