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 Kinoko::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 if (CrossXZ(m_pos + m_tangent, m_pos, m_anchor) >= 0.0f) {
143 randAngle *= -1.0f;
144 }
145
146 EGG::Vector3f vStack_40 = m_anchor - EGG::Vector3f(m_pos.x, m_anchor.y, m_pos.z);
147 vStack_40.normalise2();
148
149 EGG::Vector3f vStack_4c = RotateXZByYaw(DEG2RAD * randAngle, vStack_40);
150 f32 fVar2 = m_chainLength < 3000.0f ? 0.5f : 0.7f;
151 m_target = vStack_4c * m_chainLength * fVar2 + m_anchor;
152}
153
155void ObjectWanwan::enterAttack() {
156 m_vel.x = 0.0f;
157 m_vel.z = 0.0f;
158 m_accel.x = 0.0f;
159 m_accel.z = 0.0f;
160 m_speed = 0.0f;
161 m_pitch = 0.0f;
162 m_wanderTimer = 0;
163 m_attackStill = false;
164 m_chainTaut = false;
165 m_anchor.y += 30.0f;
166
167 calcRandomTarget();
168}
169
171void ObjectWanwan::enterBack() {
172 m_vel.x = 0.0f;
173 m_vel.z = 0.0f;
174 m_accel.x = 0.0f;
175 m_accel.z = 0.0f;
176 m_speed = 15.0f;
177 m_pitch = -ObjectDirector::Instance()->WanwanMaxPitch();
178 m_backDir = m_anchor - m_pos;
179 m_backDir.y = 0.0f;
180 m_backDir.normalise2();
181 m_attackStill = false;
182 m_chainTaut = false;
183 m_anchor.y -= 30.0f;
184}
185
187void ObjectWanwan::calcWait() {
188 constexpr f32 ANGLE_RANGE = 0.33f * 0.5f * 60.0f;
189 constexpr f32 ANGLE_NORMALIZATION = 0.66f * 0.5f * 60.0f;
190
191 EGG::Vector3f targetDir = m_target - m_pos;
192 targetDir.y = 0.0f;
193
194 // In the base game, if the dot product is less than epsilon, then r31 contains F_PI
195 f32 distFromTarget = F_PI;
196 if (targetDir.squaredLength() > std::numeric_limits<f32>::epsilon()) {
197 distFromTarget = targetDir.normalise();
198 }
199
200 if (!m_retarget && distFromTarget < 0.55f * m_chainLength) {
201 EGG::Vector3f relTarget = m_target - m_anchor;
202 auto &rand = System::RaceManager::Instance()->random();
203 f32 angle = rand.getF32(ANGLE_RANGE) + ANGLE_NORMALIZATION;
204 if (CrossXZ(m_target, m_attackArcCenter, m_anchor) >= 0.0f) {
205 angle *= -1.0f;
206 }
207
208 m_target = RotateXZByYaw(angle * DEG2RAD, relTarget) + m_anchor;
209 m_retarget = true;
210 }
211
212 m_targetDir = targetDir;
213
214 calcTangent(0.04f);
215 calcUp(0.1f);
216
217 calcSpeed();
218 calcBounce();
219
220 calcWanderTimer();
221}
222
224void ObjectWanwan::calcAttack() {
225 constexpr u32 ATTACK_DURATION = 120;
226 constexpr f32 PITCH_STEP = 25.0f;
227
228 if (m_currentFrame > ATTACK_DURATION) {
229 m_nextStateId = 2;
230 }
231
232 f32 maxPitch = ObjectDirector::Instance()->WanwanMaxPitch();
233 if (EGG::Mathf::abs(m_pitch) < EGG::Mathf::abs(maxPitch)) {
234 m_pitch -= maxPitch / PITCH_STEP;
235 }
236
237 calcTangent(10.0f * 0.04f);
238 calcUp(0.1f);
239
240 if (m_chainTaut || m_attackStill) {
241 if (!m_attackStill) {
242 m_target = m_anchor + (m_chainAttachPos - m_anchor) * 2.0f;
243 m_targetDir = m_target - m_pos;
244 m_targetDir.y = 0.0f;
245 m_targetDir.normalise2();
246 EGG::Vector3f posOffset = m_pos - m_chainAttachPos;
247 posOffset.y = 0.0f;
248 f32 radius = posOffset.length();
249 EGG::Vector3f chainDir = m_chainAttachPos - m_anchor;
250 chainDir.y = 0.0f;
251 chainDir.normalise2();
252 m_flags.setBit(eFlags::Position);
253 m_pos.x = m_chainAttachPos.x + chainDir.x * radius;
254 m_pos.z = m_chainAttachPos.z + chainDir.z * radius;
255 EGG::Vector3f tangent = m_tangent + chainDir;
256 tangent.y = 0.0f;
257 if (tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
258 tangent.normalise2();
259 }
260
261 m_tangent = tangent;
262 }
263
264 m_attackStill = true;
265 m_vel.y = 0.0f;
266 m_vel *= -0.85f;
267 } else {
268 m_vel.x = m_tangent.x * 120.0f;
269 m_vel.z = m_tangent.z * 120.0f;
270 }
271}
272
274void ObjectWanwan::calcBack() {
275 if (EGG::Mathf::abs(m_pitch) > 2.0f) {
276 m_pitch += ObjectDirector::Instance()->WanwanMaxPitch() / 15.0f;
277 }
278
279 m_vel.x = m_backDir.x * m_speed * 1.5f;
280 m_vel.z = m_backDir.z * m_speed * 1.5f;
281
282 if (m_touchingFloor) {
283 m_vel.y = 0.0f;
284 m_accel += EGG::Vector3f::ey * 12.0f;
285 } else {
286 m_accel.y = 0.0f;
287 }
288
289 if (m_currentFrame > 90) {
290 m_nextStateId = 0;
291 }
292}
293
295void ObjectWanwan::calcPos() {
296 m_vel += m_accel - GRAVITY;
297 m_pos += m_vel;
298 m_flags.setBit(eFlags::Position);
299 m_accel.setZero();
300}
301
303void ObjectWanwan::calcCollision() {
304 constexpr EGG::Vector3f POS_OFFSET = EGG::Vector3f(0.0f, -570.0f, 0.0f);
305
306 m_touchingFloor = false;
307 CollisionInfo info;
308 KCLTypeMask mask;
309 EGG::Vector3f pos = m_pos + POS_OFFSET;
310 auto *colDir = CollisionDirector::Instance();
311
312 bool hasCol = colDir->checkSphereFullPush(30.0f, pos, EGG::Vector3f::inf, KCL_TYPE_FLOOR, &info,
313 &mask, 0);
314
315 if (!hasCol) {
316 return;
317 }
318
319 m_touchingFloor = true;
320 EGG::Vector3f local_84 = info.tangentOff;
321 f32 scale = local_84.normalise();
322 m_pos += EGG::Vector3f::ey * scale;
323 m_flags.setBit(eFlags::Position);
324
325 if (info.floorDist > -std::numeric_limits<f32>::min()) {
326 m_targetUp = info.floorNrm;
327 }
328
329 m_accel += GRAVITY;
330}
331
333void ObjectWanwan::calcMat() {
334 EGG::Matrix34f mat;
335 if (m_currentStateId == 1) {
336 SetRotTangentHorizontal(mat, EGG::Vector3f::ey, m_tangent);
337 } else {
338 SetRotTangentHorizontal(mat, m_up, m_tangent);
339 }
340 mat.setBase(3, EGG::Vector3f::zero);
341
342 EGG::Vector3f rot = EGG::Vector3f(m_pitch * DEG2RAD, 0.0f, 0.0f);
343 EGG::Matrix34f rtMat;
344 rtMat.makeRT(rot, EGG::Vector3f::zero);
345 mat = mat.multiplyTo(rtMat);
346 mat.setBase(3, m_pos);
347 setTransform(mat);
348}
349
351void ObjectWanwan::calcChainAttachMat() {
352 u32 idx = m_currentStateId == 1 ? 0 : m_frame % 15;
353
354 calcTransform();
355
356 EGG::Matrix34f mat = m_transform;
357 mat.setBase(3, EGG::Vector3f::zero);
358 EGG::Vector3f keyFramePos = m_transformKeyframes[idx].base(3);
359 EGG::Vector3f posOffset = mat.ps_multVector(keyFramePos) * 2.0f;
360 mat.setBase(3, posOffset + m_pos);
361
362 m_chainAttachMat = mat;
363}
364
366void ObjectWanwan::calcSpeed() {
367 if (m_speed < 8.0f) {
368 m_speed += 0.5f;
369 m_accel.x = m_tangent.x * 0.5f;
370 m_accel.z = m_tangent.z * 0.5f;
371 } else {
372 m_speed = 8.0f;
373 m_accel.x = 0.0f;
374 m_accel.z = 0.0f;
375 m_vel.x = m_tangent.x * 8.0f;
376 m_vel.z = m_tangent.z * 8.0f;
377 }
378}
379
381void ObjectWanwan::calcBounce() {
382 if (m_touchingFloor) {
383 m_vel.y = 0.0f;
384 m_accel += EGG::Vector3f::ey * 12.0f;
385 } else {
386 m_accel.y = 0.0f;
387 }
388}
389
391void ObjectWanwan::calcTangent(f32 t) {
392 m_tangent = Interpolate(t, m_tangent, m_targetDir);
393 if (m_tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
394 m_tangent.normalise2();
395 } else {
396 m_tangent = m_targetDir;
397 }
398}
399
401void ObjectWanwan::calcUp(f32 t) {
402 m_up = Interpolate(t, m_up, m_targetUp);
403 if (m_up.squaredLength() > std::numeric_limits<f32>::epsilon()) {
404 m_up.normalise2();
405 } else {
406 m_up = EGG::Vector3f::ey;
407 }
408}
409
411void ObjectWanwan::calcRandomTarget() {
412 f32 angle = System::RaceManager::Instance()->random().getF32(m_attackArc * 2.0f);
413 EGG::Vector3f attackArcTarget = EGG::Vector3f(m_attackArcTargetX, 0.0f, m_attackArcTargetZ);
414 EGG::Vector3f attackArcDir = attackArcTarget - m_anchor;
415 attackArcDir.y = 0.0f;
416 attackArcDir.normalise2();
417 EGG::Vector3f attackTargetDir = RotateXZByYaw((angle - m_attackArc) * DEG2RAD, attackArcDir);
418 m_target = m_anchor + attackTargetDir * m_attackDistance;
419 m_targetDir = m_target - m_pos;
420 m_targetDir.y = 0.0f;
421 m_targetDir.normalise2();
422}
423
425void ObjectWanwan::initTransformKeyframes() {
426 std::array<f32, 15> xRot;
427 SampleHermiteInterp(0.0f, 10.0f, 2.0f, -1.111111f, std::span(xRot.begin(), 6));
428 SampleHermiteInterp(10.0f, -10.0f, -1.111111f, -1.111111f, std::span(xRot.begin() + 5, 5));
429 SampleHermiteInterp(-10.0f, 0.0f, -1.111111f, 2.0f, std::span(xRot.begin() + 9, 6));
430
431 std::array<f32, 15> yPos;
432 SampleHermiteInterp(0.0f, -27.35f, -9.1166658f, 3.75f, std::span(yPos.begin(), 4));
433 SampleHermiteInterp(-27.35f, 33.75f, 3.75f, 2.4863639f, std::span(yPos.begin() + 3, 7));
434 SampleHermiteInterp(33.75f, 0.0f, 2.4863639f, -6.75f, std::span(yPos.begin() + 9, 6));
435
436 std::array<f32, 15> zPos;
437 SampleHermiteInterp(0.0f, -33.75f, -6.75f, -4.8214278f, std::span(zPos.begin(), 6));
438 SampleHermiteInterp(-33.75f, -33.75f, -4.8214278f, 8.4375f, std::span(zPos.begin() + 5, 3));
439 SampleHermiteInterp(-33.75f, 0.0f, 8.4375f, 14.464999f, std::span(zPos.begin() + 7, 3));
440 SampleHermiteInterp(0.0f, 24.11f, 14.464999f, 0.0f, std::span(zPos.begin() + 9, 3));
441 SampleHermiteInterp(24.11f, 0.0f, 0.0f, -8.0366669f, std::span(zPos.begin() + 11, 4));
442
443 for (u8 i = 0; i < m_transformKeyframes.size(); ++i) {
444 EGG::Matrix34f mat;
445 mat.makeR(EGG::Vector3f(xRot[i] * DEG2RAD, 0.0f, 0.0f));
446 mat.setBase(3, EGG::Vector3f(0.0f, yPos[i], zPos[i]));
447 m_transformKeyframes[i] = mat;
448 }
449}
450
452void ObjectWanwan::calcAttackPos() {
453 constexpr f32 SCALED_CHAIN_LENGTH = CHAIN_LENGTH * SCALE;
454
455 if (m_currentStateId != 1) {
456 return;
457 }
458
459 calcMat();
460 calcTransform();
461 calcChainAttachPos(m_transform);
462
463 EGG::Vector3f dir = m_chainAttachPos - m_anchor;
464 f32 dist = dir.normalise();
465 dist -= SCALED_CHAIN_LENGTH * static_cast<f32>(m_chainCount);
466
467 if (dist > 0.0f || m_attackStill) {
468 m_chainTaut = true;
469 m_pos -= dir * dist;
470 m_pos += dir * 35.0f;
471 m_flags.setBit(eFlags::Position);
472 }
473}
474
475void ObjectWanwan::calcChainAttachPos(EGG::Matrix34f mat) {
476 EGG::Vector3f pos = mat.base(3);
477 pos -= mat.base(2) * 250.0f * m_scale.x;
478 mat.setBase(3, pos);
479
480 EGG::Vector3f backOffset = mat.base(2) * 140.0f * m_scale.x;
481 EGG::Vector3f verticalOffset = mat.base(1) * 20.0f * m_scale.x;
482 m_chainAttachPos = pos - backOffset + verticalOffset;
483}
484
486void ObjectWanwan::SampleHermiteInterp(f32 start, f32 end, f32 startTangent, f32 endTangent,
487 std::span<f32> dst) {
488 dst.front() = start;
489 dst.back() = end;
490
491 f32 scalar = 1.0f / static_cast<f32>(dst.size() - 1);
492
493 for (u8 i = 1; i < dst.size() - 1; ++i) {
494 dst[i] = EGG::Mathf::Hermite(start, startTangent, end, endTangent,
495 scalar * static_cast<f32>(i));
496 }
497}
498
499} // namespace Kinoko::Field
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
Pertains to collision.