A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
ObjectCarTGE.cc
1#include "ObjectCarTGE.hh"
2
3#include "game/field/ObjectCollisionCylinder.hh"
4#include "game/field/ObjectDirector.hh"
5#include "game/field/RailManager.hh"
6#include "game/field/obj/ObjectHighwayManager.hh"
7
8#include "game/kart/KartObject.hh"
9
10#include "game/system/RaceConfig.hh"
11
12namespace Field {
13
15ObjectCarTGE::ObjectCarTGE(const System::MapdataGeoObj &params)
16 : ObjectCollidable(params), StateManager(this), m_auxCollision(nullptr), m_carName{},
17 m_mdlName{}, m_carType(CarType::Normal), m_dummyId(ObjectId::None),
18 m_scaledTangentDir(EGG::Vector3f::zero), m_currSpeed(0.0f), m_up(EGG::Vector3f::zero),
19 m_tangent(EGG::Vector3f::zero) {
20 u32 carVariant = static_cast<u32>(params.setting(3));
21 m_highwayVel = static_cast<f32>(params.setting(2));
22 m_localVel = static_cast<f32>(params.setting(1));
23
24 s16 pathId = params.pathId();
25
26 // The base game returns before the StateManager sets its state entries.
27 // Since we handle this in the template specialization's constructor in Kinoko,
28 // we need to reset the StateManager parameters back to their default values before returning.
29 if (pathId == -1) {
30 m_nextStateId = -1;
31 m_currentStateId = 0;
32 m_currentFrame = 0;
33 m_entries = {};
34 m_obj = nullptr;
35
36 return;
37 }
38
39 // The base game preemptively calculates collision for all points along the rail.
40 RailManager::Instance()->rail(pathId)->checkSphereFull();
41
42 const auto *name = getName();
43
44 if (strcmp(name, "car_body") == 0) {
45 snprintf(m_carName, sizeof(m_carName), "%s", "K_car_body");
46 m_carType = CarType::Normal;
47 } else if (strcmp(name, "kart_truck") == 0) {
48 snprintf(m_carName, sizeof(m_carName), "%s", "K_truck");
49 m_carType = CarType::Truck;
50 } else if (strcmp(name, "K_bomb_car") == 0) {
51 PANIC("Bomb cars are not implemented!");
52 }
53
54 const auto *resourceName = getResources();
55
56 switch (carVariant) {
57 case 0: {
58 if (strcmp(resourceName, "K_truck") == 0) {
59 snprintf(m_mdlName, sizeof(m_mdlName), "%s_b", "K_truck");
60 } else if (strcmp(resourceName, "K_car_body") == 0) {
61 snprintf(m_mdlName, sizeof(m_mdlName), "%s_b", "K_car");
62 }
63 } break;
64 case 1: {
65 if (strcmp(resourceName, "K_truck") == 0) {
66 snprintf(m_mdlName, sizeof(m_mdlName), "%s_o", "K_truck");
67 } else if (strcmp(resourceName, "K_car_body") == 0) {
68 snprintf(m_mdlName, sizeof(m_mdlName), "%s_r", "K_car");
69 }
70 } break;
71 case 2: {
72 if (strcmp(resourceName, "K_truck") == 0) {
73 snprintf(m_mdlName, sizeof(m_mdlName), "%s_g", "K_truck");
74 } else if (strcmp(resourceName, "K_car_body") == 0) {
75 snprintf(m_mdlName, sizeof(m_mdlName), "%s_y", "K_car");
76 }
77 } break;
78 default: {
79 PANIC("Bomb cars are not implemented!");
80 break;
81 }
82 }
83
84 switch (m_carType) {
85 case CarType::Normal:
86 m_dummyId = ObjectDirector::Instance()->flowTable().getIdfFromName("car_body_dummy");
87 break;
88 case CarType::Truck:
89 m_dummyId = ObjectDirector::Instance()->flowTable().getIdfFromName("kart_truck_dummy");
90 break;
91 default:
92 PANIC("Bomb cars are not implemented!");
93 break;
94 }
95
96 if (System::RaceConfig::Instance()->raceScenario().course == Course::Moonview_Highway) {
97 registerManagedObject();
98 }
99}
100
102ObjectCarTGE::~ObjectCarTGE() {
103 delete m_auxCollision;
104}
105
107void ObjectCarTGE::init() {
108 constexpr f32 HIT_ANGLE_TRUCK = 20.0f;
109 constexpr f32 HIT_ANGLE_NORMAL = 40.0f;
110
111 if (m_mapObj->pathId() == -1) {
112 return;
113 }
114
115 u16 idx = m_mapObj->setting(0);
116 m_railInterpolator->init(0.0f, idx);
117
118 auto *rail = RailManager::Instance()->rail(m_mapObj->pathId());
119 u16 speedSetting = rail->points()[idx].setting[1];
120 if (speedSetting == 1) {
121 m_railInterpolator->setCurrVel(m_highwayVel);
122 } else if (speedSetting == 0) {
123 m_railInterpolator->setCurrVel(m_localVel);
124 }
125
126 u16 curPointSpeedSetting = m_railInterpolator->curPoint().setting[1];
127 u16 nextPointSpeedSetting = m_railInterpolator->nextPoint().setting[1];
128
129 if (curPointSpeedSetting == 0 && nextPointSpeedSetting == 1) {
130 m_nextStateId = 1;
131 } else if (curPointSpeedSetting == 1 && nextPointSpeedSetting == 0) {
132 m_nextStateId = 2;
133 }
134
135 m_squashed = false;
136 m_pos = m_railInterpolator->curPos();
137 m_flags.setBit(eFlags::Position);
138 m_currSpeed = m_railInterpolator->speed();
139 m_scaledTangentDir = m_railInterpolator->curTangentDir() * m_currSpeed;
140 m_hitAngle = (m_carType == CarType::Truck) ? HIT_ANGLE_TRUCK : HIT_ANGLE_NORMAL;
141}
142
144void ObjectCarTGE::calc() {
145 if (m_nextStateId >= 0) {
146 m_currentStateId = m_nextStateId;
147 m_nextStateId = -1;
148 m_currentFrame = 0;
149
150 auto enterFunc = m_entries[m_entryIds[m_currentStateId]].onEnter;
151 (this->*enterFunc)();
152 } else {
153 ++m_currentFrame;
154 }
155
156 auto calcFunc = m_entries[m_entryIds[m_currentStateId]].onCalc;
157 (this->*calcFunc)();
158
159 if (m_railInterpolator->calc() == RailInterpolator::Status::SegmentEnd) {
160 u16 curPointSpeedSetting = m_railInterpolator->curPoint().setting[1];
161 u16 nextPointSpeedSetting = m_railInterpolator->nextPoint().setting[1];
162
163 if (curPointSpeedSetting == 0 && nextPointSpeedSetting == 1) {
164 m_nextStateId = 1;
165 } else if (curPointSpeedSetting == 1 && nextPointSpeedSetting == 0) {
166 m_nextStateId = 2;
167 }
168 }
169
170 calcPos();
171
172 m_hasAuxCollision = false;
173}
174
176void ObjectCarTGE::createCollision() {
177 constexpr f32 TRUCK_RADIUS = 190.0f;
178 constexpr f32 TRUCK_HEIGHT = 500.0f;
179 constexpr f32 NORMAL_RADIUS = 150.0f;
180 constexpr f32 NORMAL_HEIGHT = 200.0f;
181
182 ASSERT(m_carType == CarType::Truck || m_carType == CarType::Normal);
183
184 bool isTruck = (m_carType == CarType::Truck);
185
186 ObjectCollidable::createCollision();
187
188 f32 radius = isTruck ? TRUCK_RADIUS : NORMAL_RADIUS;
189 f32 height = isTruck ? TRUCK_HEIGHT : NORMAL_HEIGHT;
190
191 m_auxCollision = new ObjectCollisionCylinder(radius, height, collisionCenter());
192}
193
195void ObjectCarTGE::calcCollisionTransform() {
196 auto *col = collision();
197
198 if (!col) {
199 return;
200 }
201
202 calcTransform();
203 col->transform(m_transform, m_scale, m_scaledTangentDir);
204 calcTransform();
205 EGG::Matrix34f mat;
206 SetRotTangentHorizontal(mat, m_transform.base(2), EGG::Vector3f::ey);
207 calcTransform();
208 mat.setBase(3, m_transform.base(3));
209 m_auxCollision->transform(mat, m_scale, m_scaledTangentDir);
210}
211
213f32 ObjectCarTGE::getCollisionRadius() const {
214 constexpr f32 NORMAL_RADIUS = 600.0f;
215 constexpr f32 TRUCK_RADIUS = 1100.0f;
216
217 ASSERT(m_carType == CarType::Truck || m_carType == CarType::Normal);
218 return (m_carType == CarType::Truck) ? TRUCK_RADIUS : NORMAL_RADIUS;
219}
220
222Kart::Reaction ObjectCarTGE::onCollision(Kart::KartObject *kartObj, Kart::Reaction reactionOnKart,
223 Kart::Reaction /*reactionOnObj*/, EGG::Vector3f &hitDepth) {
224 constexpr u32 SQUASH_INVULNERABILITY = 200;
225
226 if (!m_hasAuxCollision) {
227 EGG::Vector3f hitDepthNorm = hitDepth;
228 hitDepthNorm.normalise2();
229
230 if (hitDepthNorm.y > 0.9f) {
231 return Kart::Reaction::UntrickableJumpPad;
232 }
233 }
234
235 if (m_highwayMgr && m_highwayMgr->squashTimer() < SQUASH_INVULNERABILITY) {
236 const auto &hitTable = ObjectDirector::Instance()->hitTableKart();
237 reactionOnKart = hitTable.reaction(hitTable.slot(static_cast<ObjectId>(m_dummyId)));
238 }
239
240 // In the base game, behavior branches on reactionOnObj, but for time trials it's always 0.
241 if (reactionOnKart != Kart::Reaction::None && reactionOnKart != Kart::Reaction::WallAllSpeed) {
242 m_squashed = true;
243 calcTransform();
244 EGG::Vector3f v2 = m_transform.base(2);
245 v2.y = 0.0f;
246 v2.normalise2();
247 EGG::Vector3f posDelta = kartObj->pos() - m_pos;
248 posDelta.y = 0.0f;
249 posDelta.normalise2();
250
251 if (v2.dot(posDelta) < EGG::Mathf::CosFIdx(0.7111111f * m_hitAngle) && !m_hasAuxCollision) {
252 reactionOnKart = Kart::Reaction::LaunchSpin;
253 }
254
255 hitDepth.setZero();
256 }
257
258 return reactionOnKart;
259}
260
262bool ObjectCarTGE::checkCollision(ObjectCollisionBase *lhs, EGG::Vector3f &dist) {
263 dist = EGG::Vector3f::zero;
264 bool hasCol = lhs->check(*m_collision, dist);
265
266 if (!hasCol) {
267 hasCol = lhs->check(*m_auxCollision, dist);
268 m_hasAuxCollision = hasCol;
269 }
270
271 return hasCol;
272}
273
275const EGG::Vector3f &ObjectCarTGE::collisionCenter() const {
276 static constexpr EGG::Vector3f CENTER_TRUCK = EGG::Vector3f(0.0f, 300.0f, 0.0f);
277 static constexpr EGG::Vector3f CENTER_NORMAL = EGG::Vector3f(0.0f, 100.0f, 0.0f);
278 static constexpr EGG::Vector3f CENTER_DEFAULT = EGG::Vector3f(0.0f, 0.0f, 0.0f);
279
280 switch (m_carType) {
281 case CarType::Truck:
282 return CENTER_TRUCK;
283 case CarType::Normal:
284 return CENTER_NORMAL;
285 default:
286 return CENTER_DEFAULT;
287 }
288}
289
290void ObjectCarTGE::enterStateStub() {}
291
292void ObjectCarTGE::calcStateStub() {}
293
298void ObjectCarTGE::calcState1() {
299 m_currSpeed += TOLL_BOOTH_ACCEL;
300
301 if (m_currSpeed > m_highwayVel) {
302 m_currSpeed = m_highwayVel;
303 m_nextStateId = 0;
304 }
305
306 m_railInterpolator->setCurrVel(m_currSpeed);
307 m_scaledTangentDir = m_railInterpolator->curTangentDir() * m_currSpeed;
308}
309
314void ObjectCarTGE::calcState2() {
315 m_currSpeed -= TOLL_BOOTH_ACCEL;
316
317 if (m_currSpeed < m_localVel) {
318 m_currSpeed = m_localVel;
319 m_nextStateId = 0;
320 }
321
322 m_railInterpolator->setCurrVel(m_currSpeed);
323 m_scaledTangentDir = m_railInterpolator->curTangentDir() * m_currSpeed;
324}
325
327void ObjectCarTGE::calcPos() {
328 constexpr f32 NORMAL_SPEED = 1500.0f;
329 constexpr f32 TRUCK_SPEED = 1600.0f;
330
331 f32 speed = (m_carType == CarType::Truck) ? TRUCK_SPEED : NORMAL_SPEED;
332 f32 t = speed * m_scale.z * 0.5f;
333
334 EGG::Vector3f curDir;
335 EGG::Vector3f curTangentDir;
336 m_railInterpolator->evalCubicBezierOnPath(t, curDir, curTangentDir);
337
338 const EGG::Vector3f curPos = m_railInterpolator->curPos();
339 EGG::Vector3f posDelta = curPos - curDir;
340 posDelta.normalise2();
341 m_tangent += 0.1f * (posDelta - m_tangent);
342 m_tangent.y = posDelta.y;
343 m_tangent.normalise2();
344
345 if (m_tangent.y > -0.05f) {
346 m_pos = curPos - m_tangent * t * 0.5f;
347 } else {
348 m_pos = (curPos - m_tangent * t * 0.5f) - EGG::Vector3f::ey * 5.0f;
349 }
350
351 m_flags.setBit(eFlags::Position);
352 m_up = OrthonormalBasis(m_tangent).base(1);
353 setMatrixTangentTo(m_up, m_tangent);
354}
355
356const std::array<StateManagerEntry<ObjectCarTGE>, 3> StateManager<ObjectCarTGE>::STATE_ENTRIES = {{
357 {0, &ObjectCarTGE::enterStateStub, &ObjectCarTGE::calcStateStub},
358 {1, &ObjectCarTGE::enterStateStub, &ObjectCarTGE::calcState1},
359 {2, &ObjectCarTGE::enterStateStub, &ObjectCarTGE::calcState2},
360}};
361
362StateManager<ObjectCarTGE>::StateManager(ObjectCarTGE *obj) {
363 constexpr size_t ENTRY_COUNT = 3;
364
365 m_obj = obj;
366 m_entries = std::span{STATE_ENTRIES};
367 m_entryIds = std::span(new u16[ENTRY_COUNT], ENTRY_COUNT);
368
369 // The base game initializes all entries to 0xffff, possibly to avoid an uninitialized value
370 for (auto &id : m_entryIds) {
371 id = 0xffff;
372 }
373
374 for (size_t i = 0; i < m_entryIds.size(); ++i) {
375 m_entryIds[STATE_ENTRIES[i].id] = i;
376 }
377}
378
379StateManager<ObjectCarTGE>::~StateManager() {
380 delete[] m_entryIds.data();
381}
382
383} // namespace Field
A 3 x 4 matrix.
Definition Matrix.hh:8
void setBase(size_t col, const Vector3f &base)
Sets one column of a matrix.
Definition Matrix.cc:181
The highest level abstraction for a kart.
Definition KartObject.hh:11
EGG core library.
Definition Archive.cc:6
Pertains to collision.
A 3D float vector.
Definition Vector.hh:87
f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
Definition Vector.hh:186