A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
ObjectFireSnake.cc
1#include "ObjectFireSnake.hh"
2
3#include "game/field/CollisionDirector.hh"
4#include "game/field/ObjectDirector.hh"
5
6#include "game/system/RaceManager.hh"
7
8namespace Kinoko::Field {
9
11ObjectFireSnakeKid::ObjectFireSnakeKid(const System::MapdataGeoObj &params)
12 : ObjectCollidable(params) {}
13
15ObjectFireSnakeKid::~ObjectFireSnakeKid() = default;
16
18ObjectFireSnake::ObjectFireSnake(const System::MapdataGeoObj &params)
19 : StateManager(this, STATE_ENTRIES), ObjectProjectile(params), m_initialPos(params.pos()),
20 m_maxAge(static_cast<s16>(params.setting(1))) {
21 if (ObjectDirector::Instance()->managedObjects().size() > 0) {
22 registerManagedObject();
23 }
24
25 for (u32 i = 0; i < 2; ++i) {
26 auto *&kid = m_kids[i];
27 kid = EGG::egg_new<ObjectFireSnakeKid>(params);
28 kid->load();
29 f32 kidScale = (0.75f - 0.25f * static_cast<f32>(i)) * scale().y;
30 kid->setScale(kidScale);
31 }
32
33 m_prevTransforms.fill(EGG::Matrix34f::ident);
34}
35
37ObjectFireSnake::~ObjectFireSnake() = default;
38
40void ObjectFireSnake::init() {
41 m_nextStateId = 0;
42 enterDespawned();
43
44 m_sunPos.setZero();
45
46 calcTransform();
47 m_initRot = transform().base(0);
48 m_bounceDir = transform().base(0);
49}
50
52void ObjectFireSnake::calc() {
53 StateManager::calc();
54
55 if (isCollisionEnabled()) {
56 if (++m_age >= m_maxAge && m_currentStateId == 3) {
57 m_nextStateId = 0;
58 }
59 }
60
61 calcChildren();
62}
63
66 m_sunPos = pos;
67
68 m_xzSunDist = m_initialPos - m_sunPos;
69 m_xzSunDist.y = 0.0f;
70 m_xzSunDist.normalise2();
71
72 EGG::Vector3f xzDist = m_sunPos - m_initialPos;
73 xzDist.y = 0.0f;
74 f32 dist = xzDist.length();
75
76 m_fallAxis = RotateXZByYaw(F_PI / 2.0f, m_xzSunDist);
77 m_xzSpeed = dist * EGG::Mathf::sqrt(GRAVITY * 0.5f / (m_sunPos.y - m_initialPos.y));
78 m_fallDuration = static_cast<u16>(dist / m_xzSpeed);
79}
80
83 m_nextStateId = 1;
84}
85
87void ObjectFireSnake::enterDespawned() {
88 if (getUnit()) {
89 unregisterCollision();
90 }
91
92 m_visualPos = m_sunPos;
93 setPos(m_sunPos);
94}
95
97void ObjectFireSnake::enterFalling() {
98 if (!getUnit()) {
99 loadAABB(0.0f);
100 }
101}
102
104void ObjectFireSnake::enterHighBounce() {
105 m_visualPos = m_initialPos;
106 setPos(m_initialPos);
107
108 f32 rand = System::RaceManager::Instance()->random().getF32();
109 m_bounceDir = rand >= 0.5f ? m_initRot : -m_initRot;
110
111 m_age = 0;
112}
113
115void ObjectFireSnake::enterRest() {
116 constexpr f32 XZ_RANGE = 1000.0f * 1000.0f;
117
118 // If it strays too far from original position, start bouncing the opposite direction
119 if (m_visualPos.sqDistance(m_initialPos) >= XZ_RANGE) {
120 m_bounceDir = m_initialPos - m_visualPos;
121 m_bounceDir.normalise2();
122 } else {
123 // 45 degree random fluctuation, with a 50% chance to flip direction
124 auto &rand = System::RaceManager::Instance()->random();
125 f32 angle = rand.getF32(90.0f) - 45.0f;
126
127 if (rand.getF32() >= 0.5f) {
128 angle += 180.0f;
129 }
130
131 m_bounceDir = RotateXZByYaw(angle * DEG2RAD, m_initRot);
132 }
133}
134
136void ObjectFireSnake::calcFalling() {
137 EGG::Vector2f fallVel;
138 fallVel.y = -GRAVITY * static_cast<f32>(m_currentFrame);
139 f32 fallVelY = fallVel.y;
140 f32 posY = m_visualPos.y + fallVel.y;
141
142 if (posY <= m_initialPos.y) {
143 m_nextStateId = 2;
144 setPos(m_visualPos * 0.5f + m_initialPos * 0.5f);
145 } else {
146 fallVel.x = m_xzSpeed;
147 m_visualPos.y = posY;
148 m_visualPos.x += m_xzSunDist.x * fallVel.x;
149 m_visualPos.z += m_xzSunDist.z * fallVel.x;
150 fallVel.normalise2();
151
152 f32 angle = fallVel.x;
153 EGG::Vector3f up = RotateAxisAngle(EGG::Mathf::acos(angle), m_fallAxis, EGG::Vector3f::ey);
154 auto axis = EGG::Vector3f(m_xzSunDist.x * m_xzSpeed, -fallVelY, m_xzSunDist.z * m_xzSpeed);
155 axis.normalise2();
156 auto spiral = RotateAxisAngle(50.0f * static_cast<f32>(m_currentFrame) * DEG2RAD, axis, up);
157
158 f32 scaledDuration = 0.9f * static_cast<f32>(m_fallDuration);
159 f32 phase = m_currentFrame - static_cast<u16>(scaledDuration);
160 f32 spiralRadius = phase < 0 ? 200.0f : 200.0f - (200.0f / scaledDuration) * phase;
161
162 setPos(m_visualPos + spiral * spiralRadius);
163 }
164}
165
167void ObjectFireSnake::calcHighBounce() {
168 constexpr f32 INITIAL_VELOCITY = 1.6f * 60.0f;
169
170 calcBounce(INITIAL_VELOCITY);
171}
172
174void ObjectFireSnake::calcRest() {
175 if (m_currentFrame >= 30) {
176 m_nextStateId = 4;
177 }
178
179 calcTransform();
180
181 EGG::Vector3f tangent = Interpolate(0.3f, transform().base(2), m_bounceDir);
182 tangent.normalise2();
183
184 setMatrixTangentTo(EGG::Vector3f::ey, tangent);
185}
186
188void ObjectFireSnake::calcBounce() {
189 constexpr f32 INITIAL_VELOCITY = 60.0f;
190
191 calcBounce(INITIAL_VELOCITY);
192}
193
195void ObjectFireSnake::calcChildren() {
196 // Shift all matrices up by one index
197 for (size_t i = m_prevTransforms.size() - 1; i > 0; --i) {
198 m_prevTransforms[i] = m_prevTransforms[i - 1];
199 }
200
201 calcTransform();
202 m_prevTransforms[0] = transform();
203 m_prevTransforms[0].setBase(3, pos());
204
205 u32 idx = 10;
206 for (auto *&kid : m_kids) {
207 if (m_currentStateId == 1 && idx == m_currentFrame) {
208 if (!kid->getUnit()) {
209 kid->loadAABB(0.0f);
210 }
211 } else if (m_currentStateId == 0 && m_currentFrame == 0) {
212 if (kid->getUnit()) {
213 kid->unregisterCollision();
214 }
215 }
216
217 if (m_currentStateId == 1 && m_currentFrame < idx) {
218 calcTransform();
219 kid->setTransform(transform());
220 } else {
221 kid->setTransform(m_prevTransforms[idx]);
222 }
223
224 idx += 10;
225 }
226}
227
229void ObjectFireSnake::calcBounce(f32 initialVel) {
230 // Collision checks only occur after 10 frames in the bounce state.
231 constexpr u32 BOUNCE_COL_CHECK_DELAY = 10;
232 constexpr f32 RADIUS = 130.0f;
233 constexpr f32 BOUNCE_SPEED = 20.0f;
234
235 m_visualPos.x += m_bounceDir.x * BOUNCE_SPEED;
236 m_visualPos.z += m_bounceDir.z * BOUNCE_SPEED;
237 m_visualPos.y += initialVel - GRAVITY * static_cast<f32>(m_currentFrame);
238
239 CollisionInfo info;
240
241 if (m_currentFrame > BOUNCE_COL_CHECK_DELAY) {
242 bool hasCol = CollisionDirector::Instance()->checkSphereFull(RADIUS, m_visualPos,
243 EGG::Vector3f::inf, KCL_TYPE_64EBDFFF, &info, nullptr, 0);
244
245 if (hasCol) {
246 m_visualPos += info.tangentOff;
247 m_nextStateId = 3;
248 }
249 }
250
251 setPos(m_visualPos);
252}
253
254} // namespace Kinoko::Field
static EGG::Vector3f RotateXZByYaw(f32 angle, const EGG::Vector3f &v)
Rotates a vector around the Y-axis and returns the XZ-plane portion of the vector.
u16 m_fallDuration
How long the firesnake falls from the sun.
void initProjectile(const EGG::Vector3f &pos) override
Callback function called by the managing ObjectSniper.
void onLaunch() override
Callback function use by the ObjectSniper that wants to throw this projectile.
Pertains to collision.
A 3D float vector.
Definition Vector.hh:107
constexpr f32 length() const
The square root of the vector's dot product.
Definition Vector.hh:211