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 Kinoko::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 ASSERT(m_mapObj);
31 setScale(m_mapObj->scale());
32 setPos(m_mapObj->pos());
33 setRot(m_mapObj->rot() * DEG2RAD);
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 = 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, pos() + POS_OFFSET,
55 EGG::Vector3f::inf, KCL_TYPE_64EBDFFF, &info, nullptr, 0);
56
57 if (hasCol) {
58 addPos(info.tangentOff);
59
60 if (info.floorDist > -std::numeric_limits<f32>::min()) {
61 m_floorNrm = info.floorNrm;
62 }
63
64 m_velocity.y = 0.0f;
65 m_upForce = GRAVITY_FORCE;
66 } else {
67 m_upForce = EGG::Vector3f::zero;
68 }
69}
70
72void ObjectCow::calcPos() {
73 EGG::Vector3f accel =
74 m_tangent * m_tangentFactor + (m_tangent - m_prevTangent) * m_xzSpeed + m_upForce;
75 m_velocity += accel - GRAVITY_FORCE;
76 m_xzSpeed = EGG::Mathf::sqrt(m_velocity.x * m_velocity.x + m_velocity.z * m_velocity.z);
77
78 if (m_tangent.z * m_velocity.z + m_tangent.x * m_velocity.x < 0.0f) {
79 m_velocity.x = 0.0f;
80 m_velocity.z = 0.0f;
81 m_xzSpeed = 0.0f;
82 }
83
84 addPos(m_velocity);
85 m_tangentFactor = 0.0f;
86}
87
89f32 ObjectCow::setTarget(const EGG::Vector3f &v) {
90 m_state1TargetPos = v;
91 EGG::Vector3f posDiff = m_state1TargetPos - pos();
92 f32 dist = posDiff.normalise();
93 m_targetDir = m_state1TargetPos + posDiff * 1000.0f - pos();
94 m_targetDir.normalise2();
95
96 return dist;
97}
98
100ObjectCowLeader::ObjectCowLeader(const System::MapdataGeoObj &params)
101 : ObjectCow(params), StateManager(this, STATE_ENTRIES) {}
102
104ObjectCowLeader::~ObjectCowLeader() = default;
105
107void ObjectCowLeader::init() {
108 setup();
109 m_railInterpolator->init(0.0f, 0);
110 setPos(m_railInterpolator->curPos());
111
112 setTarget(pos() + m_railInterpolator->curTangentDir() * 10.0f);
113
114 m_railInterpolator->setCurrVel(static_cast<f32>(m_mapObj->setting(1)));
115
116 m_railSpeed = 0.0f;
117 m_endedRailSegment = false;
118 m_state1AnmType = AnmType::EatST;
119 m_eatFrames = 0;
120 m_interpRate = 1.0f;
121 m_nextStateId = 2;
122}
123
125void ObjectCowLeader::calc() {
126 u32 t = System::RaceManager::Instance()->timer();
127
128 if (t >= m_startFrame) {
129 StateManager::calc();
130 }
131
132 calcPos();
133 calcFloor();
134
135 m_prevTangent = m_tangent;
136 m_tangent = Interpolate(m_interpRate, m_tangent, m_targetDir);
137
138 if (m_tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
139 m_tangent.normalise2();
140 } else {
141 m_tangent = EGG::Vector3f::ez;
142 }
143
144 m_up = Interpolate(0.1f, m_up, m_floorNrm);
145
146 if (m_up.squaredLength() > std::numeric_limits<f32>::epsilon()) {
147 m_up.normalise2();
148 } else {
149 m_up = EGG::Vector3f::ey;
150 }
151
152 setMatrixTangentTo(m_up, m_tangent);
153}
154
156void ObjectCowLeader::calcFloor() {
157 m_velocity.y = 0.0f;
158 m_upForce = GRAVITY_FORCE;
159 m_floorNrm = m_railInterpolator->floorNrm(m_railInterpolator->nextPointIdx());
160}
161
163void ObjectCowLeader::enterWait() {
164 setTarget(m_railInterpolator->curPos() + m_railInterpolator->curTangentDir() * 10.0f);
165}
166
168void ObjectCowLeader::enterEat() {
169 m_state1AnmType = AnmType::EatST;
170 u32 rand = System::RaceManager::Instance()->random().getU32(120);
171 m_eatFrames = rand + 120;
172}
173
175void ObjectCowLeader::enterRoam() {
176 m_endedRailSegment = false;
177}
178
180void ObjectCowLeader::calcWait() {
181 if (m_currentFrame > m_railInterpolator->curPoint().setting[0]) {
182 m_nextStateId = 2;
183 }
184}
185
187void ObjectCowLeader::calcEat() {
188 constexpr u16 EAT_ST_FRAMES = 40;
189 constexpr u16 EAT_ED_FRAMES = 60;
190
191 switch (m_state1AnmType) {
192 case AnmType::EatST: {
193 if (m_currentFrame == EAT_ST_FRAMES) {
194 m_state1AnmType = AnmType::Eat;
195 }
196 } break;
197 case AnmType::Eat: {
198 if (m_currentFrame > static_cast<u16>(m_eatFrames + EAT_ST_FRAMES)) {
199 m_state1AnmType = AnmType::EatED;
200 }
201 } break;
202 case AnmType::EatED: {
203 if (static_cast<u16>(m_eatFrames + EAT_ST_FRAMES + EAT_ED_FRAMES) == m_currentFrame) {
204 m_nextStateId = 2;
205 }
206 } break;
207 default:
208 break;
209 }
210}
211
213void ObjectCowLeader::calcRoam() {
214 if (m_endedRailSegment) {
215 m_railSpeed -= 0.1f;
216
217 if (m_railSpeed < 0.0f) {
218 m_railSpeed = 0.0f;
219
220 if (m_railInterpolator->curPoint().setting[1] == 0) {
221 m_nextStateId = 0;
222 } else {
223 m_nextStateId = 1;
224 }
225 }
226 } else {
227 if (m_railSpeed < 4.0f) {
228 m_railSpeed += 0.1f;
229 } else {
230 m_railSpeed = 4.0f;
231 }
232 }
233
234 m_railInterpolator->setCurrVel(m_railSpeed);
235
236 auto status = m_railInterpolator->calc();
237 const auto &curPoint = m_railInterpolator->curPoint();
238
239 if (status == RailInterpolator::Status::SegmentEnd &&
240 (curPoint.setting[0] != 0 || curPoint.setting[1] != 0)) {
241 m_endedRailSegment = true;
242 }
243
244 setPos(m_railInterpolator->curPos() - EGG::Vector3f::ey * 10.0f);
245 setTarget(m_railInterpolator->curPos() + m_railInterpolator->curTangentDir() * 10.0f);
246}
247
249ObjectCowFollower::ObjectCowFollower(const System::MapdataGeoObj &params, const EGG::Vector3f &pos,
250 f32 initRot)
251 : ObjectCow(params), StateManager(this, STATE_ENTRIES), m_posOffset(pos), m_rail(nullptr) {
252 addPos(m_posOffset);
253 setRot(EGG::Vector3f(rot().x, initRot, rot().z));
254}
255
257ObjectCowFollower::~ObjectCowFollower() = default;
258
260void ObjectCowFollower::init() {
261 setup();
262 addPos(m_posOffset);
263 EGG::Vector3f local_1c = m_posOffset;
264 local_1c.normalise2();
265 setMatrixTangentTo(EGG::Vector3f::ey, local_1c);
266 m_nextStateId = 0;
267
268 enterWait();
269
270 m_waitFrames = 0;
271 m_bStopping = false;
272 m_railSegThreshold = 0.0f;
273}
274
276void ObjectCowFollower::calc() {
277 u32 t = System::RaceManager::Instance()->timer();
278
279 if (t < m_startFrame) {
280 if (m_currentFrame > m_waitFrames) {
281 m_nextStateId = 1;
282 }
283
284 if (m_rail->segmentT() > m_railSegThreshold) {
285 m_nextStateId = 2;
286 }
287
288 setTarget(pos() + m_posOffset * 2.0f);
289 } else {
290 StateManager::calc();
291 }
292
293 calcPos();
294 calcFloor();
295
296 m_prevTangent = m_tangent;
297 m_tangent = Interpolate(m_interpRate, m_tangent, m_targetDir);
298
299 if (m_tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
300 m_tangent.normalise2();
301 } else {
302 m_tangent = EGG::Vector3f::ez;
303 }
304
305 m_up = Interpolate(0.1f, m_up, m_floorNrm);
306
307 if (m_up.squaredLength() > std::numeric_limits<f32>::epsilon()) {
308 m_up.normalise2();
309 } else {
310 m_up = EGG::Vector3f::ey;
311 }
312
313 setMatrixTangentTo(m_up, m_tangent);
314}
315
317void ObjectCowFollower::enterWait() {
318 constexpr u32 BASE_WAIT_FRAMES = 100;
319 constexpr u32 WAIT_FRAMES_VARIANCE = 60;
320 constexpr f32 BASE_RAIL_THRESHOLD = 0.2f;
321 constexpr f32 RAIL_THRESHOLD_VARIANCE = 0.8f;
322
323 auto &rand = System::RaceManager::Instance()->random();
324 m_waitFrames = rand.getU32(WAIT_FRAMES_VARIANCE) + BASE_WAIT_FRAMES;
325 m_railSegThreshold = BASE_RAIL_THRESHOLD + rand.getF32(RAIL_THRESHOLD_VARIANCE);
326}
327
329void ObjectCowFollower::enterFreeRoam() {
330 constexpr f32 BASE_WALK_DISTANCE = 400.0f;
331 constexpr f32 WALK_DISTANCE_VARIANCE = 300.0f;
332 constexpr f32 AVG_ANGLE = DEG2RAD360 * 10.0f;
333 STATIC_ASSERT(AVG_ANGLE == 0.34906584f);
334
335 m_bStopping = false;
336 auto &rand = System::RaceManager::Instance()->random();
337
338 m_topSpeed = BASE_TOP_SPEED + rand.getF32(TOP_SPEED_VARIANCE);
339
340 f32 dVar2 = AVG_ANGLE + rand.getF32(AVG_ANGLE);
341
342 // Adjust the cow's yaw so that it is biased towards the rail's position.
343 // This means the cow will always walk in a sort of zig-zag generally following the rail.
344 f32 fVar3 = CheckPointAgainstLineSegment(pos(), m_rail->curPoint().pos, m_rail->curPos());
345 f32 angle = fVar3 > 0.0f ? -dVar2 : dVar2;
346
347 EGG::Vector3f dir = RotateXZByYaw(angle, m_tangent);
348 dir.y = 0.0f;
349 dir.normalise2();
350
351 f32 distance = BASE_WALK_DISTANCE + rand.getF32(WALK_DISTANCE_VARIANCE);
352 setTarget(pos() + dir * distance);
353}
354
356void ObjectCowFollower::enterFollowLeader() {
357 m_bStopping = false;
358 m_interpRate = 0.01f;
359
360 auto &rand = System::RaceManager::Instance()->random();
361 m_topSpeed = BASE_TOP_SPEED + rand.getF32(TOP_SPEED_VARIANCE);
362}
363
365void ObjectCowFollower::calcWait() {
366 if (m_currentFrame > m_waitFrames) {
367 m_nextStateId = 1;
368 }
369
370 if (m_rail->segmentT() > m_railSegThreshold) {
371 m_nextStateId = 2;
372 }
373}
374
376void ObjectCowFollower::calcFreeRoam() {
377 if (m_bStopping) {
378 m_tangentFactor = -0.1f;
379
380 if (m_xzSpeed == 0.0f) {
381 m_nextStateId = 0;
382 }
383 } else {
384 if (m_xzSpeed < m_topSpeed) {
385 m_tangentFactor = 0.1f;
386 }
387 }
388
389 EGG::Vector3f local_28 = m_state1TargetPos - pos();
390 if (local_28.x * local_28.x + local_28.z * local_28.z < DIST_THRESHOLD * DIST_THRESHOLD) {
391 m_bStopping = true;
392 }
393
394 if (m_rail->segmentT() > m_railSegThreshold) {
395 m_nextStateId = 2;
396 }
397}
398
400void ObjectCowFollower::calcFollowLeader() {
401 f32 dist = 0.0f;
402
403 if (m_bStopping) {
404 m_tangentFactor = -0.1f;
405
406 if (m_xzSpeed == 0.0f) {
407 m_interpRate = 0.05f;
408 m_nextStateId = 0;
409 }
410 } else {
411 dist = setTarget(m_rail->curPos() + m_posOffset);
412
413 if (m_xzSpeed < m_topSpeed) {
414 m_interpRate = 0.05f;
415 m_tangentFactor = 0.1f;
416 }
417 }
418
419 if (dist < DIST_THRESHOLD) {
420 m_bStopping = true;
421 }
422}
423
425ObjectCowHerd::ObjectCowHerd(const System::MapdataGeoObj &params) : ObjectCollidable(params) {
426 constexpr f32 FOLLOWER_SPACING = 600.0f;
427
428 u8 followerCount = params.setting(0);
429
430 m_leader = EGG::egg_new<ObjectCowLeader>(params);
431 m_leader->load();
432
433 m_followers = owning_span<ObjectCowFollower *>(followerCount);
434
435 for (u32 i = 0; i < followerCount; ++i) {
436 auto *&child = m_followers[i];
437 f32 rot = F_TAU / static_cast<f32>(followerCount) * static_cast<f32>(i);
438 f32 z = EGG::Mathf::SinFIdx(RAD2FIDX * rot);
439 f32 x = EGG::Mathf::CosFIdx(RAD2FIDX * rot);
440 EGG::Vector3f pos = EGG::Vector3f(x, 0.0f, z) * FOLLOWER_SPACING;
441
442 child = EGG::egg_new<ObjectCowFollower>(params, pos, rot);
443 child->load();
444 }
445
446 auto *rail = RailManager::Instance()->rail(static_cast<size_t>(params.pathId()));
447 rail->checkSphereFull();
448}
449
451ObjectCowHerd::~ObjectCowHerd() = default;
452
456 for (auto *&child : m_followers) {
457 child->m_rail = m_leader->m_railInterpolator;
458 }
459}
460
463 constexpr f32 MAX_DIST = 4000.0f;
464
466
467 for (auto *&follower : m_followers) {
468 EGG::Vector3f posDelta = follower->pos() - m_leader->pos();
469
470 if (posDelta.squaredLength() > MAX_DIST * MAX_DIST) {
471 follower->m_nextStateId = 2;
472 }
473 }
474}
475
479 constexpr f32 WIDTH = 400.0f;
480
481 for (u32 i = 0; i < m_followers.size() - 1; ++i) {
482 auto *iFollower = m_followers[i];
483
484 for (u32 j = i + 1; j < m_followers.size(); ++j) {
485 auto *jFollower = m_followers[j];
486
487 EGG::Vector3f posDelta = jFollower->pos() - iFollower->pos();
488 f32 length = posDelta.normalise();
489
490 if (length < WIDTH) {
491 EGG::Vector3f change = posDelta * (WIDTH - length) * 1.5f;
492 jFollower->addPos(change);
493 iFollower->subPos(change);
494 }
495 }
496 }
497
498 for (auto *&follower : m_followers) {
499 EGG::Vector3f posDelta = m_leader->pos() - follower->pos();
500 f32 length = posDelta.normalise();
501
502 if (length < WIDTH) {
503 EGG::Vector3f change = posDelta * (WIDTH - length) * 1.5f;
504
505 follower->subPos(change);
506 }
507 }
508}
509
510} // namespace Kinoko::Field
void checkIntraCollision()
Prevents cows from walking into each other.
Definition ObjectCow.cc:478
void init() override
Assigns the herd's rail to each child.
Definition ObjectCow.cc:455
Pertains to collision.
A 3D float vector.
Definition Vector.hh:107
constexpr f32 normalise()
Normalizes the vector and returns the original length.
Definition Vector.hh:278
constexpr f32 squaredLength() const
The dot product between the vector and itself.
Definition Vector.hh:201