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