A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
ObjectPylon.cc
1#include "ObjectPylon.hh"
2
3#include "game/field/CollisionDirector.hh"
4#include "game/field/ObjectDirector.hh"
5
6#include "game/kart/KartObject.hh"
7
8#include "game/system/RaceManager.hh"
9
10namespace Field {
11
13ObjectPylon::ObjectPylon(const System::MapdataGeoObj &params)
14 : ObjectCollidable(params), m_initPos(m_pos), m_initScale(m_scale), m_initRot(m_rot) {}
15
17ObjectPylon::~ObjectPylon() = default;
18
20void ObjectPylon::init() {
21 constexpr f32 NEIGHBOR_SQUARE_RADIUS = 2400.0f;
22
23 m_state = State::Idle;
24 m_stateStartFrame = 0;
25 m_vel.setZero();
26 m_numBounces = 0;
27 m_neighbors = {nullptr};
28 m_flags.setBit(eFlags::Position);
29 m_pos.y -= FALL_VEL;
30
31 CollisionInfo info;
32 KCLTypeMask mask;
33 EGG::Vector3f pos = m_pos + EGG::Vector3f::ey * RADIUS;
34 EGG::Vector3f prevPos = m_pos + EGG::Vector3f::ey * (RADIUS + FALL_VEL);
35
36 bool hasCol = CollisionDirector::Instance()->checkSphereFull(RADIUS, pos, prevPos,
37 KCL_TYPE_6CEBDFFF, &info, &mask, 0);
38
39 if (hasCol) {
40 m_pos += info.tangentOff;
41 m_flags.setBit(eFlags::Position);
42
43 if (mask & KCL_TYPE_FLOOR) {
44 setMatrixTangentTo(info.floorNrm, EGG::Vector3f::ez);
45 }
46 }
47
48 auto *objDir = ObjectDirector::Instance();
49 auto &managedObjs = objDir->managedObjects();
50
51 for (auto *&obj : managedObjs) {
52 if ((obj->pos() - m_pos).length() >= NEIGHBOR_SQUARE_RADIUS) {
53 continue;
54 }
55
56 // The base game assumes that each managed object is pylon.
57 ASSERT(obj->id() == ObjectId::Pylon);
58 auto *&pylon = reinterpret_cast<ObjectPylon *&>(obj);
59 for (auto *&neighbor : m_neighbors) {
60 if (!neighbor) {
61 neighbor = pylon;
62 break;
63 }
64 }
65
66 for (auto *&neighbor : pylon->m_neighbors) {
67 if (!neighbor) {
68 neighbor = this;
69 break;
70 }
71 }
72 }
73
74 objDir->addManagedObject(this);
75}
76
78void ObjectPylon::calc() {
79 switch (m_state) {
80 case State::Hit:
81 calcHit();
82 break;
83 case State::Hiding:
84 calcHiding();
85 break;
86 case State::Hide:
87 calcHide();
88 break;
89 case State::ComeBack:
90 calcComeBack();
91 break;
92 case State::Moving:
93 if (System::RaceManager::Instance()->timer() - m_stateStartFrame > STATE_COOLDOWN_FRAMES) {
94 m_state = State::Idle;
95 }
96 break;
97 default:
98 break;
99 }
100}
101
107Kart::Reaction ObjectPylon::onCollision(Kart::KartObject *kartObj,
108 Kart::Reaction /*reactionOnKart*/, Kart::Reaction /*reactionOnObj*/,
109 EGG::Vector3f &hitDepth) {
110 constexpr f32 HIT_SPEED_RATIO_THRESHOLD = 0.7f;
111 constexpr f32 PI_OVER_SIX = 0.52359879f;
112 constexpr f32 FIVE_PI_OVER_SIX = 2.617994f;
113 constexpr f32 COME_BACK_HIT_FACTOR = 0.5f;
114
115 u32 t = System::RaceManager::Instance()->timer();
116
117 if (m_state == State::ComeBack) {
118 startHit(COME_BACK_HIT_FACTOR, hitDepth);
119 m_stateStartFrame = t;
120 return Kart::Reaction::WeakWall;
121 }
122
123 u32 stateFrame = t - m_stateStartFrame;
124 bool canChangeState = stateFrame > STATE_COOLDOWN_FRAMES;
125 if (canChangeState && m_state == State::Moving) {
126 m_state = State::Idle;
127 return Kart::Reaction::WeakWall;
128 }
129
130 if (canChangeState && m_state == State::Hit) {
131 return Kart::Reaction::WeakWall;
132 }
133
134 if (m_state == State::Hit) {
135 return Kart::Reaction::None;
136 }
137
138 f32 speedRatio = kartObj->speedRatioCapped();
139 if ((speedRatio >= HIT_SPEED_RATIO_THRESHOLD && m_state != State::Moving) ||
140 m_state == State::Hiding || m_state == State::Hide) {
141 if (m_state == State::Idle) {
142 startHit(speedRatio, hitDepth);
143 m_stateStartFrame = t;
144 }
145
146 return Kart::Reaction::WeakWall;
147 }
148
149 checkCollision(hitDepth);
150 m_state = State::Moving;
151 const EGG::Vector3f &zAxis = kartObj->componentZAxis();
152 EGG::Vector3f cross = hitDepth.cross(zAxis);
153 f32 atan = EGG::Mathf::abs(EGG::Mathf::atan2(cross.length(), hitDepth.dot(zAxis)));
154
155 // Act as a weak wall for angles outside of 30-150 degrees
156 if (atan >= PI_OVER_SIX && atan < FIVE_PI_OVER_SIX) {
157 return Kart::Reaction::None;
158 } else {
159 return Kart::Reaction::WeakWall;
160 }
161}
162
171void ObjectPylon::checkCollision(const EGG::Vector3f &hitDepth) {
172 constexpr f32 TRAVEL_RADIUS = 1200.0f;
173
174 m_pos -= hitDepth;
175 m_flags.setBit(eFlags::Position);
176
177 EGG::Vector3f dist;
178 for (auto *&neighbor : m_neighbors) {
179 if (neighbor && collision()->check(*neighbor->collision(), dist)) {
180 m_pos += dist;
181 m_flags.setBit(eFlags::Position);
182 }
183 }
184
185 EGG::Vector3f delta = m_pos - m_initPos;
186
187 // Enforce that cones stay within a TRAVEL_RADIUS radius from their initial position
188 if (delta.length() > TRAVEL_RADIUS) {
189 delta.x += hitDepth.z;
190 delta.z -= hitDepth.x;
191 delta.normalise2();
192
193 m_pos = m_initPos + delta * TRAVEL_RADIUS;
194 m_flags.setBit(eFlags::Position);
195 }
196
197 m_pos.y -= FALL_VEL;
198 m_flags.setBit(eFlags::Position);
199
200 CollisionInfo info;
201 KCLTypeMask mask;
202 EGG::Vector3f pos = m_pos + EGG::Vector3f::ey * RADIUS;
203
204 bool hasCol = CollisionDirector::Instance()->checkSphereFullPush(RADIUS, pos, m_pos,
205 KCL_TYPE_60E8DFFF, &info, &mask, 0);
206 if (hasCol) {
207 m_pos += info.tangentOff;
208 m_flags.setBit(eFlags::Position);
209
210 if (info.floorDist > -std::numeric_limits<f32>::min()) {
211 setMatrixTangentTo(info.floorNrm, EGG::Vector3f::ez);
212 }
213 }
214}
215
217void ObjectPylon::startHit(f32 velFactor, EGG::Vector3f &hitDepth) {
218 constexpr f32 ANG_VEL_SCALAR = 0.5f;
219 constexpr f32 VEL_SCALAR = 100.0f;
220
221 m_state = State::Hit;
222 m_vel = EGG::Vector3f(-hitDepth.x, 0.0f, -hitDepth.z);
223 m_vel.normalise2();
224 m_angVel = m_vel * velFactor * ANG_VEL_SCALAR;
225 m_vel.y = 0.85f;
226 m_vel *= velFactor * VEL_SCALAR;
227 hitDepth.normalise2();
228}
229
230void ObjectPylon::calcHit() {
231 constexpr f32 GRAVITY = 3.0f;
232 constexpr f32 SQ_VEL_MIN = 0.5f;
233 constexpr f32 HIT_DURATION = 300.0f;
234 constexpr f32 VEL_DAMPENER = 0.75f;
235 constexpr u32 MAX_BOUNCES = 4;
236 constexpr f32 SIDEWAYS_SCALAR = 0.6f;
237 constexpr f32 FORWARD_SCALAR = 0.4f;
238 constexpr f32 MIN_SPEED = 0.0f;
239
240 m_vel.y -= GRAVITY;
241
242 u32 t = System::RaceManager::Instance()->timer();
243 u32 stateFrames = t - m_stateStartFrame;
244 if (m_vel.squaredLength() < SQ_VEL_MIN || m_pos.y < 0.0f ||
245 static_cast<f32>(stateFrames) > HIT_DURATION) {
246 m_stateStartFrame = t;
247 m_state = State::Hiding;
248 return;
249 }
250
251 CollisionInfo info;
252 KCLTypeMask maskOut;
253 EGG::Vector3f pos = m_pos + m_vel;
254
255 KCLTypeMask mask = KCL_TYPE_OBJECT_WALL;
256 if (stateFrames >= STATE_COOLDOWN_FRAMES) {
257 mask |= KCL_TYPE_FLOOR;
258 }
259
260 bool hasCol = CollisionDirector::Instance()->checkSphereFullPush(
261 (RADIUS + FALL_VEL) * m_scale.x, pos, m_pos, mask, &info, &maskOut, 0);
262
263 if (hasCol) {
264 if ((maskOut & KCL_TYPE_FLOOR) && stateFrames > STATE_COOLDOWN_FRAMES) {
265 m_vel = AdjustVecForward(SIDEWAYS_SCALAR, FORWARD_SCALAR, MIN_SPEED, m_vel,
266 info.floorNrm);
267 m_vel.y = VEL_DAMPENER * -m_vel.y;
268 } else if (maskOut & KCL_TYPE_WALL) {
269 m_vel = AdjustVecForward(SIDEWAYS_SCALAR, FORWARD_SCALAR, MIN_SPEED, m_vel,
270 info.wallNrm);
271 }
272
273 calcRotLock();
274
275 m_rot += m_angVel;
276 m_flags.setBit(eFlags::Rotation);
277
278 m_pos = m_pos + m_vel + info.tangentOff;
279 m_flags.setBit(eFlags::Position);
280
281 if (m_numBounces++ >= MAX_BOUNCES) {
282 m_state = State::Hiding;
283 m_stateStartFrame = t;
284 }
285 } else {
286 calcRotLock();
287
288 m_rot += m_angVel;
289 m_pos += m_vel;
290 m_flags.setBit(eFlags::Rotation, eFlags::Position);
291 }
292}
293
294void ObjectPylon::calcHiding() {
295 constexpr u32 HIDING_DURATION = 10;
296
297 u32 t = System::RaceManager::Instance()->timer();
298 u32 stateFrames = t - m_stateStartFrame;
299 if (stateFrames > HIDING_DURATION) {
300 m_state = State::Hide;
301 m_stateStartFrame = t;
302 m_rot = m_initRot;
303 m_rotLock = true;
304 m_flags.setBit(eFlags::Position);
305 } else {
306 calcRotLock();
307
308 m_rot += m_angVel;
309 m_scale.set(1.0f / static_cast<f32>(stateFrames));
310 m_flags.setBit(eFlags::Rotation, eFlags::Scale);
311 }
312
313 disableCollision();
314}
315
316void ObjectPylon::calcHide() {
317 constexpr u32 HIDE_DURATION = 900;
318
319 u32 t = System::RaceManager::Instance()->timer();
320 if (t - m_stateStartFrame > HIDE_DURATION) {
321 m_state = State::ComeBack;
322 m_stateStartFrame = t;
323 m_rot = m_initRot;
324 m_rotLock = true;
325 m_flags.setBit(eFlags::Rotation);
326 }
327}
328
329void ObjectPylon::calcComeBack() {
330 constexpr u32 COME_BACK_DURATION = 10;
331 constexpr f32 COME_BACK_VEL = 10.0f;
332 constexpr f32 INIT_DISPLACEMENT = COME_BACK_VEL * static_cast<f32>(COME_BACK_DURATION);
333
334 enableCollision();
335
336 u32 t = System::RaceManager::Instance()->timer();
337 u32 stateFrames = t - m_stateStartFrame;
338
339 if (stateFrames > COME_BACK_DURATION) {
340 m_numBounces = 0;
341 m_state = State::Idle;
342 m_stateStartFrame = t;
343 m_vel.setZero();
344 m_angVel.setZero();
345
346 m_pos = m_initPos;
347 m_scale = m_initScale;
348 m_rot = m_initRot;
349 m_pos.y -= COME_BACK_VEL;
350 m_flags.setBit(eFlags::Position, eFlags::Rotation, eFlags::Scale);
351 m_rotLock = true;
352
353 CollisionInfo info;
354 KCLTypeMask mask;
355 EGG::Vector3f pos = m_pos + EGG::Vector3f::ey * RADIUS * m_scale.x;
356 EGG::Vector3f prevPos = m_pos + EGG::Vector3f::ey * (RADIUS + COME_BACK_VEL) * m_scale.x;
357
358 bool hasCol = CollisionDirector::Instance()->checkSphereFullPush(RADIUS * m_scale.x, pos,
359 prevPos, KCL_TYPE_60E8DFFF, &info, &mask, 0);
360
361 if (hasCol) {
362 m_pos += info.tangentOff;
363 m_flags.setBit(eFlags::Position);
364
365 if (mask & KCL_TYPE_FLOOR) {
366 setMatrixTangentTo(info.floorNrm, EGG::Vector3f::ez);
367 m_state = State::Idle;
368 }
369 }
370 } else {
371 m_numBounces = 0;
372 m_vel.setZero();
373 m_angVel.setZero();
374 m_pos = m_initPos +
375 EGG::Vector3f::ey * INIT_DISPLACEMENT *
376 static_cast<f32>(COME_BACK_DURATION - stateFrames);
377 m_scale = m_initScale;
378 m_rot = m_initRot;
379 m_flags.setBit(eFlags::Position, eFlags::Scale, eFlags::Rotation);
380 m_rotLock = true;
381
382 CollisionInfo info;
383 EGG::Vector3f pos = m_pos + EGG::Vector3f::ey * RADIUS * m_scale.x;
384 EGG::Vector3f prevPos = m_pos + EGG::Vector3f::ey * (RADIUS + COME_BACK_VEL) * m_scale.x;
385
386 bool hasCol = CollisionDirector::Instance()->checkSphereFull(RADIUS * m_scale.x, pos,
387 prevPos, KCL_TYPE_60E8DFFF, &info, nullptr, 0);
388
389 if (hasCol) {
390 m_pos += info.tangentOff;
391 m_flags.setBit(eFlags::Position);
392 }
393 }
394}
395
396} // namespace Field
#define KCL_TYPE_OBJECT_WALL
0x4000D000
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
#define KCL_TYPE_WALL
0xD010F000
The highest level abstraction for a kart.
Definition KartObject.hh:11
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
f32 length() const
The square root of the vector's dot product.
Definition Vector.hh:192