A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
ObjectCow.cc
1#include "ObjectCow.hh"
2
3#include "game/field/CollisionDirector.hh"
4#include "game/field/RailInterpolator.hh"
5#include "game/field/RailManager.hh"
6
7#include "game/kart/KartCollide.hh"
8#include "game/kart/KartObject.hh"
9
10#include "game/system/RaceManager.hh"
11
12namespace Field {
13
15ObjectCow::ObjectCow(const System::MapdataGeoObj &params) : ObjectCollidable(params) {
16 m_startFrame = params.setting(2);
17}
18
20ObjectCow::~ObjectCow() = default;
21
23Kart::Reaction ObjectCow::onCollision(Kart::KartObject *kartObj, Kart::Reaction reactionOnKart,
24 Kart::Reaction /*reactionOnObj*/, EGG::Vector3f & /*hitDepth*/) {
25 return kartObj->speedRatioCapped() < 0.5f ? Kart::Reaction::WallAllSpeed : reactionOnKart;
26}
27
29void ObjectCow::setup() {
30 m_scale = m_mapObj->scale();
31 m_pos = m_mapObj->pos();
32 m_rot = m_mapObj->rot() * DEG2RAD;
33 m_flags.setBit(eFlags::Position, eFlags::Rotation, eFlags::Scale);
34 m_tangent = EGG::Vector3f::ez;
35 m_prevTangent = EGG::Vector3f::ez;
36 m_up = EGG::Vector3f::ey;
37 m_velocity = EGG::Vector3f::zero;
38 m_xzSpeed = 0.0f;
39 m_tangentFactor = 0.0f;
40 m_floorNrm = EGG::Vector3f::ey;
41 m_state1TargetPos = m_pos;
42 m_targetDir = EGG::Vector3f::ez;
43 m_upForce = EGG::Vector3f::zero;
44 m_interpRate = 0.05f;
45}
46
48void ObjectCow::calcFloor() {
49 constexpr f32 RADIUS = 50.0f;
50 constexpr EGG::Vector3f POS_OFFSET = EGG::Vector3f(0.0f, RADIUS, 0.0f);
51
52 CollisionInfo info;
53
54 bool hasCol = CollisionDirector::Instance()->checkSphereFull(RADIUS, m_pos + POS_OFFSET,
55 EGG::Vector3f::inf, KCL_TYPE_64EBDFFF, &info, nullptr, 0);
56
57 if (hasCol) {
58 m_pos += info.tangentOff;
59 m_flags.setBit(eFlags::Position);
60
61 if (info.floorDist > -std::numeric_limits<f32>::min()) {
62 m_floorNrm = info.floorNrm;
63 }
64
65 m_velocity.y = 0.0f;
66 m_upForce = GRAVITY_FORCE;
67 } else {
68 m_upForce = EGG::Vector3f::zero;
69 }
70}
71
73void ObjectCow::calcPos() {
74 EGG::Vector3f accel =
75 m_tangent * m_tangentFactor + (m_tangent - m_prevTangent) * m_xzSpeed + m_upForce;
76 m_velocity += accel - GRAVITY_FORCE;
77 m_xzSpeed = EGG::Mathf::sqrt(m_velocity.x * m_velocity.x + m_velocity.z * m_velocity.z);
78
79 if (m_tangent.z * m_velocity.z + m_tangent.x * m_velocity.x < 0.0f) {
80 m_velocity.x = 0.0f;
81 m_velocity.z = 0.0f;
82 m_xzSpeed = 0.0f;
83 }
84
85 m_pos += m_velocity;
86 m_flags.setBit(eFlags::Position);
87 m_tangentFactor = 0.0f;
88}
89
91f32 ObjectCow::setTarget(const EGG::Vector3f &v) {
92 m_state1TargetPos = v;
93 EGG::Vector3f posDiff = m_state1TargetPos - m_pos;
94 f32 dist = posDiff.normalise();
95 m_targetDir = m_state1TargetPos + posDiff * 1000.0f - m_pos;
96 m_targetDir.normalise2();
97
98 return dist;
99}
100
102ObjectCowLeader::ObjectCowLeader(const System::MapdataGeoObj &params)
103 : ObjectCow(params), StateManager(this) {}
104
106ObjectCowLeader::~ObjectCowLeader() = default;
107
109void ObjectCowLeader::init() {
110 setup();
111 m_railInterpolator->init(0.0f, 0);
112 m_pos = m_railInterpolator->curPos();
113 m_flags.setBit(eFlags::Position);
114
115 setTarget(m_pos + m_railInterpolator->curTangentDir() * 10.0f);
116
117 m_railInterpolator->setCurrVel(static_cast<f32>(m_mapObj->setting(1)));
118
119 m_railSpeed = 0.0f;
120 m_endedRailSegment = false;
121 m_state1AnmType = AnmType::EatST;
122 m_eatFrames = 0;
123 m_interpRate = 1.0f;
124 m_nextStateId = 2;
125}
126
128void ObjectCowLeader::calc() {
129 u32 t = System::RaceManager::Instance()->timer();
130
131 if (t >= m_startFrame) {
132 if (m_nextStateId >= 0) {
133 m_currentStateId = m_nextStateId;
134 m_nextStateId = -1;
135 m_currentFrame = 0;
136
137 auto enterFunc = m_entries[m_entryIds[m_currentStateId]].onEnter;
138 (this->*enterFunc)();
139 } else {
140 ++m_currentFrame;
141 }
142
143 auto calcFunc = m_entries[m_entryIds[m_currentStateId]].onCalc;
144 (this->*calcFunc)();
145 }
146
147 calcPos();
148 calcFloor();
149
150 m_prevTangent = m_tangent;
151 m_tangent = Interpolate(m_interpRate, m_tangent, m_targetDir);
152
153 if (m_tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
154 m_tangent.normalise2();
155 } else {
156 m_tangent = EGG::Vector3f::ez;
157 }
158
159 m_up = Interpolate(0.1f, m_up, m_floorNrm);
160
161 if (m_up.squaredLength() > std::numeric_limits<f32>::epsilon()) {
162 m_up.normalise2();
163 } else {
164 m_up = EGG::Vector3f::ey;
165 }
166
167 setMatrixTangentTo(m_up, m_tangent);
168}
169
171void ObjectCowLeader::calcFloor() {
172 m_velocity.y = 0.0f;
173 m_upForce = GRAVITY_FORCE;
174 m_floorNrm = m_railInterpolator->floorNrm(m_railInterpolator->nextPointIdx());
175}
176
178void ObjectCowLeader::enterWait() {
179 setTarget(m_railInterpolator->curPos() + m_railInterpolator->curTangentDir() * 10.0f);
180}
181
183void ObjectCowLeader::enterEat() {
184 m_state1AnmType = AnmType::EatST;
185 u32 rand = System::RaceManager::Instance()->random().getU32(120);
186 m_eatFrames = rand + 120;
187}
188
190void ObjectCowLeader::enterRoam() {
191 m_endedRailSegment = false;
192}
193
195void ObjectCowLeader::calcWait() {
196 if (m_currentFrame > m_railInterpolator->curPoint().setting[0]) {
197 m_nextStateId = 2;
198 }
199}
200
202void ObjectCowLeader::calcEat() {
203 constexpr u16 EAT_ST_FRAMES = 40;
204 constexpr u16 EAT_ED_FRAMES = 60;
205
206 switch (m_state1AnmType) {
207 case AnmType::EatST: {
208 if (m_currentFrame == EAT_ST_FRAMES) {
209 m_state1AnmType = AnmType::Eat;
210 }
211 } break;
212 case AnmType::Eat: {
213 if (m_currentFrame > static_cast<u16>(m_eatFrames + EAT_ST_FRAMES)) {
214 m_state1AnmType = AnmType::EatED;
215 }
216 } break;
217 case AnmType::EatED: {
218 if (static_cast<u16>(m_eatFrames + EAT_ST_FRAMES + EAT_ED_FRAMES) == m_currentFrame) {
219 m_nextStateId = 2;
220 }
221 } break;
222 default:
223 break;
224 }
225}
226
228void ObjectCowLeader::calcRoam() {
229 if (m_endedRailSegment) {
230 m_railSpeed -= 0.1f;
231
232 if (m_railSpeed < 0.0f) {
233 m_railSpeed = 0.0f;
234
235 if (m_railInterpolator->curPoint().setting[1] == 0) {
236 m_nextStateId = 0;
237 } else {
238 m_nextStateId = 1;
239 }
240 }
241 } else {
242 if (m_railSpeed < 4.0f) {
243 m_railSpeed += 0.1f;
244 } else {
245 m_railSpeed = 4.0f;
246 }
247 }
248
249 m_railInterpolator->setCurrVel(m_railSpeed);
250
251 auto status = m_railInterpolator->calc();
252 const auto &curPoint = m_railInterpolator->curPoint();
253
254 if (status == RailInterpolator::Status::SegmentEnd &&
255 (curPoint.setting[0] != 0 || curPoint.setting[1] != 0)) {
256 m_endedRailSegment = true;
257 }
258
259 m_pos = m_railInterpolator->curPos() - EGG::Vector3f::ey * 10.0f;
260 m_flags.setBit(eFlags::Position);
261
262 setTarget(m_railInterpolator->curPos() + m_railInterpolator->curTangentDir() * 10.0f);
263}
264
265const std::array<StateManagerEntry<ObjectCowLeader>, 3>
266 StateManager<ObjectCowLeader>::STATE_ENTRIES = {{
267 {0, &ObjectCowLeader::enterWait, &ObjectCowLeader::calcWait},
268 {1, &ObjectCowLeader::enterEat, &ObjectCowLeader::calcEat},
269 {2, &ObjectCowLeader::enterRoam, &ObjectCowLeader::calcRoam},
270 }};
271
272StateManager<ObjectCowLeader>::StateManager(ObjectCowLeader *obj) {
273 constexpr size_t ENTRY_COUNT = 3;
274
275 m_obj = obj;
276 m_entries = std::span{STATE_ENTRIES};
277 m_entryIds = std::span(new u16[ENTRY_COUNT], ENTRY_COUNT);
278
279 // The base game initializes all entries to 0xffff, possibly to avoid an uninitialized value
280 for (auto &id : m_entryIds) {
281 id = 0xffff;
282 }
283
284 for (size_t i = 0; i < m_entryIds.size(); ++i) {
285 m_entryIds[STATE_ENTRIES[i].id] = i;
286 }
287}
288
289StateManager<ObjectCowLeader>::~StateManager() {
290 delete[] m_entryIds.data();
291}
292
294ObjectCowFollower::ObjectCowFollower(const System::MapdataGeoObj &params, const EGG::Vector3f &pos,
295 f32 rot)
296 : ObjectCow(params), StateManager(this), m_posOffset(pos), m_rail(nullptr) {
297 m_pos += m_posOffset;
298 m_flags.setBit(eFlags::Position, eFlags::Rotation);
299 m_rot.y = rot;
300}
301
303ObjectCowFollower::~ObjectCowFollower() = default;
304
306void ObjectCowFollower::init() {
307 setup();
308 m_pos += m_posOffset;
309 m_flags.setBit(eFlags::Position);
310 EGG::Vector3f local_1c = m_posOffset;
311 local_1c.normalise2();
312 setMatrixTangentTo(EGG::Vector3f::ey, local_1c);
313 m_nextStateId = 0;
314
315 enterWait();
316
317 m_waitFrames = 0;
318 m_bStopping = false;
319 m_railSegThreshold = 0.0f;
320}
321
323void ObjectCowFollower::calc() {
324 u32 t = System::RaceManager::Instance()->timer();
325
326 if (t < m_startFrame) {
327 if (m_currentFrame > m_waitFrames) {
328 m_nextStateId = 1;
329 }
330
331 if (m_rail->segmentT() > m_railSegThreshold) {
332 m_nextStateId = 2;
333 }
334
335 setTarget(m_pos + m_posOffset * 2.0f);
336 } else {
337 if (m_nextStateId >= 0) {
338 m_currentStateId = m_nextStateId;
339 m_nextStateId = -1;
340 m_currentFrame = 0;
341
342 auto enterFunc = m_entries[m_entryIds[m_currentStateId]].onEnter;
343 (this->*enterFunc)();
344 } else {
345 ++m_currentFrame;
346 }
347
348 auto calcFunc = m_entries[m_entryIds[m_currentStateId]].onCalc;
349 (this->*calcFunc)();
350 }
351
352 calcPos();
353 calcFloor();
354
355 m_prevTangent = m_tangent;
356 m_tangent = Interpolate(m_interpRate, m_tangent, m_targetDir);
357
358 if (m_tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
359 m_tangent.normalise2();
360 } else {
361 m_tangent = EGG::Vector3f::ez;
362 }
363
364 m_up = Interpolate(0.1f, m_up, m_floorNrm);
365
366 if (m_up.squaredLength() > std::numeric_limits<f32>::epsilon()) {
367 m_up.normalise2();
368 } else {
369 m_up = EGG::Vector3f::ey;
370 }
371
372 setMatrixTangentTo(m_up, m_tangent);
373}
374
376void ObjectCowFollower::enterWait() {
377 constexpr u32 BASE_WAIT_FRAMES = 100;
378 constexpr u32 WAIT_FRAMES_VARIANCE = 60;
379 constexpr f32 BASE_RAIL_THRESHOLD = 0.2f;
380 constexpr f32 RAIL_THRESHOLD_VARIANCE = 0.8f;
381
382 auto &rand = System::RaceManager::Instance()->random();
383 m_waitFrames = rand.getU32(WAIT_FRAMES_VARIANCE) + BASE_WAIT_FRAMES;
384 m_railSegThreshold = BASE_RAIL_THRESHOLD + rand.getF32(RAIL_THRESHOLD_VARIANCE);
385}
386
388void ObjectCowFollower::enterFreeRoam() {
389 constexpr f32 BASE_WALK_DISTANCE = 400.0f;
390 constexpr f32 WALK_DISTANCE_VARIANCE = 300.0f;
391 constexpr f32 AVG_ANGLE = DEG2RAD360 * 10.0f;
392 STATIC_ASSERT(AVG_ANGLE == 0.34906584f);
393
394 m_bStopping = false;
395 auto &rand = System::RaceManager::Instance()->random();
396
397 m_topSpeed = BASE_TOP_SPEED + rand.getF32(TOP_SPEED_VARIANCE);
398
399 f32 dVar2 = AVG_ANGLE + rand.getF32(AVG_ANGLE);
400
401 // Adjust the cow's yaw so that it is biased towards the rail's position.
402 // This means the cow will always walk in a sort of zig-zag generally following the rail.
403 f32 fVar3 = CheckPointAgainstLineSegment(m_pos, m_rail->curPoint().pos, m_rail->curPos());
404 f32 angle = fVar3 > 0.0f ? -dVar2 : dVar2;
405
406 EGG::Vector3f dir = RotateXZByYaw(angle, m_tangent);
407 dir.y = 0.0f;
408 dir.normalise2();
409
410 f32 distance = BASE_WALK_DISTANCE + rand.getF32(WALK_DISTANCE_VARIANCE);
411 setTarget(m_pos + dir * distance);
412}
413
415void ObjectCowFollower::enterFollowLeader() {
416 m_bStopping = false;
417 m_interpRate = 0.01f;
418
419 auto &rand = System::RaceManager::Instance()->random();
420 m_topSpeed = BASE_TOP_SPEED + rand.getF32(TOP_SPEED_VARIANCE);
421}
422
424void ObjectCowFollower::calcWait() {
425 if (m_currentFrame > m_waitFrames) {
426 m_nextStateId = 1;
427 }
428
429 if (m_rail->segmentT() > m_railSegThreshold) {
430 m_nextStateId = 2;
431 }
432}
433
435void ObjectCowFollower::calcFreeRoam() {
436 if (m_bStopping) {
437 m_tangentFactor = -0.1f;
438
439 if (m_xzSpeed == 0.0f) {
440 m_nextStateId = 0;
441 }
442 } else {
443 if (m_xzSpeed < m_topSpeed) {
444 m_tangentFactor = 0.1f;
445 }
446 }
447
448 EGG::Vector3f local_28 = m_state1TargetPos - m_pos;
449 if (local_28.x * local_28.x + local_28.z * local_28.z < DIST_THRESHOLD * DIST_THRESHOLD) {
450 m_bStopping = true;
451 }
452
453 if (m_rail->segmentT() > m_railSegThreshold) {
454 m_nextStateId = 2;
455 }
456}
457
459void ObjectCowFollower::calcFollowLeader() {
460 f32 dist = 0.0f;
461
462 if (m_bStopping) {
463 m_tangentFactor = -0.1f;
464
465 if (m_xzSpeed == 0.0f) {
466 m_interpRate = 0.05f;
467 m_nextStateId = 0;
468 }
469 } else {
470 dist = setTarget(m_rail->curPos() + m_posOffset);
471
472 if (m_xzSpeed < m_topSpeed) {
473 m_interpRate = 0.05f;
474 m_tangentFactor = 0.1f;
475 }
476 }
477
478 if (dist < DIST_THRESHOLD) {
479 m_bStopping = true;
480 }
481}
482
483const std::array<StateManagerEntry<ObjectCowFollower>, 3>
484 StateManager<ObjectCowFollower>::STATE_ENTRIES = {{
485 {0, &ObjectCowFollower::enterWait, &ObjectCowFollower::calcWait},
486 {1, &ObjectCowFollower::enterFreeRoam, &ObjectCowFollower::calcFreeRoam},
487 {2, &ObjectCowFollower::enterFollowLeader, &ObjectCowFollower::calcFollowLeader},
488 }};
489
490StateManager<ObjectCowFollower>::StateManager(ObjectCowFollower *obj) {
491 constexpr size_t ENTRY_COUNT = 3;
492
493 m_obj = obj;
494 m_entries = std::span{STATE_ENTRIES};
495 m_entryIds = std::span(new u16[ENTRY_COUNT], ENTRY_COUNT);
496
497 // The base game initializes all entries to 0xffff, possibly to avoid an uninitialized value
498 for (auto &id : m_entryIds) {
499 id = 0xffff;
500 }
501
502 for (size_t i = 0; i < m_entryIds.size(); ++i) {
503 m_entryIds[STATE_ENTRIES[i].id] = i;
504 }
505}
506
507StateManager<ObjectCowFollower>::~StateManager() {
508 delete[] m_entryIds.data();
509}
510
512ObjectCowHerd::ObjectCowHerd(const System::MapdataGeoObj &params) : ObjectCollidable(params) {
513 constexpr f32 FOLLOWER_SPACING = 600.0f;
514
515 u8 followerCount = params.setting(0);
516
517 m_leader = new ObjectCowLeader(params);
518 m_leader->load();
519
520 m_followers =
521 std::span<ObjectCowFollower *>(new ObjectCowFollower *[followerCount], followerCount);
522
523 for (u32 i = 0; i < followerCount; ++i) {
524 auto *&child = m_followers[i];
525 f32 rot = F_TAU / static_cast<f32>(followerCount) * static_cast<f32>(i);
526 f32 z = EGG::Mathf::SinFIdx(RAD2FIDX * rot);
527 f32 x = EGG::Mathf::CosFIdx(RAD2FIDX * rot);
528 EGG::Vector3f pos = EGG::Vector3f(x, 0.0f, z) * FOLLOWER_SPACING;
529
530 child = new ObjectCowFollower(params, pos, rot);
531 child->load();
532 }
533
534 auto *rail = RailManager::Instance()->rail(static_cast<size_t>(params.pathId()));
535 rail->checkSphereFull();
536}
537
539ObjectCowHerd::~ObjectCowHerd() {
540 delete[] m_followers.data();
541}
542
545void ObjectCowHerd::init() {
546 for (auto *&child : m_followers) {
547 child->m_rail = m_leader->m_railInterpolator;
548 }
549}
550
552void ObjectCowHerd::calc() {
553 constexpr f32 MAX_DIST = 4000.0f;
554
555 checkCollision();
556
557 for (auto *&follower : m_followers) {
558 EGG::Vector3f posDelta = follower->pos() - m_leader->pos();
559
560 if (posDelta.squaredLength() > MAX_DIST * MAX_DIST) {
561 follower->m_nextStateId = 2;
562 }
563 }
564}
565
568void ObjectCowHerd::checkCollision() {
569 constexpr f32 WIDTH = 400.0f;
570
571 for (u32 i = 0; i < m_followers.size() - 1; ++i) {
572 auto *iFollower = m_followers[i];
573
574 for (u32 j = i + 1; j < m_followers.size(); ++j) {
575 auto *jFollower = m_followers[j];
576
577 EGG::Vector3f posDelta = jFollower->pos() - iFollower->pos();
578 f32 length = posDelta.normalise();
579
580 if (length < WIDTH) {
581 EGG::Vector3f change = posDelta * (WIDTH - length) * 1.5f;
582
583 jFollower->m_pos += change;
584 jFollower->m_flags.setBit(eFlags::Position);
585
586 iFollower->m_pos -= change;
587 iFollower->m_flags.setBit(eFlags::Position);
588 }
589 }
590 }
591
592 for (auto *&follower : m_followers) {
593 EGG::Vector3f posDelta = m_leader->pos() - follower->pos();
594 f32 length = posDelta.normalise();
595
596 if (length < WIDTH) {
597 EGG::Vector3f change = posDelta * (WIDTH - length) * 1.5f;
598
599 follower->m_pos -= change;
600 follower->m_flags.setBit(eFlags::Position);
601 }
602 }
603}
604
605} // namespace Field
The highest level abstraction for a kart.
Definition KartObject.hh:11
Pertains to collision.
A 3D float vector.
Definition Vector.hh:87
f32 normalise()
Normalizes the vector and returns the original length.
Definition Vector.cc:44
f32 squaredLength() const
The dot product between the vector and itself.
Definition Vector.hh:181