A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
ObjectWanwan.cc
1#include "ObjectWanwan.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/RaceConfig.hh"
9
10#include "game/system/RaceManager.hh"
11
12namespace Field {
13
15ObjectWanwan::ObjectWanwan(const System::MapdataGeoObj &params)
16 : ObjectCollidable(params), StateManager(this, STATE_ENTRIES), m_pitch(0.0f),
17 m_chainLength(static_cast<f32>(params.setting(0))),
18 m_attackDistance(4800.0f + static_cast<f32>(params.setting(2))),
19 m_attackArcTargetX(10.0f * static_cast<f32>(static_cast<s16>(params.setting(3)))),
20 m_attackArcTargetZ(10.0f * static_cast<f32>(static_cast<s16>(params.setting(4)))),
21 m_chainAttachMat(EGG::Matrix34f::ident) {
22 constexpr EGG::Vector3f ANCHOR_OFFSET = EGG::Vector3f(0.0f, 20.0f, 0.0f);
23 constexpr EGG::Vector3f POS_OFFSET_MCWII = EGG::Vector3f(14500.0f, 1300.0f, 44850.0f);
24 constexpr EGG::Vector3f POS_OFFSET_RMC = EGG::Vector3f(8012.0f, 1668.0f, -30150.0f);
25
26 m_idleDuration = static_cast<u32>(params.setting(5));
27 m_attackArc = static_cast<f32>(params.setting(6));
28
29 if (m_idleDuration == 0) {
30 m_idleDuration = 300;
31 }
32
33 if (m_attackArc == 0.0f) {
34 m_attackArc = 30.0f;
35 }
36
37 auto *pile = new ObjectWanwanPile(m_pos, m_rot, m_scale);
38 pile->load();
39
40 m_anchor = m_pos + ANCHOR_OFFSET;
41
42 setScale(EGG::Vector3f(SCALE, SCALE, SCALE));
43
44 f32 sqLen = m_chainLength * m_chainLength + 300.0f * (300.0f * m_scale.y) * m_scale.y;
45 m_chainCount = static_cast<u32>(EGG::Mathf::sqrt(sqLen) / (CHAIN_LENGTH * m_scale.y));
46 if (m_chainCount > 0) {
47 --m_chainCount;
48 }
49
50 m_initPos = m_anchor + EGG::Vector3f(0.5f * -m_chainLength, 600.0f, 0.5f * -m_chainLength);
51
52 auto course = System::RaceConfig::Instance()->raceScenario().course;
53 if (course == Course::Mario_Circuit) {
54 EGG::Vector3f pos = POS_OFFSET_MCWII - m_anchor;
55 pos.y = 0.0f;
56 pos.normalise2();
57 m_attackArcCenter = pos * m_chainLength + m_anchor;
58 m_attackArcCenter.y = POS_OFFSET_MCWII.y;
59 } else if (course == Course::GCN_Mario_Circuit) {
60 EGG::Vector3f pos = POS_OFFSET_RMC - m_anchor;
61 pos.y = 0.0f;
62 pos.normalise2();
63 m_attackArcCenter = pos * m_chainLength + m_anchor;
64 m_attackArcCenter.y = POS_OFFSET_RMC.y;
65 } else {
66 m_attackArcCenter = m_anchor;
67 m_attackArcCenter.z += m_chainLength;
68 }
69
70 initTransformKeyframes();
71}
72
74ObjectWanwan::~ObjectWanwan() = default;
75
77void ObjectWanwan::init() {
78 setPos(m_initPos);
79 m_chainAttachPos = m_initPos;
80 m_vel.setZero();
81 m_accel.setZero();
82 m_speed = 0.0f;
83 m_tangent = EGG::Vector3f::ex;
84 m_up = EGG::Vector3f::ey;
85 m_targetUp = EGG::Vector3f::ey;
86 m_touchingFloor = false;
87 m_chainTaut = false;
88 m_frame = 0;
89 m_target.setZero();
90 m_targetDir = EGG::Vector3f::ez;
91 m_retarget = false;
92 m_wanderTimer = 0;
93 m_attackStill = false;
94 m_backDir = EGG::Vector3f::ex;
95 m_nextStateId = 0;
96}
97
99void ObjectWanwan::calc() {
100 StateManager::calc();
101
102 calcPos();
103 calcAttackPos();
104 calcCollision();
105 calcMat();
106 calcChainAttachMat();
107 calcChain();
108
109 ++m_frame;
110
111 if (m_pos.y < m_anchor.y - 1000.0f) {
112 m_flags.setBit(eFlags::Position);
113 m_pos.y = m_anchor.y + 1000.0f;
114 m_vel.y = 0.0f;
115 }
116}
117
119Kart::Reaction ObjectWanwan::onCollision(Kart::KartObject *kartObj, Kart::Reaction reactionOnKart,
120 Kart::Reaction /*reactionOnObj*/, EGG::Vector3f & /*hitDepth*/) {
121 if (m_currentStateId == 1 && !m_attackStill) {
122 return reactionOnKart;
123 }
124
125 return kartObj->speedRatioCapped() < 0.5f ? Kart::Reaction::WallAllSpeed : reactionOnKart;
126}
127
129void ObjectWanwan::enterWait() {
130 constexpr f32 ANGLE_RANGE = 0.33f * 60.0f;
131 constexpr f32 ANGLE_NORMALIZATION = 0.66f * 60.0f;
132 m_retarget = false;
133 m_vel.x = 0.0f;
134 m_vel.z = 0.0f;
135 m_accel.x = 0.0f;
136 m_accel.z = 0.0f;
137 m_speed = 0.0f;
138
139 f32 randAngle =
140 System::RaceManager::Instance()->random().getF32(ANGLE_RANGE) + ANGLE_NORMALIZATION;
141
142 EGG::Vector3f vStack_58 = m_pos + m_tangent;
143 if (CrossXZ(m_pos + m_tangent, m_pos, m_anchor) >= 0.0f) {
144 randAngle *= -1.0f;
145 }
146
147 EGG::Vector3f vStack_40 = m_anchor - EGG::Vector3f(m_pos.x, m_anchor.y, m_pos.z);
148 vStack_40.normalise2();
149
150 EGG::Vector3f vStack_4c = RotateXZByYaw(DEG2RAD * randAngle, vStack_40);
151 f32 fVar2 = m_chainLength < 3000.0f ? 0.5f : 0.7f;
152 m_target = vStack_4c * m_chainLength * fVar2 + m_anchor;
153}
154
156void ObjectWanwan::enterAttack() {
157 m_vel.x = 0.0f;
158 m_vel.z = 0.0f;
159 m_accel.x = 0.0f;
160 m_accel.z = 0.0f;
161 m_speed = 0.0f;
162 m_pitch = 0.0f;
163 m_wanderTimer = 0;
164 m_attackStill = false;
165 m_chainTaut = false;
166 m_anchor.y += 30.0f;
167
168 calcRandomTarget();
169}
170
172void ObjectWanwan::enterBack() {
173 m_vel.x = 0.0f;
174 m_vel.z = 0.0f;
175 m_accel.x = 0.0f;
176 m_accel.z = 0.0f;
177 m_speed = 15.0f;
178 m_pitch = -ObjectDirector::Instance()->WanwanMaxPitch();
179 m_backDir = m_anchor - m_pos;
180 m_backDir.y = 0.0f;
181 m_backDir.normalise2();
182 m_attackStill = false;
183 m_chainTaut = false;
184 m_anchor.y -= 30.0f;
185}
186
188void ObjectWanwan::calcWait() {
189 constexpr f32 ANGLE_RANGE = 0.33f * 0.5f * 60.0f;
190 constexpr f32 ANGLE_NORMALIZATION = 0.66f * 0.5f * 60.0f;
191
192 EGG::Vector3f targetDir = m_target - m_pos;
193 targetDir.y = 0.0f;
194
195 // In the base game, if the dot product is less than epsilon, then r31 contains F_PI
196 f32 distFromTarget = F_PI;
197 if (targetDir.squaredLength() > std::numeric_limits<f32>::epsilon()) {
198 distFromTarget = targetDir.normalise();
199 }
200
201 if (!m_retarget && distFromTarget < 0.55f * m_chainLength) {
202 EGG::Vector3f relTarget = m_target - m_anchor;
203 auto &rand = System::RaceManager::Instance()->random();
204 f32 angle = rand.getF32(ANGLE_RANGE) + ANGLE_NORMALIZATION;
205 if (CrossXZ(m_target, m_attackArcCenter, m_anchor) >= 0.0f) {
206 angle *= -1.0f;
207 }
208
209 m_target = RotateXZByYaw(angle * DEG2RAD, relTarget) + m_anchor;
210 m_retarget = true;
211 }
212
213 m_targetDir = targetDir;
214
215 calcTangent(0.04f);
216 calcUp(0.1f);
217
218 calcSpeed();
219 calcBounce();
220
221 calcWanderTimer();
222}
223
225void ObjectWanwan::calcAttack() {
226 constexpr u32 ATTACK_DURATION = 120;
227 constexpr f32 PITCH_STEP = 25.0f;
228
229 if (m_currentFrame > ATTACK_DURATION) {
230 m_nextStateId = 2;
231 }
232
233 f32 maxPitch = ObjectDirector::Instance()->WanwanMaxPitch();
234 if (EGG::Mathf::abs(m_pitch) < EGG::Mathf::abs(maxPitch)) {
235 m_pitch -= maxPitch / PITCH_STEP;
236 }
237
238 calcTangent(10.0f * 0.04f);
239 calcUp(0.1f);
240
241 if (m_chainTaut || m_attackStill) {
242 if (!m_attackStill) {
243 m_target = m_anchor + (m_chainAttachPos - m_anchor) * 2.0f;
244 m_targetDir = m_target - m_pos;
245 m_targetDir.y = 0.0f;
246 m_targetDir.normalise2();
247 EGG::Vector3f posOffset = m_pos - m_chainAttachPos;
248 posOffset.y = 0.0f;
249 f32 radius = posOffset.length();
250 EGG::Vector3f chainDir = m_chainAttachPos - m_anchor;
251 chainDir.y = 0.0f;
252 chainDir.normalise2();
253 m_flags.setBit(eFlags::Position);
254 m_pos.x = m_chainAttachPos.x + chainDir.x * radius;
255 m_pos.z = m_chainAttachPos.z + chainDir.z * radius;
256 EGG::Vector3f tangent = m_tangent + chainDir;
257 tangent.y = 0.0f;
258 if (tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
259 tangent.normalise2();
260 }
261
262 m_tangent = tangent;
263 }
264
265 m_attackStill = true;
266 m_vel.y = 0.0f;
267 m_vel *= -0.85f;
268 } else {
269 m_vel.x = m_tangent.x * 120.0f;
270 m_vel.z = m_tangent.z * 120.0f;
271 }
272}
273
275void ObjectWanwan::calcBack() {
276 if (EGG::Mathf::abs(m_pitch) > 2.0f) {
277 m_pitch += ObjectDirector::Instance()->WanwanMaxPitch() / 15.0f;
278 }
279
280 m_vel.x = m_backDir.x * m_speed * 1.5f;
281 m_vel.z = m_backDir.z * m_speed * 1.5f;
282
283 if (m_touchingFloor) {
284 m_vel.y = 0.0f;
285 m_accel += EGG::Vector3f::ey * 12.0f;
286 } else {
287 m_accel.y = 0.0f;
288 }
289
290 if (m_currentFrame > 90) {
291 m_nextStateId = 0;
292 }
293}
294
296void ObjectWanwan::calcPos() {
297 m_vel += m_accel - GRAVITY;
298 m_pos += m_vel;
299 m_flags.setBit(eFlags::Position);
300 m_accel.setZero();
301}
302
304void ObjectWanwan::calcCollision() {
305 constexpr EGG::Vector3f POS_OFFSET = EGG::Vector3f(0.0f, -570.0f, 0.0f);
306
307 m_touchingFloor = false;
308 CollisionInfo info;
309 KCLTypeMask mask;
310 EGG::Vector3f pos = m_pos + POS_OFFSET;
311 auto *colDir = CollisionDirector::Instance();
312
313 bool hasCol = colDir->checkSphereFullPush(30.0f, pos, EGG::Vector3f::inf, KCL_TYPE_FLOOR, &info,
314 &mask, 0);
315
316 if (!hasCol) {
317 return;
318 }
319
320 m_touchingFloor = true;
321 EGG::Vector3f local_84 = info.tangentOff;
322 f32 scale = local_84.normalise();
323 m_pos += EGG::Vector3f::ey * scale;
324 m_flags.setBit(eFlags::Position);
325
326 if (info.floorDist > -std::numeric_limits<f32>::min()) {
327 m_targetUp = info.floorNrm;
328 }
329
330 m_accel += GRAVITY;
331}
332
334void ObjectWanwan::calcMat() {
335 EGG::Matrix34f mat;
336 if (m_currentStateId == 1) {
337 SetRotTangentHorizontal(mat, EGG::Vector3f::ey, m_tangent);
338 } else {
339 SetRotTangentHorizontal(mat, m_up, m_tangent);
340 }
341 mat.setBase(3, EGG::Vector3f::zero);
342
343 EGG::Vector3f rot = EGG::Vector3f(m_pitch * DEG2RAD, 0.0f, 0.0f);
344 EGG::Matrix34f rtMat;
345 rtMat.makeRT(rot, EGG::Vector3f::zero);
346 mat = mat.multiplyTo(rtMat);
347 mat.setBase(3, m_pos);
348 setTransform(mat);
349}
350
352void ObjectWanwan::calcChainAttachMat() {
353 u32 idx = m_currentStateId == 1 ? 0 : m_frame % 15;
354
355 calcTransform();
356
357 EGG::Matrix34f mat = m_transform;
358 mat.setBase(3, EGG::Vector3f::zero);
359 EGG::Vector3f keyFramePos = m_transformKeyframes[idx].base(3);
360 EGG::Vector3f posOffset = mat.ps_multVector(keyFramePos) * 2.0f;
361 mat.setBase(3, posOffset + m_pos);
362
363 m_chainAttachMat = mat;
364}
365
367void ObjectWanwan::calcSpeed() {
368 if (m_speed < 8.0f) {
369 m_speed += 0.5f;
370 m_accel.x = m_tangent.x * 0.5f;
371 m_accel.z = m_tangent.z * 0.5f;
372 } else {
373 m_speed = 8.0f;
374 m_accel.x = 0.0f;
375 m_accel.z = 0.0f;
376 m_vel.x = m_tangent.x * 8.0f;
377 m_vel.z = m_tangent.z * 8.0f;
378 }
379}
380
382void ObjectWanwan::calcBounce() {
383 if (m_touchingFloor) {
384 m_vel.y = 0.0f;
385 m_accel += EGG::Vector3f::ey * 12.0f;
386 } else {
387 m_accel.y = 0.0f;
388 }
389}
390
392void ObjectWanwan::calcTangent(f32 t) {
393 m_tangent = Interpolate(t, m_tangent, m_targetDir);
394 if (m_tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
395 m_tangent.normalise2();
396 } else {
397 m_tangent = m_targetDir;
398 }
399}
400
402void ObjectWanwan::calcUp(f32 t) {
403 m_up = Interpolate(t, m_up, m_targetUp);
404 if (m_up.squaredLength() > std::numeric_limits<f32>::epsilon()) {
405 m_up.normalise2();
406 } else {
407 m_up = EGG::Vector3f::ey;
408 }
409}
410
412void ObjectWanwan::calcRandomTarget() {
413 f32 angle = System::RaceManager::Instance()->random().getF32(m_attackArc * 2.0f);
414 EGG::Vector3f attackArcTarget = EGG::Vector3f(m_attackArcTargetX, 0.0f, m_attackArcTargetZ);
415 EGG::Vector3f attackArcDir = attackArcTarget - m_anchor;
416 attackArcDir.y = 0.0f;
417 attackArcDir.normalise2();
418 EGG::Vector3f attackTargetDir = RotateXZByYaw((angle - m_attackArc) * DEG2RAD, attackArcDir);
419 m_target = m_anchor + attackTargetDir * m_attackDistance;
420 m_targetDir = m_target - m_pos;
421 m_targetDir.y = 0.0f;
422 m_targetDir.normalise2();
423}
424
426void ObjectWanwan::initTransformKeyframes() {
427 std::array<f32, 15> xRot;
428 SampleHermiteInterp(0.0f, 10.0f, 2.0f, -1.111111f, std::span(xRot.begin(), 6));
429 SampleHermiteInterp(10.0f, -10.0f, -1.111111f, -1.111111f, std::span(xRot.begin() + 5, 5));
430 SampleHermiteInterp(-10.0f, 0.0f, -1.111111f, 2.0f, std::span(xRot.begin() + 9, 6));
431
432 std::array<f32, 15> yPos;
433 SampleHermiteInterp(0.0f, -27.35f, -9.1166658f, 3.75f, std::span(yPos.begin(), 4));
434 SampleHermiteInterp(-27.35f, 33.75f, 3.75f, 2.4863639f, std::span(yPos.begin() + 3, 7));
435 SampleHermiteInterp(33.75f, 0.0f, 2.4863639f, -6.75f, std::span(yPos.begin() + 9, 6));
436
437 std::array<f32, 15> zPos;
438 SampleHermiteInterp(0.0f, -33.75f, -6.75f, -4.8214278f, std::span(zPos.begin(), 6));
439 SampleHermiteInterp(-33.75f, -33.75f, -4.8214278f, 8.4375f, std::span(zPos.begin() + 5, 3));
440 SampleHermiteInterp(-33.75f, 0.0f, 8.4375f, 14.464999f, std::span(zPos.begin() + 7, 3));
441 SampleHermiteInterp(0.0f, 24.11f, 14.464999f, 0.0f, std::span(zPos.begin() + 9, 3));
442 SampleHermiteInterp(24.11f, 0.0f, 0.0f, -8.0366669f, std::span(zPos.begin() + 11, 4));
443
444 for (u8 i = 0; i < m_transformKeyframes.size(); ++i) {
445 EGG::Matrix34f mat;
446 mat.makeR(EGG::Vector3f(xRot[i] * DEG2RAD, 0.0f, 0.0f));
447 mat.setBase(3, EGG::Vector3f(0.0f, yPos[i], zPos[i]));
448 m_transformKeyframes[i] = mat;
449 }
450}
451
453void ObjectWanwan::calcAttackPos() {
454 constexpr f32 SCALED_CHAIN_LENGTH = CHAIN_LENGTH * SCALE;
455
456 if (m_currentStateId != 1) {
457 return;
458 }
459
460 calcMat();
461 calcTransform();
462 calcChainAttachPos(m_transform);
463
464 EGG::Vector3f dir = m_chainAttachPos - m_anchor;
465 f32 dist = dir.normalise();
466 dist -= SCALED_CHAIN_LENGTH * static_cast<f32>(m_chainCount);
467
468 if (dist > 0.0f || m_attackStill) {
469 m_chainTaut = true;
470 m_pos -= dir * dist;
471 m_pos += dir * 35.0f;
472 m_flags.setBit(eFlags::Position);
473 }
474}
475
476void ObjectWanwan::calcChainAttachPos(EGG::Matrix34f mat) {
477 EGG::Vector3f pos = mat.base(3);
478 pos -= mat.base(2) * 250.0f * m_scale.x;
479 mat.setBase(3, pos);
480
481 EGG::Vector3f backOffset = mat.base(2) * 140.0f * m_scale.x;
482 EGG::Vector3f verticalOffset = mat.base(1) * 20.0f * m_scale.x;
483 m_chainAttachPos = pos - backOffset + verticalOffset;
484}
485
487void ObjectWanwan::SampleHermiteInterp(f32 start, f32 end, f32 startTangent, f32 endTangent,
488 std::span<f32> dst) {
489 dst.front() = start;
490 dst.back() = end;
491
492 f32 scalar = 1.0f / static_cast<f32>(dst.size() - 1);
493
494 for (u8 i = 1; i < dst.size() - 1; ++i) {
495 dst[i] = EGG::Mathf::Hermite(start, startTangent, end, endTangent,
496 scalar * static_cast<f32>(i));
497 }
498}
499
500} // namespace Field
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
A 3 x 4 matrix.
Definition Matrix.hh:8
Matrix34f multiplyTo(const Matrix34f &rhs) const
Multiplies two matrices.
Definition Matrix.cc:202
void setBase(size_t col, const Vector3f &base)
Sets one column of a matrix.
Definition Matrix.cc:194
Vector3f base(size_t col) const
Get a particular column from a matrix.
Definition Matrix.hh:72
void makeR(const Vector3f &r)
Sets 3x3 rotation matrix from a vector of Euler angles.
Definition Matrix.cc:98
void makeRT(const Vector3f &r, const Vector3f &t)
Sets rotation-translation matrix.
Definition Matrix.cc:71
Vector3f ps_multVector(const Vector3f &vec) const
Paired-singles impl. of multVector.
Definition Matrix.cc:237
The highest level abstraction for a kart.
Definition KartObject.hh:11
f32 Hermite(f32 p0, f32 m0, f32 p1, f32 m1, f32 t)
Evaluates a cubic Hermite curve at a given parameter t.
Definition Math.cc:479
EGG core library.
Definition Archive.cc:6
Pertains to collision.
A 3D float vector.
Definition Vector.hh:88
f32 normalise()
Normalizes the vector and returns the original length.
Definition Vector.cc:52
f32 length() const
The square root of the vector's dot product.
Definition Vector.hh:192
f32 squaredLength() const
The dot product between the vector and itself.
Definition Vector.hh:182