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

View File

@ -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
};
}
} // 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
{
// 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

View File

@ -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);
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, If not, test collision.
for (auto triggerPair = triggers.begin(); triggerPair != triggers.end();)
{
// Test collision of every trigger.
Trigger& oldTrigger = triggerPair->second;
Trigger newTrigger = oldTrigger;
const bool IS_COLLIDING = SHCollisionDispatcher::Collide(*newTrigger.A, *newTrigger.B);
auto& collisionState = newTrigger.state;
updateCollisionState(IS_COLLIDING, collisionState);
const bool IS_INVALID = collisionState == SHCollisionState::INVALID;
if (IS_INVALID)
triggerPair = triggers.erase(triggerPair);
else
++manifold;
++triggerPair;
}
}
// 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
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

View File

@ -54,13 +54,14 @@ 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 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;
@ -72,8 +73,16 @@ namespace SHADE
/* Type Definitions */
/*---------------------------------------------------------------------------------*/
struct Trigger
{
SHCollisionShape* A = nullptr;
SHCollisionShape* B = nullptr;
SHCollisionState state = SHCollisionState::INVALID;
};
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 */
@ -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 <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>
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>
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())
return;
@ -40,7 +40,7 @@ namespace SHADE
}
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())
return;

View File

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