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