Separated collision detection and added contact manager

This commit is contained in:
Diren D Bharwani 2022-12-20 23:10:23 +08:00
parent 5def5392a1
commit b58b475c04
22 changed files with 1011 additions and 734 deletions

View File

@ -46,14 +46,16 @@ namespace SHADE
/*---------------------------------------------------------------------------------*/
/** Standard Epsilon value for comparing Single-Precision Floating-Point values. */
static constexpr float EPSILON = 0.001f;
static constexpr float EPSILON = 0.001f;
/** Single-Precision Floating-Point value of infinity */
static constexpr float INF = std::numeric_limits<float>::infinity();
static constexpr float INF = std::numeric_limits<float>::infinity();
static constexpr float PI = std::numbers::pi_v<float>;
static constexpr float HALF_PI = PI * 0.5f;
static constexpr float TWO_PI = 2.0f * PI;
static constexpr float PI = std::numbers::pi_v<float>;
static constexpr float HALF_PI = PI * 0.5f;
static constexpr float TWO_PI = 2.0f * PI;
static constexpr float EULER_CONSTANT = std::numbers::egamma_v<float>;
/*---------------------------------------------------------------------------------*/
/* Static Function Members */

View File

@ -122,7 +122,7 @@ namespace SHADE
/* Data Members */
/*---------------------------------------------------------------------------------*/
static constexpr float AABB_EXTENSION = 0.1f;
static constexpr float AABB_EXTENSION = 0.2f;
// For quick access
std::unordered_map<SHCollisionShapeID, int32_t, SHCollisionShapeIDHash> nodeMap;

View File

@ -37,7 +37,7 @@ namespace SHADE
friend class SHCollider;
friend class SHColliderComponent;
friend class SHCollisionShapeFactory;
friend class SHPhysicsWorld;
friend class SHCollisionSpace;
public:
/*---------------------------------------------------------------------------------*/

View File

@ -29,6 +29,7 @@ namespace SHADE
SHCollider::SHCollider(EntityID eid, const SHTransform& worldTransform) noexcept
: entityID { eid }
, active { true }
, debugDraw { false }
, hasMoved { true }
, rigidBody { nullptr }
@ -39,6 +40,7 @@ namespace SHADE
SHCollider::SHCollider(const SHCollider& rhs) noexcept
: entityID { rhs.entityID }
, active { rhs.active }
, debugDraw { rhs.debugDraw }
, hasMoved { rhs.hasMoved }
, rigidBody { rhs.rigidBody }
@ -57,6 +59,7 @@ namespace SHADE
SHCollider::SHCollider(SHCollider&& rhs) noexcept
: entityID { rhs.entityID }
, active { rhs.active }
, debugDraw { rhs.debugDraw }
, hasMoved { rhs.hasMoved }
, rigidBody { rhs.rigidBody }
@ -101,6 +104,7 @@ namespace SHADE
}
entityID = rhs.entityID;
active = rhs.active;
debugDraw = rhs.debugDraw;
hasMoved = rhs.hasMoved;
rigidBody = rhs.rigidBody;
@ -122,6 +126,7 @@ namespace SHADE
}
entityID = rhs.entityID;
active = rhs.active;
debugDraw = rhs.debugDraw;
hasMoved = rhs.hasMoved;
rigidBody = rhs.rigidBody;
@ -138,6 +143,16 @@ namespace SHADE
/* Getter Function Definitions */
/*-----------------------------------------------------------------------------------*/
EntityID SHCollider::GetEntityID() const noexcept
{
return entityID;
}
bool SHCollider::IsActive() const noexcept
{
return active;
}
bool SHCollider::GetDebugDrawState() const noexcept
{
return debugDraw;
@ -182,6 +197,25 @@ namespace SHADE
/* Setter Function Definitions */
/*-----------------------------------------------------------------------------------*/
void SHCollider::SetIsActive(bool state) noexcept
{
if (active == state)
return;
active = state;
if (!broadphase)
return;
for (auto* shape : shapes)
{
if (active) // Previously inactive
broadphase->Insert(shape->id, shape->ComputeAABB());
else // Previously active
broadphase->Remove(shape->id);
}
}
void SHCollider::SetDebugDrawState(bool state) noexcept
{
debugDraw = state;

View File

@ -40,7 +40,7 @@ namespace SHADE
/* Friends */
/*---------------------------------------------------------------------------------*/
friend class SHPhysicsWorld;
friend class SHCollisionSpace;
public:
/*---------------------------------------------------------------------------------*/
@ -79,6 +79,8 @@ namespace SHADE
/* Getter Functions */
/*---------------------------------------------------------------------------------*/
[[nodiscard]] EntityID GetEntityID () const noexcept;
[[nodiscard]] bool IsActive () const noexcept;
[[nodiscard]] bool GetDebugDrawState () const noexcept;
[[nodiscard]] const SHTransform& GetTransform () const noexcept;
@ -93,6 +95,7 @@ namespace SHADE
/* Setter Functions */
/*---------------------------------------------------------------------------------*/
void SetIsActive (bool state) noexcept;
void SetDebugDrawState (bool state) noexcept;
void SetRigidBody (SHRigidBody* rb) noexcept;
@ -157,6 +160,7 @@ namespace SHADE
EntityID entityID;
bool active;
bool debugDraw;
bool hasMoved;

View File

@ -1,100 +0,0 @@
/****************************************************************************************
* \file SHCollisionInfo.cpp
* \author Diren D Bharwani, diren.dbharwani, 390002520
* \brief Implementation for Collision Info.
*
* \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or
* disclosure of this file or its contents without the prior written consent
* of DigiPen Institute of Technology is prohibited.
****************************************************************************************/
#include <SHpch.h>
// Primary Header
#include "SHCollisionInfo.h"
// Project Headers
#include "Physics/Collision/SHCollider.h"
#include "Physics/Interface/SHColliderComponent.h"
namespace SHADE
{
/*-----------------------------------------------------------------------------------*/
/* Constructors & Destructor Definitions */
/*-----------------------------------------------------------------------------------*/
SHCollisionInfo::SHCollisionInfo() noexcept
: collisionState { State::INVALID }
{
ids[ENTITY_A] = MAX_EID;
ids[ENTITY_B] = MAX_EID;
ids[COLLIDER_A] = std::numeric_limits<uint32_t>::max();
ids[COLLIDER_B] = std::numeric_limits<uint32_t>::max();
}
SHCollisionInfo::SHCollisionInfo(EntityID entityA, EntityID entityB) noexcept
: collisionState { State::INVALID }
{
ids[ENTITY_A] = entityA;
ids[ENTITY_B] = entityB;
ids[COLLIDER_A] = std::numeric_limits<uint32_t>::max();
ids[COLLIDER_B] = std::numeric_limits<uint32_t>::max();
}
/*-----------------------------------------------------------------------------------*/
/* Operator Overload Definitions */
/*-----------------------------------------------------------------------------------*/
bool SHCollisionInfo::operator==(const SHCollisionInfo& rhs) const noexcept
{
return value[0] == rhs.value[0] && value[1] == rhs.value[1];
}
bool SHCollisionInfo::operator!=(const SHCollisionInfo& rhs) const noexcept
{
return value[0] != rhs.value[0] || value[1] != rhs.value[1];
}
/*-----------------------------------------------------------------------------------*/
/* Getter Function Definitions */
/*-----------------------------------------------------------------------------------*/
EntityID SHCollisionInfo::GetEntityA() const noexcept
{
return ids[ENTITY_A];
}
EntityID SHCollisionInfo::GetEntityB() const noexcept
{
return ids[ENTITY_B];
}
const SHRigidBodyComponent* SHCollisionInfo::GetRigidBodyA() const noexcept
{
return SHComponentManager::GetComponent_s<SHRigidBodyComponent>(ids[ENTITY_A]);
}
const SHRigidBodyComponent* SHCollisionInfo::GetRigidBodyB() const noexcept
{
return SHComponentManager::GetComponent_s<SHRigidBodyComponent>(ids[ENTITY_B]);
}
const SHCollisionShape* SHCollisionInfo::GetColliderA() const noexcept
{
const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent<SHColliderComponent>(ids[ENTITY_A]);
return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[COLLIDER_A]);
}
const SHCollisionShape* SHCollisionInfo::GetColliderB() const noexcept
{
const auto* COLLIDER_COMPONENT = SHComponentManager::GetComponent<SHColliderComponent>(ids[ENTITY_B]);
return COLLIDER_COMPONENT->GetCollider()->GetCollisionShape(ids[COLLIDER_B]);
}
SHCollisionInfo::State SHCollisionInfo::GetCollisionState() const noexcept
{
return collisionState;
}
} // namespace SHADE

View File

@ -1,102 +0,0 @@
/****************************************************************************************
* \file SHCollisionInfo.h
* \author Diren D Bharwani, diren.dbharwani, 390002520
* \brief Interface for Collision Information for Collision & Triggers.
*
* \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or
* disclosure of this file or its contents without the prior written consent
* of DigiPen Institute of Technology is prohibited.
****************************************************************************************/
#pragma once
// Project Headers
#include "Physics/Interface/SHColliderComponent.h"
#include "Physics/Interface/SHRigidBodyComponent.h"
namespace SHADE
{
/*-----------------------------------------------------------------------------------*/
/* Type Definitions */
/*-----------------------------------------------------------------------------------*/
class SH_API SHCollisionInfo
{
private:
/*---------------------------------------------------------------------------------*/
/* Friends */
/*---------------------------------------------------------------------------------*/
friend class SHCollisionListener;
public:
/*---------------------------------------------------------------------------------*/
/* Type Definitions */
/*---------------------------------------------------------------------------------*/
enum class State
{
ENTER
, STAY
, EXIT
, TOTAL
, INVALID = -1
};
/*---------------------------------------------------------------------------------*/
/* Constructors & Destructor */
/*---------------------------------------------------------------------------------*/
SHCollisionInfo () noexcept;
SHCollisionInfo (EntityID entityA, EntityID entityB) noexcept;
SHCollisionInfo (const SHCollisionInfo& rhs) = default;
SHCollisionInfo (SHCollisionInfo&& rhs) = default;
~SHCollisionInfo () = default;
/*---------------------------------------------------------------------------------*/
/* Operator Overloads */
/*---------------------------------------------------------------------------------*/
bool operator== (const SHCollisionInfo& rhs) const noexcept;
bool operator!= (const SHCollisionInfo& rhs) const noexcept;
SHCollisionInfo& operator= (const SHCollisionInfo& rhs) = default;
SHCollisionInfo& operator= (SHCollisionInfo&& rhs) = default;
/*---------------------------------------------------------------------------------*/
/* Getter Functions */
/*---------------------------------------------------------------------------------*/
[[nodiscard]] EntityID GetEntityA () const noexcept;
[[nodiscard]] EntityID GetEntityB () const noexcept;
[[nodiscard]] const SHRigidBodyComponent* GetRigidBodyA () const noexcept;
[[nodiscard]] const SHRigidBodyComponent* GetRigidBodyB () const noexcept;
[[nodiscard]] const SHCollisionShape* GetColliderA () const noexcept;
[[nodiscard]] const SHCollisionShape* GetColliderB () const noexcept;
[[nodiscard]] State GetCollisionState () const noexcept;
private:
static constexpr uint32_t ENTITY_A = 0;
static constexpr uint32_t ENTITY_B = 1;
static constexpr uint32_t COLLIDER_A = 2;
static constexpr uint32_t COLLIDER_B = 3;
/*---------------------------------------------------------------------------------*/
/* Data Members */
/*---------------------------------------------------------------------------------*/
union
{
uint64_t value[2]; // EntityValue, ColliderIndexValue
uint32_t ids [4]; // EntityA, EntityB, ColliderIndexA, ColliderIndexB
};
State collisionState;
};
} // namespace SHADE

View File

@ -0,0 +1,223 @@
/****************************************************************************************
* \file SHCollisionSpace.cpp
* \author Diren D Bharwani, diren.dbharwani, 390002520
* \brief Implementation for a Collision Space that handles collision detetction.
*
* \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or
* disclosure of this file or its contents without the prior written consent
* of DigiPen Institute of Technology is prohibited.
****************************************************************************************/
#include <SHpch.h>
// Primary Header
#include "SHCollisionSpace.h"
#include "Narrowphase/SHCollisionDispatch.h"
namespace SHADE
{
/*-----------------------------------------------------------------------------------*/
/* Getter Functions Definitions */
/*-----------------------------------------------------------------------------------*/
const SHAABBTree::AABBs& SHCollisionSpace::GetBroadphaseAABBs() const noexcept
{
return broadphase.GetAABBs();
}
/*-----------------------------------------------------------------------------------*/
/* Setter Functions Definitions */
/*-----------------------------------------------------------------------------------*/
void SHCollisionSpace::SetContactManager(SHContactManager* _contactManager) noexcept
{
contactManager = _contactManager;
}
/*-----------------------------------------------------------------------------------*/
/* Public Member Functions Definitions */
/*-----------------------------------------------------------------------------------*/
void SHCollisionSpace::AddCollider(SHCollider* collider) noexcept
{
const bool INSERTED = colliders.emplace(collider->entityID, collider).second;
if (!INSERTED)
{
SHLOG_WARNING_D("Attempting to add duplicate collider {} to the Physics World!", collider->entityID)
return;
}
collider->broadphase = &broadphase;
// Add all existing shapes to the broadphase
for (const auto* shape : collider->shapes)
broadphase.Insert(shape->id, shape->ComputeAABB());
}
void SHCollisionSpace::RemoveCollider(SHCollider* collider) noexcept
{
colliders.erase(collider->entityID);
const uint32_t NUM_SHAPES = static_cast<uint32_t>(collider->shapes.size());
if (NUM_SHAPES == 0)
return;
for (uint32_t i = 0; i < NUM_SHAPES; ++i)
broadphase.Remove(collider->shapes[i]->id);
if (contactManager)
{
contactManager->RemoveInvalidatedTrigger(collider->entityID);
contactManager->RemoveInvalidatedManifold(collider->entityID);
}
/*
* TODO:
* Get collider's rigid body
* Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep
*/
}
void SHCollisionSpace::UpdateBroadphase() noexcept
{
// Update any colliders that have moved
for (auto& collider : colliders | std::views::values)
{
const bool IS_ACTIVE = collider->active;
const bool HAS_MOVED = collider->hasMoved;
if (!IS_ACTIVE || !HAS_MOVED)
continue;
// Clear hasMoved flag here
collider->hasMoved = false;
// Update moved shapes in broadphase
for (auto* shape : collider->shapes)
broadphase.Update(shape->id, shape->ComputeAABB());
}
}
void SHCollisionSpace::DetectCollisions() noexcept
{
/*
* Broad-phase
*/
// Broadphase Queries: Kinematic Triggers, Awake Dynamic Bodies & Dynamic Triggers
for (auto& collider : colliders | std::views::values)
{
// Colliders without bodies are considered to be static bodies
// This is specific to this engine because of Unity's stupid convention.
const bool IS_IMPLICIT_STATIC = !collider->rigidBody;
const bool IS_EXPLICIT_STATIC = collider->rigidBody->GetType() == SHRigidBody::Type::STATIC;
const bool IS_ACTIVE = collider->active;
// Skip inactive colliders
if (!IS_ACTIVE || IS_IMPLICIT_STATIC || IS_EXPLICIT_STATIC)
continue;
// All remaining are kinematic or dynamic
// Iterate through shapes: if kinematic / dynamic trigger, else if dynamic & awake
// Results are loaded into the narrowphase batch
broadphaseQuery(collider->rigidBody->GetType(), collider);
}
/*
* Narrow-phase
*/
// If no potential collisions, we can skip the entire narrow phase. No further updates necessary.
// All contact / trigger states persist in this step.
if (narrowphaseBatch.empty())
return;
// All narrowphase IDs are unique, there should be no duplicate collision checks.
// This applies both ways: A -> B and B -> A.
for (auto& [key, narrowphasePair] : narrowphaseBatch)
{
const bool IS_A_TRIGGER = narrowphasePair.A->IsTrigger();
const bool IS_B_TRIGGER = narrowphasePair.B->IsTrigger();
if (IS_A_TRIGGER || IS_B_TRIGGER)
collideTriggers(key, narrowphasePair);
else
collideManifolds(key, narrowphasePair);
}
// Clear every frame
narrowphaseBatch.clear();
}
/*-----------------------------------------------------------------------------------*/
/* Private Member Functions Definitions */
/*-----------------------------------------------------------------------------------*/
void SHCollisionSpace::broadphaseQuery(SHRigidBody::Type rigidBodyType, SHCollider* collider) noexcept
{
for (auto* shape : collider->shapes)
{
// For kinematic shapes, we only query triggers against everything else
if (rigidBodyType == SHRigidBody::Type::KINEMATIC && !shape->IsTrigger())
continue;
auto& potentialCollisions = broadphase.Query(shape->id, shape->ComputeAABB());
// Build narrow-phase pairs
auto* shapeA = shape;
const EntityID ID_A = shape->id.GetEntityID();
const uint32_t INDEX_A = shape->id.GetShapeIndex();
for (auto& id : potentialCollisions)
{
// Get corresponding shape
const EntityID ID_B = id.GetEntityID();
const uint32_t INDEX_B = id.GetShapeIndex();
auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B);
// Build collision ID
SHCollisionKey collisionKey;
collisionKey.SetEntityA(ID_A);
collisionKey.SetEntityB(ID_B);
collisionKey.SetCollisionShapeA(INDEX_A);
collisionKey.SetCollisionShapeB(INDEX_B);
// Check if it already exists. If it doesn't, put into batch.
// The overloaded equality operator ensures no duplicate collision tests are performed.
auto narrowphasePair = narrowphaseBatch.find(collisionKey);
if (narrowphasePair == narrowphaseBatch.end())
narrowphaseBatch.emplace(collisionKey, NarrowphasePair{ shapeA, shapeB });
}
}
}
void SHCollisionSpace::collideTriggers(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept
{
const auto* A = narrowphasePair.A;
const auto* B = narrowphasePair.B;
const bool COLLIDING = SHCollisionDispatcher::Collide(*A, *B);
// Send results to contact manager
if (contactManager)
contactManager->AddTrigger(COLLIDING, key);
}
void SHCollisionSpace::collideManifolds(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept
{
auto* A = narrowphasePair.A;
auto* B = narrowphasePair.B;
SHManifold newManifold { .A = A, .B = B };
const bool COLLIDING = SHCollisionDispatcher::Collide(newManifold, *A, *B);
// Send results to contact manager
if (contactManager)
contactManager->AddManifold(COLLIDING, key, newManifold);
}
} // namespace SHADE

View File

@ -0,0 +1,131 @@
/****************************************************************************************
* \file SHCollisionSpace.h
* \author Diren D Bharwani, diren.dbharwani, 390002520
* \brief Interface for a Collision Space that handles collision detetction.
* This is to separate the logic between dynamics and collision detection,
* but the collision space does send information to the contact manager
* for dynamic resolution and collision state reporting.
*
* \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or
* disclosure of this file or its contents without the prior written consent
* of DigiPen Institute of Technology is prohibited.
****************************************************************************************/
#pragma once
// Project Headers
#include "Broadphase/SHDynamicAABBTree.h"
#include "Contacts/SHCollisionEvents.h"
#include "Physics/Dynamics/SHContactManager.h"
#include "SHCollider.h"
namespace SHADE
{
/*-----------------------------------------------------------------------------------*/
/* Type Definitions */
/*-----------------------------------------------------------------------------------*/
/**
* @brief
* Allows collision detection to be performed with the use of colliders & collision shapes.
* The space will generate manifold data for resolution when needed.
*/
class SH_API SHCollisionSpace
{
public:
/*---------------------------------------------------------------------------------*/
/* Constructors & Destructor */
/*---------------------------------------------------------------------------------*/
SHCollisionSpace () noexcept = default;
~SHCollisionSpace () noexcept = default;
/*---------------------------------------------------------------------------------*/
/* Getter Functions */
/*---------------------------------------------------------------------------------*/
const SHAABBTree::AABBs& GetBroadphaseAABBs () const noexcept;
/*---------------------------------------------------------------------------------*/
/* Setter Functions */
/*---------------------------------------------------------------------------------*/
void SetContactManager(SHContactManager* contactManager) noexcept;
/*---------------------------------------------------------------------------------*/
/* Member Functions */
/*---------------------------------------------------------------------------------*/
/**
* @brief
* Adds a collider to the collision space for it to be tested for collision with
* other colliders.
* @param collider
* A collider to add. Duplicates will be ignored.
*/
void AddCollider (SHCollider* collider) noexcept;
/**
* @brief
* Removes a collider from the collision space. This will prevent any collisions
* being detected between it and other colliders unless manually tested.
* @param collider
* A collider to remove. If a reference to it doesn't exist, it will be ignored.
*/
void RemoveCollider (SHCollider* collider) noexcept;
/**
* @brief
* Invoke this method to update the broadphase of colliders that have been moved since
* the last frame.
*/
void UpdateBroadphase () noexcept;
/**
* @brief
* Detects collisions between all colliders. Results are sent to the attached contact
* manager for resolution.
*/
void DetectCollisions () noexcept;
private:
/*---------------------------------------------------------------------------------*/
/* Type Definitions */
/*---------------------------------------------------------------------------------*/
struct NarrowphasePair
{
SHCollisionShape* A = nullptr;
SHCollisionShape* B = nullptr;
};
using Colliders = std::unordered_map<EntityID, SHCollider*>;
using NarrowphaseBatch = std::unordered_map<SHCollisionKey, NarrowphasePair, SHCollisionKeyHash>;
/*---------------------------------------------------------------------------------*/
/* Data Members */
/*---------------------------------------------------------------------------------*/
SHContactManager* contactManager = nullptr;
Colliders colliders;
NarrowphaseBatch narrowphaseBatch;
SHAABBTree broadphase;
/*---------------------------------------------------------------------------------*/
/* Member Functions */
/*---------------------------------------------------------------------------------*/
// Broadphase helpers
void broadphaseQuery (SHRigidBody::Type rigidBodyType, SHCollider* collider) noexcept;
// Narrowphase helpers
void collideTriggers (const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept;
void collideManifolds (const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept;
};
} // namespace SHADE

View File

@ -0,0 +1,173 @@
/****************************************************************************************
* \file SHContactManager.h
* \author Diren D Bharwani, diren.dbharwani, 390002520
* \brief Implementation for a Contact Manager that stores collision information and
* resolves contact constraints.
*
* \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or
* disclosure of this file or its contents without the prior written consent
* of DigiPen Institute of Technology is prohibited.
****************************************************************************************/
#include <SHpch.h>
// Primary Header
#include "SHContactManager.h"
namespace SHADE
{
/*-----------------------------------------------------------------------------------*/
/* Getter Functions Definitions */
/*-----------------------------------------------------------------------------------*/
const SHContactManager::TriggerEvents& SHContactManager::GetTriggerEvents() const noexcept
{
static TriggerEvents triggerEvents;
triggerEvents.clear();
for (auto& [id, state] : triggers)
triggerEvents.emplace_back(SHTriggerEvent{ id, state });
return triggerEvents;
}
const SHContactManager::CollisionEvents& SHContactManager::GetCollisionEvents() const noexcept
{
static CollisionEvents collisionEvents;
collisionEvents.clear();
for (auto& [id, manifold] : manifolds)
{
SHCollisionEvent collisionEvent
{
.info = id
, .state = manifold.state
, .normal = manifold.normal
};
for (uint32_t i = 0; i < manifold.numContacts; ++i)
collisionEvent.contactPoints[i] = manifold.contacts[i].position;
collisionEvents.emplace_back(collisionEvent);
}
return collisionEvents;
}
/*-----------------------------------------------------------------------------------*/
/* Public Member Functions Definitions */
/*-----------------------------------------------------------------------------------*/
void SHContactManager::Update() noexcept
{
// Clear expired or invalid collisions
for (auto manifold = manifolds.begin(); manifold != manifolds.end();)
{
const auto COLLISION_STATE = manifold->second.state;
const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT;
const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID;
if (IS_EXIT || IS_INVALID)
manifold = manifolds.erase(manifold);
else
++manifold;
}
// Clear expired or invalid triggers
for (auto trigger = triggers.begin(); trigger != triggers.end();)
{
const auto COLLISION_STATE = trigger->second;
const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT;
const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID;
if (IS_EXIT || IS_INVALID)
trigger = triggers.erase(trigger);
else
++trigger;
}
}
void SHContactManager::AddTrigger(bool isColliding, const SHCollisionKey& key) noexcept
{
auto trigger = triggers.find(key);
// If id not found, emplace new object.
// New object is in the invalid state
if (trigger == triggers.end())
trigger = triggers.emplace(key, SHCollisionState::INVALID).first;
SHCollisionState& state = trigger->second;
updateCollisionState(isColliding, state);
// If it was a false positive, remove the manifold immediately.
// Remove using iterator as it is on average faster.
if (state == SHCollisionState::INVALID)
trigger = triggers.erase(trigger);
}
void SHContactManager::AddManifold(bool isColliding, const SHCollisionKey& key, const SHManifold& newManifold) noexcept
{
auto manifold = manifolds.find(key);
// If id not found, emplace new manifold
if (manifold == manifolds.end())
manifold = manifolds.emplace(key, newManifold).first;
else
{
// TODO: Update existing manifolds with new data
}
SHCollisionState& state = manifold->second.state;
updateCollisionState(isColliding, state);
// If it was a false positive, remove the manifold immediately.
// Remove using iterator as it is on average faster.
if (state == SHCollisionState::INVALID)
manifold = manifolds.erase(manifold);
}
void SHContactManager::RemoveInvalidatedTrigger(EntityID eid) noexcept
{
remove(triggers, eid);
}
void SHContactManager::RemoveInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) noexcept
{
remove(triggers, eid, shapeIndex);
}
void SHContactManager::RemoveInvalidatedManifold(EntityID eid) noexcept
{
remove(manifolds, eid);
}
void SHContactManager::RemoveInvalidatedManifold(EntityID eid, uint32_t shapeIndex) noexcept
{
remove(manifolds, eid, shapeIndex);
}
/*-----------------------------------------------------------------------------------*/
/* Private Member Functions Definitions */
/*-----------------------------------------------------------------------------------*/
void SHContactManager::updateCollisionState(bool isColliding, SHCollisionState& state) noexcept
{
if (isColliding)
{
// New states start at invalid. In the first frame of collision, move to enter.
// If it already in enter, move to stay
state = state == SHCollisionState::INVALID ? SHCollisionState::ENTER : SHCollisionState::STAY;
}
else
{
// New states start at invalid. In false positive, remain unchanged.
// If previously colliding, move to exit.
state = state == SHCollisionState::INVALID ? SHCollisionState::INVALID : SHCollisionState::EXIT;
}
}
} // namespace SHADE

View File

@ -0,0 +1,103 @@
/****************************************************************************************
* \file SHContactManager.h
* \author Diren D Bharwani, diren.dbharwani, 390002520
* \brief Interface for a Contact Manager that stores collision information and
* resolves contact constraints.
*
* \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or
* disclosure of this file or its contents without the prior written consent
* of DigiPen Institute of Technology is prohibited.
****************************************************************************************/
#pragma once
#include <unordered_map>
// Project Headers
#include "Physics/Collision/Contacts/SHCollisionEvents.h"
#include "Physics/Collision/Contacts/SHCollisionKey.h"
#include "Physics/Collision/Contacts/SHManifold.h"
namespace SHADE
{
/*-----------------------------------------------------------------------------------*/
/* Type Definitions */
/*-----------------------------------------------------------------------------------*/
class SH_API SHContactManager
{
public:
/*---------------------------------------------------------------------------------*/
/* Type Definitions */
/*---------------------------------------------------------------------------------*/
using TriggerEvents = std::vector<SHTriggerEvent>;
using CollisionEvents = std::vector<SHCollisionEvent>;
/*---------------------------------------------------------------------------------*/
/* Constructors & Destructor */
/*---------------------------------------------------------------------------------*/
SHContactManager () noexcept = default;
~SHContactManager () noexcept = default;
/*---------------------------------------------------------------------------------*/
/* Operator Overloads */
/*---------------------------------------------------------------------------------*/
const TriggerEvents& GetTriggerEvents () const noexcept;
const CollisionEvents& GetCollisionEvents () const noexcept;
/*---------------------------------------------------------------------------------*/
/* Member Functions */
/*---------------------------------------------------------------------------------*/
/**
* @brief
* Removes any invalidated contacts and triggers.
* @return
*/
void Update () noexcept;
void AddTrigger (bool isColliding, const SHCollisionKey& key) noexcept;
void AddManifold (bool isColliding, const SHCollisionKey& key, const SHManifold& newManifold) noexcept;
void RemoveInvalidatedTrigger (EntityID eid) noexcept;
void RemoveInvalidatedTrigger (EntityID eid, uint32_t shapeIndex) noexcept;
void RemoveInvalidatedManifold (EntityID eid) noexcept;
void RemoveInvalidatedManifold (EntityID eid, uint32_t shapeIndex) noexcept;
private:
/*---------------------------------------------------------------------------------*/
/* Type Definitions */
/*---------------------------------------------------------------------------------*/
using Manifolds = std::unordered_map<SHCollisionKey, SHManifold, SHCollisionKeyHash>;
using Triggers = std::unordered_map<SHCollisionKey, SHCollisionState, SHCollisionKeyHash>;
/*---------------------------------------------------------------------------------*/
/* Data Members */
/*---------------------------------------------------------------------------------*/
Manifolds manifolds;
Triggers triggers;
/*---------------------------------------------------------------------------------*/
/* Member Functions */
/*---------------------------------------------------------------------------------*/
void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept;
// Removal Helpers
template <typename T>
void remove (std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid);
template <typename T>
void remove (std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid, uint32_t shapeIndex);
};
} // namespace SHADE
#include "SHContactManager.hpp"

View File

@ -0,0 +1,61 @@
/****************************************************************************************
* \file SHContactManager.hpp
* \author Diren D Bharwani, diren.dbharwani, 390002520
* \brief Implementation for templated methods of the Contact Manager.
*
* \copyright Copyright (C) 2022 DigiPen Institute of Technology. Reproduction or
* disclosure of this file or its contents without the prior written consent
* of DigiPen Institute of Technology is prohibited.
****************************************************************************************/
#pragma once
// Primary Header
#include "SHContactManager.h"
namespace SHADE
{
/*-----------------------------------------------------------------------------------*/
/* Private Member Functions Definitions */
/*-----------------------------------------------------------------------------------*/
template <typename T>
void SHContactManager::remove(std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid)
{
if (container.empty())
return;
for (auto invalidated = container.begin(); invalidated != container.end();)
{
const auto& ID = invalidated->first;
const bool MATCHES_A = ID.GetEntityA() == eid;
const bool MATCHES_B = ID.GetEntityB() == eid;
if (MATCHES_A || MATCHES_B)
invalidated = container.erase(invalidated);
else
++invalidated;
}
}
template <typename T>
void SHContactManager::remove(std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid, uint32_t shapeIndex)
{
if (container.empty())
return;
for (auto invalidated = container.begin(); invalidated != container.end();)
{
const auto& ID = invalidated->first;
const bool MATCHES_A = ID.GetEntityA() == eid && ID.GetShapeIndexA() == shapeIndex;
const bool MATCHES_B = ID.GetEntityB() == eid && ID.GetShapeIndexB() == shapeIndex;
if (MATCHES_A || MATCHES_B)
invalidated = container.erase(invalidated);
else
++invalidated;
}
}
} // namespace SHADE

View File

@ -13,11 +13,6 @@
// Primary Header
#include "SHPhysicsWorld.h"
// Project Headers
#include "Physics/Collision/Narrowphase/SHCollision.h"
#include "Physics/Collision/Narrowphase/SHCollisionDispatch.h"
namespace SHADE
{
/*-----------------------------------------------------------------------------------*/
@ -25,59 +20,39 @@ namespace SHADE
/*-----------------------------------------------------------------------------------*/
SHPhysicsWorld::SHPhysicsWorld(const WorldSettings& worldSettings) noexcept
: settings { worldSettings }
: settings { worldSettings }
, collisionSpace { nullptr }
{
SHLOG_INFO_D("Creating Physics World")
}
SHPhysicsWorld::~SHPhysicsWorld() noexcept
{
collisionSpace = nullptr;
}
/*-----------------------------------------------------------------------------------*/
/* Getter Functions Definitions */
/*-----------------------------------------------------------------------------------*/
const SHAABBTree::AABBs& SHPhysicsWorld::GetBroadphaseAABBs() const noexcept
const SHContactManager::TriggerEvents& SHPhysicsWorld::GetTriggerEvents() const noexcept
{
return broadphase.GetAABBs();
return contactManager.GetTriggerEvents();
}
const SHPhysicsWorld::TriggerEvents& SHPhysicsWorld::GetTriggerEvents() const noexcept
const SHContactManager::CollisionEvents& SHPhysicsWorld::GetCollisionEvents() const noexcept
{
static TriggerEvents triggerEvents;
triggerEvents.clear();
for (auto& [id, state] : triggers)
triggerEvents.emplace_back(SHTriggerEvent{ id, state });
return triggerEvents;
return contactManager.GetCollisionEvents();
}
const SHPhysicsWorld::CollisionEvents& SHPhysicsWorld::GetCollisionEvents() const noexcept
/*-----------------------------------------------------------------------------------*/
/* Setter Functions Definitions */
/*-----------------------------------------------------------------------------------*/
void SHPhysicsWorld::SetCollisionSpace(SHCollisionSpace* _collisionSpace) noexcept
{
static CollisionEvents collisionEvents;
collisionEvents.clear();
for (auto& [id, manifold] : manifolds)
{
SHCollisionEvent collisionEvent
{
.info = id
, .state = manifold.state
, .normal = manifold.normal
};
for (uint32_t i = 0; i < manifold.numContacts; ++i)
collisionEvent.contactPoints[i] = manifold.contacts[i].position;
collisionEvents.emplace_back(collisionEvent);
}
return collisionEvents;
collisionSpace = _collisionSpace;
_collisionSpace->SetContactManager(&contactManager);
}
/*-----------------------------------------------------------------------------------*/
@ -97,93 +72,54 @@ namespace SHADE
{
rigidBodies.erase(rigidBody->entityID);
// Attempt to remove any invalidated manifolds
if (manifolds.empty())
return;
for (auto manifoldPair = manifolds.begin(); manifoldPair != manifolds.end();)
{
const auto& ID = manifoldPair->first;
const bool MATCHES_A = ID.GetEntityA() == rigidBody->entityID;
const bool MATCHES_B = ID.GetEntityB() == rigidBody->entityID;
if (MATCHES_A || MATCHES_B)
manifoldPair = manifolds.erase(manifoldPair);
else
++manifoldPair;
}
// Contact manager to remove invalidated contacts
contactManager.RemoveInvalidatedManifold(rigidBody->entityID);
// TODO: Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep
}
void SHPhysicsWorld::AddCollider(SHCollider* collider) noexcept
{
const bool INSERTED = colliders.emplace(collider->entityID, collider).second;
if (!INSERTED)
{
SHLOG_WARNING_D("Attempting to add duplicate collider {} to the Physics World!", collider->entityID)
return;
}
collider->broadphase = &broadphase;
// Add all existing shapes to the broadphase
for (const auto* shape : collider->shapes)
broadphase.Insert(shape->id, shape->ComputeAABB());
}
void SHPhysicsWorld::RemoveCollider(SHCollider* collider) noexcept
{
colliders.erase(collider->entityID);
const uint32_t NUM_SHAPES = static_cast<uint32_t>(collider->shapes.size());
if (NUM_SHAPES == 0)
return;
for (uint32_t i = 0; i < NUM_SHAPES; ++i)
{
const SHCollisionShape* SHAPE = collider->shapes[i];
broadphase.Remove(SHAPE->id);
if (SHAPE->IsTrigger())
removeInvalidatedTrigger(collider->entityID, i);
else
removeInvalidatedManifold(collider->entityID, i);
}
/*
* TODO:
* Get collider's rigid body
* Run through the rigid body's contact graph and wake all of its non-static bodies that are asleep
*/
}
void SHPhysicsWorld::UpdateBroadphase(SHCollider* collider) noexcept
{
auto& shapes = collider->GetCollisionShapes();
for (auto* shape : shapes)
broadphase.Update(shape->id, shape->ComputeAABB());
}
void SHPhysicsWorld::Step(float dt)
{
// Clear containers of exit state collisions
updateEvents();
// Contact manager to clear expired contacts
contactManager.Update();
// TODO: Profile each of these
runBroadphase ();
runNarrowphase();
/*
* Detect Collisions
*/
if (collisionSpace)
collisionSpace->DetectCollisions();
/*
* Integrate Forces
*/
for (auto* rigidBody : rigidBodies | std::views::values)
{
if (!rigidBody->IsActive())
continue;
rigidBody->ComputeWorldData();
integrateForces(*rigidBody, dt);
}
/*
* TODO: Resolve Contacts
*/
/*
* Integrate Velocities
*/
for (auto* rigidBody : rigidBodies | std::views::values)
{
if (!rigidBody->IsActive())
continue;
integrateVelocities(*rigidBody, dt);
}
}
/*-----------------------------------------------------------------------------------*/
@ -262,275 +198,4 @@ namespace SHADE
rigidBody.ClearForces();
}
void SHPhysicsWorld::runBroadphase() noexcept
{
// Update any colliders that have moved
for (auto& collider : colliders | std::views::values)
{
if (!collider->hasMoved)
continue;
// Clear hasMoved flag here
collider->hasMoved = false;
// Update moved shapes in broadphase
for (auto* shape : collider->shapes)
broadphase.Update(shape->id, shape->ComputeAABB());
}
// Query: Kinematic Triggers, Awake Dynamic Bodies & Dynamic Triggers
for (auto& collider : colliders | std::views::values)
{
// Default static bodies
if (!collider->rigidBody)
continue;
// Explicit static bodies
const bool IS_STATIC = collider->rigidBody->GetType() == SHRigidBody::Type::STATIC;
if (IS_STATIC)
continue;
// All remaining are kinematic or dynamic
// Iterate through shapes: if kinematic / dynamic trigger, else if dynamic & awake
if (collider->rigidBody->GetType() == SHRigidBody::Type::KINEMATIC)
queryKinematic(collider);
if (collider->rigidBody->GetType() == SHRigidBody::Type::DYNAMIC)
queryDynamic(collider);
}
}
void SHPhysicsWorld::queryKinematic(SHCollider* collider) noexcept
{
for (auto* shape : collider->shapes)
{
// For kinematic shapes, we only query triggers against everything else
if (!shape->IsTrigger())
continue;
auto& potentialCollisions = broadphase.Query(shape->id, shape->ComputeAABB());
// Build narrow-phase pairs
auto* shapeA = shape;
const EntityID ID_A = shape->id.GetEntityID();
const uint32_t INDEX_A = shape->id.GetShapeIndex();
for (auto& id : potentialCollisions)
{
// Get corresponding shape
const EntityID ID_B = id.GetEntityID();
const uint32_t INDEX_B = id.GetShapeIndex();
auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B);
// Build collision ID
SHCollisionKey collisionKey;
collisionKey.SetEntityA(ID_A);
collisionKey.SetEntityB(ID_B);
collisionKey.SetCollisionShapeA(INDEX_A);
collisionKey.SetCollisionShapeB(INDEX_B);
// Check if it already exists. If it doesn't, put into batch.
auto narrowphasePair = narrowphaseBatch.find(collisionKey);
if (narrowphasePair == narrowphaseBatch.end())
narrowphaseBatch.emplace(collisionKey, NarrowphasePair{ shapeA, shapeB });
}
}
}
void SHPhysicsWorld::queryDynamic(SHCollider* collider) noexcept
{
for (auto* shape : collider->shapes)
{
auto& potentialCollisions = broadphase.Query(shape->id, shape->ComputeAABB());
// Build narrow-phase pairs
auto* shapeA = shape;
const EntityID ID_A = shape->id.GetEntityID();
const uint32_t INDEX_A = shape->id.GetShapeIndex();
for (auto& id : potentialCollisions)
{
// Get corresponding shape
const EntityID ID_B = id.GetEntityID();
const uint32_t INDEX_B = id.GetShapeIndex();
auto* shapeB = colliders[ID_B]->GetCollisionShape(INDEX_B);
// Build collision ID
SHCollisionKey collisionKey;
collisionKey.SetEntityA(ID_A);
collisionKey.SetEntityB(ID_B);
collisionKey.SetCollisionShapeA(INDEX_A);
collisionKey.SetCollisionShapeB(INDEX_B);
// Check if it already exists. If it doesn't, put into batch.
auto narrowphasePair = narrowphaseBatch.find(collisionKey);
if (narrowphasePair == narrowphaseBatch.end())
narrowphaseBatch.emplace(collisionKey, NarrowphasePair{ shapeA, shapeB });
}
}
}
void SHPhysicsWorld::runNarrowphase() noexcept
{
if (narrowphaseBatch.empty())
return;
for (auto& [id, narrowphasePair] : narrowphaseBatch)
{
const bool IS_A_TRIGGER = narrowphasePair.A->IsTrigger();
const bool IS_B_TRIGGER = narrowphasePair.B->IsTrigger();
// Check if ID exists in trigger
if (IS_A_TRIGGER || IS_B_TRIGGER)
collideTriggers(id, narrowphasePair);
else
collideManifolds(id, narrowphasePair);
}
// Clear every frame
narrowphaseBatch.clear();
}
void SHPhysicsWorld::collideTriggers(const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept
{
const auto* A = narrowphasePair.A;
const auto* B = narrowphasePair.B;
const bool COLLIDING = SHCollisionDispatcher::Collide(*A, *B);
auto trigger = triggers.find(id);
// If id not found, emplace new object.
// New object is in the invalid state
if (trigger == triggers.end())
trigger = triggers.emplace(id, SHCollisionState::INVALID).first;
SHCollisionState& state = trigger->second;
updateCollisionState(COLLIDING, state);
// If it was a false positive, remove the manifold immediately.
// Remove using iterator as it is on average faster.
if (state == SHCollisionState::INVALID)
trigger = triggers.erase(trigger);
}
void SHPhysicsWorld::collideManifolds(const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept
{
auto* A = narrowphasePair.A;
auto* B = narrowphasePair.B;
SHManifold newManifold { .A = A, .B = B };
const bool COLLIDING = SHCollisionDispatcher::Collide(newManifold, *A, *B);
auto manifold = manifolds.find(id);
// If id not found, emplace new manifold
if (manifold == manifolds.end())
manifold = manifolds.emplace(id, newManifold).first;
else
{
// TODO: Update existing manifolds with new data
}
SHCollisionState& state = manifold->second.state;
updateCollisionState(COLLIDING, state);
// If it was a false positive, remove the manifold immediately.
// Remove using iterator as it is on average faster.
if (state == SHCollisionState::INVALID)
manifold = manifolds.erase(manifold);
}
void SHPhysicsWorld::updateCollisionState(bool isColliding, SHCollisionState& state) noexcept
{
if (isColliding)
{
// New states start at invalid. In the first frame of collision, move to enter.
// If it already in enter, move to stay
state = state == SHCollisionState::INVALID ? SHCollisionState::ENTER : SHCollisionState::STAY;
}
else
{
// New states start at invalid. In false positive, remain unchanged.
// If previously colliding, move to exit.
state = state == SHCollisionState::INVALID ? SHCollisionState::INVALID : SHCollisionState::EXIT;
}
}
void SHPhysicsWorld::updateEvents() noexcept
{
// Clear expired or invalid collisions
for (auto manifold = manifolds.begin(); manifold != manifolds.end();)
{
const auto COLLISION_STATE = manifold->second.state;
const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT;
const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID;
if (IS_EXIT || IS_INVALID)
manifold = manifolds.erase(manifold);
else
++manifold;
}
// Clear expired or invalid triggers
for (auto trigger = triggers.begin(); trigger != triggers.end();)
{
const auto COLLISION_STATE = trigger->second;
const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT;
const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID;
if (IS_EXIT || IS_INVALID)
trigger = triggers.erase(trigger);
else
++trigger;
}
}
void SHPhysicsWorld::removeInvalidatedTrigger(EntityID eid, uint32_t shapeIndex)
{
if (triggers.empty())
return;
for (auto invalidatedTrigger = triggers.begin(); invalidatedTrigger != triggers.end();)
{
const auto& ID = invalidatedTrigger->first;
const bool MATCHES_A = ID.GetEntityA() == eid && ID.GetShapeIndexA() == shapeIndex;
const bool MATCHES_B = ID.GetEntityB() == eid && ID.GetShapeIndexB() == shapeIndex;
if (MATCHES_A || MATCHES_B)
invalidatedTrigger = triggers.erase(invalidatedTrigger);
else
++invalidatedTrigger;
}
}
void SHPhysicsWorld::removeInvalidatedManifold(EntityID eid, uint32_t shapeIndex)
{
if (manifolds.empty())
return;
for (auto invalidatedManifold = manifolds.begin(); invalidatedManifold != manifolds.end();)
{
const auto& ID = invalidatedManifold->first;
const bool MATCHES_A = ID.GetEntityA() == eid && ID.GetShapeIndexA() == shapeIndex;
const bool MATCHES_B = ID.GetEntityB() == eid && ID.GetShapeIndexB() == shapeIndex;
if (MATCHES_A || MATCHES_B)
invalidatedManifold = manifolds.erase(invalidatedManifold);
else
++invalidatedManifold;
}
}
} // namespace SHADE

View File

@ -13,10 +13,8 @@
#include <unordered_map>
// Project Headers
#include "Physics/Collision/Broadphase/SHDynamicAABBTree.h"
#include "Physics/Collision/Contacts/SHCollisionEvents.h"
#include "Physics/Collision/Contacts/SHManifold.h"
#include "Physics/Collision/SHCollider.h"
#include "Physics/Collision/SHCollisionSpace.h"
#include "SHContactManager.h"
#include "SHRigidBody.h"
@ -26,10 +24,15 @@ namespace SHADE
/* Type Definitions */
/*-----------------------------------------------------------------------------------*/
/**
* @brief
* Encapsulates the overall simulation of physics. The bulk of dynamics are handled here,
* with the collision detection handled by an attached collision space. <br/>
* A collision space must be created separately and attached with the world.
*/
class SH_API SHPhysicsWorld
{
public:
/*---------------------------------------------------------------------------------*/
/* Type Definitions */
/*---------------------------------------------------------------------------------*/
@ -47,9 +50,6 @@ namespace SHADE
bool sleepingEnabled = true;
};
using TriggerEvents = std::vector<SHTriggerEvent>;
using CollisionEvents = std::vector<SHCollisionEvent>;
/*---------------------------------------------------------------------------------*/
/* Constructors & Destructor */
/*---------------------------------------------------------------------------------*/
@ -71,29 +71,44 @@ namespace SHADE
/* Getter Functions */
/*---------------------------------------------------------------------------------*/
const SHAABBTree::AABBs& GetBroadphaseAABBs () const noexcept;
const SHContactManager::TriggerEvents& GetTriggerEvents () const noexcept;
const SHContactManager::CollisionEvents& GetCollisionEvents () const noexcept;
const TriggerEvents& GetTriggerEvents () const noexcept;
const CollisionEvents& GetCollisionEvents () const noexcept;
/*---------------------------------------------------------------------------------*/
/* Setter Functions */
/*---------------------------------------------------------------------------------*/
void SetCollisionSpace(SHCollisionSpace* collisionSpace) noexcept;
/*---------------------------------------------------------------------------------*/
/* Member Functions */
/*---------------------------------------------------------------------------------*/
/**
* @brief
* Adds a rigid body to the world for it to be simulated using motion dynamics.
* @param rigidBody
* A rigid body to add. Duplicates will be ignored.
*/
void AddRigidBody (SHRigidBody* rigidBody) noexcept;
void RemoveRigidBody (SHRigidBody* rigidBody) noexcept;
void AddCollider (SHCollider* collider) noexcept;
void RemoveCollider (SHCollider* collider) noexcept;
/**
* @brief
* Invoke this method to update the broadphase of a collider while the simulation
* is not running.
* @param collider
* The collider to update.
* Removes a rigid body from the world. It's motion will not be affected unless
* explicitly modified.
* @param rigidBody
* A rigid body to add. Duplicates will be ignored.
*/
void RemoveRigidBody (SHRigidBody* rigidBody) noexcept;
/**
* @brief
* Performs a single simulation step. <br/>
* Detect Collisions -> Integrate Forces -> Resolve Contacts -> Integrate Velocities
* @param dt
* A discrete time step for the simulation. This should be consistent for deteministic
* behaviour.
*/
void UpdateBroadphase (SHCollider* collider) noexcept;
void Step (float dt);
private:
@ -101,42 +116,18 @@ namespace SHADE
/* Type Definitions */
/*---------------------------------------------------------------------------------*/
struct NarrowphasePair
{
SHCollisionShape* A = nullptr;
SHCollisionShape* B = nullptr;
};
// EntityIDs are used to map resolved contraints back to bodies
using RigidBodies = std::unordered_map<EntityID, SHRigidBody*>;
using Colliders = std::unordered_map<EntityID, SHCollider*>;
// Collisions
using NarrowphaseBatch = std::unordered_map<SHCollisionKey, NarrowphasePair, SHCollisionKeyHash>;
using Manifolds = std::unordered_map<SHCollisionKey, SHManifold, SHCollisionKeyHash>;
using Triggers = std::unordered_map<SHCollisionKey, SHCollisionState, SHCollisionKeyHash>;
/*---------------------------------------------------------------------------------*/
/* Data Members */
/*---------------------------------------------------------------------------------*/
WorldSettings settings;
// Containers
SHCollisionSpace* collisionSpace;
RigidBodies rigidBodies;
Colliders colliders;
NarrowphaseBatch narrowphaseBatch;
Manifolds manifolds;
Triggers triggers;
// World components
SHAABBTree broadphase;
SHContactManager contactManager;
/*---------------------------------------------------------------------------------*/
/* Function Members */
@ -146,22 +137,8 @@ namespace SHADE
void integrateForces (SHRigidBody& rigidBody, float dt) const noexcept;
void integrateVelocities (SHRigidBody& rigidBody, float dt) const noexcept;
// Broadphase helpers
void runBroadphase () noexcept;
void queryKinematic (SHCollider* collider) noexcept;
void queryDynamic (SHCollider* collider) noexcept;
// Narrowphase helpers
void runNarrowphase () noexcept;
void collideTriggers (const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept;
void collideManifolds (const SHCollisionKey& id, NarrowphasePair& narrowphasePair) noexcept;
void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept;
void updateEvents () noexcept;
void removeInvalidatedTrigger (EntityID eid, uint32_t shapeIndex);
void removeInvalidatedManifold (EntityID eid, uint32_t shapeIndex);
};

View File

@ -87,9 +87,8 @@ namespace SHADE
{
const SHColour& AABB_COLOUR = physicsDebugDrawSystem->DEBUG_DRAW_COLOURS[SHUtilities::ConvertEnum(Colours::BROADPHASE)];
auto& broadphaseAABBs = physicsSystem->GetWorld()->GetBroadphaseAABBs();
for (auto& aabb : broadphaseAABBs)
const auto& BROADPHASE_AABBS = physicsSystem->collisionSpace->GetBroadphaseAABBs();
for (auto& aabb : BROADPHASE_AABBS)
{
// Compute AABB Transform
const SHMatrix TRS = SHMatrix::Transform(aabb.GetCenter(), SHQuaternion::Identity, aabb.GetWorldExtents() * 2.0f);

View File

@ -16,6 +16,7 @@
// Project Headers
#include "ECS_Base/Managers/SHSystemManager.h"
#include "Math/Transform/SHTransformComponent.h"
#include "Scene/SHSceneManager.h"
#include "Scripting/SHScriptEngine.h"
@ -50,15 +51,25 @@ namespace SHADE
const auto& RIGIDBODY_DENSE = SHComponentManager::GetDense<SHRigidBodyComponent>();
for (auto& rigidBodyComponent : RIGIDBODY_DENSE)
{
auto* transformComponent = SHComponentManager::GetComponent_s<SHTransformComponent>(rigidBodyComponent.GetEID());
const EntityID EID = rigidBodyComponent.GetEID();
// Skip missing transforms
auto* transformComponent = SHComponentManager::GetComponent_s<SHTransformComponent>(EID);
if (!transformComponent)
continue;
// Skip invalid bodies (Should not occur)
if (!rigidBodyComponent.rigidBody)
continue;
// Skip inactive bodies
const bool IS_ACTIVE = SHSceneManager::CheckNodeAndComponentsActive<SHRigidBodyComponent>(EID);
if (!IS_ACTIVE)
continue;
const SHMotionState& MOTION_STATE = rigidBodyComponent.rigidBody->GetMotionState();
// Skip objects that have not moved
if (!MOTION_STATE)
continue;

View File

@ -16,6 +16,7 @@
// Project Headers
#include "ECS_Base/Managers/SHComponentManager.h"
#include "Math/Transform/SHTransformComponent.h"
#include "Scene/SHSceneManager.h"
namespace SHADE
{
@ -40,35 +41,54 @@ namespace SHADE
for (auto& [entityID, physicsObject] : physicsObjects)
{
const auto* TRANSFORM_COMPONENT = SHComponentManager::GetComponent_s<SHTransformComponent>(entityID);
if (!TRANSFORM_COMPONENT || !TRANSFORM_COMPONENT->HasChanged())
continue;
const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition();
const SHQuaternion& WORLD_ROT = TRANSFORM_COMPONENT->GetWorldOrientation();
const SHVec3& WORLD_SCL = TRANSFORM_COMPONENT->GetWorldScale();
// Assume transform is always active
const bool UPDATE_TRANSFORM = TRANSFORM_COMPONENT && TRANSFORM_COMPONENT->HasChanged();
// We assume that all engine components and physics object components have been successfully linked
if (physicsObject.rigidBody)
{
SHMotionState& motionState = physicsObject.rigidBody->GetMotionState();
const bool IS_ACTIVE = SHSceneManager::CheckNodeAndComponentsActive<SHRigidBodyComponent>(entityID);
const bool RIGIDBODY_ACTIVE = physicsObject.rigidBody->IsActive();
motionState.ForcePosition(TRANSFORM_COMPONENT->GetWorldPosition());
motionState.ForceOrientation(TRANSFORM_COMPONENT->GetWorldOrientation());
if (IS_ACTIVE != RIGIDBODY_ACTIVE)
physicsObject.rigidBody->SetIsActive(IS_ACTIVE);
if (UPDATE_TRANSFORM)
{
const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition();
const SHQuaternion& WORLD_ROT = TRANSFORM_COMPONENT->GetWorldOrientation();
SHMotionState& motionState = physicsObject.rigidBody->GetMotionState();
motionState.ForcePosition(WORLD_POS);
motionState.ForceOrientation(WORLD_ROT);
}
}
if (physicsObject.collider)
{
physicsObject.collider->SetPosition(WORLD_POS);
physicsObject.collider->SetOrientation(WORLD_ROT);
physicsObject.collider->SetScale(WORLD_SCL);
const bool IS_ACTIVE = SHSceneManager::CheckNodeAndComponentsActive<SHColliderComponent>(entityID);
const bool COLLIDER_ACTIVE = physicsObject.collider->IsActive();
physicsObject.collider->RecomputeShapes();
if (IS_ACTIVE != COLLIDER_ACTIVE)
physicsObject.collider->SetIsActive(IS_ACTIVE);
physicsSystem->physicsWorld->UpdateBroadphase(physicsObject.collider);
if (UPDATE_TRANSFORM)
{
const SHVec3& WORLD_POS = TRANSFORM_COMPONENT->GetWorldPosition();
const SHQuaternion& WORLD_ROT = TRANSFORM_COMPONENT->GetWorldOrientation();
const SHVec3& WORLD_SCL = TRANSFORM_COMPONENT->GetWorldScale();
physicsObject.collider->SetPosition(WORLD_POS);
physicsObject.collider->SetOrientation(WORLD_ROT);
physicsObject.collider->SetScale(WORLD_SCL);
physicsObject.collider->RecomputeShapes();
}
}
}
physicsSystem->collisionSpace->UpdateBroadphase();
}
} // namespace SHADE

View File

@ -21,6 +21,7 @@
#include "Editor/SHEditor.h"
#include "Physics/Collision/CollisionTags/SHCollisionTagMatrix.h"
#include "Physics/Interface/SHColliderComponent.h"
#include "Scripting/SHScriptEngine.h"
namespace SHADE
{
@ -33,6 +34,7 @@ namespace SHADE
, interpolationFactor { 0.0 }
, fixedDT { DEFAULT_FIXED_STEP }
, physicsWorld { nullptr }
, collisionSpace { nullptr }
{
// Add more events here to register them
@ -44,6 +46,7 @@ namespace SHADE
SHPhysicsSystem::~SHPhysicsSystem() noexcept
{
delete collisionSpace;
delete physicsWorld;
}
@ -51,11 +54,6 @@ namespace SHADE
/* Getter Function Definitions */
/*-----------------------------------------------------------------------------------*/
const SHPhysicsWorld* SHPhysicsSystem::GetWorld() const noexcept
{
return physicsWorld;
}
double SHPhysicsSystem::GetFixedUpdateRate() const noexcept
{
return 1.0 / fixedDT;
@ -66,6 +64,16 @@ namespace SHADE
return fixedDT;
}
const std::vector<SHTriggerEvent>& SHPhysicsSystem::GetTriggerInfo() const noexcept
{
return physicsWorld->GetTriggerEvents();
}
const std::vector<SHCollisionEvent>& SHPhysicsSystem::GetCollisionInfo() const noexcept
{
return physicsWorld->GetCollisionEvents();
}
/*-----------------------------------------------------------------------------------*/
/* Setter Function Definitions */
/*-----------------------------------------------------------------------------------*/
@ -137,7 +145,48 @@ namespace SHADE
void SHPhysicsSystem::ForceUpdate()
{
if (!physicsWorld)
return;
auto* scriptingSystem = SHSystemManager::GetSystem<SHScriptEngine>();
if (scriptingSystem == nullptr)
{
SHLOGV_ERROR("Unable to invoke collision and trigger script events due to missing SHScriptEngine!");
}
scriptingSystem->ExecuteFixedUpdates();
physicsWorld->Step(static_cast<float>(fixedDT));
const auto& RIGIDBODY_DENSE = SHComponentManager::GetDense<SHRigidBodyComponent>();
for (auto& rigidBodyComponent : RIGIDBODY_DENSE)
{
const EntityID EID = rigidBodyComponent.GetEID();
// Skip missing transforms
auto* transformComponent = SHComponentManager::GetComponent_s<SHTransformComponent>(EID);
if (!transformComponent)
continue;
// Skip invalid bodies (Should not occur)
if (!rigidBodyComponent.rigidBody)
continue;
// Skip inactive bodies
const bool IS_ACTIVE = SHSceneManager::CheckNodeAndComponentsActive<SHRigidBodyComponent>(EID);
if (!IS_ACTIVE)
continue;
const SHMotionState& MOTION_STATE = rigidBodyComponent.rigidBody->GetMotionState();
// Skip objects that have not moved
if (!MOTION_STATE)
continue;
// We ignore interpolations here because we are only stepping once
transformComponent->SetWorldPosition(MOTION_STATE.position);
transformComponent->SetWorldOrientation(MOTION_STATE.orientation);
}
}
/*-----------------------------------------------------------------------------------*/
@ -162,17 +211,29 @@ namespace SHADE
{
if (PHYSICS_OBJECT.rigidBody)
physicsWorld->RemoveRigidBody(PHYSICS_OBJECT.rigidBody);
if (PHYSICS_OBJECT.collider)
physicsWorld->RemoveCollider(PHYSICS_OBJECT.collider);
}
delete physicsWorld;
physicsWorld = nullptr;
}
// Create the physics world
physicsWorld = new SHPhysicsWorld;
if (collisionSpace)
{
for (const auto& PHYSICS_OBJECT : physicsObjectManager.GetPhysicsObjects() | std::views::values)
{
if (PHYSICS_OBJECT.collider)
collisionSpace->RemoveCollider(PHYSICS_OBJECT.collider);
}
delete collisionSpace;
collisionSpace = nullptr;
}
// Create the physics world & collision space
physicsWorld = new SHPhysicsWorld;
collisionSpace = new SHCollisionSpace;
physicsWorld->SetCollisionSpace(collisionSpace);
// Immediately add all existing bodies and colliders to the world.
// Since we recreated the scene and the world, the initial data has been reset and determinism is guaranteed.
@ -184,7 +245,7 @@ namespace SHADE
physicsWorld->AddRigidBody(PHYSICS_OBJECT.rigidBody);
if (PHYSICS_OBJECT.collider)
physicsWorld->AddCollider(PHYSICS_OBJECT.collider);
collisionSpace->AddCollider(PHYSICS_OBJECT.collider);
}
return onSceneInitEvent.get()->handle;
@ -203,9 +264,12 @@ namespace SHADE
physicsWorld->RemoveRigidBody(PHYSICS_OBJECT.rigidBody);
if (PHYSICS_OBJECT.collider)
physicsWorld->RemoveCollider(PHYSICS_OBJECT.collider);
collisionSpace->RemoveCollider(PHYSICS_OBJECT.collider);
}
delete collisionSpace;
collisionSpace = nullptr;
delete physicsWorld;
physicsWorld = nullptr;
@ -247,10 +311,10 @@ namespace SHADE
{
physicsObjectManager.AddCollider(EID);
if (physicsWorld)
if (collisionSpace)
{
auto* collider = physicsObjectManager.GetPhysicsObject(EID)->collider;
physicsWorld->AddCollider(collider);
collisionSpace->AddCollider(collider);
}
}
@ -289,17 +353,16 @@ namespace SHADE
if (IS_COLLIDER)
{
if (physicsWorld)
if (collisionSpace)
{
auto* collider = physicsObjectManager.GetPhysicsObject(EID)->collider;
physicsWorld->RemoveCollider(collider);
collisionSpace->RemoveCollider(collider);
}
physicsObjectManager.RemoveCollider(EID);
}
return onComponentRemovedEvent.get()->handle;
}
} // namespace SHADE
} // namespace SHADE

View File

@ -33,6 +33,13 @@ namespace SHADE
*/
class SH_API SHPhysicsSystem final : public SHSystem
{
private:
/*---------------------------------------------------------------------------------*/
/* Friends */
/*---------------------------------------------------------------------------------*/
friend class SHPhysicsDebugDrawSystem;
public:
/*---------------------------------------------------------------------------------*/
/* Constructors & Destructor */
@ -45,7 +52,6 @@ namespace SHADE
/* Getter Functions */
/*---------------------------------------------------------------------------------*/
[[nodiscard]] const SHPhysicsWorld* GetWorld () const noexcept;
[[nodiscard]] double GetFixedUpdateRate() const noexcept;
[[nodiscard]] double GetFixedDT () const noexcept;
@ -56,6 +62,9 @@ namespace SHADE
void SetFixedUpdateRate(double fixedUpdateRate) noexcept;
void SetFixedDT(double fixedDt) noexcept;
const std::vector<SHTriggerEvent>& GetTriggerInfo () const noexcept;
const std::vector<SHCollisionEvent>& GetCollisionInfo () const noexcept;
/*---------------------------------------------------------------------------------*/
/* Member Functions */
/*---------------------------------------------------------------------------------*/
@ -137,6 +146,7 @@ namespace SHADE
// Sub-systems / managers
SHPhysicsWorld* physicsWorld;
SHCollisionSpace* collisionSpace;
SHPhysicsObjectManager physicsObjectManager;
/*---------------------------------------------------------------------------------*/

View File

@ -1,8 +1,6 @@
/************************************************************************************//*!
\file SHPhysicsSystemInterface.cpp
\author Tng Kah Wei, kahwei.tng, 390009620
\par email: kahwei.tng\@digipen.edu
\date Oct 31, 2022
\author Diren D Bharwani, diren.dbharwani, 390002520
\brief Contains the definitions of the functions of the static
SHPhysicsSystemInterface class.
@ -10,10 +8,13 @@ Copyright (C) 2022 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the prior written consent
of DigiPen Institute of Technology is prohibited.
*//*************************************************************************************/
// Precompiled Headers
#include "SHpch.h"
// Primary Header
#include "SHPhysicsSystemInterface.h"
// Project Includes
#include "ECS_Base/Managers/SHSystemManager.h"
#include "Physics/System/SHPhysicsSystem.h"
@ -23,42 +24,47 @@ namespace SHADE
/*-----------------------------------------------------------------------------------*/
/* Static Usage Functions */
/*-----------------------------------------------------------------------------------*/
const std::vector<SHCollisionInfo>& SHPhysicsSystemInterface::GetCollisionInfo() noexcept
const std::vector<SHCollisionEvent>& SHPhysicsSystemInterface::GetCollisionInfo() noexcept
{
static std::vector<SHCollisionInfo> emptyVec;
static std::vector<SHCollisionEvent> emptyVec;
//auto phySystem = SHSystemManager::GetSystem<SHPhysicsSystem>();
//if (phySystem)
//{
// return phySystem->GetAllCollisionInfo();
//}
auto* physicsSystem = SHSystemManager::GetSystem<SHPhysicsSystem>();
if (physicsSystem)
return physicsSystem->GetCollisionInfo();
//SHLOGV_WARNING("Failed to get collision events. Empty vector returned instead.");
SHLOGV_WARNING("Failed to get collision events. Empty vector returned instead.");
return emptyVec;
}
const std::vector<SHCollisionInfo>& SHPhysicsSystemInterface::GetTriggerInfo() noexcept
const std::vector<SHTriggerEvent>& SHPhysicsSystemInterface::GetTriggerInfo() noexcept
{
static std::vector<SHCollisionInfo> emptyVec;
static std::vector<SHTriggerEvent> emptyVec;
//auto phySystem = SHSystemManager::GetSystem<SHPhysicsSystem>();
//if (phySystem)
//{
// return phySystem->GetAllTriggerInfo();
//}
auto* physicsSystem = SHSystemManager::GetSystem<SHPhysicsSystem>();
if (physicsSystem)
return physicsSystem->GetTriggerInfo();
//SHLOGV_WARNING("Failed to get trigger events. Empty vector returned instead.");
SHLOGV_WARNING("Failed to get trigger events. Empty vector returned instead.");
return emptyVec;
}
double SHPhysicsSystemInterface::GetFixedDT() noexcept
{
auto phySystem = SHSystemManager::GetSystem<SHPhysicsSystem>();
if (phySystem)
{
return 1.0 / phySystem->GetFixedUpdateRate();
}
auto* physicsSystem = SHSystemManager::GetSystem<SHPhysicsSystem>();
if (physicsSystem)
return physicsSystem->GetFixedDT();
SHLOGV_WARNING("Failed to get fixed delta time. 0.0 returned instead.");
return 0.0;
}
int SHPhysicsSystemInterface::GetFixedUpdateRate() noexcept
{
auto* physicsSystem = SHSystemManager::GetSystem<SHPhysicsSystem>();
if (physicsSystem)
return physicsSystem->GetFixedUpdateRate();
SHLOGV_WARNING("Failed to get fixed update rate. 0.0 returned instead.");
return 0.0;
}
}

View File

@ -1,22 +1,20 @@
/************************************************************************************//*!
\file SHPhysicsSystemInterface.h
\author Tng Kah Wei, kahwei.tng, 390009620
\par email: kahwei.tng\@digipen.edu
\date Oct 31, 2022
\author Diren D Bharwani, diren.dbharwani, 390002520
\brief Contains the definition of the SHGraphicsSystemInterface static class.
Copyright (C) 2022 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the prior written consent
of DigiPen Institute of Technology is prohibited.
*//*************************************************************************************/
#pragma once
// STL Includes
#include <vector>
// Project Headers
#include "ECS_Base/Entity/SHEntity.h"
#include "Physics/Collision/SHCollisionInfo.h"
#include "Physics/Collision/Contacts/SHCollisionEvents.h"
namespace SHADE
@ -49,8 +47,8 @@ namespace SHADE
/* Static Usage Functions */
/*---------------------------------------------------------------------------------*/
[[nodiscard]] static const std::vector<SHCollisionInfo>& GetCollisionInfo () noexcept;
[[nodiscard]] static const std::vector<SHCollisionInfo>& GetTriggerInfo () noexcept;
[[nodiscard]] static const std::vector<SHCollisionEvent>& GetCollisionInfo () noexcept;
[[nodiscard]] static const std::vector<SHTriggerEvent>& GetTriggerInfo () noexcept;
[[nodiscard]] static double GetFixedDT () noexcept;
[[nodiscard]] static int GetFixedUpdateRate () noexcept;
};

View File

@ -30,7 +30,6 @@ of DigiPen Institute of Technology is prohibited.
#include "Engine/Application.hxx"
#include "Physics/System/SHPhysicsSystemInterface.h"
#include "Physics/SHPhysicsEvents.h"
#include "Physics/Collision/SHCollisionInfo.h"
namespace SHADE
{
@ -605,8 +604,8 @@ namespace SHADE
{
auto entities =
{
std::make_pair(collisionInfo.GetEntityA(), collisionInfo.GetEntityB()),
std::make_pair(collisionInfo.GetEntityB(), collisionInfo.GetEntityA())
std::make_pair(collisionInfo.info.GetEntityA(), collisionInfo.info.GetEntityB()),
std::make_pair(collisionInfo.info.GetEntityB(), collisionInfo.info.GetEntityA())
};
for (auto entity : entities)
{
@ -625,15 +624,15 @@ namespace SHADE
for (int i = 0; i < entityScripts->Count; ++i)
{
Script^ script = entityScripts[i];
switch (collisionInfo.GetCollisionState())
switch (collisionInfo.state)
{
case SHCollisionInfo::State::ENTER:
case SHCollisionState::ENTER:
script->OnCollisionEnter(info);
break;
case SHCollisionInfo::State::STAY:
case SHCollisionState::STAY:
script->OnCollisionStay(info);
break;
case SHCollisionInfo::State::EXIT:
case SHCollisionState::EXIT:
script->OnCollisionExit(info);
break;
}
@ -647,8 +646,8 @@ namespace SHADE
{
auto entities =
{
std::make_pair(triggerInfo.GetEntityA(), triggerInfo.GetEntityB()),
std::make_pair(triggerInfo.GetEntityB(), triggerInfo.GetEntityA())
std::make_pair(triggerInfo.info.GetEntityA(), triggerInfo.info.GetEntityB()),
std::make_pair(triggerInfo.info.GetEntityB(), triggerInfo.info.GetEntityA())
};
for (auto entity : entities)
{
@ -667,15 +666,15 @@ namespace SHADE
for (int i = 0; i < entityScripts->Count; ++i)
{
Script^ script = entityScripts[i];
switch (triggerInfo.GetCollisionState())
switch (triggerInfo.state)
{
case SHCollisionInfo::State::ENTER:
case SHCollisionState::ENTER:
script->OnTriggerEnter(info);
break;
case SHCollisionInfo::State::STAY:
case SHCollisionState::STAY:
script->OnTriggerStay(info);
break;
case SHCollisionInfo::State::EXIT:
case SHCollisionState::EXIT:
script->OnTriggerExit(info);
break;
}