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, STATE_ENTRIES), m_auxCollision(nullptr),
17 m_carName{}, 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
34 return;
35 }
36
37 // The base game preemptively calculates collision for all points along the rail.
38 RailManager::Instance()->rail(pathId)->checkSphereFull();
39
40 const auto *name = getName();
41
42 if (strcmp(name, "car_body") == 0) {
43 snprintf(m_carName, sizeof(m_carName), "%s", "K_car_body");
44 m_carType = CarType::Normal;
45 } else if (strcmp(name, "kart_truck") == 0) {
46 snprintf(m_carName, sizeof(m_carName), "%s", "K_truck");
47 m_carType = CarType::Truck;
48 } else if (strcmp(name, "K_bomb_car") == 0) {
49 PANIC("Bomb cars are not implemented!");
50 }
51
52 const auto *resourceName = getResources();
53
54 switch (carVariant) {
55 case 0: {
56 if (strcmp(resourceName, "K_truck") == 0) {
57 snprintf(m_mdlName, sizeof(m_mdlName), "%s_b", "K_truck");
58 } else if (strcmp(resourceName, "K_car_body") == 0) {
59 snprintf(m_mdlName, sizeof(m_mdlName), "%s_b", "K_car");
60 }
61 } break;
62 case 1: {
63 if (strcmp(resourceName, "K_truck") == 0) {
64 snprintf(m_mdlName, sizeof(m_mdlName), "%s_o", "K_truck");
65 } else if (strcmp(resourceName, "K_car_body") == 0) {
66 snprintf(m_mdlName, sizeof(m_mdlName), "%s_r", "K_car");
67 }
68 } break;
69 case 2: {
70 if (strcmp(resourceName, "K_truck") == 0) {
71 snprintf(m_mdlName, sizeof(m_mdlName), "%s_g", "K_truck");
72 } else if (strcmp(resourceName, "K_car_body") == 0) {
73 snprintf(m_mdlName, sizeof(m_mdlName), "%s_y", "K_car");
74 }
75 } break;
76 default: {
77 PANIC("Bomb cars are not implemented!");
78 break;
79 }
80 }
81
82 switch (m_carType) {
83 case CarType::Normal:
84 m_dummyId = ObjectDirector::Instance()->flowTable().getIdFromName("car_body_dummy");
85 break;
86 case CarType::Truck:
87 m_dummyId = ObjectDirector::Instance()->flowTable().getIdFromName("kart_truck_dummy");
88 break;
89 default:
90 PANIC("Bomb cars are not implemented!");
91 break;
92 }
93
94 if (System::RaceConfig::Instance()->raceScenario().course == Course::Moonview_Highway) {
95 registerManagedObject();
96 }
97}
98
100ObjectCarTGE::~ObjectCarTGE() {
101 delete m_auxCollision;
102}
103
105void ObjectCarTGE::init() {
106 constexpr f32 HIT_ANGLE_TRUCK = 20.0f;
107 constexpr f32 HIT_ANGLE_NORMAL = 40.0f;
108
109 ASSERT(m_mapObj);
110 if (m_mapObj->pathId() == -1) {
111 return;
112 }
113
114 u16 idx = m_mapObj->setting(0);
115 m_railInterpolator->init(0.0f, idx);
116
117 auto *rail = RailManager::Instance()->rail(m_mapObj->pathId());
118 u16 speedSetting = rail->points()[idx].setting[1];
119 if (speedSetting == 1) {
120 m_railInterpolator->setCurrVel(m_highwayVel);
121 } else if (speedSetting == 0) {
122 m_railInterpolator->setCurrVel(m_localVel);
123 }
124
125 u16 curPointSpeedSetting = m_railInterpolator->curPoint().setting[1];
126 u16 nextPointSpeedSetting = m_railInterpolator->nextPoint().setting[1];
127
128 if (curPointSpeedSetting == 0 && nextPointSpeedSetting == 1) {
129 m_nextStateId = 1;
130 } else if (curPointSpeedSetting == 1 && nextPointSpeedSetting == 0) {
131 m_nextStateId = 2;
132 }
133
134 m_squashed = false;
135 m_pos = m_railInterpolator->curPos();
136 m_flags.setBit(eFlags::Position);
137 m_currSpeed = m_railInterpolator->speed();
138 m_scaledTangentDir = m_railInterpolator->curTangentDir() * m_currSpeed;
139 m_hitAngle = (m_carType == CarType::Truck) ? HIT_ANGLE_TRUCK : HIT_ANGLE_NORMAL;
140}
141
143void ObjectCarTGE::calc() {
144 StateManager::calc();
145
146 if (m_railInterpolator->calc() == RailInterpolator::Status::SegmentEnd) {
147 u16 curPointSpeedSetting = m_railInterpolator->curPoint().setting[1];
148 u16 nextPointSpeedSetting = m_railInterpolator->nextPoint().setting[1];
149
150 if (curPointSpeedSetting == 0 && nextPointSpeedSetting == 1) {
151 m_nextStateId = 1;
152 } else if (curPointSpeedSetting == 1 && nextPointSpeedSetting == 0) {
153 m_nextStateId = 2;
154 }
155 }
156
157 calcPos();
158
159 m_hasAuxCollision = false;
160}
161
163void ObjectCarTGE::createCollision() {
164 constexpr f32 TRUCK_RADIUS = 190.0f;
165 constexpr f32 TRUCK_HEIGHT = 500.0f;
166 constexpr f32 NORMAL_RADIUS = 150.0f;
167 constexpr f32 NORMAL_HEIGHT = 200.0f;
168
169 ASSERT(m_carType == CarType::Truck || m_carType == CarType::Normal);
170
171 bool isTruck = (m_carType == CarType::Truck);
172
173 ObjectCollidable::createCollision();
174
175 f32 radius = isTruck ? TRUCK_RADIUS : NORMAL_RADIUS;
176 f32 height = isTruck ? TRUCK_HEIGHT : NORMAL_HEIGHT;
177
178 m_auxCollision = new ObjectCollisionCylinder(radius, height, collisionCenter());
179}
180
182void ObjectCarTGE::calcCollisionTransform() {
183 auto *col = collision();
184
185 if (!col) {
186 return;
187 }
188
189 calcTransform();
190 col->transform(m_transform, m_scale, m_scaledTangentDir);
191 calcTransform();
192 EGG::Matrix34f mat;
193 SetRotTangentHorizontal(mat, m_transform.base(2), EGG::Vector3f::ey);
194 calcTransform();
195 mat.setBase(3, m_transform.base(3));
196 m_auxCollision->transform(mat, m_scale, m_scaledTangentDir);
197}
198
200f32 ObjectCarTGE::getCollisionRadius() const {
201 constexpr f32 NORMAL_RADIUS = 600.0f;
202 constexpr f32 TRUCK_RADIUS = 1100.0f;
203
204 ASSERT(m_carType == CarType::Truck || m_carType == CarType::Normal);
205 return (m_carType == CarType::Truck) ? TRUCK_RADIUS : NORMAL_RADIUS;
206}
207
209Kart::Reaction ObjectCarTGE::onCollision(Kart::KartObject *kartObj, Kart::Reaction reactionOnKart,
210 Kart::Reaction /*reactionOnObj*/, EGG::Vector3f &hitDepth) {
211 constexpr u32 SQUASH_INVULNERABILITY = 200;
212
213 if (!m_hasAuxCollision) {
214 EGG::Vector3f hitDepthNorm = hitDepth;
215 hitDepthNorm.normalise2();
216
217 if (hitDepthNorm.y > 0.9f) {
218 return Kart::Reaction::UntrickableJumpPad;
219 }
220 }
221
222 if (m_highwayMgr && m_highwayMgr->squashTimer() < SQUASH_INVULNERABILITY) {
223 const auto &hitTable = ObjectDirector::Instance()->hitTableKart();
224 reactionOnKart = hitTable.reaction(hitTable.slot(static_cast<ObjectId>(m_dummyId)));
225 }
226
227 // In the base game, behavior branches on reactionOnObj, but for time trials it's always 0.
228 if (reactionOnKart != Kart::Reaction::None && reactionOnKart != Kart::Reaction::WallAllSpeed) {
229 m_squashed = true;
230 calcTransform();
231 EGG::Vector3f v2 = m_transform.base(2);
232 v2.y = 0.0f;
233 v2.normalise2();
234 EGG::Vector3f posDelta = kartObj->pos() - m_pos;
235 posDelta.y = 0.0f;
236 posDelta.normalise2();
237
238 if (v2.dot(posDelta) < EGG::Mathf::CosFIdx(0.7111111f * m_hitAngle) && !m_hasAuxCollision) {
239 reactionOnKart = Kart::Reaction::LaunchSpin;
240 }
241
242 hitDepth.setZero();
243 }
244
245 return reactionOnKart;
246}
247
249bool ObjectCarTGE::checkCollision(ObjectCollisionBase *lhs, EGG::Vector3f &dist) {
250 dist = EGG::Vector3f::zero;
251 bool hasCol = lhs->check(*m_collision, dist);
252
253 if (!hasCol) {
254 hasCol = lhs->check(*m_auxCollision, dist);
255 m_hasAuxCollision = hasCol;
256 }
257
258 return hasCol;
259}
260
262const EGG::Vector3f &ObjectCarTGE::collisionCenter() const {
263 static constexpr EGG::Vector3f CENTER_TRUCK = EGG::Vector3f(0.0f, 300.0f, 0.0f);
264 static constexpr EGG::Vector3f CENTER_NORMAL = EGG::Vector3f(0.0f, 100.0f, 0.0f);
265 static constexpr EGG::Vector3f CENTER_DEFAULT = EGG::Vector3f(0.0f, 0.0f, 0.0f);
266
267 switch (m_carType) {
268 case CarType::Truck:
269 return CENTER_TRUCK;
270 case CarType::Normal:
271 return CENTER_NORMAL;
272 default:
273 return CENTER_DEFAULT;
274 }
275}
276
277void ObjectCarTGE::enterStateStub() {}
278
279void ObjectCarTGE::calcStateStub() {}
280
285void ObjectCarTGE::calcState1() {
286 m_currSpeed += TOLL_BOOTH_ACCEL;
287
288 if (m_currSpeed > m_highwayVel) {
289 m_currSpeed = m_highwayVel;
290 m_nextStateId = 0;
291 }
292
293 m_railInterpolator->setCurrVel(m_currSpeed);
294 m_scaledTangentDir = m_railInterpolator->curTangentDir() * m_currSpeed;
295}
296
301void ObjectCarTGE::calcState2() {
302 m_currSpeed -= TOLL_BOOTH_ACCEL;
303
304 if (m_currSpeed < m_localVel) {
305 m_currSpeed = m_localVel;
306 m_nextStateId = 0;
307 }
308
309 m_railInterpolator->setCurrVel(m_currSpeed);
310 m_scaledTangentDir = m_railInterpolator->curTangentDir() * m_currSpeed;
311}
312
314void ObjectCarTGE::calcPos() {
315 constexpr f32 NORMAL_SPEED = 1500.0f;
316 constexpr f32 TRUCK_SPEED = 1600.0f;
317
318 f32 speed = (m_carType == CarType::Truck) ? TRUCK_SPEED : NORMAL_SPEED;
319 f32 t = speed * m_scale.z * 0.5f;
320
321 EGG::Vector3f curDir;
322 EGG::Vector3f curTangentDir;
323 m_railInterpolator->evalCubicBezierOnPath(t, curDir, curTangentDir);
324
325 const EGG::Vector3f curPos = m_railInterpolator->curPos();
326 EGG::Vector3f posDelta = curPos - curDir;
327 posDelta.normalise2();
328 m_tangent += 0.1f * (posDelta - m_tangent);
329 m_tangent.y = posDelta.y;
330 m_tangent.normalise2();
331
332 if (m_tangent.y > -0.05f) {
333 m_pos = curPos - m_tangent * t * 0.5f;
334 } else {
335 m_pos = (curPos - m_tangent * t * 0.5f) - EGG::Vector3f::ey * 5.0f;
336 }
337
338 m_flags.setBit(eFlags::Position);
339 m_up = OrthonormalBasis(m_tangent).base(1);
340 setMatrixTangentTo(m_up, m_tangent);
341}
342
343} // 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:194
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:88
f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
Definition Vector.hh:187