diff --git a/Assets/Scenes/PhysicsSandbox.shade b/Assets/Scenes/PhysicsSandbox.shade index 21840aca..d983c22b 100644 --- a/Assets/Scenes/PhysicsSandbox.shade +++ b/Assets/Scenes/PhysicsSandbox.shade @@ -4,8 +4,8 @@ NumberOfChildren: 0 Components: Transform Component: - Translate: {x: 0, y: 3, z: 0} - Rotate: {x: 0, y: 0, z: -0} + Translate: {x: 0, y: 0.0579863191, z: 0} + Rotate: {x: -0, y: 0, z: -0} Scale: {x: 1, y: 1, z: 1} IsActive: true RigidBody Component: @@ -15,7 +15,7 @@ Drag: 1 Angular Drag: 1 Use Gravity: true - Gravity Scale: 1 + Gravity Scale: 10 Interpolate: true Sleeping Enabled: true Freeze Position X: false @@ -48,7 +48,7 @@ NumberOfChildren: 0 Components: Camera Component: - Position: {x: 0, y: 2, z: 3} + Position: {x: 0, y: 0.5, z: 2} Pitch: 0 Yaw: 0 Roll: 0 diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h index 0534f749..c2b6ddb2 100644 --- a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.h @@ -25,6 +25,23 @@ namespace SHADE struct SH_API SHManifold { public: + /*---------------------------------------------------------------------------------*/ + /* Constructors & Destructor */ + /*---------------------------------------------------------------------------------*/ + + SHManifold (SHCollisionShape* a, SHCollisionShape* b) noexcept; + SHManifold (const SHManifold& rhs) noexcept; + SHManifold (SHManifold&& rhs) noexcept; + + ~SHManifold () noexcept = default; + + /*---------------------------------------------------------------------------------*/ + /* Operator Overloads */ + /*---------------------------------------------------------------------------------*/ + + SHManifold& operator=(const SHManifold& rhs) noexcept; + SHManifold& operator=(SHManifold&& rhs) noexcept; + /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ @@ -32,8 +49,8 @@ namespace SHADE // We only need 4 contact points to build a stable manifold. static constexpr int MAX_NUM_CONTACTS = 4; - SHCollisionShape* A = nullptr; - SHCollisionShape* B = nullptr; + SHCollisionShape* A; + SHCollisionShape* B; uint32_t numContacts = 0; SHCollisionState state = SHCollisionState::INVALID; @@ -44,4 +61,7 @@ namespace SHADE }; -} \ No newline at end of file +} // namespace SHADE + +#include "SHManifold.hpp" + diff --git a/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp new file mode 100644 index 00000000..f0e1374f --- /dev/null +++ b/SHADE_Engine/src/Physics/Collision/Contacts/SHManifold.hpp @@ -0,0 +1,151 @@ +/**************************************************************************************** + * \file SHManifold.h + * \author Diren D Bharwani, diren.dbharwani, 390002520 + * \brief Interface for a Collision Manifold + * + * \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 "SHManifold.h" + +namespace SHADE +{ + /*-----------------------------------------------------------------------------------*/ + /* Constructors & Destructor Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline SHManifold::SHManifold(SHCollisionShape* a, SHCollisionShape* b) noexcept + : A { a } + , B { b } + {} + + inline SHManifold::SHManifold(const SHManifold& rhs) noexcept + : A { rhs.A } + , B { rhs.B } + , numContacts { rhs.numContacts } + , state { rhs.state } + , normal { rhs.normal } + { + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) + tangents[i] = rhs.tangents[i]; + + for (int i = 0; i < MAX_NUM_CONTACTS; ++i) + { + contacts[i].penetration = rhs.contacts[i].penetration; + contacts[i].bias = rhs.contacts[i].bias; + contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; + contacts[i].normalMass = rhs.contacts[i].normalMass; + + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; + contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; + } + + contacts[i].position = rhs.contacts[i].position; + contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; + } + } + + inline SHManifold::SHManifold(SHManifold&& rhs) noexcept + : A { rhs.A } + , B { rhs.B } + , numContacts { rhs.numContacts } + , state { rhs.state } + , normal { rhs.normal } + { + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) + tangents[i] = rhs.tangents[i]; + + for (int i = 0; i < MAX_NUM_CONTACTS; ++i) + { + contacts[i].penetration = rhs.contacts[i].penetration; + contacts[i].bias = rhs.contacts[i].bias; + contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; + contacts[i].normalMass = rhs.contacts[i].normalMass; + + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; + contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; + } + + contacts[i].position = rhs.contacts[i].position; + contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; + } + } + + /*-----------------------------------------------------------------------------------*/ + /* Operator Overload Definitions */ + /*-----------------------------------------------------------------------------------*/ + + inline SHManifold& SHManifold::operator=(const SHManifold& rhs) noexcept + { + if (this == &rhs) + return *this; + + A = rhs.A; + B = rhs.B; + numContacts = rhs.numContacts; + state = rhs.state; + normal = rhs.normal; + + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) + tangents[i] = rhs.tangents[i]; + + for (int i = 0; i < MAX_NUM_CONTACTS; ++i) + { + contacts[i].penetration = rhs.contacts[i].penetration; + contacts[i].bias = rhs.contacts[i].bias; + contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; + contacts[i].normalMass = rhs.contacts[i].normalMass; + + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; + contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; + } + + contacts[i].position = rhs.contacts[i].position; + contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; + } + + return *this; + } + + inline SHManifold& SHManifold::operator=(SHManifold&& rhs) noexcept + { + A = rhs.A; + B = rhs.B; + numContacts = rhs.numContacts; + state = rhs.state; + normal = rhs.normal; + + for (int i = 0; i < SHContact::NUM_TANGENTS; ++i) + tangents[i] = rhs.tangents[i]; + + for (int i = 0; i < MAX_NUM_CONTACTS; ++i) + { + contacts[i].penetration = rhs.contacts[i].penetration; + contacts[i].bias = rhs.contacts[i].bias; + contacts[i].normalImpulse = rhs.contacts[i].normalImpulse; + contacts[i].normalMass = rhs.contacts[i].normalMass; + + for (int j = 0; j < SHContact::NUM_TANGENTS; ++j) + { + contacts[i].tangentImpulse[j] = rhs.contacts[i].tangentImpulse[j]; + contacts[i].tangentMass[j] = rhs.contacts[i].tangentMass[j]; + } + + contacts[i].position = rhs.contacts[i].position; + contacts[i].featurePair.key = rhs.contacts[i].featurePair.key; + } + + return *this; + } +} // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp b/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp index bc76d14a..ab4520ee 100644 --- a/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp +++ b/SHADE_Engine/src/Physics/Collision/SHCollisionSpace.cpp @@ -102,6 +102,8 @@ namespace SHADE void SHCollisionSpace::DetectCollisions() noexcept { + // TODO: Profile broad-phase and narrow-phase + /* * Broad-phase */ @@ -112,11 +114,14 @@ namespace SHADE // 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) + if (!IS_ACTIVE || IS_IMPLICIT_STATIC) + continue; + + const bool IS_EXPLICIT_STATIC = collider->rigidBody->GetType() == SHRigidBody::Type::STATIC; + if (IS_EXPLICIT_STATIC) continue; // All remaining are kinematic or dynamic @@ -149,6 +154,10 @@ namespace SHADE // Clear every frame narrowphaseBatch.clear(); + + // Test all collisions + if (contactManager) + contactManager->Update(); } @@ -198,14 +207,12 @@ namespace SHADE void SHCollisionSpace::collideTriggers(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept { - const auto* A = narrowphasePair.A; - const auto* B = narrowphasePair.B; + auto* A = narrowphasePair.A; + auto* B = narrowphasePair.B; - const bool COLLIDING = SHCollisionDispatcher::Collide(*A, *B); - - // Send results to contact manager + // Send to contact manager if (contactManager) - contactManager->AddTrigger(COLLIDING, key); + contactManager->AddTrigger(key, A, B); } void SHCollisionSpace::collideManifolds(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept @@ -213,11 +220,8 @@ namespace SHADE 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 + // Send to contact manager if (contactManager) - contactManager->AddManifold(COLLIDING, key, newManifold); + contactManager->AddManifold(key, A, B); } } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp index be65128d..0ab20b38 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.cpp @@ -14,6 +14,9 @@ // Primary Header #include "SHContactManager.h" +// Project Headers +#include "Physics/Collision/Narrowphase/SHCollisionDispatch.h" + namespace SHADE { /*-----------------------------------------------------------------------------------*/ @@ -26,8 +29,8 @@ namespace SHADE triggerEvents.clear(); - for (auto& [id, state] : triggers) - triggerEvents.emplace_back(SHTriggerEvent{ id, state }); + for (auto& [id, trigger] : triggers) + triggerEvents.emplace_back(SHTriggerEvent{ id, trigger.state }); return triggerEvents; } @@ -62,92 +65,83 @@ namespace SHADE void SHContactManager::Update() noexcept { - // Clear expired or invalid collisions - for (auto manifold = manifolds.begin(); manifold != manifolds.end();) + // Clear expired or invalid collisions. If not, test collision. + for (auto manifoldPair = manifolds.begin(); manifoldPair != manifolds.end();) { - const auto COLLISION_STATE = manifold->second.state; + // Test collision of every manifold. + SHManifold& oldManifold = manifoldPair->second; + SHManifold newManifold = oldManifold; - const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; - const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; + const bool IS_COLLIDING = SHCollisionDispatcher::Collide(newManifold, *newManifold.A, *newManifold.B); - if (IS_EXIT || IS_INVALID) - manifold = manifolds.erase(manifold); - else - ++manifold; + auto& collisionState = newManifold.state; + updateCollisionState(IS_COLLIDING, collisionState); + + const bool IS_INVALID = collisionState == SHCollisionState::INVALID; + if (IS_INVALID) + { + manifoldPair = manifolds.erase(manifoldPair); + continue; + } + + updateManifold(oldManifold, newManifold); + ++manifoldPair; } - // Clear expired or invalid triggers - for (auto trigger = triggers.begin(); trigger != triggers.end();) + // Clear expired or invalid triggers, If not, test collision. + for (auto triggerPair = triggers.begin(); triggerPair != triggers.end();) { - const auto COLLISION_STATE = trigger->second; + // Test collision of every trigger. + Trigger& oldTrigger = triggerPair->second; + Trigger newTrigger = oldTrigger; - const bool IS_EXIT = COLLISION_STATE == SHCollisionState::EXIT; - const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID; + const bool IS_COLLIDING = SHCollisionDispatcher::Collide(*newTrigger.A, *newTrigger.B); - if (IS_EXIT || IS_INVALID) - trigger = triggers.erase(trigger); + auto& collisionState = newTrigger.state; + updateCollisionState(IS_COLLIDING, collisionState); + + const bool IS_INVALID = collisionState == SHCollisionState::INVALID; + if (IS_INVALID) + triggerPair = triggers.erase(triggerPair); else - ++trigger; + ++triggerPair; } } - void SHContactManager::AddTrigger(bool isColliding, const SHCollisionKey& key) noexcept + void SHContactManager::AddTrigger(const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept { + // If id not found, emplace new trigger. 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); + triggers.emplace(key, Trigger{ A, B, SHCollisionState::INVALID }).first; } - void SHContactManager::AddManifold(bool isColliding, const SHCollisionKey& key, const SHManifold& newManifold) noexcept + void SHContactManager::AddManifold(const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept { - auto manifold = manifolds.find(key); - // If id not found, emplace new manifold + auto manifold = manifolds.find(key); 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); + manifolds.emplace(key, SHManifold{ A, B }).first; } void SHContactManager::RemoveInvalidatedTrigger(EntityID eid) noexcept { - remove(triggers, eid); + removeInvalidObject(triggers, eid); } void SHContactManager::RemoveInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) noexcept { - remove(triggers, eid, shapeIndex); + removeInvalidObject(triggers, eid, shapeIndex); } void SHContactManager::RemoveInvalidatedManifold(EntityID eid) noexcept { - remove(manifolds, eid); + removeInvalidObject(manifolds, eid); } void SHContactManager::RemoveInvalidatedManifold(EntityID eid, uint32_t shapeIndex) noexcept { - remove(manifolds, eid, shapeIndex); + removeInvalidObject(manifolds, eid, shapeIndex); } /*-----------------------------------------------------------------------------------*/ @@ -164,10 +158,26 @@ namespace SHADE } else { - // New states start at invalid. In false positive, remain unchanged. + // If already exited and still not colliding, the collision has expired. + // Invalid states are removed in the next frame + if (state == SHCollisionState::EXIT) + state = SHCollisionState::INVALID; + // If previously colliding, move to exit. - state = state == SHCollisionState::INVALID ? SHCollisionState::INVALID : SHCollisionState::EXIT; + if (state == SHCollisionState::ENTER || state == SHCollisionState::STAY) + state = SHCollisionState::EXIT; } } + void SHContactManager::updateManifold(SHManifold& oldManifold, SHManifold& newManifold) noexcept + { + oldManifold.state = newManifold.state; + + // Early out since exiting a collision does not require an update beyond updating the state + if (newManifold.state == SHCollisionState::EXIT) + return; + + + } + } // namespace SHADE \ No newline at end of file diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h index 06a03a6a..9e082db1 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.h @@ -54,26 +54,35 @@ namespace SHADE /** * @brief - * Removes any invalidated contacts and triggers. + * Removes any invalidated contacts and triggers, then performs narrowphase collision + * detection on existing triggers and manifolds. * @return */ - void Update () noexcept; + void Update () noexcept; - void AddTrigger (bool isColliding, const SHCollisionKey& key) noexcept; - void AddManifold (bool isColliding, const SHCollisionKey& key, const SHManifold& newManifold) noexcept; + void AddTrigger (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept; + void AddManifold (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) 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; + 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 */ /*---------------------------------------------------------------------------------*/ + struct Trigger + { + SHCollisionShape* A = nullptr; + SHCollisionShape* B = nullptr; + + SHCollisionState state = SHCollisionState::INVALID; + }; + using Manifolds = std::unordered_map; - using Triggers = std::unordered_map; + using Triggers = std::unordered_map; /*---------------------------------------------------------------------------------*/ /* Data Members */ @@ -86,14 +95,15 @@ namespace SHADE /* Member Functions */ /*---------------------------------------------------------------------------------*/ - void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; + static void updateCollisionState (bool isColliding, SHCollisionState& state) noexcept; + static void updateManifold (SHManifold& oldManifold, SHManifold& newManifold) noexcept; // Removal Helpers template - void remove (std::unordered_map& container, EntityID eid); + static void removeInvalidObject (std::unordered_map& container, EntityID eid) noexcept; template - void remove (std::unordered_map& container, EntityID eid, uint32_t shapeIndex); + static void removeInvalidObject (std::unordered_map& container, EntityID eid, uint32_t shapeIndex) noexcept; }; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp index 1403cd79..04a4b234 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHContactManager.hpp @@ -20,7 +20,7 @@ namespace SHADE /*-----------------------------------------------------------------------------------*/ template - void SHContactManager::remove(std::unordered_map& container, EntityID eid) + void SHContactManager::removeInvalidObject(std::unordered_map& container, EntityID eid) noexcept { if (container.empty()) return; @@ -40,7 +40,7 @@ namespace SHADE } template - void SHContactManager::remove(std::unordered_map& container, EntityID eid, uint32_t shapeIndex) + void SHContactManager::removeInvalidObject(std::unordered_map& container, EntityID eid, uint32_t shapeIndex) noexcept { if (container.empty()) return; diff --git a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp index b5dba378..bb42364d 100644 --- a/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp +++ b/SHADE_Engine/src/Physics/Dynamics/SHPhysicsWorld.cpp @@ -81,9 +81,6 @@ namespace SHADE void SHPhysicsWorld::Step(float dt) { - // Contact manager to clear expired contacts - contactManager.Update(); - /* * Detect Collisions */