From 170610bcb1c1b24b031beca0935087135a436b88 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Tue, 15 Dec 2015 14:49:52 +0100 Subject: [PATCH] Added a TriggerSystem, seems to work as intended. --- include/Engine/Collision/Collision.h | 1 + include/Engine/Collision/ETrigger.h | 2 +- include/Engine/Collision/TriggerSystem.h | 18 +++- include/Game/PlayerSystem.h | 10 ++ resources/Schema/Entities/Test.xml | 6 +- src/Engine/Collision/Collision.cpp | 14 +++ src/Engine/Collision/TriggerSystem.cpp | 121 +++++++++++++++++++++-- src/Engine/Core/OctTree.cpp | 12 +-- src/Game/PlayerSystem.cpp | 18 ++++ 9 files changed, 178 insertions(+), 24 deletions(-) diff --git a/include/Engine/Collision/Collision.h b/include/Engine/Collision/Collision.h index 87f521bd..8b1f96b2 100644 --- a/include/Engine/Collision/Collision.h +++ b/include/Engine/Collision/Collision.h @@ -37,6 +37,7 @@ bool RayVsModel(const Ray& ray, //Return true if the boxes are intersecting. bool AABBVsAABB(const AABB& a, const AABB& b); +bool IsSameBoxProbably(const AABB& first, const AABB& second, const float epsilon = 0.0001f); } diff --git a/include/Engine/Collision/ETrigger.h b/include/Engine/Collision/ETrigger.h index aaa5ef06..8852ee8c 100644 --- a/include/Engine/Collision/ETrigger.h +++ b/include/Engine/Collision/ETrigger.h @@ -25,7 +25,7 @@ struct TriggerLeave : Event EntityID Trigger; }; -/** Thrown when an entity is completely inside a trigger. */ +/** Thrown once, when an entity is completely inside a trigger. */ struct TriggerEnter : Event { /** The id of the entity that entered the trigger. */ diff --git a/include/Engine/Collision/TriggerSystem.h b/include/Engine/Collision/TriggerSystem.h index a7b2122f..59718018 100644 --- a/include/Engine/Collision/TriggerSystem.h +++ b/include/Engine/Collision/TriggerSystem.h @@ -8,6 +8,8 @@ #include "Core/EventBroker.h" #include "ETrigger.h" +class AABB; + class TriggerSystem : public System { public: @@ -18,7 +20,21 @@ public: virtual void Update(World* world, ComponentWrapper& collision, double dt) override; private: - std::unordered_map> m_EntitiesInTrigger; + std::unordered_map> m_EntitiesTouchingTrigger; + std::unordered_map> m_EntitiesCompletelyInTrigger; + + bool getEntityBox(World* world, EntityID id, AABB& outBox); + //True if leave event was thrown. + bool throwLeaveIfWasInTrigger(std::unordered_set& triggerSet, EntityID pId, EntityID tId); + void attachAABBComponentFromModel(World* world, EntityID id); + template + void publish(EntityID pId, EntityID tId) + { + Event e; + e.Trigger = tId; + e.Entity = pId; + m_EventBroker->Publish(e); + } }; #endif \ No newline at end of file diff --git a/include/Game/PlayerSystem.h b/include/Game/PlayerSystem.h index 18752ac6..de2a27a6 100644 --- a/include/Game/PlayerSystem.h +++ b/include/Game/PlayerSystem.h @@ -9,6 +9,7 @@ #include "Core/EventBroker.h" #include "Core/EKeyDown.h" #include "Core/EKeyUp.h" +#include "Collision/ETrigger.h" struct KeyInput { @@ -26,6 +27,9 @@ public: { EVENT_SUBSCRIBE_MEMBER(m_EKeyDown, &PlayerSystem::OnKeyDown); EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &PlayerSystem::OnKeyUp); + EVENT_SUBSCRIBE_MEMBER(m_ETouch, &PlayerSystem::OnTouch); + EVENT_SUBSCRIBE_MEMBER(m_EEnter, &PlayerSystem::OnEnter); + EVENT_SUBSCRIBE_MEMBER(m_ELeave, &PlayerSystem::OnLeave); } virtual void Update(World* world, ComponentWrapper& player, double dt) override; @@ -39,6 +43,12 @@ private: bool OnKeyDown(const Events::KeyDown &event); EventRelay m_EKeyUp; bool OnKeyUp(const Events::KeyUp &event); + EventRelay m_EEnter; + bool OnEnter(const Events::TriggerEnter &event); + EventRelay m_ETouch; + bool PlayerSystem::OnTouch(const Events::TriggerTouch &event); + EventRelay m_ELeave; + bool PlayerSystem::OnLeave(const Events::TriggerLeave &event); }; #endif \ No newline at end of file diff --git a/resources/Schema/Entities/Test.xml b/resources/Schema/Entities/Test.xml index 78494ce1..ef194ad0 100644 --- a/resources/Schema/Entities/Test.xml +++ b/resources/Schema/Entities/Test.xml @@ -14,11 +14,13 @@ - + Models/ScaleWidget.obj + + @@ -42,6 +44,8 @@ Models/Core/UnitCube.obj + + diff --git a/src/Engine/Collision/Collision.cpp b/src/Engine/Collision/Collision.cpp index f8dfedcb..baf599c0 100644 --- a/src/Engine/Collision/Collision.cpp +++ b/src/Engine/Collision/Collision.cpp @@ -154,4 +154,18 @@ bool RayVsModel(const Ray& ray, return hit; } +bool IsSameBoxProbably(const AABB& first, const AABB& second, const float epsilon) +{ + const glm::vec3& ma1 = first.MaxCorner(); + const glm::vec3& ma2 = first.MaxCorner(); + const glm::vec3& mi1 = second.MinCorner(); + const glm::vec3& mi2 = second.MinCorner(); + return (std::abs(ma1.x - ma2.x) < epsilon) && + (std::abs(mi1.x - mi2.x) < epsilon) && + (std::abs(ma1.z - ma2.z) < epsilon) && + (std::abs(mi1.z - mi2.z) < epsilon) && + (std::abs(ma1.y - ma2.y) < epsilon) && + (std::abs(mi1.y - mi2.y) < epsilon); +} + } diff --git a/src/Engine/Collision/TriggerSystem.cpp b/src/Engine/Collision/TriggerSystem.cpp index 4b6c0de4..38a8fac4 100644 --- a/src/Engine/Collision/TriggerSystem.cpp +++ b/src/Engine/Collision/TriggerSystem.cpp @@ -1,22 +1,123 @@ #include "Collision/TriggerSystem.h" +#include "Collision/Collision.h" #include "Core/AABB.h" +#include "Rendering/Model.h" void TriggerSystem::Update(World* world, ComponentWrapper& trigger, double dt) { + //Currently only players can trigger things. auto players = world->GetComponents("Player"); if (players == nullptr) { return; } - //WTODO: Assumes box exists. - ComponentWrapper& cBox = world->GetComponent(trigger.EntityID, "AABB"); - AABB aabb; - aabb.CreateFromCenter(cBox["BoxCenter"], cBox["BoxSize"]); - for (auto& c : *players) { - - Events::TriggerEnter e; - e.Trigger = trigger.EntityID; - e.Entity = 41; - m_EventBroker->Publish(e); + EntityID tId = trigger.EntityID; + AABB triggerBox; + //The trigger *should* have a bounding box, or something, to test against so it can be triggered. + if (!getEntityBox(world, tId, triggerBox)) { + return; + } + for (auto& pc : *players) { + EntityID pId = pc.EntityID; + AABB playerBox; + //The player can't trigger anything without an AABB. + if (!getEntityBox(world, pId, playerBox)) { + continue; + } + if (!Collision::AABBVsAABB(triggerBox, playerBox)) { + //Entity is not touching the trigger, + //Throw event if it was previously. + if (throwLeaveIfWasInTrigger(m_EntitiesTouchingTrigger[tId], pId, tId)) { + continue; + } + //This only occurs if the entity was completely inside the trigger one frame, + //then completely outside the trigger, e.g. when dying and respawning. + throwLeaveIfWasInTrigger(m_EntitiesCompletelyInTrigger[tId], pId, tId); + } else { + //Entity is at least touching the trigger. + AABB completelyInsideBox; + completelyInsideBox.CreateFromCenter(triggerBox.Center(), triggerBox.Size() - playerBox.Size()); + if (Collision::AABBVsAABB(completelyInsideBox, playerBox)) { + //Entity is completely inside the trigger. + //If it was only touching before, it is erased. + m_EntitiesTouchingTrigger[tId].erase(pId); + std::unordered_set& completeSet = m_EntitiesCompletelyInTrigger[tId]; + if (completeSet.count(pId) == 0) { + //If it wasn't completely in the trigger, throw Enter and add to the set. + completeSet.insert(pId); + publish(pId, tId); + } + } else { + //Entity is only touching the trigger. + std::unordered_set& touchSet = m_EntitiesTouchingTrigger[tId]; + std::unordered_set& completeSet = m_EntitiesCompletelyInTrigger[tId]; + const auto& it = completeSet.find(pId); + //If it was completely inside before. + if (it != completeSet.end()) { + completeSet.erase(it); + touchSet.insert(pId); + //If it was completely outside before. + } else if (touchSet.count(pId) == 0) { + publish(pId, tId); + touchSet.insert(pId); + } + //Else, it was touching the trigger last frame too and nothing is done. + } + } } } +bool TriggerSystem::getEntityBox(World* world, EntityID id, AABB& outBox) +{ + //TODO: Improve checking if component exists. Remove try + bool retry; + do { + retry = false; + try { + ComponentWrapper& cBox = world->GetComponent(id, "AABB"); + outBox.CreateFromCenter(cBox["BoxCenter"], cBox["BoxSize"]); + } catch (std::out_of_range e) { + retry = true; + attachAABBComponentFromModel(world, id); + } + } while (retry); + return true; +} + +bool TriggerSystem::throwLeaveIfWasInTrigger(std::unordered_set& triggerSet, EntityID pId, EntityID tId) +{ + const auto& it = triggerSet.find(pId); + if (it != triggerSet.end()) { + //If it was in the trigger, but not anymore, throw leaveEvent and erase from the set. + triggerSet.erase(it); + publish(pId, tId); + return true; + } + return false; +} + +void TriggerSystem::attachAABBComponentFromModel(World* world, EntityID id) +{ + ComponentWrapper model = world->GetComponent(id, "Model"); + ComponentWrapper transform = world->GetComponent(id, "Transform"); + ComponentWrapper collision = world->AttachComponent(id, "AABB"); + Model* modelRes = ResourceManager::Load(model["Resource"]); + + glm::mat4 modelMatrix = modelRes->m_Matrix * + glm::translate(glm::mat4(), (glm::vec3)transform["Position"]) * + glm::toMat4((glm::quat)transform["Orientation"]) * + glm::scale((glm::vec3)transform["Scale"]); + + glm::vec3 mini = glm::vec3(INFINITY, INFINITY, INFINITY); + glm::vec3 maxi = glm::vec3(-INFINITY, -INFINITY, -INFINITY); + for (const auto& v : modelRes->m_Vertices) { + const auto& wPos = modelMatrix * glm::vec4(v.Position.x, v.Position.y, v.Position.z, 1); + maxi.x = std::max(wPos.x, maxi.x); + maxi.y = std::max(wPos.y, maxi.y); + maxi.z = std::max(wPos.z, maxi.z); + mini.x = std::min(wPos.x, mini.x); + mini.y = std::min(wPos.y, mini.y); + mini.z = std::min(wPos.z, mini.z); + } + collision["BoxCenter"] = 0.5f * (maxi + mini); + collision["BoxSize"] = maxi - mini; +} diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index b5fed86b..8c262faf 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -21,16 +21,6 @@ bool isFirstLower(const ChildInfo& first, const ChildInfo& second) return first.Distance < second.Distance; } -bool isSameBoxProbably(const AABB& first, const AABB& second) -{ - const float EPS = 0.0001f; - const auto& ma = first.MaxCorner(); - const auto& mi = first.MinCorner(); - return (std::abs(ma.x - mi.x) < EPS) && - (std::abs(ma.z - mi.z) < EPS) && - (std::abs(ma.y - mi.y) < EPS); -} - } OctTree::OctTree() @@ -228,7 +218,7 @@ bool OctTree::OctChild::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersect for (int i : m_DynamicObjIndices) { if (!m_DynamicObjectsRef[i].Checked) { const AABB& objBox = m_DynamicObjectsRef[i].Box; - if (!isSameBoxProbably(boxToTest, objBox) && + if (!Collision::IsSameBoxProbably(boxToTest, objBox) && Collision::AABBVsAABB(boxToTest, objBox)) { outBoxIntersected = objBox; return true; diff --git a/src/Game/PlayerSystem.cpp b/src/Game/PlayerSystem.cpp index 736ea105..4f86a2de 100644 --- a/src/Game/PlayerSystem.cpp +++ b/src/Game/PlayerSystem.cpp @@ -56,3 +56,21 @@ bool PlayerSystem::OnKeyUp(const Events::KeyUp & event) } return false; } + +bool PlayerSystem::OnTouch(const Events::TriggerTouch &event) +{ + LOG_INFO("Player %i touched widget (entity %i).", event.Entity, event.Trigger); + return false; +} + +bool PlayerSystem::OnEnter(const Events::TriggerEnter &event) +{ + LOG_INFO("Player %i entered widget (entity %i).", event.Entity, event.Trigger); + return false; +} + +bool PlayerSystem::OnLeave(const Events::TriggerLeave &event) +{ + LOG_INFO("Player %i left widget (entity %i).", event.Entity, event.Trigger); + return false; +} \ No newline at end of file