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 StateManager::calc();
133 }
134
135 calcPos();
136 calcFloor();
137
138 m_prevTangent = m_tangent;
139 m_tangent = Interpolate(m_interpRate, m_tangent, m_targetDir);
140
141 if (m_tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
142 m_tangent.normalise2();
143 } else {
144 m_tangent = EGG::Vector3f::ez;
145 }
146
147 m_up = Interpolate(0.1f, m_up, m_floorNrm);
148
149 if (m_up.squaredLength() > std::numeric_limits<f32>::epsilon()) {
150 m_up.normalise2();
151 } else {
152 m_up = EGG::Vector3f::ey;
153 }
154
155 setMatrixTangentTo(m_up, m_tangent);
156}
157
159void ObjectCowLeader::calcFloor() {
160 m_velocity.y = 0.0f;
161 m_upForce = GRAVITY_FORCE;
162 m_floorNrm = m_railInterpolator->floorNrm(m_railInterpolator->nextPointIdx());
163}
164
166void ObjectCowLeader::enterWait() {
167 setTarget(m_railInterpolator->curPos() + m_railInterpolator->curTangentDir() * 10.0f);
168}
169
171void ObjectCowLeader::enterEat() {
172 m_state1AnmType = AnmType::EatST;
173 u32 rand = System::RaceManager::Instance()->random().getU32(120);
174 m_eatFrames = rand + 120;
175}
176
178void ObjectCowLeader::enterRoam() {
179 m_endedRailSegment = false;
180}
181
183void ObjectCowLeader::calcWait() {
184 if (m_currentFrame > m_railInterpolator->curPoint().setting[0]) {
185 m_nextStateId = 2;
186 }
187}
188
190void ObjectCowLeader::calcEat() {
191 constexpr u16 EAT_ST_FRAMES = 40;
192 constexpr u16 EAT_ED_FRAMES = 60;
193
194 switch (m_state1AnmType) {
195 case AnmType::EatST: {
196 if (m_currentFrame == EAT_ST_FRAMES) {
197 m_state1AnmType = AnmType::Eat;
198 }
199 } break;
200 case AnmType::Eat: {
201 if (m_currentFrame > static_cast<u16>(m_eatFrames + EAT_ST_FRAMES)) {
202 m_state1AnmType = AnmType::EatED;
203 }
204 } break;
205 case AnmType::EatED: {
206 if (static_cast<u16>(m_eatFrames + EAT_ST_FRAMES + EAT_ED_FRAMES) == m_currentFrame) {
207 m_nextStateId = 2;
208 }
209 } break;
210 default:
211 break;
212 }
213}
214
216void ObjectCowLeader::calcRoam() {
217 if (m_endedRailSegment) {
218 m_railSpeed -= 0.1f;
219
220 if (m_railSpeed < 0.0f) {
221 m_railSpeed = 0.0f;
222
223 if (m_railInterpolator->curPoint().setting[1] == 0) {
224 m_nextStateId = 0;
225 } else {
226 m_nextStateId = 1;
227 }
228 }
229 } else {
230 if (m_railSpeed < 4.0f) {
231 m_railSpeed += 0.1f;
232 } else {
233 m_railSpeed = 4.0f;
234 }
235 }
236
237 m_railInterpolator->setCurrVel(m_railSpeed);
238
239 auto status = m_railInterpolator->calc();
240 const auto &curPoint = m_railInterpolator->curPoint();
241
242 if (status == RailInterpolator::Status::SegmentEnd &&
243 (curPoint.setting[0] != 0 || curPoint.setting[1] != 0)) {
244 m_endedRailSegment = true;
245 }
246
247 m_pos = m_railInterpolator->curPos() - EGG::Vector3f::ey * 10.0f;
248 m_flags.setBit(eFlags::Position);
249
250 setTarget(m_railInterpolator->curPos() + m_railInterpolator->curTangentDir() * 10.0f);
251}
252
254ObjectCowFollower::ObjectCowFollower(const System::MapdataGeoObj &params, const EGG::Vector3f &pos,
255 f32 rot)
256 : ObjectCow(params), StateManager(this), m_posOffset(pos), m_rail(nullptr) {
257 m_pos += m_posOffset;
258 m_flags.setBit(eFlags::Position, eFlags::Rotation);
259 m_rot.y = rot;
260}
261
263ObjectCowFollower::~ObjectCowFollower() = default;
264
266void ObjectCowFollower::init() {
267 setup();
268 m_pos += m_posOffset;
269 m_flags.setBit(eFlags::Position);
270 EGG::Vector3f local_1c = m_posOffset;
271 local_1c.normalise2();
272 setMatrixTangentTo(EGG::Vector3f::ey, local_1c);
273 m_nextStateId = 0;
274
275 enterWait();
276
277 m_waitFrames = 0;
278 m_bStopping = false;
279 m_railSegThreshold = 0.0f;
280}
281
283void ObjectCowFollower::calc() {
284 u32 t = System::RaceManager::Instance()->timer();
285
286 if (t < m_startFrame) {
287 if (m_currentFrame > m_waitFrames) {
288 m_nextStateId = 1;
289 }
290
291 if (m_rail->segmentT() > m_railSegThreshold) {
292 m_nextStateId = 2;
293 }
294
295 setTarget(m_pos + m_posOffset * 2.0f);
296 } else {
297 StateManager::calc();
298 }
299
300 calcPos();
301 calcFloor();
302
303 m_prevTangent = m_tangent;
304 m_tangent = Interpolate(m_interpRate, m_tangent, m_targetDir);
305
306 if (m_tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
307 m_tangent.normalise2();
308 } else {
309 m_tangent = EGG::Vector3f::ez;
310 }
311
312 m_up = Interpolate(0.1f, m_up, m_floorNrm);
313
314 if (m_up.squaredLength() > std::numeric_limits<f32>::epsilon()) {
315 m_up.normalise2();
316 } else {
317 m_up = EGG::Vector3f::ey;
318 }
319
320 setMatrixTangentTo(m_up, m_tangent);
321}
322
324void ObjectCowFollower::enterWait() {
325 constexpr u32 BASE_WAIT_FRAMES = 100;
326 constexpr u32 WAIT_FRAMES_VARIANCE = 60;
327 constexpr f32 BASE_RAIL_THRESHOLD = 0.2f;
328 constexpr f32 RAIL_THRESHOLD_VARIANCE = 0.8f;
329
330 auto &rand = System::RaceManager::Instance()->random();
331 m_waitFrames = rand.getU32(WAIT_FRAMES_VARIANCE) + BASE_WAIT_FRAMES;
332 m_railSegThreshold = BASE_RAIL_THRESHOLD + rand.getF32(RAIL_THRESHOLD_VARIANCE);
333}
334
336void ObjectCowFollower::enterFreeRoam() {
337 constexpr f32 BASE_WALK_DISTANCE = 400.0f;
338 constexpr f32 WALK_DISTANCE_VARIANCE = 300.0f;
339 constexpr f32 AVG_ANGLE = DEG2RAD360 * 10.0f;
340 STATIC_ASSERT(AVG_ANGLE == 0.34906584f);
341
342 m_bStopping = false;
343 auto &rand = System::RaceManager::Instance()->random();
344
345 m_topSpeed = BASE_TOP_SPEED + rand.getF32(TOP_SPEED_VARIANCE);
346
347 f32 dVar2 = AVG_ANGLE + rand.getF32(AVG_ANGLE);
348
349 // Adjust the cow's yaw so that it is biased towards the rail's position.
350 // This means the cow will always walk in a sort of zig-zag generally following the rail.
351 f32 fVar3 = CheckPointAgainstLineSegment(m_pos, m_rail->curPoint().pos, m_rail->curPos());
352 f32 angle = fVar3 > 0.0f ? -dVar2 : dVar2;
353
354 EGG::Vector3f dir = RotateXZByYaw(angle, m_tangent);
355 dir.y = 0.0f;
356 dir.normalise2();
357
358 f32 distance = BASE_WALK_DISTANCE + rand.getF32(WALK_DISTANCE_VARIANCE);
359 setTarget(m_pos + dir * distance);
360}
361
363void ObjectCowFollower::enterFollowLeader() {
364 m_bStopping = false;
365 m_interpRate = 0.01f;
366
367 auto &rand = System::RaceManager::Instance()->random();
368 m_topSpeed = BASE_TOP_SPEED + rand.getF32(TOP_SPEED_VARIANCE);
369}
370
372void ObjectCowFollower::calcWait() {
373 if (m_currentFrame > m_waitFrames) {
374 m_nextStateId = 1;
375 }
376
377 if (m_rail->segmentT() > m_railSegThreshold) {
378 m_nextStateId = 2;
379 }
380}
381
383void ObjectCowFollower::calcFreeRoam() {
384 if (m_bStopping) {
385 m_tangentFactor = -0.1f;
386
387 if (m_xzSpeed == 0.0f) {
388 m_nextStateId = 0;
389 }
390 } else {
391 if (m_xzSpeed < m_topSpeed) {
392 m_tangentFactor = 0.1f;
393 }
394 }
395
396 EGG::Vector3f local_28 = m_state1TargetPos - m_pos;
397 if (local_28.x * local_28.x + local_28.z * local_28.z < DIST_THRESHOLD * DIST_THRESHOLD) {
398 m_bStopping = true;
399 }
400
401 if (m_rail->segmentT() > m_railSegThreshold) {
402 m_nextStateId = 2;
403 }
404}
405
407void ObjectCowFollower::calcFollowLeader() {
408 f32 dist = 0.0f;
409
410 if (m_bStopping) {
411 m_tangentFactor = -0.1f;
412
413 if (m_xzSpeed == 0.0f) {
414 m_interpRate = 0.05f;
415 m_nextStateId = 0;
416 }
417 } else {
418 dist = setTarget(m_rail->curPos() + m_posOffset);
419
420 if (m_xzSpeed < m_topSpeed) {
421 m_interpRate = 0.05f;
422 m_tangentFactor = 0.1f;
423 }
424 }
425
426 if (dist < DIST_THRESHOLD) {
427 m_bStopping = true;
428 }
429}
430
432ObjectCowHerd::ObjectCowHerd(const System::MapdataGeoObj &params) : ObjectCollidable(params) {
433 constexpr f32 FOLLOWER_SPACING = 600.0f;
434
435 u8 followerCount = params.setting(0);
436
437 m_leader = new ObjectCowLeader(params);
438 m_leader->load();
439
440 m_followers =
441 std::span<ObjectCowFollower *>(new ObjectCowFollower *[followerCount], followerCount);
442
443 for (u32 i = 0; i < followerCount; ++i) {
444 auto *&child = m_followers[i];
445 f32 rot = F_TAU / static_cast<f32>(followerCount) * static_cast<f32>(i);
446 f32 z = EGG::Mathf::SinFIdx(RAD2FIDX * rot);
447 f32 x = EGG::Mathf::CosFIdx(RAD2FIDX * rot);
448 EGG::Vector3f pos = EGG::Vector3f(x, 0.0f, z) * FOLLOWER_SPACING;
449
450 child = new ObjectCowFollower(params, pos, rot);
451 child->load();
452 }
453
454 auto *rail = RailManager::Instance()->rail(static_cast<size_t>(params.pathId()));
455 rail->checkSphereFull();
456}
457
459ObjectCowHerd::~ObjectCowHerd() {
460 delete[] m_followers.data();
461}
462
465void ObjectCowHerd::init() {
466 for (auto *&child : m_followers) {
467 child->m_rail = m_leader->m_railInterpolator;
468 }
469}
470
472void ObjectCowHerd::calc() {
473 constexpr f32 MAX_DIST = 4000.0f;
474
475 checkCollision();
476
477 for (auto *&follower : m_followers) {
478 EGG::Vector3f posDelta = follower->pos() - m_leader->pos();
479
480 if (posDelta.squaredLength() > MAX_DIST * MAX_DIST) {
481 follower->m_nextStateId = 2;
482 }
483 }
484}
485
488void ObjectCowHerd::checkCollision() {
489 constexpr f32 WIDTH = 400.0f;
490
491 for (u32 i = 0; i < m_followers.size() - 1; ++i) {
492 auto *iFollower = m_followers[i];
493
494 for (u32 j = i + 1; j < m_followers.size(); ++j) {
495 auto *jFollower = m_followers[j];
496
497 EGG::Vector3f posDelta = jFollower->pos() - iFollower->pos();
498 f32 length = posDelta.normalise();
499
500 if (length < WIDTH) {
501 EGG::Vector3f change = posDelta * (WIDTH - length) * 1.5f;
502
503 jFollower->m_pos += change;
504 jFollower->m_flags.setBit(eFlags::Position);
505
506 iFollower->m_pos -= change;
507 iFollower->m_flags.setBit(eFlags::Position);
508 }
509 }
510 }
511
512 for (auto *&follower : m_followers) {
513 EGG::Vector3f posDelta = m_leader->pos() - follower->pos();
514 f32 length = posDelta.normalise();
515
516 if (length < WIDTH) {
517 EGG::Vector3f change = posDelta * (WIDTH - length) * 1.5f;
518
519 follower->m_pos -= change;
520 follower->m_flags.setBit(eFlags::Position);
521 }
522 }
523}
524
525} // namespace Field
The highest level abstraction for a kart.
Definition KartObject.hh:11
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 squaredLength() const
The dot product between the vector and itself.
Definition Vector.hh:182