diff --git a/config/G2ME01/splits.txt b/config/G2ME01/splits.txt index 2d339bc97..60e6fecbe 100644 --- a/config/G2ME01/splits.txt +++ b/config/G2ME01/splits.txt @@ -332,6 +332,10 @@ MetroidPrime/Player/CPlayerOrbit.cpp: .text start:0x8011C3A8 end:0x80123510 .ctors start:0x803A5524 end:0x803A5528 +MetroidPrime/CGameCollision.cpp: + .text start:0x80123510 end:0x801285DC + .ctors start:0x803A5528 end:0x803A552C + MetroidPrime/CGroundMovement.cpp: .text start:0x8012878C end:0x8012CA24 .sdata start:0x80418328 end:0x80418338 diff --git a/config/G2ME01/symbols.txt b/config/G2ME01/symbols.txt index 0f957487a..69a05d966 100644 --- a/config/G2ME01/symbols.txt +++ b/config/G2ME01/symbols.txt @@ -4953,57 +4953,57 @@ ValidateCurrentOrbitTargetId__7CPlayerFR13CStateManager = .text:0x80122E7C; // t ValidateOrbitTargetIdAndPointer__7CPlayerCF9TUniqueIdRC13CStateManager = .text:0x801232E0; // type:function size:0x54 fn_80123334 = .text:0x80123334; // type:function size:0x68 fn_8012339C = .text:0x8012339C; // type:function size:0x174 align:4 -fn_80123510 = .text:0x80123510; // type:function size:0x664 align:4 -fn_80123B74 = .text:0x80123B74; // type:function size:0xD8 -fn_80123C4C = .text:0x80123C4C; // type:function size:0x5A8 -fn_801241F4 = .text:0x801241F4; // type:function size:0x5E0 align:4 +PushActorAwayFromWalls__14CGameCollisionFR13CStateManagerR13CPhysicsActorffffif = .text:0x80123510; // type:function size:0x664 align:4 +GetActorRelativeVelocities__14CGameCollisionFPC13CPhysicsActorPC13CPhysicsActor = .text:0x80123B74; // type:function size:0xD8 +FindNonIntersectingVector__14CGameCollisionFRC13CStateManagerR13CPhysicsActorRC19CCollisionPrimitiveRCQ24rstl27optional_object<9CVector3f> = .text:0x80123C4C; // type:function size:0x5A8 +CollisionFailsafe__14CGameCollisionFRC13CStateManagerR19CAreaCollisionCacheR13CPhysicsActorRC19CCollisionPrimitiveRCQ24rstl32reserved_vector<9TUniqueId,1024>fUif = .text:0x801241F4; // type:function size:0x5E0 align:4 fn_801247D4 = .text:0x801247D4; // type:function size:0x24 -fn_801247F8 = .text:0x801247F8; // type:function size:0x138 +MovePlayer__14CGameCollisionFR13CStateManagerR13CPhysicsActorfPCQ24rstl32reserved_vector<9TUniqueId,1024> = .text:0x801247F8; // type:function size:0x138 Move__14CGameCollisionFR13CStateManagerR13CPhysicsActorfPCQ24rstl32reserved_vector<9TUniqueId,1024> = .text:0x80124930; // type:function size:0x1F4 -fn_80124B24 = .text:0x80124B24; // type:function size:0x1EC -fn_80124D10 = .text:0x80124D10; // type:function size:0x4A0 -fn_801251B0 = .text:0x801251B0; // type:function size:0xD8 +CollideWithStaticBodyNoRot__14CGameCollisionFR13CPhysicsActorRC13CMaterialListRC13CMaterialListRC13CUnitVector3ffb = .text:0x80124B24; // type:function size:0x1EC +CollideWithDynamicBodyNoRot__14CGameCollisionFR13CPhysicsActorR13CPhysicsActorRC14CCollisionInfofb = .text:0x80124D10; // type:function size:0x4A0 +ResolveCollisions__14CGameCollisionFR13CPhysicsActorP13CPhysicsActorRC18CCollisionInfoList = .text:0x801251B0; // type:function size:0xD8 fn_80125288 = .text:0x80125288; // type:function size:0xC4 -fn_8012534C = .text:0x8012534C; // type:function size:0x120 +GetMinExtentForCollisionPrimitive__14CGameCollisionFRC19CCollisionPrimitive = .text:0x8012534C; // type:function size:0x120 CanBlock__14CGameCollisionFRC13CMaterialListRC9CVector3f = .text:0x8012546C; // type:function size:0xC0 IsFloor__14CGameCollisionFRC13CMaterialListRC9CVector3f = .text:0x8012552C; // type:function size:0x48 -fn_80125574 = .text:0x80125574; // type:function size:0x18 -fn_8012558C = .text:0x8012558C; // type:function size:0x24 -fn_801255B0 = .text:0x801255B0; // type:function size:0x8 -fn_801255B8 = .text:0x801255B8; // type:function size:0x4 -fn_801255BC = .text:0x801255BC; // type:function size:0x60 -fn_8012561C = .text:0x8012561C; // type:function size:0x2D0 +CollisionImpulseFiniteVsInfinite__Ffff = .text:0x80125574; // type:function size:0x18 +CollisionImpulseFiniteVsFinite__Fffff = .text:0x8012558C; // type:function size:0x24 +GetCoefficientOfRestitution__14CGameCollisionFRC14CCollisionInfo = .text:0x801255B0; // type:function size:0x8 +ShowCollisionResults__14CGameCollisionFR18CCollisionInfoListRC6CColor = .text:0x801255B8; // type:function size:0x4 +SendMaterialMessage__14CGameCollisionFR13CStateManagerRC13CMaterialListR6CActor = .text:0x801255BC; // type:function size:0x60 +SendScriptMessages__14CGameCollisionFR13CStateManagerR6CActorP6CActorRC18CCollisionInfoList = .text:0x8012561C; // type:function size:0x2D0 MakeCollisionCallbacks__14CGameCollisionFR13CStateManagerR13CPhysicsActorRC9TUniqueIdRC18CCollisionInfoList = .text:0x801258EC; // type:function size:0x104 -fn_801259F0 = .text:0x801259F0; // type:function size:0x5E8 -fn_80125FD8 = .text:0x80125FD8; // type:function size:0x2B4 -fn_8012628C = .text:0x8012628C; // type:function size:0x32C -fn_801265B8 = .text:0x801265B8; // type:function size:0x2AC align:4 -fn_80126864 = .text:0x80126864; // type:function size:0x318 -fn_80126B7C = .text:0x80126B7C; // type:function size:0x88 -fn_80126C04 = .text:0x80126C04; // type:function size:0xB8 -fn_80126CBC = .text:0x80126CBC; // type:function size:0xD0 -fn_80126D8C = .text:0x80126D8C; // type:function size:0x98 -fn_80126E24 = .text:0x80126E24; // type:function size:0xC0 -fn_80126EE4 = .text:0x80126EE4; // type:function size:0x1A4 -fn_80127088 = .text:0x80127088; // type:function size:0xC8 -fn_80127150 = .text:0x80127150; // type:function size:0xC8 +DetectStaticCollision_Cached_Moving__14CGameCollisionFRC13CStateManagerR19CAreaCollisionCacheRC19CCollisionPrimitiveRC12CTransform4fRC15CMaterialFilterRC9CVector3fR14CCollisionInfoRd = .text:0x801259F0; // type:function size:0x5E8 +DetectStaticCollisionBoolean__14CGameCollisionFRC13CStateManagerRC19CCollisionPrimitiveRC12CTransform4fRC15CMaterialFilter = .text:0x80125FD8; // type:function size:0x2B4 +DetectStaticCollisionBoolean_Cached__14CGameCollisionFRC13CStateManagerR19CAreaCollisionCacheRC19CCollisionPrimitiveRC12CTransform4fRC15CMaterialFilter = .text:0x8012628C; // type:function size:0x32C +DetectStaticCollision__14CGameCollisionFRC13CStateManagerRC19CCollisionPrimitiveRC12CTransform4fRC15CMaterialFilterR18CCollisionInfoList = .text:0x801265B8; // type:function size:0x2AC align:4 +DetectStaticCollision_Cached__14CGameCollisionFRC13CStateManagerR19CAreaCollisionCacheRC19CCollisionPrimitiveRC12CTransform4fRC15CMaterialFilterR18CCollisionInfoList = .text:0x80126864; // type:function size:0x318 +CollideCachedAABox__FRC19CAreaCollisionCacheRC6CAABoxRC15CMaterialFilterR18CCollisionInfoListRC19CCollisionPrimitive = .text:0x80126B7C; // type:function size:0x88 +DetectDynamicCollision__14CGameCollisionFRC19CCollisionPrimitiveRC12CTransform4fRCQ24rstl32reserved_vector<9TUniqueId,1024>R9TUniqueIdR18CCollisionInfoListRC13CStateManager = .text:0x80126C04; // type:function size:0xB8 +DetectDynamicCollision__14CGameCollisionFRC19CCollisionPrimitiveRC12CTransform4fRC13CPhysicsActorR18CCollisionInfoList = .text:0x80126CBC; // type:function size:0xD0 +DetectDynamicCollisionBoolean__14CGameCollisionFRC19CCollisionPrimitiveRC12CTransform4fRCQ24rstl32reserved_vector<9TUniqueId,1024>RC13CStateManager = .text:0x80126D8C; // type:function size:0x98 +DetectDynamicCollisionBoolean__14CGameCollisionFRC19CCollisionPrimitiveRC12CTransform4fRC13CPhysicsActor = .text:0x80126E24; // type:function size:0xC0 +DetectDynamicCollisionMoving__14CGameCollisionFRC19CCollisionPrimitiveRC12CTransform4fRCQ24rstl32reserved_vector<9TUniqueId,1024>RC9CVector3fR9TUniqueIdR14CCollisionInfoRdRC13CStateManager = .text:0x80126EE4; // type:function size:0x1A4 +DetectDynamicCollisionMoving__14CGameCollisionFRC19CCollisionPrimitiveRC12CTransform4fRC13CPhysicsActorRC9CVector3fR14CCollisionInfoRd = .text:0x80127088; // type:function size:0xC8 +DetectCollision_Cached_Moving__14CGameCollisionFRC13CStateManagerR19CAreaCollisionCacheRC19CCollisionPrimitiveRC12CTransform4fRC15CMaterialFilterRCQ24rstl32reserved_vector<9TUniqueId,1024>RC9CVector3fR9TUniqueIdR14CCollisionInfoRd = .text:0x80127150; // type:function size:0xC8 DetectCollision_Cached__14CGameCollisionFRC13CStateManagerR19CAreaCollisionCacheRC19CCollisionPrimitiveRC12CTransform4fRC15CMaterialFilterRCQ24rstl32reserved_vector<9TUniqueId,1024>R9TUniqueIdR18CCollisionInfoList = .text:0x80127218; // type:function size:0xC0 align:4 DetectCollisionBoolean_Cached__14CGameCollisionFRC13CStateManagerR19CAreaCollisionCacheRC19CCollisionPrimitiveRC12CTransform4fRC15CMaterialFilterRCQ24rstl32reserved_vector<9TUniqueId,1024> = .text:0x801272D8; // type:function size:0xB0 -fn_80127388 = .text:0x80127388; // type:function size:0xB0 -fn_80127438 = .text:0x80127438; // type:function size:0x104 +DetectCollisionBoolean__14CGameCollisionFRC13CStateManagerRC19CCollisionPrimitiveRC12CTransform4fRC15CMaterialFilterRCQ24rstl32reserved_vector<9TUniqueId,1024> = .text:0x80127388; // type:function size:0xB0 +BuildAreaCollisionCache__14CGameCollisionFRC13CStateManagerR19CAreaCollisionCache = .text:0x80127438; // type:function size:0x104 fn_8012753C = .text:0x8012753C; // type:function size:0x48 -fn_80127584 = .text:0x80127584; // type:function size:0x288 -fn_8012780C = .text:0x8012780C; // type:function size:0x118 align:4 -fn_80127924 = .text:0x80127924; // type:function size:0x134 -fn_80127A58 = .text:0x80127A58; // type:function size:0x15C -fn_80127BB4 = .text:0x80127BB4; // type:function size:0x1C4 -fn_80127D78 = .text:0x80127D78; // type:function size:0x104 -fn_80127E7C = .text:0x80127E7C; // type:function size:0x8 align:4 -fn_80127E84 = .text:0x80127E84; // type:function size:0x8 align:4 -fn_80127E8C = .text:0x80127E8C; // type:function size:0x8 align:4 -fn_80127E94 = .text:0x80127E94; // type:function size:0x16C +RayStaticIntersection__14CGameCollisionFRC13CStateManagerRC9CVector3fRC9CVector3ffRC15CMaterialFilter = .text:0x80127584; // type:function size:0x288 +RayStaticLineOfSightTest__14CGameCollisionFRC9CGameAreaRC9CVector3fRC9CVector3ffRC15CMaterialFilter = .text:0x8012780C; // type:function size:0x118 align:4 +RayStaticLineOfSightTest__14CGameCollisionFRC13CStateManagerRC9CVector3fRC9CVector3ffRC15CMaterialFilter = .text:0x80127924; // type:function size:0x134 +RayDynamicLineOfSightTest__14CGameCollisionFRC13CStateManagerRC9CVector3fRC9CVector3ffRC15CMaterialFilterRCQ24rstl32reserved_vector<9TUniqueId,1024>PC6CActor = .text:0x80127A58; // type:function size:0x15C +RayDynamicIntersection__14CGameCollisionFRC13CStateManagerR9TUniqueIdRC9CVector3fRC9CVector3ffRC15CMaterialFilterRCQ24rstl32reserved_vector<9TUniqueId,1024> = .text:0x80127BB4; // type:function size:0x1C4 +RayWorldIntersection__14CGameCollisionFRC13CStateManagerR9TUniqueIdRC9CVector3fRC9CVector3ffRC15CMaterialFilterRCQ24rstl32reserved_vector<9TUniqueId,1024> = .text:0x80127D78; // type:function size:0x104 +NullBooleanCollider__14CGameCollisionFRC27CInternalCollisionStructure = .text:0x80127E7C; // type:function size:0x8 align:4 +NullMovingCollider__14CGameCollisionFRC27CInternalCollisionStructureRC9CVector3fRdR14CCollisionInfo = .text:0x80127E84; // type:function size:0x8 align:4 +NullCollisionCollider__14CGameCollisionFRC27CInternalCollisionStructureR18CCollisionInfoList = .text:0x80127E8C; // type:function size:0x8 align:4 +UninitializeCollision__14CGameCollisionFv = .text:0x80127E94; // type:function size:0x16C fn_80128000 = .text:0x80128000; // type:function size:0xC8 -fn_801280C8 = .text:0x801280C8; // type:function size:0x418 +InitCollision__14CGameCollisionFP13CStateManager = .text:0x801280C8; // type:function size:0x418 fn_801284E0 = .text:0x801284E0; // type:function size:0xFC align:4 fn_801285DC = .text:0x801285DC; // type:function size:0x28 align:4 fn_80128604 = .text:0x80128604; // type:function size:0x28 align:4 diff --git a/configure.py b/configure.py index 3571c4cda..ee56c8658 100644 --- a/configure.py +++ b/configure.py @@ -483,6 +483,7 @@ def MatchingFor(*versions): Object(NonMatching, "MetroidPrime/CModelData.cpp"), Object(NonMatching, "MetroidPrime/CActorLights.cpp"), Object(NonMatching, "MetroidPrime/CGroundMovement.cpp"), + Object(NonMatching, "MetroidPrime/CGameCollision.cpp"), Object(NonMatching, "MetroidPrime/Enemies/CAi.cpp"), Object(NonMatching, "MetroidPrime/Enemies/CKnockBackMgr.cpp"), Object(NonMatching, "MetroidPrime/Enemies/CPatterned.cpp"), diff --git a/include/Collision/CCollidableAABox.hpp b/include/Collision/CCollidableAABox.hpp index f05b9d054..0e784ad78 100644 --- a/include/Collision/CCollidableAABox.hpp +++ b/include/Collision/CCollidableAABox.hpp @@ -20,6 +20,7 @@ class CCollidableAABox : public CCollisionPrimitive { CAABox CalculateAABox(const CTransform4f&) const; CAABox CalculateLocalAABox() const; CAABox Transform(const CTransform4f& xf) const; + const CAABox& GetBox() const { return mAabb; } FourCC GetPrimType() const; CRayCastResult CastRayInternal(const CInternalRayCastStructure&) const; diff --git a/include/Collision/CCollidableAABoxSphere.hpp b/include/Collision/CCollidableAABoxSphere.hpp new file mode 100644 index 000000000..79aa54d78 --- /dev/null +++ b/include/Collision/CCollidableAABoxSphere.hpp @@ -0,0 +1,19 @@ +#ifndef _CCOLLIDABLEAABOXSPHERE +#define _CCOLLIDABLEAABOXSPHERE + +#include "Collision/CCollidableAABox.hpp" +#include "Collision/CCollidableSphere.hpp" + +// Guessed name, correlated with Prime's ABSH compound primitive. +class CCollidableAABoxSphere : public CCollisionPrimitive { +public: + const CCollidableAABox& GetCollidableAABox() const { return mAabb; } + const CCollidableSphere& GetCollidableSphere() const { return mSphere; } + +private: + CCollidableAABox mAabb; + CCollidableSphere mSphere; +}; +CHECK_SIZEOF(CCollidableAABoxSphere, 0x58) + +#endif // _CCOLLIDABLEAABOXSPHERE diff --git a/include/Collision/CCollisionPrimitive.hpp b/include/Collision/CCollisionPrimitive.hpp index 1c5dcc2d4..e1d7907bf 100644 --- a/include/Collision/CCollisionPrimitive.hpp +++ b/include/Collision/CCollisionPrimitive.hpp @@ -4,6 +4,7 @@ #include "types.h" #include "Collision/CMaterialList.hpp" +#include "Collision/CInternalCollisionStructure.hpp" #include "Kyoto/IObjectStore.hpp" #include "Kyoto/Math/CAABox.hpp" @@ -55,6 +56,15 @@ class CCollisionPrimitive { static void InitEndTypes(); static void Uninitialize(); + static bool Collide(const CInternalCollisionStructure::CPrimDesc& left, + const CInternalCollisionStructure::CPrimDesc& right, + CCollisionInfoList& collisions); + static bool CollideBoolean(const CInternalCollisionStructure::CPrimDesc& left, + const CInternalCollisionStructure::CPrimDesc& right); + static bool CollideMoving(const CInternalCollisionStructure::CPrimDesc& left, + const CInternalCollisionStructure::CPrimDesc& right, + const CVector3f& direction, double& distance, CCollisionInfo& collision); + private: static int sNumTypes; static bool sInitComplete; diff --git a/include/Collision/CInternalCollisionStructure.hpp b/include/Collision/CInternalCollisionStructure.hpp new file mode 100644 index 000000000..7742b031a --- /dev/null +++ b/include/Collision/CInternalCollisionStructure.hpp @@ -0,0 +1,40 @@ +#ifndef _CINTERNALCOLLISIONSTRUCTURE +#define _CINTERNALCOLLISIONSTRUCTURE + +#include "Kyoto/Math/CTransform4f.hpp" + +class CCollisionPrimitive; +class CMaterialFilter; + +class CInternalCollisionStructure { +public: + class CPrimDesc { + public: + CPrimDesc(const CCollisionPrimitive& primitive, const CMaterialFilter& filter, + const CTransform4f& transform) + : mPrimitive(primitive), mFilter(filter), mTransform(transform) {} + + const CCollisionPrimitive& GetPrim() const { return mPrimitive; } + const CMaterialFilter& GetFilter() const { return mFilter; } + const CTransform4f& GetTransform() const { return mTransform; } + + private: + const CCollisionPrimitive& mPrimitive; + const CMaterialFilter& mFilter; + CTransform4f mTransform; + }; + + CInternalCollisionStructure(const CPrimDesc& left, const CPrimDesc& right) + : mLeft(left), mRight(right) {} + + const CPrimDesc& GetLeft() const { return mLeft; } + const CPrimDesc& GetRight() const { return mRight; } + +private: + CPrimDesc mLeft; + CPrimDesc mRight; +}; +NESTED_CHECK_SIZEOF(CInternalCollisionStructure, CPrimDesc, 0x38) +CHECK_SIZEOF(CInternalCollisionStructure, 0x70) + +#endif // _CINTERNALCOLLISIONSTRUCTURE diff --git a/include/Collision/CInternalRayCastStructure.hpp b/include/Collision/CInternalRayCastStructure.hpp index 37c797170..728e58d51 100644 --- a/include/Collision/CInternalRayCastStructure.hpp +++ b/include/Collision/CInternalRayCastStructure.hpp @@ -8,6 +8,10 @@ class CMaterialFilter; class CInternalRayCastStructure { public: + CInternalRayCastStructure(const CVector3f& start, const CVector3f& direction, float length, + const CTransform4f& transform, const CMaterialFilter& filter) + : mRay(start, direction, length), mMaxTime(length), mTransform(transform), mFilter(filter) {} + const CMRay& GetRay() const { return mRay; } float GetMaxTime() const { return mMaxTime; } const CTransform4f& GetTransform() const { return mTransform; } diff --git a/include/Collision/CMaterialFilter.hpp b/include/Collision/CMaterialFilter.hpp index f960eb8ea..01a95c110 100644 --- a/include/Collision/CMaterialFilter.hpp +++ b/include/Collision/CMaterialFilter.hpp @@ -14,6 +14,7 @@ class CMaterialFilter { kFT_Include, kFT_Exclude, kFT_IncludeExclude, + kFT_Never, // Guessed name. }; CMaterialFilter() : include(0x00000000FFFFFFFF), exclude(0), type(kFT_Always) {} @@ -36,6 +37,32 @@ class CMaterialFilter { bool Passes(const CMaterialList& other) const; const CMaterialList& GetIncludeList() const { return include; } const CMaterialList& GetExcludeList() const { return exclude; } + EFilterType GetType() const { return type; } + + // Guessed name; simplify the filter for geometry carrying these fixed materials. + CMaterialFilter WithImplicitMaterials(const CMaterialList& materials) const { + switch (type) { + case kFT_Always: + case kFT_Never: + return *this; + case kFT_Include: + return include.SharesMaterials(materials) ? GetPassEverything() : *this; + case kFT_Exclude: + if (exclude.SharesMaterials(materials)) { + return CMaterialFilter(CMaterialList(), CMaterialList(0x00000000FFFFFFFF), kFT_Never); + } + return *this; + case kFT_IncludeExclude: + if (include.SharesMaterials(materials)) { + return CMaterialFilter(CMaterialList(0x00000000FFFFFFFF), exclude, kFT_Exclude); + } + if (exclude.SharesMaterials(materials)) { + return CMaterialFilter(CMaterialList(), CMaterialList(0x00000000FFFFFFFF), kFT_Never); + } + return *this; + } + return GetPassEverything(); + } private: CMaterialList include; diff --git a/include/MetroidPrime/CGameCollision.hpp b/include/MetroidPrime/CGameCollision.hpp index 6ae0cde5b..6602b33c2 100644 --- a/include/MetroidPrime/CGameCollision.hpp +++ b/include/MetroidPrime/CGameCollision.hpp @@ -1,27 +1,128 @@ #ifndef _CGAMECOLLISION #define _CGAMECOLLISION +#include "Kyoto/Math/CVector3f.hpp" #include "MetroidPrime/TGameTypes.hpp" +#include "rstl/optional_object.hpp" #include "rstl/reserved_vector.hpp" +class CActor; class CAreaCollisionCache; +class CColor; class CCollisionCache; // Guessed name; distinct from CAreaCollisionCache. class CCollisionInfo; class CCollisionInfoList; class CCollisionPrimitive; +class CGameArea; +class CInternalCollisionStructure; class CMaterialFilter; class CMaterialList; class CPhysicsActor; +class CRayCastResult; class CStateManager; class CTransform4f; -class CVector3f; +class CUnitVector3f; class CGameCollision { public: + static void InitCollision(CStateManager* mgr); + // Guessed name. + static void UninitializeCollision(); + static bool NullBooleanCollider(const CInternalCollisionStructure& collision); + static bool NullMovingCollider(const CInternalCollisionStructure& collision, + const CVector3f& direction, double& distance, + CCollisionInfo& info); + static bool NullCollisionCollider(const CInternalCollisionStructure& collision, + CCollisionInfoList& collisions); + + static void PushActorAwayFromWalls(CStateManager& mgr, CPhysicsActor& actor, float dt, + float height, float distance, float acceleration, + int iterations, float radius); + static CVector3f GetActorRelativeVelocities(const CPhysicsActor* actor, + const CPhysicsActor* other); + // Guessed name, correlated with Prime's search for a non-intersecting displacement. + static rstl::optional_object< CVector3f > + FindNonIntersectingVector(const CStateManager& mgr, CPhysicsActor& actor, + const CCollisionPrimitive& primitive, + const rstl::optional_object< CVector3f >& origin); + static void CollisionFailsafe(const CStateManager& mgr, CAreaCollisionCache& cache, + CPhysicsActor& actor, const CCollisionPrimitive& primitive, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, float dt, + uint failsafeTicks, float impulseScale); + static void MovePlayer(CStateManager& mgr, CPhysicsActor& actor, float dt, + const rstl::reserved_vector< TUniqueId, 1024 >* nearList); static void Move(CStateManager& mgr, CPhysicsActor& actor, float dt, const rstl::reserved_vector< TUniqueId, 1024 >* nearList); + static void CollideWithStaticBodyNoRot(CPhysicsActor& actor, const CMaterialList& material, + const CMaterialList& otherMaterial, + const CUnitVector3f& normal, float restitution, + bool flattenNormal); + static void CollideWithDynamicBodyNoRot(CPhysicsActor& actor, CPhysicsActor& other, + const CCollisionInfo& collision, float restitution, + bool flattenNormal); + static void ResolveCollisions(CPhysicsActor& actor, CPhysicsActor* other, + const CCollisionInfoList& collisions); + static float GetMinExtentForCollisionPrimitive(const CCollisionPrimitive& primitive); static bool IsFloor(const CMaterialList& material, const CVector3f& normal); static bool CanBlock(const CMaterialList& material, const CVector3f& normal); + static float GetCoefficientOfRestitution(const CCollisionInfo& collision); + static void ShowCollisionResults(CCollisionInfoList& collisions, const CColor& color); + static void SendMaterialMessage(CStateManager& mgr, const CMaterialList& material, CActor& actor); + static void SendScriptMessages(CStateManager& mgr, CActor& actor, CActor* other, + const CCollisionInfoList& collisions); + + static bool DetectStaticCollisionBoolean(const CStateManager& mgr, + const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CMaterialFilter& filter); + static bool DetectStaticCollisionBoolean_Cached(const CStateManager& mgr, + CAreaCollisionCache& cache, + const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CMaterialFilter& filter); + static bool DetectStaticCollision(const CStateManager& mgr, const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CMaterialFilter& filter, + CCollisionInfoList& collisions); + static bool DetectStaticCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, + const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CMaterialFilter& filter, + CCollisionInfoList& collisions); + static bool DetectStaticCollision_Cached_Moving( + const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CMaterialFilter& filter, const CVector3f& direction, + CCollisionInfo& collision, double& distance); + static bool DetectDynamicCollision(const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, + TUniqueId& idOut, CCollisionInfoList& collisions, + const CStateManager& mgr); + static bool + DetectDynamicCollisionBoolean(const CCollisionPrimitive& primitive, const CTransform4f& transform, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, + const CStateManager& mgr); + // Guessed overload identities for Echoes's per-actor collision adapters. + static bool DetectDynamicCollision(const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CPhysicsActor& actor, + CCollisionInfoList& collisions); + static bool DetectDynamicCollisionBoolean(const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CPhysicsActor& actor); + static bool DetectDynamicCollisionMoving(const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CPhysicsActor& actor, const CVector3f& direction, + CCollisionInfo& collision, double& distance); + static bool DetectDynamicCollisionMoving(const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, + const CVector3f& direction, TUniqueId& idOut, + CCollisionInfo& collision, double& distance, + const CStateManager& mgr); + static bool DetectCollision_Cached_Moving( + const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, const CVector3f& direction, + TUniqueId& idOut, CCollisionInfo& collision, double& distance); static bool DetectCollision_Cached(const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& primitive, const CTransform4f& transform, const CMaterialFilter& filter, @@ -34,6 +135,35 @@ class CGameCollision { const rstl::reserved_vector< TUniqueId, 1024 >& nearList); static void MakeCollisionCallbacks(CStateManager& mgr, CPhysicsActor& actor, const TUniqueId& id, const CCollisionInfoList& collisions); + static bool DetectCollisionBoolean(const CStateManager& mgr, const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList); + static void BuildAreaCollisionCache(const CStateManager& mgr, CAreaCollisionCache& cache); + + static CRayCastResult RayStaticIntersection(const CStateManager& mgr, const CVector3f& position, + const CVector3f& direction, float length, + const CMaterialFilter& filter); + static bool RayStaticLineOfSightTest(const CGameArea& area, const CVector3f& position, + const CVector3f& direction, float length, + const CMaterialFilter& filter); + static bool RayStaticLineOfSightTest(const CStateManager& mgr, const CVector3f& position, + const CVector3f& direction, float length, + const CMaterialFilter& filter); + static bool RayDynamicLineOfSightTest(const CStateManager& mgr, const CVector3f& position, + const CVector3f& direction, float length, + const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, + const CActor* ignoreActor); + static CRayCastResult + RayDynamicIntersection(const CStateManager& mgr, TUniqueId& idOut, const CVector3f& position, + const CVector3f& direction, float length, const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList); + static CRayCastResult + RayWorldIntersection(const CStateManager& mgr, TUniqueId& idOut, const CVector3f& position, + const CVector3f& direction, float length, const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList); + + // Echoes's packed cache is a separate interface from the legacy area cache above. static bool DetectCollision_Cached_Moving( const CStateManager& mgr, CCollisionCache& cache, const CCollisionPrimitive& primitive, const CTransform4f& transform, const CMaterialFilter& filter, diff --git a/src/MetroidPrime/CGameCollision.cpp b/src/MetroidPrime/CGameCollision.cpp new file mode 100644 index 000000000..d92f28c6a --- /dev/null +++ b/src/MetroidPrime/CGameCollision.cpp @@ -0,0 +1,869 @@ +#include "MetroidPrime/CGameCollision.hpp" + +#include "Collision/CCollidableAABoxSphere.hpp" +#include "Collision/CCollidableSphere.hpp" +#include "Collision/CCollisionInfoList.hpp" +#include "Collision/CCollisionPrimitive.hpp" +#include "Collision/CInternalRayCastStructure.hpp" +#include "Collision/CMaterialFilter.hpp" +#include "Collision/CRayCastResult.hpp" +#include "Kyoto/Math/CLine.hpp" +#include "Kyoto/Math/CMath.hpp" +#include "MetroidPrime/CPhysicsActor.hpp" +#include "MetroidPrime/CStateManager.hpp" +#include "MetroidPrime/CWorld.hpp" +#include "MetroidPrime/ScriptObjects/CScriptPlatform.hpp" +#include "MetroidPrime/TCastTo.hpp" +#include "WorldFormat/CAreaOctTree.hpp" +#include "WorldFormat/CMetroidAreaCollider.hpp" +#include "rstl/math.hpp" + +#include + +// The meanings of the two implicit static-geometry materials are not yet known. +static const CMaterialList skStaticGeometryMaterials(kMT_Unknown59, + static_cast< EMaterialTypes >(60)); + +static float CollisionImpulseFiniteVsInfinite(float, float, float); +static float CollisionImpulseFiniteVsFinite(float, float, float, float); +static bool CollideCachedAABox(const CAreaCollisionCache&, const CAABox&, const CMaterialFilter&, + CCollisionInfoList&, const CCollisionPrimitive&); +static void EnsureCacheBounds(const CStateManager&, CAreaCollisionCache&, const CAABox&); + +void CGameCollision::InitCollision(CStateManager*) { + // TODO: OBB-tree-group collider registration, mode-dependent duplicate buffers, and debug models. +} + +void CGameCollision::UninitializeCollision() { + // TODO: release debug-model tokens, unregister their cache views, and free duplicate buffers. + CCollisionPrimitive::Uninitialize(); +} + +bool CGameCollision::NullCollisionCollider(const CInternalCollisionStructure&, + CCollisionInfoList&) { + return false; +} + +bool CGameCollision::NullMovingCollider(const CInternalCollisionStructure&, const CVector3f&, + double&, CCollisionInfo&) { + return false; +} + +bool CGameCollision::NullBooleanCollider(const CInternalCollisionStructure&) { return false; } + +CRayCastResult +CGameCollision::RayWorldIntersection(const CStateManager& mgr, TUniqueId& idOut, + const CVector3f& position, const CVector3f& direction, + float length, const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList) { + const CRayCastResult staticResult = + RayStaticIntersection(mgr, position, direction, length, filter); + const CRayCastResult dynamicResult = + RayDynamicIntersection(mgr, idOut, position, direction, length, filter, nearList); + if (dynamicResult.IsValid() && + (!staticResult.IsValid() || dynamicResult.GetTime() <= staticResult.GetTime())) { + return dynamicResult; + } + idOut = kInvalidUniqueId; + return staticResult; +} + +CRayCastResult +CGameCollision::RayDynamicIntersection(const CStateManager& mgr, TUniqueId& idOut, + const CVector3f& position, const CVector3f& direction, + float length, const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList) { + float closest = length > 0.f ? length : 100000.f; + CRayCastResult result; + for (const TUniqueId* id = nearList.begin(); id != nearList.end(); ++id) { + if (const CPhysicsActor* actor = TCastToConstPtr< CPhysicsActor >(mgr.GetObjectById(*id))) { + const CInternalRayCastStructure ray(position, direction, closest, + actor->GetPrimitiveTransform(), filter); + const CRayCastResult candidate = actor->GetCollisionPrimitive()->CastRayInternal(ray); + if (candidate.IsValid() && candidate.GetTime() < closest) { + result = candidate; + closest = candidate.GetTime(); + idOut = actor->GetUniqueId(); + if (closest <= FLT_EPSILON) { + break; + } + } + } + } + return result; +} + +bool CGameCollision::RayDynamicLineOfSightTest( + const CStateManager& mgr, const CVector3f& position, const CVector3f& direction, float length, + const CMaterialFilter& filter, const rstl::reserved_vector< TUniqueId, 1024 >& nearList, + const CActor* ignoreActor) { + const float maxDistance = length > 0.f ? length : 100000.f; + for (const TUniqueId* id = nearList.begin(); id != nearList.end(); ++id) { + if (const CPhysicsActor* actor = TCastToConstPtr< CPhysicsActor >(mgr.GetObjectById(*id))) { + if (ignoreActor == nullptr || actor->GetUniqueId() != ignoreActor->GetUniqueId()) { + const CInternalRayCastStructure ray(position, direction, maxDistance, + actor->GetPrimitiveTransform(), filter); + if (actor->GetCollisionPrimitive()->CastRayInternal(ray).IsValid()) { + return false; + } + } + } + } + return true; +} + +bool CGameCollision::RayStaticLineOfSightTest(const CStateManager& mgr, const CVector3f& position, + const CVector3f& direction, float length, + const CMaterialFilter& filter) { + const CMaterialFilter staticFilter = filter.WithImplicitMaterials(skStaticGeometryMaterials); + if (staticFilter.GetType() == CMaterialFilter::kFT_Never) { + return false; + } + const CLine line(position, CUnitVector3f(direction, CUnitVector3f::kN_No)); + const float maxDistance = length > 0.f ? length : 100000.f; + for (CGameArea::CConstChainIterator area = mgr.GetWorld()->GetChainHead(CWorld::kC_Alive); + area != CWorld::skGlobalEnd; ++area) { + const CAreaOctTree& tree = *area->GetPostConstructed()->mCollision; + if (!tree.GetRootNode().LineTest(line, staticFilter, maxDistance)) { + return false; + } + } + return true; +} + +bool CGameCollision::RayStaticLineOfSightTest(const CGameArea& area, const CVector3f& position, + const CVector3f& direction, float length, + const CMaterialFilter& filter) { + const CMaterialFilter staticFilter = filter.WithImplicitMaterials(skStaticGeometryMaterials); + if (staticFilter.GetType() == CMaterialFilter::kFT_Never) { + return false; + } + const CLine line(position, CUnitVector3f(direction, CUnitVector3f::kN_No)); + const CAreaOctTree& tree = *area.GetPostConstructed()->mCollision; + return tree.GetRootNode().LineTest(line, staticFilter, length > 0.f ? length : 100000.f); +} + +CRayCastResult CGameCollision::RayStaticIntersection(const CStateManager& mgr, + const CVector3f& position, + const CVector3f& direction, float length, + const CMaterialFilter& filter) { + CRayCastResult result; + const CMaterialFilter staticFilter = filter.WithImplicitMaterials(skStaticGeometryMaterials); + if (staticFilter.GetType() == CMaterialFilter::kFT_Never) { + return result; + } + const CLine line(position, CUnitVector3f(direction, CUnitVector3f::kN_No)); + float closest = length > 0.f ? length : 100000.f; + for (CGameArea::CConstChainIterator area = mgr.GetWorld()->GetChainHead(CWorld::kC_Alive); + area != CWorld::skGlobalEnd; ++area) { + CAreaOctTree::SRayResult candidate; + const CAreaOctTree& tree = *area->GetPostConstructed()->mCollision; + tree.GetRootNode().LineTestEx(line, staticFilter, candidate, length); + if (candidate.mSurface && (length == 0.f || candidate.mT <= length) && candidate.mT < closest) { + result = CRayCastResult(candidate.mT, position + candidate.mT * direction, candidate.mPlane, + CMaterialList(candidate.mSurface->GetSurfaceFlags())); + closest = candidate.mT; + } + } + return result; +} + +void CGameCollision::BuildAreaCollisionCache(const CStateManager& mgr, CAreaCollisionCache& cache) { + cache.ClearCache(); + for (CGameArea::CConstChainIterator area = mgr.GetWorld()->GetChainHead(CWorld::kC_Alive); + area != CWorld::skGlobalEnd; ++area) { + const CAreaOctTree& tree = *area->GetPostConstructed()->mCollision; + CMetroidAreaCollider::COctreeLeafCache leaves(tree, area->GetId()); + CMetroidAreaCollider::BuildOctreeLeafCache(tree.GetRootNode(), cache.GetCacheBounds(), leaves); + cache.AddOctreeLeafCache(leaves); + } +} + +bool CGameCollision::DetectCollisionBoolean( + const CStateManager& mgr, const CCollisionPrimitive& primitive, const CTransform4f& transform, + const CMaterialFilter& filter, const rstl::reserved_vector< TUniqueId, 1024 >& nearList) { + if (!filter.GetExcludeList().HasMaterial(kMT_NoStaticCollision) && + DetectStaticCollisionBoolean(mgr, primitive, transform, filter)) { + return true; + } + return DetectDynamicCollisionBoolean(primitive, transform, nearList, mgr); +} + +bool CGameCollision::DetectCollisionBoolean_Cached( + const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList) { + if (!filter.GetExcludeList().HasMaterial(kMT_NoStaticCollision) && + DetectStaticCollisionBoolean_Cached(mgr, cache, primitive, transform, filter)) { + return true; + } + return DetectDynamicCollisionBoolean(primitive, transform, nearList, mgr); +} + +bool CGameCollision::DetectCollision_Cached( + const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, TUniqueId& idOut, + CCollisionInfoList& collisions) { + idOut = kInvalidUniqueId; + bool hit = false; + if (!filter.GetExcludeList().HasMaterial(kMT_NoStaticCollision)) { + hit = DetectStaticCollision_Cached(mgr, cache, primitive, transform, filter, collisions); + } + TUniqueId dynamicId = kInvalidUniqueId; + if (DetectDynamicCollision(primitive, transform, nearList, dynamicId, collisions, mgr)) { + hit = true; + idOut = dynamicId; + } + return hit; +} + +bool CGameCollision::DetectCollision_Cached_Moving( + const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CMaterialFilter& filter, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, const CVector3f& direction, + TUniqueId& idOut, CCollisionInfo& collision, double& distance) { + idOut = kInvalidUniqueId; + bool hit = false; + if (!filter.GetExcludeList().HasMaterial(kMT_NoStaticCollision)) { + hit = DetectStaticCollision_Cached_Moving(mgr, cache, primitive, transform, filter, direction, + collision, distance); + } + if (DetectDynamicCollisionMoving(primitive, transform, nearList, direction, idOut, collision, + distance, mgr)) { + hit = true; + } + return hit; +} + +bool CGameCollision::DetectDynamicCollisionMoving(const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CPhysicsActor& actor, + const CVector3f& direction, + CCollisionInfo& collision, double& distance) { + const CMaterialFilter& filter = CMaterialFilter::GetPassEverything(); + return CCollisionPrimitive::CollideMoving( + CInternalCollisionStructure::CPrimDesc(primitive, filter, transform), + CInternalCollisionStructure::CPrimDesc(*actor.GetCollisionPrimitive(), filter, + actor.GetPrimitiveTransform()), + direction, distance, collision); +} + +bool CGameCollision::DetectDynamicCollisionMoving( + const CCollisionPrimitive& primitive, const CTransform4f& transform, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, const CVector3f& direction, + TUniqueId& idOut, CCollisionInfo& collision, double& distance, const CStateManager& mgr) { + bool hit = false; + for (const TUniqueId* id = nearList.begin(); id != nearList.end(); ++id) { + const CPhysicsActor* actor = TCastToConstPtr< CPhysicsActor >(mgr.GetObjectById(*id)); + double candidateDistance = distance; + CCollisionInfo candidate; + if (actor != nullptr) { + if (DetectDynamicCollisionMoving(primitive, transform, *actor, direction, candidate, + candidateDistance) && + candidateDistance < distance) { + hit = true; + collision = candidate; + distance = candidateDistance; + idOut = actor->GetUniqueId(); + } + } + } + return hit; +} + +bool CGameCollision::DetectDynamicCollisionBoolean(const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CPhysicsActor& actor) { + const CMaterialFilter& filter = CMaterialFilter::GetPassEverything(); + return CCollisionPrimitive::CollideBoolean( + CInternalCollisionStructure::CPrimDesc(primitive, filter, transform), + CInternalCollisionStructure::CPrimDesc(*actor.GetCollisionPrimitive(), filter, + actor.GetPrimitiveTransform())); +} + +bool CGameCollision::DetectDynamicCollisionBoolean( + const CCollisionPrimitive& primitive, const CTransform4f& transform, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, const CStateManager& mgr) { + for (const TUniqueId* id = nearList.begin(); id != nearList.end(); ++id) { + if (const CPhysicsActor* actor = TCastToConstPtr< CPhysicsActor >(mgr.GetObjectById(*id))) { + if (DetectDynamicCollisionBoolean(primitive, transform, *actor)) { + return true; + } + } + } + return false; +} + +bool CGameCollision::DetectDynamicCollision(const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CPhysicsActor& actor, + CCollisionInfoList& collisions) { + const CMaterialFilter& filter = CMaterialFilter::GetPassEverything(); + return CCollisionPrimitive::Collide( + CInternalCollisionStructure::CPrimDesc(primitive, filter, transform), + CInternalCollisionStructure::CPrimDesc(*actor.GetCollisionPrimitive(), filter, + actor.GetPrimitiveTransform()), + collisions); +} + +bool CGameCollision::DetectDynamicCollision( + const CCollisionPrimitive& primitive, const CTransform4f& transform, + const rstl::reserved_vector< TUniqueId, 1024 >& nearList, TUniqueId& idOut, + CCollisionInfoList& collisions, const CStateManager& mgr) { + for (const TUniqueId* id = nearList.begin(); id != nearList.end(); ++id) { + if (const CPhysicsActor* actor = TCastToConstPtr< CPhysicsActor >(mgr.GetObjectById(*id))) { + if (DetectDynamicCollision(primitive, transform, *actor, collisions)) { + idOut = actor->GetUniqueId(); + return true; + } + } + } + idOut = kInvalidUniqueId; + return false; +} + +// Guessed name for the legacy cache's AABox contact-collection helper. +static bool CollideCachedAABox(const CAreaCollisionCache& cache, const CAABox& bounds, + const CMaterialFilter& filter, CCollisionInfoList& collisions, + const CCollisionPrimitive& primitive) { + bool hit = false; + for (uint i = 0; i < cache.GetNumCaches(); ++i) { + if (CMetroidAreaCollider::AABoxCollisionCheck_Cached( + cache.GetOctreeLeafCache(i), bounds, filter, primitive.GetMaterial(), collisions)) { + hit = true; + } + } + return hit; +} + +bool CGameCollision::DetectStaticCollision_Cached( + const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CMaterialFilter& filter, CCollisionInfoList& collisions) { + const CMaterialFilter staticFilter = filter.WithImplicitMaterials(skStaticGeometryMaterials); + if (staticFilter.GetType() == CMaterialFilter::kFT_Never || primitive.GetPrimType() == 'OBTG') { + return false; + } + const CAABox bounds = primitive.CalculateAABox(transform); + EnsureCacheBounds(mgr, cache, bounds); + if (cache.HasCacheOverflowed()) { + return DetectStaticCollision(mgr, primitive, transform, staticFilter, collisions); + } + bool hit = false; + if (primitive.GetPrimType() == 'AABX') { + hit = CollideCachedAABox(cache, bounds, staticFilter, collisions, primitive); + } else if (primitive.GetPrimType() == 'SPHR') { + const CSphere& localSphere = static_cast< const CCollidableSphere& >(primitive).GetSphere(); + const CSphere sphere(transform * localSphere.GetCenter(), localSphere.GetRadius()); + for (uint i = 0; i < cache.GetNumCaches(); ++i) { + if (CMetroidAreaCollider::SphereCollisionCheck_Cached(cache.GetOctreeLeafCache(i), bounds, + sphere, primitive.GetMaterial(), + staticFilter, collisions)) { + hit = true; + } + } + } + if (primitive.GetPrimType() == 'ABSH') { + const CCollidableAABoxSphere& compound = + static_cast< const CCollidableAABoxSphere& >(primitive); + hit = DetectStaticCollision_Cached(mgr, cache, compound.GetCollidableAABox(), transform, + staticFilter, collisions); + if (DetectStaticCollision_Cached(mgr, cache, compound.GetCollidableSphere(), transform, + staticFilter, collisions)) { + hit = true; + } + } + return hit; +} + +bool CGameCollision::DetectStaticCollision(const CStateManager& mgr, + const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CMaterialFilter& filter, + CCollisionInfoList& collisions) { + const CMaterialFilter staticFilter = filter.WithImplicitMaterials(skStaticGeometryMaterials); + if (staticFilter.GetType() == CMaterialFilter::kFT_Never || primitive.GetPrimType() == 'OBTG') { + return false; + } + bool hit = false; + if (primitive.GetPrimType() == 'AABX') { + const CAABox bounds = primitive.CalculateAABox(transform); + for (CGameArea::CConstChainIterator area = mgr.GetWorld()->GetChainHead(CWorld::kC_Alive); + area != CWorld::skGlobalEnd; ++area) { + if (CMetroidAreaCollider::AABoxCollisionCheck(*area->GetPostConstructed()->mCollision, bounds, + staticFilter, primitive.GetMaterial(), + collisions)) { + hit = true; + } + } + } else if (primitive.GetPrimType() == 'SPHR') { + const CAABox bounds = primitive.CalculateAABox(transform); + const CSphere& localSphere = static_cast< const CCollidableSphere& >(primitive).GetSphere(); + const CSphere sphere(transform * localSphere.GetCenter(), localSphere.GetRadius()); + for (CGameArea::CConstChainIterator area = mgr.GetWorld()->GetChainHead(CWorld::kC_Alive); + area != CWorld::skGlobalEnd; ++area) { + if (CMetroidAreaCollider::SphereCollisionCheck(*area->GetPostConstructed()->mCollision, + bounds, sphere, primitive.GetMaterial(), + staticFilter, collisions)) { + hit = true; + } + } + } + if (primitive.GetPrimType() == 'ABSH') { + const CCollidableAABoxSphere& compound = + static_cast< const CCollidableAABoxSphere& >(primitive); + hit = DetectStaticCollision(mgr, compound.GetCollidableAABox(), transform, staticFilter, + collisions); + if (DetectStaticCollision(mgr, compound.GetCollidableSphere(), transform, staticFilter, + collisions)) { + hit = true; + } + } + return hit; +} + +bool CGameCollision::DetectStaticCollisionBoolean_Cached(const CStateManager& mgr, + CAreaCollisionCache& cache, + const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CMaterialFilter& filter) { + const CMaterialFilter staticFilter = filter.WithImplicitMaterials(skStaticGeometryMaterials); + if (staticFilter.GetType() == CMaterialFilter::kFT_Never || primitive.GetPrimType() == 'OBTG') { + return false; + } + const CAABox bounds = primitive.CalculateAABox(transform); + EnsureCacheBounds(mgr, cache, bounds); + if (cache.HasCacheOverflowed()) { + return DetectStaticCollisionBoolean(mgr, primitive, transform, staticFilter); + } + if (primitive.GetPrimType() == 'AABX') { + for (uint i = 0; i < cache.GetNumCaches(); ++i) { + if (CMetroidAreaCollider::AABoxCollisionCheckBoolean_Cached(cache.GetOctreeLeafCache(i), + bounds, staticFilter)) { + return true; + } + } + } else if (primitive.GetPrimType() == 'SPHR') { + const CSphere& localSphere = static_cast< const CCollidableSphere& >(primitive).GetSphere(); + const CSphere sphere(transform * localSphere.GetCenter(), localSphere.GetRadius()); + for (uint i = 0; i < cache.GetNumCaches(); ++i) { + if (CMetroidAreaCollider::SphereCollisionCheckBoolean_Cached(cache.GetOctreeLeafCache(i), + bounds, sphere, staticFilter)) { + return true; + } + } + } + if (primitive.GetPrimType() == 'ABSH') { + const CCollidableAABoxSphere& compound = + static_cast< const CCollidableAABoxSphere& >(primitive); + return DetectStaticCollisionBoolean_Cached(mgr, cache, compound.GetCollidableAABox(), transform, + staticFilter) || + DetectStaticCollisionBoolean_Cached(mgr, cache, compound.GetCollidableSphere(), + transform, staticFilter); + } + return false; +} + +bool CGameCollision::DetectStaticCollisionBoolean(const CStateManager& mgr, + const CCollisionPrimitive& primitive, + const CTransform4f& transform, + const CMaterialFilter& filter) { + const CMaterialFilter staticFilter = filter.WithImplicitMaterials(skStaticGeometryMaterials); + if (staticFilter.GetType() == CMaterialFilter::kFT_Never || primitive.GetPrimType() == 'OBTG') { + return false; + } + if (primitive.GetPrimType() == 'AABX') { + const CAABox bounds = primitive.CalculateAABox(transform); + for (CGameArea::CConstChainIterator area = mgr.GetWorld()->GetChainHead(CWorld::kC_Alive); + area != CWorld::skGlobalEnd; ++area) { + if (CMetroidAreaCollider::AABoxCollisionCheckBoolean(*area->GetPostConstructed()->mCollision, + bounds, staticFilter)) { + return true; + } + } + } else if (primitive.GetPrimType() == 'SPHR') { + const CAABox bounds = primitive.CalculateAABox(transform); + const CSphere& localSphere = static_cast< const CCollidableSphere& >(primitive).GetSphere(); + const CSphere sphere(transform * localSphere.GetCenter(), localSphere.GetRadius()); + for (CGameArea::CConstChainIterator area = mgr.GetWorld()->GetChainHead(CWorld::kC_Alive); + area != CWorld::skGlobalEnd; ++area) { + if (CMetroidAreaCollider::SphereCollisionCheckBoolean(*area->GetPostConstructed()->mCollision, + bounds, sphere, staticFilter)) { + return true; + } + } + } + if (primitive.GetPrimType() == 'ABSH') { + const CCollidableAABoxSphere& compound = + static_cast< const CCollidableAABoxSphere& >(primitive); + return DetectStaticCollisionBoolean(mgr, compound.GetCollidableAABox(), transform, + staticFilter) || + DetectStaticCollisionBoolean(mgr, compound.GetCollidableSphere(), transform, + staticFilter); + } + return false; +} + +bool CGameCollision::DetectStaticCollision_Cached_Moving( + const CStateManager& mgr, CAreaCollisionCache& cache, const CCollisionPrimitive& primitive, + const CTransform4f& transform, const CMaterialFilter& filter, const CVector3f& direction, + CCollisionInfo& collision, double& distance) { + const CMaterialFilter staticFilter = filter.WithImplicitMaterials(skStaticGeometryMaterials); + if (staticFilter.GetType() == CMaterialFilter::kFT_Never || primitive.GetPrimType() == 'OBTG') { + return false; + } + const CAABox bounds = primitive.CalculateAABox(transform); + const CVector3f displacement = float(distance) * direction; + CAABox sweptBounds(bounds); + sweptBounds.AccumulateBounds(bounds.GetMinPoint() + displacement); + sweptBounds.AccumulateBounds(bounds.GetMaxPoint() + displacement); + EnsureCacheBounds(mgr, cache, sweptBounds); + + if (primitive.GetPrimType() == 'AABX') { + for (uint i = 0; i < cache.GetNumCaches(); ++i) { + CCollisionInfo candidate; + double candidateDistance = distance; + if (CMetroidAreaCollider::MovingAABoxCollisionCheck_Cached( + cache.GetOctreeLeafCache(i), bounds, staticFilter, CMaterialList(kMT_Unknown59), + direction, float(distance), candidate, candidateDistance) && + candidateDistance < distance) { + collision = candidate; + distance = float(candidateDistance); + } + } + } else if (primitive.GetPrimType() == 'SPHR') { + const CSphere& sphere = static_cast< const CCollidableSphere& >(primitive).GetSphere(); + for (uint i = 0; i < cache.GetNumCaches(); ++i) { + CCollisionInfo candidate; + double candidateDistance = distance; + if (CMetroidAreaCollider::MovingSphereCollisionCheck_Cached( + cache.GetOctreeLeafCache(i), bounds, + CSphere(transform * sphere.GetCenter(), sphere.GetRadius()), staticFilter, + CMaterialList(kMT_Unknown59), direction, float(distance), candidate, + candidateDistance) && + candidateDistance < distance) { + collision = candidate; + distance = float(candidateDistance); + } + } + } + return collision.IsValid(); +} + +void CGameCollision::MakeCollisionCallbacks(CStateManager& mgr, CPhysicsActor& actor, + const TUniqueId& id, + const CCollisionInfoList& collisions) { + actor.CollidedWith(id, collisions, mgr); + if (id != kInvalidUniqueId) { + if (CPhysicsActor* other = TCastToPtr< CPhysicsActor >(mgr.ObjectById(id))) { + CCollisionInfoList swapped(collisions); + for (int i = 0; i < swapped.GetCount(); ++i) { + swapped[i].Swap(); + } + // The original passes the unswapped list despite constructing the swapped copy. + other->CollidedWith(actor.GetUniqueId(), collisions, mgr); + } + } +} + +void CGameCollision::SendScriptMessages(CStateManager& mgr, CActor& actor, CActor* other, + const CCollisionInfoList& collisions) { + CMaterialList materials; + bool hasFloor = false; + bool hasPlatform = false; + for (int i = 0; i < collisions.GetCount(); ++i) { + materials.Add(collisions[i].GetMaterialLeft()); + } + for (int i = 0; i < collisions.GetCount(); ++i) { + const CCollisionInfo& collision = collisions[i]; + if (IsFloor(collision.GetMaterialLeft(), collision.GetNormalLeft())) { + hasFloor = true; + if (collision.GetMaterialLeft().HasMaterial(kMT_Platform)) { + hasPlatform = true; + } + } + } + SendMaterialMessage(mgr, materials, actor); + if (hasFloor) { + mgr.DeliverScriptMsg(CScriptMsg(kInvalidUniqueId, kInvalidUniqueId, actor.GetUniqueId(), + kSM_OnFloor, kSS_InvalidState)); + if (hasPlatform) { + if (CScriptPlatform* platform = TCastToPtr< CScriptPlatform >(other)) { + mgr.DeliverScriptMsg( + CScriptMsg(actor.GetUniqueId(), kInvalidUniqueId, platform->GetUniqueId(), + static_cast< EScriptObjectMessage >('XONP'), kSS_InvalidState)); + } + } else { + mgr.DeliverScriptMsg(CScriptMsg(kInvalidUniqueId, kInvalidUniqueId, actor.GetUniqueId(), + static_cast< EScriptObjectMessage >('XLSG'), + kSS_InvalidState)); + } + } else if (other != nullptr) { + if (CScriptPlatform* platform = TCastToPtr< CScriptPlatform >(&actor)) { + for (int i = 0; i < collisions.GetCount(); ++i) { + const CCollisionInfo& collision = collisions[i]; + if (IsFloor(collision.GetMaterialRight(), collision.GetNormalRight()) && + collision.GetMaterialRight().HasMaterial(kMT_Platform)) { + hasPlatform = true; + break; + } + } + if (hasPlatform) { + mgr.DeliverScriptMsg( + CScriptMsg(other->GetUniqueId(), kInvalidUniqueId, platform->GetUniqueId(), + static_cast< EScriptObjectMessage >('XONP'), kSS_InvalidState)); + } + } + } +} + +void CGameCollision::SendMaterialMessage(CStateManager& mgr, const CMaterialList&, CActor& actor) { + // Echoes always sends the normal-surface message here. + mgr.DeliverScriptMsg(CScriptMsg(kInvalidUniqueId, kInvalidUniqueId, actor.GetUniqueId(), + static_cast< EScriptObjectMessage >('XOND'), kSS_InvalidState)); +} + +void CGameCollision::ShowCollisionResults(CCollisionInfoList&, const CColor&) {} + +float CGameCollision::GetCoefficientOfRestitution(const CCollisionInfo&) { return 0.f; } + +static float CollisionImpulseFiniteVsFinite(float mass, float otherMass, float velocity, + float restitution) { + return -(1.f + restitution) * velocity / (1.f / mass + 1.f / otherMass); +} + +static float CollisionImpulseFiniteVsInfinite(float mass, float velocity, float restitution) { + return mass * -(1.f + restitution) * velocity; +} + +bool CGameCollision::IsFloor(const CMaterialList& material, const CVector3f& normal) { + return material.HasMaterial(kMT_Floor) || normal.GetZ() > 0.85f; +} + +bool CGameCollision::CanBlock(const CMaterialList& material, const CVector3f& normal) { + if (material.HasMaterial(kMT_Character) && !material.HasMaterial(kMT_SolidCharacter)) { + return false; + } + if (material.HasMaterial(kMT_NoPlayerCollision)) { + return false; + } + return material.HasMaterial(kMT_Floor) || normal.GetZ() > 0.85f; +} + +float CGameCollision::GetMinExtentForCollisionPrimitive(const CCollisionPrimitive& primitive) { + if (primitive.GetPrimType() == 'SPHR') { + return 2.f * static_cast< const CCollidableSphere& >(primitive).GetSphere().GetRadius(); + } + if (primitive.GetPrimType() == 'AABX') { + const CAABox& bounds = static_cast< const CCollidableAABox& >(primitive).GetBox(); + const CVector3f extent = bounds.GetMaxPoint() - bounds.GetMinPoint(); + return rstl::min_val(rstl::min_val(extent.GetX(), extent.GetY()), extent.GetZ()); + } + if (primitive.GetPrimType() == 'ABSH') { + const CCollidableAABoxSphere& compound = + static_cast< const CCollidableAABoxSphere& >(primitive); + return rstl::min_val(GetMinExtentForCollisionPrimitive(compound.GetCollidableAABox()), + GetMinExtentForCollisionPrimitive(compound.GetCollidableSphere())); + } + return 1.f; +} + +void CGameCollision::ResolveCollisions(CPhysicsActor& actor, CPhysicsActor* other, + const CCollisionInfoList& collisions) { + for (int i = 0; i < collisions.GetCount(); ++i) { + const CCollisionInfo collision = collisions[i]; + const float restitution = + GetCoefficientOfRestitution(collision) + actor.GetCoefficientOfRestitutionModifier(); + if (other != nullptr) { + CollideWithDynamicBodyNoRot(actor, *other, collision, restitution, false); + } else { + const CVector3f& normal = collision.GetNormalLeft(); + const CUnitVector3f unitNormal(normal.CanBeNormalized() ? normal.AsNormalized() + : CVector3f::Zero(), + CUnitVector3f::kN_No); + CollideWithStaticBodyNoRot(actor, collision.GetMaterialLeft(), collision.GetMaterialRight(), + unitNormal, restitution, false); + } + } +} + +void CGameCollision::CollideWithDynamicBodyNoRot(CPhysicsActor& actor, CPhysicsActor& other, + const CCollisionInfo& collision, float restitution, + bool flattenNormal) { + CVector3f normal = collision.GetNormalLeft(); + if (flattenNormal) { + normal.SetZ(0.f); + } + const float mass = actor.GetMass(); + const float otherMass = other.GetMass(); + const float normalVelocity = CVector3f::Dot(GetActorRelativeVelocities(&actor, &other), normal); + const float maxSpeed = + rstl::max_val(actor.GetMaximumCollisionVelocity(), actor.GetVelocityWR().Magnitude()); + const float otherMaxSpeed = + rstl::max_val(other.GetMaximumCollisionVelocity(), other.GetVelocityWR().Magnitude()); + const bool immovable = actor.GetMaterialList().HasMaterial(kMT_Immovable) || mass == 0.f; + const bool otherImmovable = + other.GetMaterialList().HasMaterial(kMT_Immovable) || otherMass == 0.f; + + if (normalVelocity < -0.0001f) { + if (immovable && otherImmovable) { + actor.SetVelocityWR(CVector3f::Zero()); + other.SetVelocityWR(CVector3f::Zero()); + } else if (immovable) { + const float impulse = + CollisionImpulseFiniteVsInfinite(otherMass, normalVelocity, restitution); + other.ApplyImpulseWR(-impulse * normal, CAxisAngle::Identity()); + } else if (otherImmovable) { + const float impulse = CollisionImpulseFiniteVsInfinite(mass, normalVelocity, restitution); + actor.ApplyImpulseWR(impulse * normal, CAxisAngle::Identity()); + } else { + const float impulse = + CollisionImpulseFiniteVsFinite(mass, otherMass, normalVelocity, restitution); + actor.ApplyImpulseWR(impulse * normal, CAxisAngle::Identity()); + other.ApplyImpulseWR(-impulse * normal, CAxisAngle::Identity()); + } + actor.UseCollisionImpulses(); + other.UseCollisionImpulses(); + } else if (normalVelocity < 0.1f) { + if (!immovable) { + actor.ApplyImpulseWR((0.05f * mass) * normal, CAxisAngle::Identity()); + actor.UseCollisionImpulses(); + } + if (!otherImmovable) { + other.ApplyImpulseWR((-0.05f * otherMass) * normal, CAxisAngle::Identity()); + other.UseCollisionImpulses(); + } + } + const float speed = actor.GetVelocityWR().Magnitude(); + if (speed > maxSpeed) { + actor.SetVelocityWR(maxSpeed * (actor.GetVelocityWR() / speed)); + } + const float otherSpeed = other.GetVelocityWR().Magnitude(); + if (otherSpeed > otherMaxSpeed) { + other.SetVelocityWR(otherMaxSpeed * (other.GetVelocityWR() / otherSpeed)); + } +} + +void CGameCollision::CollideWithStaticBodyNoRot(CPhysicsActor& actor, const CMaterialList& material, + const CMaterialList& otherMaterial, + const CUnitVector3f& normal, float restitution, + bool flattenNormal) { + CVector3f collisionNormal(normal); + if (flattenNormal && material.HasMaterial(kMT_Player) && !otherMaterial.HasMaterial(kMT_Floor)) { + collisionNormal.SetZ(0.f); + } + if (!collisionNormal.CanBeNormalized()) { + return; + } + collisionNormal.Normalize(); + const CVector3f velocity = actor.GetVelocityWR(); + const float normalVelocity = CVector3f::Dot(velocity, collisionNormal); + if (normalVelocity < -0.0001f) { + const float impulse = + CollisionImpulseFiniteVsInfinite(actor.GetMass(), normalVelocity, restitution); + actor.ApplyImpulseWR(impulse * collisionNormal, CAxisAngle::Identity()); + actor.UseCollisionImpulses(); + } else { + const float speed = velocity.Magnitude(); + const float cosAngle = speed > 0.001f ? normalVelocity / speed : 0.f; + if (normalVelocity < 0.001f || cosAngle < 0.0008f) { + actor.ApplyImpulseWR((0.05f * actor.GetMass()) * collisionNormal, CAxisAngle::Identity()); + actor.UseCollisionImpulses(); + } + } +} + +void CGameCollision::Move(CStateManager&, CPhysicsActor&, float, + const rstl::reserved_vector< TUniqueId, 1024 >*) { + // TODO: recover the movement/filter dispatch and player failsafe sampling condition. +} + +void CGameCollision::MovePlayer(CStateManager&, CPhysicsActor&, float, + const rstl::reserved_vector< TUniqueId, 1024 >*) { + // TODO: recover the ball collision filter and packed-cache movement dispatcher. +} + +void CGameCollision::CollisionFailsafe(const CStateManager&, CAreaCollisionCache&, + CPhysicsActor& actor, const CCollisionPrimitive&, + const rstl::reserved_vector< TUniqueId, 1024 >&, float, uint, + float) { + actor.MoveCollisionPrimitive(CVector3f::Zero()); + // TODO: restore the last safe state, collision-normal impulse, and displacement search. +} + +rstl::optional_object< CVector3f > +CGameCollision::FindNonIntersectingVector(const CStateManager&, CPhysicsActor&, + const CCollisionPrimitive&, + const rstl::optional_object< CVector3f >&) { + // TODO: recover the 26-direction search and its world-ray/area-boundary checks. + return rstl::optional_object_null(); +} + +CVector3f CGameCollision::GetActorRelativeVelocities(const CPhysicsActor* actor, + const CPhysicsActor* other) { + CVector3f velocity = actor->GetVelocityWR(); + if (other != nullptr) { + const CScriptPlatform* platform = TCastToConstPtr< CScriptPlatform >(other); + if (platform == nullptr || !platform->IsRider(actor->GetUniqueId())) { + velocity -= other->GetVelocityWR(); + } + } + return velocity; +} + +void CGameCollision::PushActorAwayFromWalls(CStateManager& mgr, CPhysicsActor& actor, float dt, + float height, float distance, float acceleration, + int iterations, float radius) { + const CVector3f actorPosition = actor.GetTranslation(); + const CVector3f center = actorPosition + CVector3f(0.f, 0.f, height); + const float cacheRadius = 1.2f * radius; + const CVector3f extent(distance + cacheRadius, distance + cacheRadius, cacheRadius); + CAreaCollisionCache cache(CAABox(center - extent, center + extent)); + BuildAreaCollisionCache(mgr, cache); + const CSphere sphere(center, radius); + const CMaterialFilter filter = CMaterialFilter::MakeExclude(CMaterialList(kMT_Floor)); + if (DetectStaticCollisionBoolean_Cached(mgr, cache, + CCollidableSphere(sphere, CMaterialList(kMT_Unknown59)), + CTransform4f::Identity(), filter)) { + return; + } + + CVector3f correction = CVector3f::Zero(); + const float angleStep = M_2PIF / float(iterations); + for (int i = 0; i < iterations; ++i) { + const float angle = angleStep * float(i); + const CVector3f direction(CMath::SlowSineR(angle), CMath::SlowCosineR(angle), 0.f); + double collisionDistance = distance; + CCollisionInfo collision; + if (cache.HasCacheOverflowed()) { + cache.ClearCache(); + CAABox bounds(center, center); + bounds.AccumulateBounds(actorPosition + distance * direction); + const CVector3f radiusVector(radius, radius, radius); + // Both corners use the minimum point in the original. + cache.SetCacheBounds( + CAABox(bounds.GetMinPoint() - radiusVector, bounds.GetMinPoint() + radiusVector)); + BuildAreaCollisionCache(mgr, cache); + } + if (DetectStaticCollision_Cached_Moving( + mgr, cache, CCollidableSphere(sphere, CMaterialList(kMT_Unknown59)), + CTransform4f::Identity(), filter, direction, collision, collisionDistance)) { + const float fraction = float(distance - collisionDistance) / distance / float(iterations); + correction -= fraction * direction; + } + } + actor.SetVelocityWR(actor.GetVelocityWR() + dt * (acceleration * correction)); +} + +// Guessed name; shared source helper for the legacy cache's expanding query bounds. +static void EnsureCacheBounds(const CStateManager& mgr, CAreaCollisionCache& cache, + const CAABox& bounds) { + if (!bounds.Inside(cache.GetCacheBounds())) { + const CVector3f margin(0.2f, 0.2f, 0.2f); + CAABox expanded(bounds.GetMinPoint() - margin, bounds.GetMaxPoint() + margin); + expanded.AccumulateBounds(cache.GetCacheBounds().GetMinPoint()); + expanded.AccumulateBounds(cache.GetCacheBounds().GetMaxPoint()); + cache.SetCacheBounds(expanded); + CGameCollision::BuildAreaCollisionCache(mgr, cache); + } +}