diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index 9a90dbe37..0ac8129d4 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -87,9 +87,31 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, const int32_t c } } +void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effectType, const float effectScale); + PhantomPhysicsComponent::~PhantomPhysicsComponent() { - if (m_dpEntity) { - dpWorld::RemoveEntity(m_dpEntity); + if (!m_dpEntity) return; + + if (m_PhysicsActive) dpWorld::RemoveEntity(m_dpEntity); + else delete m_dpEntity; +} + +void PhantomPhysicsComponent::SetPhysicsActive(const bool active) { + if (!m_dpEntity || m_PhysicsActive == active) return; + m_PhysicsActive = active; + + if (active) { + // Anything already inside enters on the next physics step + dpWorld::AddEntity(m_dpEntity); + return; + } + + dpWorld::DetachEntity(m_dpEntity); + const std::vector inside(m_dpEntity->GetCurrentlyCollidingObjects().begin(), m_dpEntity->GetCurrentlyCollidingObjects().end()); + m_dpEntity->ClearCollisions(); + for (const auto id : inside) { + ApplyCollisionEffect(id, m_EffectType, 1.0f); + m_Parent->OnCollisionLeavePhantom(id); } } @@ -151,7 +173,7 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec } void PhantomPhysicsComponent::Update(float deltaTime) { - if (!m_dpEntity) return; + if (!m_dpEntity || !m_PhysicsActive) return; //Process enter events for (const auto id : m_dpEntity->GetNewObjects()) { diff --git a/dGame/dComponents/PhantomPhysicsComponent.h b/dGame/dComponents/PhantomPhysicsComponent.h index 9a9e18801..d12e238d5 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.h +++ b/dGame/dComponents/PhantomPhysicsComponent.h @@ -99,6 +99,14 @@ public: dpEntity* GetdpEntity() const { return m_dpEntity; } + /** + * Switches the volume on or off (the ActivatePhysics trigger command; the client activates or deactivates + * the physics component the same way). Whatever was inside leaves when it is switched off. + */ + void SetPhysicsActive(bool active); + + bool GetPhysicsActive() const { return m_PhysicsActive; } + /** * Spawns an object at each of the vertices for debugging purposes */ @@ -161,6 +169,9 @@ private: */ dpEntity* m_dpEntity; + // Whether m_dpEntity is in the physics world + bool m_PhysicsActive = true; + /** * Whether or not this physics object represents an object that updates the respawn pos of an entity that crosses it */ diff --git a/dGame/dComponents/TriggerComponent.cpp b/dGame/dComponents/TriggerComponent.cpp index 46037f6ac..a159ff654 100644 --- a/dGame/dComponents/TriggerComponent.cpp +++ b/dGame/dComponents/TriggerComponent.cpp @@ -467,13 +467,18 @@ void TriggerComponent::HandleDestroySpawnerNetworkObjects(std::string args) { } void TriggerComponent::HandleActivatePhysics(Entity* targetEntity, std::string args) { - if (args == "true") { - // TODO add physics entity if there isn't one - } else if (args == "false") { - // TODO remove Phsyics entity if there is one - } else { + if (args != "true" && args != "false") { LOG_DEBUG("Invalid argument for ActivatePhysics Trigger: %s", args.c_str()); + return; } + + auto* const phantomPhysicsComponent = targetEntity->GetComponent(); + if (!phantomPhysicsComponent) { + LOG_DEBUG("ActivatePhysics on %llu:%i which has no phantom physics", targetEntity->GetObjectID(), targetEntity->GetLOT()); + return; + } + + phantomPhysicsComponent->SetPhysicsActive(args == "true"); } void TriggerComponent::HandleSetPath(Entity* targetEntity, std::vector argArray) { diff --git a/dPhysics/dpEntity.h b/dPhysics/dpEntity.h index 1c302bf11..81db85d46 100644 --- a/dPhysics/dpEntity.h +++ b/dPhysics/dpEntity.h @@ -57,6 +57,9 @@ public: void PreUpdate() { m_NewObjects.clear(); m_RemovedObjects.clear(); } + // Forgets everything it touches (it was taken out of the world) + void ClearCollisions() { m_NewObjects.clear(); m_RemovedObjects.clear(); m_CurrentlyCollidingObjects.clear(); } + const LWOOBJID& GetObjectID() const { return m_ObjectID; } void SetGrid(dpGrid* grid); diff --git a/dPhysics/dpGrid.cpp b/dPhysics/dpGrid.cpp index c338efe32..79e434468 100644 --- a/dPhysics/dpGrid.cpp +++ b/dPhysics/dpGrid.cpp @@ -73,6 +73,12 @@ void dpGrid::Move(dpEntity* entity, float x, float z) { } void dpGrid::Delete(dpEntity* entity) { + if (!entity) return; + Remove(entity); + delete entity; +} + +void dpGrid::Remove(dpEntity* entity) { if (!entity) return; int oldCellX = static_cast(std::round(entity->m_Position.x)) / dpGrid::CELL_SIZE + NUM_CELLS / 2; int oldCellZ = static_cast(std::round(entity->m_Position.z)) / dpGrid::CELL_SIZE + NUM_CELLS / 2; @@ -88,10 +94,11 @@ void dpGrid::Delete(dpEntity* entity) { cell.pop_back(); } - m_GargantuanObjects.erase(entity->m_ObjectID); + const auto gargantuan = m_GargantuanObjects.find(entity->m_ObjectID); + if (gargantuan != m_GargantuanObjects.end() && gargantuan->second == entity) m_GargantuanObjects.erase(gargantuan); - if (entity) delete entity; - entity = nullptr; + // Out of the grid: moving it mustn't put it back into a cell + entity->m_Grid = nullptr; } void dpGrid::Update(float deltaTime) { diff --git a/dPhysics/dpGrid.h b/dPhysics/dpGrid.h index 39a9cfe73..329776d3b 100644 --- a/dPhysics/dpGrid.h +++ b/dPhysics/dpGrid.h @@ -20,6 +20,11 @@ public: void Move(dpEntity* entity, float x, float z); void Delete(dpEntity* entity); + /** + * Takes an entity out of the grid without deleting it + */ + void Remove(dpEntity* entity); + void Update(float deltaTime); /** diff --git a/dPhysics/dpWorld.cpp b/dPhysics/dpWorld.cpp index a3df390c8..df2ea072b 100644 --- a/dPhysics/dpWorld.cpp +++ b/dPhysics/dpWorld.cpp @@ -128,6 +128,20 @@ void dpWorld::AddEntity(dpEntity* entity) { } } +void dpWorld::DetachEntity(dpEntity* entity) { + if (!entity) return; + + if (m_Grid) { + m_Grid->Remove(entity); + return; + } + + auto& entities = entity->GetIsStatic() ? m_StaticEntities : m_DynamicEntites; + for (auto& other : entities) { + if (other == entity) other = nullptr; + } +} + void dpWorld::RemoveEntity(dpEntity* entity) { if (!entity) return; diff --git a/dPhysics/dpWorld.h b/dPhysics/dpWorld.h index fc23a15ca..6514ed4b5 100644 --- a/dPhysics/dpWorld.h +++ b/dPhysics/dpWorld.h @@ -18,5 +18,10 @@ namespace dpWorld { void AddEntity(dpEntity* entity); void RemoveEntity(dpEntity* entity); + /** + * Takes an entity out of the world without deleting it, so it can be added back later (a volume switched off) + */ + void DetachEntity(dpEntity* entity); + dNavMesh* GetNavMesh(); }; diff --git a/tests/dGameTests/CollisionFilterTests.cpp b/tests/dGameTests/CollisionFilterTests.cpp index b53bb288b..d05a63e68 100644 --- a/tests/dGameTests/CollisionFilterTests.cpp +++ b/tests/dGameTests/CollisionFilterTests.cpp @@ -77,3 +77,34 @@ TEST(CollisionFilterTests, RotatedBoxesKeepTheirShape) { EXPECT_NEAR(box->SquaredDistanceTo(NiPoint3(100.0f, 12.0f, 100.0f)), 4.0f, 1e-3f); EXPECT_NEAR(box->SquaredDistanceTo(NiPoint3(100.0f, -3.0f, 100.0f)), 9.0f, 1e-3f); } + +#include "dpWorld.h" +#include "dpGrid.h" + +TEST(CollisionFilterTests, DetachedVolumesStopColliding) { + dpGrid grid(8, 100); + auto* volume = new dpEntity(1, 5.0f); + auto* player = new dpEntity(2, 1.0f, false); + volume->SetPosition(NiPoint3Constant::ZERO); + player->SetPosition(NiPoint3(1.0f, 0.0f, 0.0f)); + volume->SetGrid(&grid); + player->SetGrid(&grid); + + grid.Update(0.1f); + ASSERT_EQ(volume->GetNewObjects().size(), 1u); + + // Switched off: out of the grid, moving it doesn't sneak it back in, and it stops seeing anything + grid.Remove(volume); + volume->ClearCollisions(); + volume->SetPosition(NiPoint3(0.5f, 0.0f, 0.0f)); + grid.Update(0.1f); + EXPECT_TRUE(volume->GetCurrentlyCollidingObjects().empty()); + + // Switched back on: sees the player again + volume->SetGrid(&grid); + grid.Update(0.1f); + EXPECT_EQ(volume->GetNewObjects().size(), 1u); + + grid.Delete(volume); + grid.Delete(player); +}