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 m_estimatorSampleCount = 10;
87 m_estimatorStep = 1.0f / static_cast<f32>(m_estimatorSampleCount);
88
89 // This is normally not set until AFTER the call to invalidateTransitions,
90 // but the expected behavior requires that this is set to false.
91 // This misordering was probably never noticed since EGG::Heap zeroes memory.
92 m_doNotAllocatePathPercentages = false;
93
94 invalidateTransitions(false);
95
96 m_pathLength = 0.0f;
97
98 for (size_t i = 0; i < m_transitionCount; ++i) {
99 m_pathLength += m_transitions[i].m_length;
100 }
101}
102
104RailSpline::~RailSpline() = default;
105
107void RailSpline::onPointsChanged() {
108 m_doNotAllocatePathPercentages = true;
109 m_transitionCount = m_isOscillating ? m_pointCount - 1 : m_pointCount;
110
111 invalidateTransitions(false);
112
113 m_pathLength = 0.0f;
114
115 for (const auto &transition : m_transitions) {
116 m_pathLength += transition.m_length;
117 }
118}
119
121void RailSpline::onPointAdded() {
122 m_doNotAllocatePathPercentages = true;
123 m_transitionCount = m_isOscillating ? m_pointCount - 1 : m_pointCount;
124
125 invalidateTransitions(true);
126
127 m_pathLength = 0.0f;
128
129 for (const auto &transition : m_transitions) {
130 m_pathLength += transition.m_length;
131 }
132}
133
135void RailSpline::invalidateTransitions(bool lastOnly) {
136 if (!m_doNotAllocatePathPercentages) {
137 size_t count = m_estimatorSampleCount * m_transitionCount + 1;
138 m_pathPercentages = owning_span<f32>(count);
139 }
140
141 m_segmentCount = 0;
142
143 if (m_isOscillating) {
144 if (!lastOnly) {
145 auto &firstTransition = m_transitions[0];
146 firstTransition.m_p0 = m_points[0].pos;
147 firstTransition.m_p1 =
148 (m_points[1].pos - m_points[0].pos).multInv(4.0f) + m_points[0].pos;
149 firstTransition.m_p2 =
150 calcCubicBezierP2(m_points[0].pos, m_points[1].pos, m_points[2].pos);
151 firstTransition.m_p3 = m_points[1].pos;
152 firstTransition.m_length = estimateLength(firstTransition, m_estimatorSampleCount);
153 firstTransition.m_lengthInv = 1.0f / firstTransition.m_length;
154
155 for (u16 i = 1; i < m_transitionCount - 1; ++i) {
156 calcCubicBezierControlPoints(m_points[i - 1].pos, m_points[i].pos,
157 m_points[i + 1].pos, m_points[i + 2].pos, m_estimatorSampleCount,
158 m_transitions[i]);
159 }
160 }
161
162 auto &lastTransition = m_transitions.back();
163 lastTransition.m_p0 = m_points[m_transitionCount - 1].pos;
164 lastTransition.m_p1 = calcCubicBezierP1(m_points[m_transitionCount - 2].pos,
165 m_points[m_transitionCount - 1].pos, m_points[m_transitionCount].pos);
166 lastTransition.m_p2 =
167 (m_points[m_transitionCount - 1].pos - m_points[m_transitionCount].pos)
168 .multInv(4.0f) +
169 m_points[m_transitionCount].pos;
170 lastTransition.m_p3 = m_points[m_transitionCount].pos;
171 lastTransition.m_length = estimateLength(lastTransition, m_estimatorSampleCount);
172 lastTransition.m_lengthInv = 1.0f / lastTransition.m_length;
173 } else {
174 if (!lastOnly) {
175 auto &firstTransition = m_transitions[0];
176 firstTransition.m_p0 = m_points[0].pos;
177 firstTransition.m_p1 = calcCubicBezierP1(m_points[m_transitionCount - 1].pos,
178 m_points[0].pos, m_points[1].pos);
179 firstTransition.m_p2 =
180 calcCubicBezierP2(m_points[0].pos, m_points[1].pos, m_points[2].pos);
181 firstTransition.m_p3 = m_points[1].pos;
182 firstTransition.m_length = estimateLength(firstTransition, m_estimatorSampleCount);
183 firstTransition.m_lengthInv = 1.0f / firstTransition.m_length;
184
185 for (u16 i = 1; i < m_transitionCount - 1; ++i) {
186 if (i + 2 != m_transitionCount) {
187 calcCubicBezierControlPoints(m_points[i - 1].pos, m_points[i].pos,
188 m_points[i + 1].pos, m_points[i + 2].pos, m_estimatorSampleCount,
189 m_transitions[i]);
190 } else {
191 calcCubicBezierControlPoints(m_points[i - 1].pos, m_points[i].pos,
192 m_points[i + 1].pos, m_points[0].pos, m_estimatorSampleCount,
193 m_transitions[i]);
194 }
195 }
196 }
197
198 auto &lastTransition = m_transitions.back();
199 lastTransition.m_p0 = m_points[m_transitionCount - 1].pos;
200 lastTransition.m_p1 = calcCubicBezierP1(m_points[m_transitionCount - 2].pos,
201 m_points[m_transitionCount - 1].pos, m_points[0].pos);
202 lastTransition.m_p2 = calcCubicBezierP2(m_points[m_transitionCount - 1].pos,
203 m_points[0].pos, m_points[1].pos);
204 lastTransition.m_p3 = m_points[0].pos;
205 lastTransition.m_length = estimateLength(lastTransition, m_estimatorSampleCount);
206 lastTransition.m_lengthInv = 1.0f / lastTransition.m_length;
207 }
208}
209
211void RailSpline::calcCubicBezierControlPoints(const EGG::Vector3f &p0, const EGG::Vector3f &p1,
212 const EGG::Vector3f &p2, const EGG::Vector3f &p3, s32 count,
213 RailSplineTransition &transition) {
214 transition.m_p0 = p1;
215 transition.m_p1 = calcCubicBezierP1(p0, p1, p2);
216 transition.m_p2 = calcCubicBezierP2(p1, p2, p3);
217 transition.m_p3 = p2;
218 transition.m_length = estimateLength(transition, count);
219 transition.m_lengthInv = 1.0f / transition.m_length;
220}
221
223f32 RailSpline::estimateLength(const RailSplineTransition &transition, s32 count) {
224 std::array<EGG::Vector3f, 11> waypoints;
225
226 for (s32 i = 0; i < count + 1; ++i) {
227 waypoints[i] = cubicBezier(m_estimatorStep * static_cast<f32>(i), transition);
228 }
229 f32 length = 0.0f;
230
231 // Numerator loop
232 for (s32 i = 0; i < count; ++i) {
233 m_pathPercentages[m_segmentCount++] = length;
234 length += (waypoints[i] - waypoints[i + 1]).length();
235 }
236
237 // Denominator loop
238 for (s32 i = m_segmentCount - 1; i > m_segmentCount - count - 1; --i) {
239 m_pathPercentages[i] /= length;
240 }
241
242 return length;
243}
244
246EGG::Vector3f RailSpline::calcCubicBezierP1(const EGG::Vector3f &p0, const EGG::Vector3f &p1,
247 const EGG::Vector3f &p2) const {
248 EGG::Vector3f res = p2 - p0;
249 f32 len = res.length();
250 res.normalise2();
251 return p1 + res * (len * m_someScale);
252}
253
255EGG::Vector3f RailSpline::calcCubicBezierP2(const EGG::Vector3f &p0, const EGG::Vector3f &p1,
256 const EGG::Vector3f &p2) const {
257 EGG::Vector3f res = p0 - p2;
258 f32 len = res.length();
259 res.normalise2();
260 return p1 + res * (len * m_someScale);
261}
262
264EGG::Vector3f RailSpline::cubicBezier(f32 t, const RailSplineTransition &transition) const {
265 f32 dt = 1.0f - t;
266
267 EGG::Vector3f res = transition.m_p0 * (dt * dt * dt);
268 res += transition.m_p1 * (3.0f * t * (dt * dt));
269 res += transition.m_p2 * (3.0f * (t * t) * dt);
270 res += transition.m_p3 * (t * t * t);
271
272 return res;
273}
274
275} // namespace Kinoko::Field
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
Pertains to collision.