Slight refactor to fix collision states for very fast moving objects

This commit is contained in:
Diren D Bharwani 2022-12-21 00:40:01 +08:00
parent b58b475c04
commit 265a5bece8
8 changed files with 283 additions and 91 deletions

View File

@ -4,8 +4,8 @@
NumberOfChildren: 0 NumberOfChildren: 0
Components: Components:
Transform Component: Transform Component:
Translate: {x: 0, y: 3, z: 0} Translate: {x: 0, y: 0.0579863191, z: 0}
Rotate: {x: 0, y: 0, z: -0} Rotate: {x: -0, y: 0, z: -0}
Scale: {x: 1, y: 1, z: 1} Scale: {x: 1, y: 1, z: 1}
IsActive: true IsActive: true
RigidBody Component: RigidBody Component:
@ -15,7 +15,7 @@
Drag: 1 Drag: 1
Angular Drag: 1 Angular Drag: 1
Use Gravity: true Use Gravity: true
Gravity Scale: 1 Gravity Scale: 10
Interpolate: true Interpolate: true
Sleeping Enabled: true Sleeping Enabled: true
Freeze Position X: false Freeze Position X: false
@ -48,7 +48,7 @@
NumberOfChildren: 0 NumberOfChildren: 0
Components: Components:
Camera Component: Camera Component:
Position: {x: 0, y: 2, z: 3} Position: {x: 0, y: 0.5, z: 2}
Pitch: 0 Pitch: 0
Yaw: 0 Yaw: 0
Roll: 0 Roll: 0

View File

@ -25,6 +25,23 @@ namespace SHADE
struct SH_API SHManifold struct SH_API SHManifold
{ {
public: 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 */ /* Data Members */
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
@ -32,8 +49,8 @@ namespace SHADE
// We only need 4 contact points to build a stable manifold. // We only need 4 contact points to build a stable manifold.
static constexpr int MAX_NUM_CONTACTS = 4; static constexpr int MAX_NUM_CONTACTS = 4;
SHCollisionShape* A = nullptr; SHCollisionShape* A;
SHCollisionShape* B = nullptr; SHCollisionShape* B;
uint32_t numContacts = 0; uint32_t numContacts = 0;
SHCollisionState state = SHCollisionState::INVALID; SHCollisionState state = SHCollisionState::INVALID;
@ -44,4 +61,7 @@ namespace SHADE
}; };
} } // namespace SHADE
#include "SHManifold.hpp"

View File

@ -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

View File

@ -102,6 +102,8 @@ namespace SHADE
void SHCollisionSpace::DetectCollisions() noexcept void SHCollisionSpace::DetectCollisions() noexcept
{ {
// TODO: Profile broad-phase and narrow-phase
/* /*
* Broad-phase * Broad-phase
*/ */
@ -112,11 +114,14 @@ namespace SHADE
// Colliders without bodies are considered to be static bodies // Colliders without bodies are considered to be static bodies
// This is specific to this engine because of Unity's stupid convention. // This is specific to this engine because of Unity's stupid convention.
const bool IS_IMPLICIT_STATIC = !collider->rigidBody; const bool IS_IMPLICIT_STATIC = !collider->rigidBody;
const bool IS_EXPLICIT_STATIC = collider->rigidBody->GetType() == SHRigidBody::Type::STATIC;
const bool IS_ACTIVE = collider->active; const bool IS_ACTIVE = collider->active;
// Skip inactive colliders // 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; continue;
// All remaining are kinematic or dynamic // All remaining are kinematic or dynamic
@ -149,6 +154,10 @@ namespace SHADE
// Clear every frame // Clear every frame
narrowphaseBatch.clear(); 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 void SHCollisionSpace::collideTriggers(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept
{ {
const auto* A = narrowphasePair.A; auto* A = narrowphasePair.A;
const auto* B = narrowphasePair.B; auto* B = narrowphasePair.B;
const bool COLLIDING = SHCollisionDispatcher::Collide(*A, *B); // Send to contact manager
// Send results to contact manager
if (contactManager) if (contactManager)
contactManager->AddTrigger(COLLIDING, key); contactManager->AddTrigger(key, A, B);
} }
void SHCollisionSpace::collideManifolds(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept void SHCollisionSpace::collideManifolds(const SHCollisionKey& key, NarrowphasePair& narrowphasePair) const noexcept
@ -213,11 +220,8 @@ namespace SHADE
auto* A = narrowphasePair.A; auto* A = narrowphasePair.A;
auto* B = narrowphasePair.B; auto* B = narrowphasePair.B;
SHManifold newManifold { .A = A, .B = B }; // Send to contact manager
const bool COLLIDING = SHCollisionDispatcher::Collide(newManifold, *A, *B);
// Send results to contact manager
if (contactManager) if (contactManager)
contactManager->AddManifold(COLLIDING, key, newManifold); contactManager->AddManifold(key, A, B);
} }
} // namespace SHADE } // namespace SHADE

View File

@ -14,6 +14,9 @@
// Primary Header // Primary Header
#include "SHContactManager.h" #include "SHContactManager.h"
// Project Headers
#include "Physics/Collision/Narrowphase/SHCollisionDispatch.h"
namespace SHADE namespace SHADE
{ {
/*-----------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------*/
@ -26,8 +29,8 @@ namespace SHADE
triggerEvents.clear(); triggerEvents.clear();
for (auto& [id, state] : triggers) for (auto& [id, trigger] : triggers)
triggerEvents.emplace_back(SHTriggerEvent{ id, state }); triggerEvents.emplace_back(SHTriggerEvent{ id, trigger.state });
return triggerEvents; return triggerEvents;
} }
@ -62,92 +65,83 @@ namespace SHADE
void SHContactManager::Update() noexcept void SHContactManager::Update() noexcept
{ {
// Clear expired or invalid collisions // Clear expired or invalid collisions. If not, test collision.
for (auto manifold = manifolds.begin(); manifold != manifolds.end();) 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_COLLIDING = SHCollisionDispatcher::Collide(newManifold, *newManifold.A, *newManifold.B);
const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID;
if (IS_EXIT || IS_INVALID) auto& collisionState = newManifold.state;
manifold = manifolds.erase(manifold); updateCollisionState(IS_COLLIDING, collisionState);
else
++manifold; const bool IS_INVALID = collisionState == SHCollisionState::INVALID;
if (IS_INVALID)
{
manifoldPair = manifolds.erase(manifoldPair);
continue;
}
updateManifold(oldManifold, newManifold);
++manifoldPair;
} }
// Clear expired or invalid triggers // Clear expired or invalid triggers, If not, test collision.
for (auto trigger = triggers.begin(); trigger != triggers.end();) 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_COLLIDING = SHCollisionDispatcher::Collide(*newTrigger.A, *newTrigger.B);
const bool IS_INVALID = COLLISION_STATE == SHCollisionState::INVALID;
if (IS_EXIT || IS_INVALID) auto& collisionState = newTrigger.state;
trigger = triggers.erase(trigger); updateCollisionState(IS_COLLIDING, collisionState);
const bool IS_INVALID = collisionState == SHCollisionState::INVALID;
if (IS_INVALID)
triggerPair = triggers.erase(triggerPair);
else 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); auto trigger = triggers.find(key);
// If id not found, emplace new object.
// New object is in the invalid state
if (trigger == triggers.end()) if (trigger == triggers.end())
trigger = triggers.emplace(key, SHCollisionState::INVALID).first; triggers.emplace(key, Trigger{ A, B, 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 void SHContactManager::AddManifold(const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept
{ {
auto manifold = manifolds.find(key);
// If id not found, emplace new manifold // If id not found, emplace new manifold
auto manifold = manifolds.find(key);
if (manifold == manifolds.end()) if (manifold == manifolds.end())
manifold = manifolds.emplace(key, newManifold).first; manifolds.emplace(key, SHManifold{ A, B }).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 void SHContactManager::RemoveInvalidatedTrigger(EntityID eid) noexcept
{ {
remove(triggers, eid); removeInvalidObject(triggers, eid);
} }
void SHContactManager::RemoveInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) noexcept void SHContactManager::RemoveInvalidatedTrigger(EntityID eid, uint32_t shapeIndex) noexcept
{ {
remove(triggers, eid, shapeIndex); removeInvalidObject(triggers, eid, shapeIndex);
} }
void SHContactManager::RemoveInvalidatedManifold(EntityID eid) noexcept void SHContactManager::RemoveInvalidatedManifold(EntityID eid) noexcept
{ {
remove(manifolds, eid); removeInvalidObject(manifolds, eid);
} }
void SHContactManager::RemoveInvalidatedManifold(EntityID eid, uint32_t shapeIndex) noexcept void SHContactManager::RemoveInvalidatedManifold(EntityID eid, uint32_t shapeIndex) noexcept
{ {
remove(manifolds, eid, shapeIndex); removeInvalidObject(manifolds, eid, shapeIndex);
} }
/*-----------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------*/
@ -164,10 +158,26 @@ namespace SHADE
} }
else 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. // 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 } // namespace SHADE

View File

@ -54,26 +54,35 @@ namespace SHADE
/** /**
* @brief * @brief
* Removes any invalidated contacts and triggers. * Removes any invalidated contacts and triggers, then performs narrowphase collision
* detection on existing triggers and manifolds.
* @return * @return
*/ */
void Update () noexcept; void Update () noexcept;
void AddTrigger (bool isColliding, const SHCollisionKey& key) noexcept; void AddTrigger (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept;
void AddManifold (bool isColliding, const SHCollisionKey& key, const SHManifold& newManifold) noexcept; void AddManifold (const SHCollisionKey& key, SHCollisionShape* A, SHCollisionShape* B) noexcept;
void RemoveInvalidatedTrigger (EntityID eid) noexcept; void RemoveInvalidatedTrigger (EntityID eid) noexcept;
void RemoveInvalidatedTrigger (EntityID eid, uint32_t shapeIndex) noexcept; void RemoveInvalidatedTrigger (EntityID eid, uint32_t shapeIndex) noexcept;
void RemoveInvalidatedManifold (EntityID eid) noexcept; void RemoveInvalidatedManifold (EntityID eid) noexcept;
void RemoveInvalidatedManifold (EntityID eid, uint32_t shapeIndex) noexcept; void RemoveInvalidatedManifold (EntityID eid, uint32_t shapeIndex) noexcept;
private: private:
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
/* Type Definitions */ /* Type Definitions */
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
struct Trigger
{
SHCollisionShape* A = nullptr;
SHCollisionShape* B = nullptr;
SHCollisionState state = SHCollisionState::INVALID;
};
using Manifolds = std::unordered_map<SHCollisionKey, SHManifold, SHCollisionKeyHash>; using Manifolds = std::unordered_map<SHCollisionKey, SHManifold, SHCollisionKeyHash>;
using Triggers = std::unordered_map<SHCollisionKey, SHCollisionState, SHCollisionKeyHash>; using Triggers = std::unordered_map<SHCollisionKey, Trigger, SHCollisionKeyHash>;
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
/* Data Members */ /* Data Members */
@ -86,14 +95,15 @@ namespace SHADE
/* Member Functions */ /* 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 // Removal Helpers
template <typename T> template <typename T>
void remove (std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid); static void removeInvalidObject (std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid) noexcept;
template <typename T> template <typename T>
void remove (std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid, uint32_t shapeIndex); static void removeInvalidObject (std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid, uint32_t shapeIndex) noexcept;
}; };

View File

@ -20,7 +20,7 @@ namespace SHADE
/*-----------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------*/
template <typename T> template <typename T>
void SHContactManager::remove(std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid) void SHContactManager::removeInvalidObject(std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid) noexcept
{ {
if (container.empty()) if (container.empty())
return; return;
@ -40,7 +40,7 @@ namespace SHADE
} }
template <typename T> template <typename T>
void SHContactManager::remove(std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid, uint32_t shapeIndex) void SHContactManager::removeInvalidObject(std::unordered_map<SHCollisionKey, T, SHCollisionKeyHash>& container, EntityID eid, uint32_t shapeIndex) noexcept
{ {
if (container.empty()) if (container.empty())
return; return;

View File

@ -81,9 +81,6 @@ namespace SHADE
void SHPhysicsWorld::Step(float dt) void SHPhysicsWorld::Step(float dt)
{ {
// Contact manager to clear expired contacts
contactManager.Update();
/* /*
* Detect Collisions * Detect Collisions
*/ */