Skip to content

Commit

Permalink
Sync ObjectAurora (RR wavy road)
Browse files Browse the repository at this point in the history
  • Loading branch information
malleoz committed Mar 4, 2025
1 parent 1cce1cd commit 7b1ca89
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 2 deletions.
4 changes: 2 additions & 2 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ Test Case | Frames | |
[`mh-rta-1-42-872`](https://youtu.be/CellUlOYgnc) | 6578 / 6578 | ✔️ |
[`bc-rta-2-08-697`](https://youtu.be/1DEReKemoeI) | 808 / 8126 | ❌ | KMP
[`bc-ng-rta-2-20-001`](https://youtu.be/028nClzy7B4) | 808 / 8803 | ❌ | KMP
[`rr-rta-1-24-751`](https://youtu.be/dgNMHyFda14) | 3194 / 5491 | ❌ | KMP
[`rr-ng-rta-2-24-281`](https://youtu.be/O-BtWWsq82o) | 1207 / 9060 | ❌ | KMP
[`rr-rta-1-24-751`](https://youtu.be/dgNMHyFda14) | 5491 / 5491 | ✔️ |
[`rr-ng-rta-2-24-281`](https://youtu.be/O-BtWWsq82o) | 2217 / 9060 | ❌ | Cannon drop?
[`rpb-rta-0-59-978`](https://youtu.be/Z-lVl-7B-So) | 1078 / 4007 | ❌ | Moving water
[`rpb-ng-rta-1-12-656`](https://youtu.be/LujU0kJx-hU) | 2491 / 4767 | ❌ | Moving water
[`ryf-rta-0-58-648`](https://youtu.be/3IKzbmawUbk) | 583 / 3927 | ❌ | Water
Expand Down
18 changes: 18 additions & 0 deletions source/game/field/CollisionDirector.hh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ public:
void resetCollisionEntries(KCLTypeMask *ptr);
void pushCollisionEntry(f32 dist, KCLTypeMask *typeMask, KCLTypeMask kclTypeBit, u16 attribute);

/// @addr{0x807BDB5C}
void setCurrentCollisionVariant(u16 attribute) {
ASSERT(m_collisionEntryCount > 0);
u16 &entryAttr = m_entries[m_collisionEntryCount - 1].attribute;
entryAttr = (entryAttr & 0xff1f) | (attribute << 5);
}

/// @addr{0x807BDBC4}
void setCurrentCollisionTrickable(bool trickable) {
ASSERT(m_collisionEntryCount > 0);
u16 &entryAttr = m_entries[m_collisionEntryCount - 1].attribute;
entryAttr &= 0xdfff;

if (trickable) {
entryAttr |= (1 << 0xd);
}
}

bool findClosestCollisionEntry(KCLTypeMask *typeMask, KCLTypeMask type);

/// @beginGetters
Expand Down
2 changes: 2 additions & 0 deletions source/game/field/ObjectDirector.cc
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ ObjectBase *ObjectDirector::createObject(const System::MapdataGeoObj &params) {
return new ObjectParasolR(params);
case ObjectId::PuchiPakkun:
return new ObjectPuchiPakkun(params);
case ObjectId::Aurora:
return new ObjectAurora(params);
// Non-specified objects are stock collidable objects by default
// However, we need to specify an impl, so we don't use default
case ObjectId::DummyPole:
Expand Down
314 changes: 314 additions & 0 deletions source/game/field/obj/ObjectAurora.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
#include "ObjectAurora.hh"

#include "game/field/CollisionDirector.hh"

#include "game/system/RaceManager.hh"

namespace Field {

/// @addr{0x807FAB58}
ObjectAurora::ObjectAurora(const System::MapdataGeoObj &params) : ObjectDrivable(params) {}

/// @addr{0x807FB690}
ObjectAurora::~ObjectAurora() = default;

/// @addr{0x807FABC4}
void ObjectAurora::init() {}

/// @addr{0x807FB688}
u32 ObjectAurora::loadFlags() const {
return 1;
}

/// @addr{0x807FB684}
void ObjectAurora::createCollision() {}

/// @addr{0x807FB680}
void ObjectAurora::calcCollisionTransform() {}

/// @addr{0x807FB5DC}
f32 ObjectAurora::getCollisionRadius() const {
return COLLISION_SIZE.z + 100.0f;
}

/// @addr{0x807FB59C}
bool ObjectAurora::checkPointPartial(const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfoPartial *pInfo, KCLTypeMask *pFlagsOut) {
return checkSpherePartialImpl(0.0f, v0, v1, flags, pInfo, pFlagsOut, 0);
}

/// @addr{0x807FB5AC}
bool ObjectAurora::checkPointPartialPush(const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfoPartial *pInfo, KCLTypeMask *pFlagsOut) {
return checkSpherePartialPushImpl(0.0f, v0, v1, flags, pInfo, pFlagsOut, 0);
}

/// @addr{0x807FB5BC}
bool ObjectAurora::checkPointFull(const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut) {
return checkSphereFullImpl(0.0f, v0, v1, flags, pInfo, pFlagsOut, 0);
}

/// @addr{0x807FB5CC}
bool ObjectAurora::checkPointFullPush(const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut) {
return checkSphereFullPushImpl(0.0f, v0, v1, flags, pInfo, pFlagsOut, 0);
}

/// @addr{0x807FB58C}
bool ObjectAurora::checkSpherePartial(f32 radius, const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfoPartial *pInfo, KCLTypeMask *pFlagsOut, u32 timeOffset) {
return checkSpherePartialImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset);
}

/// @addr{0x807FB590}
bool ObjectAurora::checkSpherePartialPush(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f &v1, KCLTypeMask flags, CollisionInfoPartial *pInfo,
KCLTypeMask *pFlagsOut, u32 timeOffset) {
return checkSpherePartialPushImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset);
}

/// @addr{0x807FB594}
bool ObjectAurora::checkSphereFull(f32 radius, const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut, u32 timeOffset) {
return checkSphereFullImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset);
}

/// @addr{0x807FB598}
bool ObjectAurora::checkSphereFullPush(f32 radius, const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut, u32 timeOffset) {
return checkSphereFullPushImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset);
}

/// @addr{0x807FB54C}
bool ObjectAurora::checkPointCachedPartial(const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfoPartial *pInfo, KCLTypeMask *pFlagsOut) {
return checkSpherePartialImpl(0.0f, v0, v1, flags, pInfo, pFlagsOut, 0);
}

/// @addr{0x807FB55C}
bool ObjectAurora::checkPointCachedPartialPush(const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfoPartial *pInfo, KCLTypeMask *pFlagsOut) {
return checkSpherePartialPushImpl(0.0f, v0, v1, flags, pInfo, pFlagsOut, 0);
}

/// @addr{0x807FB56C}
bool ObjectAurora::checkPointCachedFull(const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut) {
return checkSphereFullImpl(0.0f, v0, v1, flags, pInfo, pFlagsOut, 0);
}

/// @addr{0x807FB57C}
bool ObjectAurora::checkPointCachedFullPush(const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut) {
return checkSphereFullPushImpl(0.0f, v0, v1, flags, pInfo, pFlagsOut, 0);
}

/// @addr{0x807FB53C}
bool ObjectAurora::checkSphereCachedPartial(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f &v1, KCLTypeMask flags, CollisionInfoPartial *pInfo,
KCLTypeMask *pFlagsOut, u32 timeOffset) {
return checkSpherePartialImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset);
}

/// @addr{0x807FB540}
bool ObjectAurora::checkSphereCachedPartialPush(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f &v1, KCLTypeMask flags, CollisionInfoPartial *pInfo,
KCLTypeMask *pFlagsOut, u32 timeOffset) {
return checkSpherePartialPushImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset);
}

/// @addr{0x807FB544}
bool ObjectAurora::checkSphereCachedFull(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f &v1, KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut,
u32 timeOffset) {
return checkSphereFullImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset);
}

/// @addr{0x807FB548}
bool ObjectAurora::checkSphereCachedFullPush(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f &v1, KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut,
u32 timeOffset) {
return checkSphereFullPushImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset);
}

/// @addr{0x807FB6D0}
bool ObjectAurora::checkSpherePartialImpl(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f &v1, KCLTypeMask flags, CollisionInfoPartial *pInfo,
KCLTypeMask *pFlagsOut, u32 timeOffset) {
return checkSpherePartialImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset, false);
}

/// @addr{0x807FB8B0}
bool ObjectAurora::checkSpherePartialPushImpl(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f &v1, KCLTypeMask flags, CollisionInfoPartial *pInfo,
KCLTypeMask *pFlagsOut, u32 timeOffset) {
return checkSpherePartialImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset, true);
}

/// @addr{0x807FBAC0}
bool ObjectAurora::checkSphereFullImpl(f32 radius, const EGG::Vector3f &v0, const EGG::Vector3f &v1,
KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut, u32 timeOffset) {
return checkSphereFullImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset, false);
}

/// @addr{0x807FBE6C}
bool ObjectAurora::checkSphereFullPushImpl(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f &v1, KCLTypeMask flags, CollisionInfo *pInfo, KCLTypeMask *pFlagsOut,
u32 timeOffset) {
return checkSphereFullImpl(radius, v0, v1, flags, pInfo, pFlagsOut, timeOffset, true);
}

/// @brief Helper function which contains frequently re-used code. Behavior branches depending on
/// whether it is a push (push entry in the CollisionDirector).
/// @param push Whether to push a collision entry
bool ObjectAurora::checkSpherePartialImpl(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f & /*v1*/, KCLTypeMask flags, CollisionInfoPartial *pInfo,
KCLTypeMask *pFlagsOut, u32 timeOffset, bool push) {
EGG::Vector3f vel = v0 - m_pos;

if (vel.z < 0.0f || vel.z > COLLISION_SIZE.z || EGG::Mathf::abs(vel.x) > COLLISION_SIZE.x) {
return false;
}

if (!(flags & KCL_TYPE_FLOOR)) {
return false;
}

u32 t = timeOffset + System::RaceManager::Instance()->timer();
EGG::Vector3f bbox;
EGG::Vector3f fnrm;
f32 dist;

if (!calcCollision(radius, vel, t, bbox, fnrm, dist)) {
return false;
}

if (pInfo) {
pInfo->bbox.min = pInfo->bbox.min.minimize(bbox);
pInfo->bbox.max = pInfo->bbox.max.maximize(bbox);
}

if (pFlagsOut) {
auto *colDirector = CollisionDirector::Instance();

if (push) {
colDirector->pushCollisionEntry(dist, pFlagsOut, KCL_TYPE_BIT(COL_TYPE_ROAD2),
COL_TYPE_ROAD2);
colDirector->setCurrentCollisionVariant(7);
colDirector->setCurrentCollisionTrickable(true);
} else {
*pFlagsOut |= KCL_TYPE_BIT(COL_TYPE_ROTATING_ROAD);
}

if (vel.z > COLLISION_SIZE.z - 600.0f * 4.0f) {
if (push) {
colDirector->pushCollisionEntry(dist, pFlagsOut, KCL_TYPE_BIT(COL_TYPE_BOOST_RAMP),
COL_TYPE_BOOST_RAMP);
} else {
*pFlagsOut |= KCL_TYPE_BIT(COL_TYPE_ROAD2) | KCL_TYPE_BIT(COL_TYPE_BOOST_RAMP);
}
}
}

return true;
}

/// @brief Helper function which contains frequently re-used code. Behavior branches depending on
/// whether it is a push (push entry in the CollisionDirector).
/// @param push Whether to push a collision entry
bool ObjectAurora::checkSphereFullImpl(f32 radius, const EGG::Vector3f &v0,
const EGG::Vector3f & /*v1*/, KCLTypeMask flags, CollisionInfo *pInfo,
KCLTypeMask *pFlagsOut, u32 timeOffset, bool push) {
EGG::Vector3f vel = v0 - m_pos;

if (vel.z < 0.0f || vel.z > COLLISION_SIZE.z || EGG::Mathf::abs(vel.x) > COLLISION_SIZE.x) {
return false;
}

if (!(flags & KCL_TYPE_FLOOR)) {
return false;
}

u32 t = timeOffset + System::RaceManager::Instance()->timer();
EGG::Vector3f bbox;
EGG::Vector3f fnrm;
f32 dist;

if (!calcCollision(radius, vel, t, bbox, fnrm, dist)) {
return false;
}

if (pInfo) {
pInfo->bbox.min = pInfo->bbox.min.minimize(bbox);
pInfo->bbox.max = pInfo->bbox.max.maximize(bbox);

pInfo->updateFloor(dist, fnrm);
}

if (pFlagsOut) {
auto *colDirector = CollisionDirector::Instance();

if (push) {
colDirector->pushCollisionEntry(dist, pFlagsOut, KCL_TYPE_BIT(COL_TYPE_ROAD2),
COL_TYPE_ROAD2);
colDirector->setCurrentCollisionVariant(7);
colDirector->setCurrentCollisionTrickable(true);
} else {
*pFlagsOut |= KCL_TYPE_BIT(COL_TYPE_ROTATING_ROAD);
}

if (vel.z > COLLISION_SIZE.z - 600.0f * 4.0f) {
if (push) {
colDirector->pushCollisionEntry(dist, pFlagsOut, KCL_TYPE_BIT(COL_TYPE_BOOST_RAMP),
COL_TYPE_BOOST_RAMP);
} else {
*pFlagsOut |= KCL_TYPE_BIT(COL_TYPE_ROAD2) | KCL_TYPE_BIT(COL_TYPE_BOOST_RAMP);
}
}
}

return true;
}

/// @addr{0x807FB060}
/// @brief Calculates the sin-like collision of the wavy road.
/// @details It seems to be modeled as a quadratic chirp with a starting frequency of 1 and a max
/// frequency of 4. The minimum frequency of 1 occurs 30s into the race, and the maximum frequency
/// occurs 2min 13s into the race. The "observed" frequency is dependent on the player's velocity
/// towards the wavy road as well.
bool ObjectAurora::calcCollision(f32 radius, const EGG::Vector3f &vel, u32 time, EGG::Vector3f &v0,
EGG::Vector3f &fnrm, f32 &dist) {
constexpr float INITIAL_FREQUENCY = 1.0f;
constexpr float MAX_FREQUENCY = 4.0f;
constexpr float PHASE_SHIFT_SECONDS = 30.0f;
constexpr float COLLISION_DISTANCE_THRESHOLD = 600.0f;
constexpr float UPWARP_THRESHOLD = 300.0f;
constexpr float UPWARP_DIST_SCALAR = 0.2f;

f32 minsNormalized = (static_cast<f32>(time) / 60.0f - PHASE_SHIFT_SECONDS) / 60.0f;
f32 frequency = std::min(INITIAL_FREQUENCY + minsNormalized * minsNormalized, MAX_FREQUENCY);
f32 velPeriod = (F_PI * (2.0f * vel.z)) / COLLISION_SIZE.z;

f32 result = EGG::Mathf::SinFIdx(
RAD2FIDX * (velPeriod * frequency + (F_PI * static_cast<f32>(time)) / 50.0f));
result = radius - (vel.y - velPeriod * 80.0f * result);

// We're not colliding if we're 600 units away or if the road is now behind us.
if (result <= 0.0f || result >= COLLISION_DISTANCE_THRESHOLD) {
return false;
}

// Responsible for the sudden upwards snap that can occur when falling off.
if (result > UPWARP_THRESHOLD) {
result *= UPWARP_DIST_SCALAR;
}

fnrm = EGG::Vector3f::ey;
v0 = fnrm * result;
dist = result;

return true;
}

} // namespace Field
Loading

0 comments on commit 7b1ca89

Please sign in to comment.