From e580540143b7827795f113f8aaaf54bef38ac114 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Wed, 9 Dec 2015 17:16:11 +0100 Subject: [PATCH 01/28] Made some optimizations in OctTree and added a unit test for BoxesInSameRegion. --- include/Engine/Core/OctTree.h | 9 +++--- src/Engine/Core/OctTree.cpp | 55 ++++++++++++++++++++++------------- src/Tests/OctTreeTest.cpp | 20 +++++++++++++ 3 files changed, 58 insertions(+), 26 deletions(-) diff --git a/include/Engine/Core/OctTree.h b/include/Engine/Core/OctTree.h index e5d5a83a..27fbcba7 100644 --- a/include/Engine/Core/OctTree.h +++ b/include/Engine/Core/OctTree.h @@ -25,9 +25,6 @@ public: OctTree(const OctTree&& other) = delete; OctTree& operator= (const OctTree& other) = delete; - //Collision test function. WTODO: Probably remove or relocate elsewhere, Collision system? - void Update(float dt, World* world, Camera* cam); - void AddDynamicObject(const AABB& box); void AddStaticObject(const AABB& box); @@ -36,10 +33,13 @@ public: void ClearObjects(); void ClearDynamicObjects(); + //Collision test function. WTODO: Probably remove or relocate elsewhere, Collision system? + void Update(float dt, World* world, Camera* cam); //Returns true if the ray collides with something in the tree. Result is written to [data]. bool RayCollides(const Ray& ray, Output& data) const; //Returns true if the box collides with something in the tree. //On collision with a box, that box is written to [outBoxIntersected]. + //Note: More efficient than calling BoxesInSameRegion from outside and testing there. bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const; private: @@ -47,11 +47,10 @@ private: //WTODO: Do -derived class from AABB- struct containing AABB, with a bool Tested, falsify at //start of Collision test, set on check, don't check if set already. Solves duplicate boxes in tree. //Store indices in the struct, pointing to grand ancestor list of boxes, need the same AABB not copies to save Tested. - //WTODO: Boxes collide with themselves? Fix somehow, maybe float epsilon stuff. std::vector m_StaticObjects; std::vector m_DynamicObjects; AABB m_Box; - + bool m_UpdatedOnce; unsigned int m_BoxID; glm::vec3 m_PrevPos; diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index 82c1b554..5fab6f04 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -21,6 +21,16 @@ 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() @@ -131,16 +141,18 @@ bool OctTree::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const return true; } } else { - std::vector> objVectors = { - m_StaticObjects, - m_DynamicObjects - }; - for (const auto& objVector : objVectors) { - for (const auto& obj : objVector) { - if (Collision::AABBVsAABB(boxToTest, obj)) { - outBoxIntersected = obj; - return true; - } + for (const auto& obj : m_StaticObjects) { + if (Collision::AABBVsAABB(boxToTest, obj)) { + outBoxIntersected = obj; + return true; + } + } + for (const auto& obj : m_DynamicObjects) { + //If there is a collision and it is not testing against itself. + if (!isSameBoxProbably(boxToTest, obj) && + Collision::AABBVsAABB(boxToTest, obj)) { + outBoxIntersected = obj; + return true; } } } @@ -170,17 +182,18 @@ bool OctTree::RayCollides(const Ray& ray, Output& data) const //Check against boxes in the node. float minDist = INFINITY; bool intersected = false; - std::vector> objVectors = { - m_StaticObjects, - m_DynamicObjects - }; - for (const auto& objVector : objVectors) { - for (const auto& obj : objVector) { - float dist; - if (Collision::RayVsAABB(ray, obj, dist)) { - minDist = std::min(dist, minDist); - intersected = true; - } + for (const auto& obj : m_StaticObjects) { + float dist; + if (Collision::RayVsAABB(ray, obj, dist)) { + minDist = std::min(dist, minDist); + intersected = true; + } + } + for (const auto& obj : m_DynamicObjects) { + float dist; + if (Collision::RayVsAABB(ray, obj, dist)) { + minDist = std::min(dist, minDist); + intersected = true; } } diff --git a/src/Tests/OctTreeTest.cpp b/src/Tests/OctTreeTest.cpp index 5392e63c..47036485 100644 --- a/src/Tests/OctTreeTest.cpp +++ b/src/Tests/OctTreeTest.cpp @@ -16,5 +16,25 @@ BOOST_AUTO_TEST_CASE(octTreeTest2) } +BOOST_AUTO_TEST_CASE(octSameRegionTest) +{ + glm::vec3 mini = glm::vec3(-1, -1, -1); + glm::vec3 maxi = glm::vec3(1, 1, 1); + OctTree tree(AABB(mini, maxi), 2); + AABB firstQuadrant(mini, 0.8f*mini); + tree.AddStaticObject(firstQuadrant); + AABB testBox(0.9f*mini, 0.8f*mini); + std::vector region; + tree.BoxesInSameRegion(testBox, region); + BOOST_REQUIRE(region.size() == 1); + AABB& box = region[0]; + BOOST_CHECK_CLOSE_FRACTION(box.Center().x, firstQuadrant.Center().x, 0.00001f); + BOOST_CHECK_CLOSE_FRACTION(box.Center().y, firstQuadrant.Center().y, 0.00001f); + BOOST_CHECK_CLOSE_FRACTION(box.Center().z, firstQuadrant.Center().z, 0.00001f); + BOOST_CHECK_CLOSE_FRACTION(box.HalfSize().x, firstQuadrant.HalfSize().x, 0.00001f); + BOOST_CHECK_CLOSE_FRACTION(box.HalfSize().y, firstQuadrant.HalfSize().y, 0.00001f); + BOOST_CHECK_CLOSE_FRACTION(box.HalfSize().z, firstQuadrant.HalfSize().z, 0.00001f); +} + BOOST_AUTO_TEST_SUITE_END() From 0d8f7acb7e229507e5c924c85eb59a8978232f6f Mon Sep 17 00:00:00 2001 From: William Moberg Date: Thu, 10 Dec 2015 16:04:55 +0100 Subject: [PATCH 02/28] OctTree doesn't check the same object multiple times if it exists in multiple places in the tree. --- include/Engine/Core/OctTree.h | 80 +++++++++--- include/Game/HardcodedTestWorld.h | 5 +- src/Engine/Core/OctTree.cpp | 201 +++++++++++++++++++++++------- 3 files changed, 222 insertions(+), 64 deletions(-) diff --git a/include/Engine/Core/OctTree.h b/include/Engine/Core/OctTree.h index 27fbcba7..2a3b87b2 100644 --- a/include/Engine/Core/OctTree.h +++ b/include/Engine/Core/OctTree.h @@ -13,7 +13,21 @@ public: struct Output { float CollideDistance; - }; + }; + struct ContainedObject + { + ContainedObject() + : Box(AABB()) + , Checked(false) + {} + ContainedObject(AABB box) + : Box(box) + , Checked(false) + {} + AABB Box; + bool Checked; + }; + OctTree(); ~OctTree(); //For the root OctTree, [octTreeBounds] should be a box containing the entire level. @@ -24,41 +38,71 @@ public: OctTree(const OctTree& other) = delete; OctTree(const OctTree&& other) = delete; OctTree& operator= (const OctTree& other) = delete; - + //Add a dynamic object (one that moves around) into the tree. void AddDynamicObject(const AABB& box); + //Add a static object (that does not move) into the tree. void AddStaticObject(const AABB& box); - - void BoxesInSameRegion(const AABB& box, std::vector& outBoxes) const; - + //Get the boxes that are in the same area as the input [box], the boxes are put in [outBoxes]. + void BoxesInSameRegion(const AABB& box, std::vector& outBoxes); + //Empty the tree of all objects, static and dynamic. void ClearObjects(); + //Empty the tree of all dynamic objects. Static objects remain in the tree. void ClearDynamicObjects(); //Collision test function. WTODO: Probably remove or relocate elsewhere, Collision system? void Update(float dt, World* world, Camera* cam); //Returns true if the ray collides with something in the tree. Result is written to [data]. - bool RayCollides(const Ray& ray, Output& data) const; + bool RayCollides(const Ray& ray, Output& data); //Returns true if the box collides with something in the tree. //On collision with a box, that box is written to [outBoxIntersected]. //Note: More efficient than calling BoxesInSameRegion from outside and testing there. - bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const; + bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected); private: - OctTree* m_Children[8]; - //WTODO: Do -derived class from AABB- struct containing AABB, with a bool Tested, falsify at - //start of Collision test, set on check, don't check if set already. Solves duplicate boxes in tree. - //Store indices in the struct, pointing to grand ancestor list of boxes, need the same AABB not copies to save Tested. - std::vector m_StaticObjects; - std::vector m_DynamicObjects; - AABB m_Box; - + struct OctChild; + OctChild* m_Root; + std::vector m_StaticObjects; + std::vector m_DynamicObjects; + bool m_UpdatedOnce; unsigned int m_BoxID; glm::vec3 m_PrevPos; glm::quat m_PrevOri; - inline bool hasChildren() const; - int childIndexContainingPoint(const glm::vec3& point) const; - std::vector childIndicesContainingBox(const AABB& box) const; + void falsifyObjectChecks(); + + struct OctChild + { + ~OctChild(); + OctChild(const AABB& octTreeBounds, + int subDivisions, + std::vector& staticObjects, + std::vector& dynamicObjects); + OctChild(const OctChild& other) = delete; + OctChild(const OctChild&& other) = delete; + OctChild& operator= (const OctChild& other) = delete; + void AddDynamicObject(const AABB& box); + void AddStaticObject(const AABB& box); + void BoxesInSameRegion(const AABB& box, std::vector& outBoxes) const; + void ClearObjects(); + void ClearDynamicObjects(); + bool RayCollides(const Ray& ray, Output& data) const; + bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const; + + OctChild* m_Children[8]; + //Indices into the lists in OctTree. + std::vector m_StaticObjIndices; + std::vector m_DynamicObjIndices; + AABB m_Box; + //Reference to the lists in OctTree. + std::vector& m_StaticObjectsRef; + std::vector& m_DynamicObjectsRef; + + inline bool hasChildren() const; + int childIndexContainingPoint(const glm::vec3& point) const; + std::vector childIndicesContainingBox(const AABB& box) const; + }; }; + #endif \ No newline at end of file diff --git a/include/Game/HardcodedTestWorld.h b/include/Game/HardcodedTestWorld.h index 5f7cc5f6..dd132971 100644 --- a/include/Game/HardcodedTestWorld.h +++ b/include/Game/HardcodedTestWorld.h @@ -108,12 +108,15 @@ private: ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model"); model["Resource"] = "Models/DummyScene.obj"; } + for (int i = 0; i < 16; ++i) { EntityID entityCollisionBox = world.CreateEntity(); ComponentWrapper transform = world.AttachComponent(entityCollisionBox, "Transform"); - transform["Position"] = glm::vec3(0.f, 2.f, 0.f); + transform["Position"] = glm::vec3(rand()%11-5, rand() % 11 - 5, rand() % 11 - 5); ComponentWrapper model = world.AttachComponent(entityCollisionBox, "Model"); model["Resource"] = "Models/Core/UnitBox.obj"; + float sc = 0.20f; + transform["Scale"] = glm::vec3(sc, sc, sc); ComponentWrapper collision = world.AttachComponent(entityCollisionBox, "Collision"); glm::vec3 pos = transform["Position"]; diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index 5fab6f04..63eba63e 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -38,11 +38,79 @@ OctTree::OctTree() {} OctTree::OctTree(const AABB& octTreeBounds, int subDivisions) - : m_Box(octTreeBounds) + : m_Root(new OctChild(octTreeBounds, subDivisions, m_StaticObjects, m_DynamicObjects)) , m_UpdatedOnce(false) +{} + +OctTree::~OctTree() +{ + delete m_Root; +} + +void OctTree::AddDynamicObject(const AABB& box) +{ + m_Root->AddDynamicObject(box); + m_DynamicObjects.push_back(box); +} + +void OctTree::AddStaticObject(const AABB& box) +{ + m_Root->AddStaticObject(box); + m_StaticObjects.push_back(box); +} + +void OctTree::BoxesInSameRegion(const AABB& box, std::vector& outBoxes) +{ + falsifyObjectChecks(); + m_Root->BoxesInSameRegion(box, outBoxes); +} + +void OctTree::ClearObjects() +{ + m_StaticObjects.clear(); + m_DynamicObjects.clear(); + m_Root->ClearObjects(); +} + +void OctTree::ClearDynamicObjects() +{ + m_DynamicObjects.clear(); + m_Root->ClearDynamicObjects(); +} + +bool OctTree::RayCollides(const Ray& ray, Output& data) +{ + falsifyObjectChecks(); + data.CollideDistance = -1; + return m_Root->RayCollides(ray, data); +} + +bool OctTree::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) +{ + falsifyObjectChecks(); + return m_Root->BoxCollides(boxToTest, outBoxIntersected); +} + +void OctTree::falsifyObjectChecks() +{ + for (auto& obj : m_StaticObjects) { + obj.Checked = false; + } + for (auto& obj : m_DynamicObjects) { + obj.Checked = false; + } +} + +OctTree::OctChild::OctChild(const AABB& octTreeBounds, + int subDivisions, + std::vector& staticObjects, + std::vector& dynamicObjects) + : m_Box(octTreeBounds) + , m_StaticObjectsRef(staticObjects) + , m_DynamicObjectsRef(dynamicObjects) { if (subDivisions == 0) { - for (OctTree*& c : m_Children) { + for (OctChild*& c : m_Children) { c = nullptr; } } else { @@ -78,14 +146,14 @@ OctTree::OctTree(const AABB& octTreeBounds, int subDivisions) minPos.z = parentMin.z; maxPos.z = parentCenter.z; } - m_Children[i] = new OctTree(AABB(minPos, maxPos), subDivisions); + m_Children[i] = new OctChild(AABB(minPos, maxPos), subDivisions, m_StaticObjectsRef, m_DynamicObjectsRef); } } } -OctTree::~OctTree() +OctTree::OctChild::~OctChild() { - for (OctTree*& c : m_Children) { + for (OctChild*& c : m_Children) { if (c != nullptr) { delete c; c = nullptr; @@ -95,19 +163,22 @@ OctTree::~OctTree() void OctTree::Update(float dt, World* world, Camera* cam) { - AABB aabb; for (ComponentWrapper& c : world->GetComponents("Collision")) { + AABB aabb; aabb.CreateFromCenter(c["BoxCenter"], c["BoxSize"]); - AddStaticObject(aabb); + AddDynamicObject(aabb); } const glm::vec4 redCol = glm::vec4(1, 0.2f, 0, 1); const glm::vec4 greenCol = glm::vec4(0.1f, 1.0f, 0.25f, 1); - const glm::vec3 boxSize = 0.1f*glm::vec3(1.0f, 1.0f, 1.0f); + const glm::vec4 blueCol = glm::vec4(0.1f, 0.05f, 0.95f, 1); + const glm::vec4 cyanCol = glm::vec4(0.1f, 0.9f, 0.85f, 1); + const glm::vec3 boxSize = 0.05f*glm::vec3(1.0f, 1.0f, 1.0f); if (!m_UpdatedOnce) { m_BoxID = world->CreateEntity(); ComponentWrapper transform = world->AttachComponent(m_BoxID, "Transform"); transform["Scale"] = boxSize; + ComponentWrapper model = world->AttachComponent(m_BoxID, "Model"); model["Resource"] = "Models/Core/UnitBox.obj"; m_UpdatedOnce = true; @@ -119,21 +190,24 @@ void OctTree::Update(float dt, World* world, Camera* cam) ComponentWrapper transform = world->GetComponent(m_BoxID, "Transform"); transform["Position"] = boxPos; ComponentWrapper model = world->GetComponent(m_BoxID, "Model"); - //if (BoxCollides(box, AABB())) { - if (Collision::AABBVsAABB(box, aabb)) { + bool collBox = BoxCollides(box, AABB()); + if (collBox) { cam->SetPosition(m_PrevPos); cam->SetOrientation(m_PrevOri); - model["Color"] = greenCol; + bool collRay = RayCollides({ cam->Position(), cam->Forward() }, Output()); + model["Color"] = collRay ? cyanCol : greenCol; + } else if (RayCollides({ cam->Position(), cam->Forward() }, Output())) { + model["Color"] = blueCol; } else { model["Color"] = redCol; } m_PrevPos = cam->Position(); m_PrevOri = cam->Orientation(); - ClearObjects(); + ClearDynamicObjects(); } -bool OctTree::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const +bool OctTree::OctChild::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const { if (hasChildren()) { for (int i : childIndicesContainingBox(boxToTest)) { @@ -141,25 +215,32 @@ bool OctTree::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const return true; } } else { - for (const auto& obj : m_StaticObjects) { - if (Collision::AABBVsAABB(boxToTest, obj)) { - outBoxIntersected = obj; - return true; + for (int i : m_StaticObjIndices) { + if (!m_StaticObjectsRef[i].Checked) { + const AABB& objBox = m_StaticObjectsRef[i].Box; + if (Collision::AABBVsAABB(boxToTest, objBox)) { + outBoxIntersected = objBox; + return true; + } + m_StaticObjectsRef[i].Checked = true; } } - for (const auto& obj : m_DynamicObjects) { - //If there is a collision and it is not testing against itself. - if (!isSameBoxProbably(boxToTest, obj) && - Collision::AABBVsAABB(boxToTest, obj)) { - outBoxIntersected = obj; - return true; + for (int i : m_DynamicObjIndices) { + if (!m_DynamicObjectsRef[i].Checked) { + const AABB& objBox = m_DynamicObjectsRef[i].Box; + if (!isSameBoxProbably(boxToTest, objBox) && + Collision::AABBVsAABB(boxToTest, objBox)) { + outBoxIntersected = objBox; + return true; + } + m_DynamicObjectsRef[i].Checked = true; } } } return false; } -bool OctTree::RayCollides(const Ray& ray, Output& data) const +bool OctTree::OctChild::RayCollides(const Ray& ray, Output& data) const { //If the node AABB is missed, everything it contains is missed. if (Collision::RayAABBIntr(ray, m_Box)) { @@ -182,19 +263,25 @@ bool OctTree::RayCollides(const Ray& ray, Output& data) const //Check against boxes in the node. float minDist = INFINITY; bool intersected = false; - for (const auto& obj : m_StaticObjects) { + for (int i : m_StaticObjIndices) { float dist; - if (Collision::RayVsAABB(ray, obj, dist)) { + //If we haven't tested against this object before, and the ray hits. + if (!m_StaticObjectsRef[i].Checked && + Collision::RayVsAABB(ray, m_StaticObjectsRef[i].Box, dist)) { minDist = std::min(dist, minDist); intersected = true; } + m_StaticObjectsRef[i].Checked = true; } - for (const auto& obj : m_DynamicObjects) { + for (int i : m_DynamicObjIndices) { float dist; - if (Collision::RayVsAABB(ray, obj, dist)) { + //If we haven't tested against this object before, and the ray hits. + if (!m_DynamicObjectsRef[i].Checked && + Collision::RayVsAABB(ray, m_DynamicObjectsRef[i].Box, dist)) { minDist = std::min(dist, minDist); intersected = true; } + m_DynamicObjectsRef[i].Checked = true; } data.CollideDistance = minDist; @@ -205,60 +292,84 @@ bool OctTree::RayCollides(const Ray& ray, Output& data) const } -void OctTree::AddDynamicObject(const AABB& box) +void OctTree::OctChild::AddDynamicObject(const AABB& box) { if (hasChildren()) { for (auto i : childIndicesContainingBox(box)) { m_Children[i]->AddDynamicObject(box); } } else { - m_DynamicObjects.push_back(box); + //Since it hasn't been added yet to the real object list, the index is after the last =size. + m_DynamicObjIndices.push_back(m_DynamicObjectsRef.size()); } } -void OctTree::AddStaticObject(const AABB& box) +void OctTree::OctChild::AddStaticObject(const AABB& box) { if (hasChildren()) { for (auto i : childIndicesContainingBox(box)) { m_Children[i]->AddStaticObject(box); } } else { - m_StaticObjects.push_back(box); + //Since it hasn't been added yet to the real object list, the index is after the last =size. + m_StaticObjIndices.push_back(m_StaticObjectsRef.size()); } } -void OctTree::BoxesInSameRegion(const AABB& box, std::vector& outBoxes) const +void OctTree::OctChild::BoxesInSameRegion(const AABB& box, std::vector& outBoxes) const { if (hasChildren()) { for (auto i : childIndicesContainingBox(box)) { m_Children[i]->BoxesInSameRegion(box, outBoxes); } } else { - outBoxes.insert(outBoxes.end(), m_StaticObjects.begin(), m_StaticObjects.end()); - outBoxes.insert(outBoxes.end(), m_DynamicObjects.begin(), m_DynamicObjects.end()); + int startIndex = outBoxes.size(); + int numDuplicates = 0; + outBoxes.resize(outBoxes.size() + m_StaticObjIndices.size() + m_DynamicObjIndices.size()); + for (size_t i = 0; i < m_StaticObjIndices.size(); ++i){ + ContainedObject& obj = m_StaticObjectsRef[m_StaticObjIndices[i]]; + if (obj.Checked) { + ++numDuplicates; + } else { + obj.Checked = true; + outBoxes[startIndex + i - numDuplicates] = obj.Box; + } + } + for (size_t i = 0; i < m_DynamicObjIndices.size(); ++i) { + ContainedObject& obj = m_DynamicObjectsRef[m_DynamicObjIndices[i]]; + if (obj.Checked) { + ++numDuplicates; + } else { + obj.Checked = true; + outBoxes[startIndex + i - numDuplicates] = obj.Box; + } + } + for (size_t i = 0; i < numDuplicates; ++i) { + outBoxes.pop_back(); + } } } -void OctTree::ClearObjects() +void OctTree::OctChild::ClearObjects() { if (hasChildren()) { - for (OctTree*& c : m_Children) { + for (OctChild*& c : m_Children) { c->ClearObjects(); } } else { - m_DynamicObjects.clear(); - m_StaticObjects.clear(); + m_DynamicObjIndices.clear(); + m_StaticObjIndices.clear(); } } -void OctTree::ClearDynamicObjects() +void OctTree::OctChild::ClearDynamicObjects() { if (hasChildren()) { - for (OctTree*& c : m_Children) { + for (OctChild*& c : m_Children) { c->ClearObjects(); } } else { - m_DynamicObjects.clear(); + m_DynamicObjIndices.clear(); } } @@ -274,13 +385,13 @@ void OctTree::ClearDynamicObjects() // x : - - - - + + + + // y : - - + + - - + + // z : - + - + - + - + -int OctTree::childIndexContainingPoint(const glm::vec3& point) const +int OctTree::OctChild::childIndexContainingPoint(const glm::vec3& point) const { const glm::vec3& c = m_Box.Center(); return (1 << 2) * (point.x >= c.x) | (1 << 1) * (point.y >= c.y) | (point.z >= c.z); } -std::vector OctTree::childIndicesContainingBox(const AABB& box) const +std::vector OctTree::OctChild::childIndicesContainingBox(const AABB& box) const { int minInd = childIndexContainingPoint(box.MinCorner()); int maxInd = childIndexContainingPoint(box.MaxCorner()); @@ -318,7 +429,7 @@ std::vector OctTree::childIndicesContainingBox(const AABB& box) const } } -inline bool OctTree::hasChildren() const +inline bool OctTree::OctChild::hasChildren() const { return m_Children[0] != nullptr; } \ No newline at end of file From 37f9ab232223a9aefa31c720d07f0dfd07740246 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Thu, 10 Dec 2015 17:53:33 +0100 Subject: [PATCH 03/28] Quick fix for unit test compilation. --- src/Tests/WorldTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/WorldTest.cpp b/src/Tests/WorldTest.cpp index 4fd4ceed..8d92a328 100644 --- a/src/Tests/WorldTest.cpp +++ b/src/Tests/WorldTest.cpp @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(WorldTestMultipleAllocations, * utf::tolerance(0.00001)) // Loop through them and check data int i = 0; - for (auto& c : w.GetComponents("Test")) { + for (auto& c : *w.GetComponents("Test")) { BOOST_TEST((int)c["TestInteger"] == i); i++; } From 4195baa5dca6a31eca4d83ce7df6a9cfc7eba022 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Thu, 10 Dec 2015 17:57:09 +0100 Subject: [PATCH 04/28] ContainedObject in OctTree made private. --- include/Engine/Core/OctTree.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/Engine/Core/OctTree.h b/include/Engine/Core/OctTree.h index 2a3b87b2..a3715480 100644 --- a/include/Engine/Core/OctTree.h +++ b/include/Engine/Core/OctTree.h @@ -14,19 +14,6 @@ public: { float CollideDistance; }; - struct ContainedObject - { - ContainedObject() - : Box(AABB()) - , Checked(false) - {} - ContainedObject(AABB box) - : Box(box) - , Checked(false) - {} - AABB Box; - bool Checked; - }; OctTree(); ~OctTree(); @@ -59,7 +46,20 @@ public: bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected); private: - struct OctChild; + struct OctChild; //Fwd declaration; + struct ContainedObject + { + ContainedObject() + : Box(AABB()) + , Checked(false) + {} + ContainedObject(AABB box) + : Box(box) + , Checked(false) + {} + AABB Box; + bool Checked; + }; OctChild* m_Root; std::vector m_StaticObjects; std::vector m_DynamicObjects; From 350038057d5273b16f016fc4df7a684516a87124 Mon Sep 17 00:00:00 2001 From: verysecrethero Date: Fri, 11 Dec 2015 13:51:07 +0100 Subject: [PATCH 05/28] Misc OctTreeTest changes --- src/Engine/Core/OctTree.cpp | 2 +- src/Tests/OctTreeTest.cpp | 4 +- src/Tests/OctTreeTestGameClass.cpp | 10 ++--- src/Tests/OctTreeTestHardCodedTestWorld.h | 49 +++++++++++------------ 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index 63eba63e..aae9762a 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -163,7 +163,7 @@ OctTree::OctChild::~OctChild() void OctTree::Update(float dt, World* world, Camera* cam) { - for (ComponentWrapper& c : world->GetComponents("Collision")) { + for (ComponentWrapper& c : *world->GetComponents("Collision")) { AABB aabb; aabb.CreateFromCenter(c["BoxCenter"], c["BoxSize"]); AddDynamicObject(aabb); diff --git a/src/Tests/OctTreeTest.cpp b/src/Tests/OctTreeTest.cpp index 23a09fcd..71d41cad 100644 --- a/src/Tests/OctTreeTest.cpp +++ b/src/Tests/OctTreeTest.cpp @@ -30,8 +30,8 @@ BOOST_AUTO_TEST_CASE(octTreeTest) BOOST_CHECK(someAABB.Center() == 0.5f * (minCorner + maxCorner)); //simple OctTree constructor check - OctTree someOctTree(someAABB, 5); - BOOST_CHECK(someOctTree.m_Children[0] != nullptr); + //OctTree someOctTree(someAABB, 5); + //BOOST_CHECK(someOctTree.m_Children[0] != nullptr); //simple destructor check in the end, just look for memleaks, then it didnt clear the AABB structure } diff --git a/src/Tests/OctTreeTestGameClass.cpp b/src/Tests/OctTreeTestGameClass.cpp index 6b2e23bc..735d3ee8 100644 --- a/src/Tests/OctTreeTestGameClass.cpp +++ b/src/Tests/OctTreeTestGameClass.cpp @@ -57,7 +57,7 @@ void Game::Tick() m_Renderer->Update(dt); m_EventBroker->Swap(); -#define TEST2 +#define TEST1 //this draws the octTree and you can set the cube inside it and see what boxes in the tree that it belongs to #ifdef TEST1 if (!m_UpdatedOnce) { @@ -81,13 +81,13 @@ void Game::Tick() //check all children again in the tree if they have a box in them or not, and colormark them if they do //contentboxarna får man ut - inte childboxarna! std::vector boxIndex; - boxIndex = m_World->someOctTree.childIndicesContainingBox(boxi); + boxIndex = m_World->someOctTree.m_Root->childIndicesContainingBox(boxi); for (auto& oneLinkedObject : m_World->linkOM) { ComponentWrapper model = m_World->GetComponent(oneLinkedObject.entId, "Model"); model["Color"] = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); - if (oneLinkedObject.child->m_DynamicObjects.size() != 0) { + if (oneLinkedObject.child->m_DynamicObjIndices.size() != 0) { model["Color"] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); } @@ -95,7 +95,7 @@ void Game::Tick() //REQUIRED: childIndicesContainingBox must be public to test this! for each (auto someBoxIndex in boxIndex) { - glm::vec3 pos = m_World->someOctTree.m_Children[someBoxIndex]->m_Box.Center(); + glm::vec3 pos = m_World->someOctTree.m_Root->m_Children[someBoxIndex]->m_Box.Center(); if (abs(pos.x - oneLinkedObject.posxyz.x) < 0.005f && abs(pos.y - oneLinkedObject.posxyz.y) < 0.005f && abs(pos.z - oneLinkedObject.posxyz.z) < 0.005f) { @@ -122,7 +122,7 @@ void Game::Tick() aabb.CreateFromCenter(glm::vec3(0, 2.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f)); if (m_UpdatedOnce) { - auto test = someOctTree.childIndicesContainingBox(aabb); + //auto test = someOctTree.childIndicesContainingBox(aabb); std::vector test2; someOctTree.BoxesInSameRegion(aabb, test2); } diff --git a/src/Tests/OctTreeTestHardCodedTestWorld.h b/src/Tests/OctTreeTestHardCodedTestWorld.h index a0762cce..46b1e00b 100644 --- a/src/Tests/OctTreeTestHardCodedTestWorld.h +++ b/src/Tests/OctTreeTestHardCodedTestWorld.h @@ -15,9 +15,9 @@ class HardcodedTestWorld : public World public: struct LinkOctTreeAndModel { EntityID entId; - OctTree* child; + OctTree::OctChild* child; glm::vec3 posxyz; - LinkOctTreeAndModel(EntityID eId, OctTree* ch, glm::vec3 pos) + LinkOctTreeAndModel(EntityID eId, OctTree::OctChild* ch, glm::vec3 pos) { entId = eId; child = ch; @@ -76,7 +76,7 @@ private: auto someAABB = AABB(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f)); //draw main box first - AddBoxModel(someAABB.Center(), someAABB.HalfSize().x, &someOctTree, tempId); + AddBoxModel(someAABB.Center(), someAABB.HalfSize().x, someOctTree.m_Root, tempId); //add anotherbox in octTree auto anotherBox = AABB(glm::vec3(0.1f, 0.1f, 0.1f), glm::vec3(0.2f, 0.2f, 0.2f)); @@ -84,15 +84,15 @@ private: someOctTree.AddDynamicObject(anotherBox); //draw anotherbox and save it in anotherBoxTransformId - AddBoxModel(anotherBox.Center(), anotherBox.HalfSize().x, &someOctTree, anotherBoxTransformId); + AddBoxModel(anotherBox.Center(), anotherBox.HalfSize().x, someOctTree.m_Root, anotherBoxTransformId); //draw the octTree for (size_t j = 0; j < 8; j++) { - AddBoxModel(someOctTree.m_Children[j]->m_Box.Center(), - someOctTree.m_Children[j]->m_Box.HalfSize().x, someOctTree.m_Children[j], tempId); + AddBoxModel(someOctTree.m_Root->m_Children[j]->m_Box.Center(), + someOctTree.m_Root->m_Children[j]->m_Box.HalfSize().x, someOctTree.m_Root->m_Children[j], tempId); - auto someChild = someOctTree.m_Children[j]; + auto someChild = someOctTree.m_Root->m_Children[j]; for (size_t i = 0; i < 8; i++) { @@ -103,24 +103,6 @@ private: } }//end CreateEnt - void AddBoxModel(const glm::vec3 ¢er, const float &halfSize, OctTree* child, EntityID &outEntityId) { - World& world = *this; - - EntityID entityDummyScene = world.CreateEntity(); - outEntityId = entityDummyScene; - ComponentWrapper transform = world.AttachComponent(entityDummyScene, "Transform"); - transform["Position"] = center; - transform["Scale"] = glm::vec3(1.0f, 1.0f, 1.0f)*halfSize*2.0f*0.97f; - ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model"); - model["Resource"] = "Models/Core/UnitBox.obj"; - model["Color"] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); - if (child->m_DynamicObjects.size() != 0) - model["Color"] = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); - - linkOM.emplace_back(entityDummyScene, child, center); - //extra - //allModels.push_back(model); - } void createTestEntitiesTest2() { World& world = *this; @@ -131,4 +113,21 @@ private: ComponentWrapper model = world.AttachComponent(entityCollisionBox, "Model"); model["Resource"] = "Models/Core/UnitBox.obj"; } + + void AddBoxModel(const glm::vec3 ¢er, const float &halfSize, OctTree::OctChild* child, EntityID &outEntityId) { + World& world = *this; + + EntityID entityDummyScene = world.CreateEntity(); + outEntityId = entityDummyScene; + ComponentWrapper transform = world.AttachComponent(entityDummyScene, "Transform"); + transform["Position"] = center; + transform["Scale"] = glm::vec3(1.0f, 1.0f, 1.0f)*halfSize*2.0f*0.97f; + ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model"); + model["Resource"] = "Models/Core/UnitBox.obj"; + model["Color"] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + if (child->m_DynamicObjIndices.size() != 0) + model["Color"] = glm::vec4(1.0f, 1.0f, 1.0f, 1.0f); + + linkOM.emplace_back(entityDummyScene, child, center); + } }; \ No newline at end of file From 81034420a9cb9502bc24cc06f95f0ff2f69e940e Mon Sep 17 00:00:00 2001 From: William Moberg Date: Fri, 11 Dec 2015 16:04:30 +0100 Subject: [PATCH 06/28] Put back old version of OctTree for testing, made unit tests. Tests shows the old version checks for collisions etc. faster, new version adds objects into tree faster, and doesn't give duplicated results. --- src/Engine/Core/OctTree.cpp | 2 +- src/Tests/OctTreeTest.cpp | 135 ++++++++++++++- src/Tests/OldOctTree.cpp | 328 ++++++++++++++++++++++++++++++++++++ src/Tests/OldOctTree.h | 69 ++++++++ 4 files changed, 532 insertions(+), 2 deletions(-) create mode 100644 src/Tests/OldOctTree.cpp create mode 100644 src/Tests/OldOctTree.h diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index 63eba63e..aae9762a 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -163,7 +163,7 @@ OctTree::OctChild::~OctChild() void OctTree::Update(float dt, World* world, Camera* cam) { - for (ComponentWrapper& c : world->GetComponents("Collision")) { + for (ComponentWrapper& c : *world->GetComponents("Collision")) { AABB aabb; aabb.CreateFromCenter(c["BoxCenter"], c["BoxSize"]); AddDynamicObject(aabb); diff --git a/src/Tests/OctTreeTest.cpp b/src/Tests/OctTreeTest.cpp index 47036485..d9ba2e86 100644 --- a/src/Tests/OctTreeTest.cpp +++ b/src/Tests/OctTreeTest.cpp @@ -2,7 +2,9 @@ using boost::unit_test_framework::test_suite; using boost::unit_test_framework::test_case; #include //srand -#include +#include "Engine/Core/OctTree.h" +#include "Engine/Core/Ray.h" +#include "OldOctTree.h" BOOST_AUTO_TEST_SUITE(octTreeTests) @@ -36,5 +38,136 @@ BOOST_AUTO_TEST_CASE(octSameRegionTest) BOOST_CHECK_CLOSE_FRACTION(box.HalfSize().z, firstQuadrant.HalfSize().z, 0.00001f); } +const int LEVEL_BOUNDS = 500; +const int MAXSIZE = 50; +const int BOXES = 400; +const int NUM_DYNAMICS = 0; +const int NUM_STATICS = BOXES - NUM_DYNAMICS; +const int SEED = 6548; +const int TEST_FRAMES = 1; //300 +const int NUM_FUNCTION_LOOPS = 1; //25 +const int TESTS = 1; //10 + +template +void RegionTest(Tree& tree) +{ + AABB aabb; + aabb.CreateFromCenter(glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS), + glm::vec3(rand() % MAXSIZE, rand() % MAXSIZE, rand() % MAXSIZE)); + std::vector outVec; + tree.BoxesInSameRegion(aabb, outVec); +} + +template +void RayTest(Tree& tree) +{ + Tree::Output data; + glm::vec3 rayStart = glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS); + glm::vec3 rayEnd = glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS); + tree.RayCollides({ rayStart , glm::normalize(rayEnd - rayStart) }, data); +} + +template +void BoxTest(Tree& tree) +{ + AABB outBox; + AABB aabb; + aabb.CreateFromCenter(glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS), + glm::vec3(rand() % MAXSIZE, rand() % MAXSIZE, rand() % MAXSIZE)); + tree.BoxCollides(aabb, outBox); +} + +template +void NopTest(Tree& tree) +{ + +} + +template +void TestLoop(TestFunction xTest) +{ + srand(SEED); + glm::vec3 mini = glm::vec3(0, 0, 0); + glm::vec3 maxi = glm::vec3(LEVEL_BOUNDS, LEVEL_BOUNDS, LEVEL_BOUNDS); + Tree tree(AABB(mini, maxi), 3); + AABB aabb; + glm::vec3 center; + glm::vec3 size; + for (int t = 0; t < TESTS; ++t) { + for (int i = 0; i < NUM_STATICS; ++i) { + center = glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS); + size = glm::vec3(rand() % MAXSIZE, rand() % MAXSIZE, rand() % MAXSIZE); + aabb.CreateFromCenter(center, size); + tree.AddStaticObject(aabb); + } + for (int fr = 0; fr < TEST_FRAMES; ++fr) { + for (int i = 0; i < NUM_DYNAMICS; ++i) { + center = glm::vec3(rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS, rand() % LEVEL_BOUNDS); + size = glm::vec3(rand() % MAXSIZE, rand() % MAXSIZE, rand() % MAXSIZE); + aabb.CreateFromCenter(center, size); + tree.AddDynamicObject(aabb); + } + + for (int fl = 0; fl < NUM_FUNCTION_LOOPS; ++fl) { + xTest(tree); + } + + tree.ClearDynamicObjects(); + } + tree.ClearObjects(); + } + int asda = 0; + asda = 123; + BOOST_CHECK(asda == 123); +} + +BOOST_AUTO_TEST_CASE(octRegionPerfTestWithDuplicates) +{ + TestLoop(RegionTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octRegionPerfTestNoDuplicates) +{ + TestLoop(RegionTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octBoxPerfTestWithDuplicates) +{ + TestLoop(BoxTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octBoxPerfTestNoDuplicates) +{ + TestLoop(BoxTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octRayPerfTestWithDuplicates) +{ + TestLoop(RayTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octRayPerfTestNoDuplicates) +{ + TestLoop(RayTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octNopPerfTestWithDuplicates) +{ + TestLoop(NopTest); + BOOST_CHECK(true); +} + +BOOST_AUTO_TEST_CASE(octNopPerfTestNoDuplicates) +{ + TestLoop(NopTest); + BOOST_CHECK(true); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/Tests/OldOctTree.cpp b/src/Tests/OldOctTree.cpp new file mode 100644 index 00000000..72decd4d --- /dev/null +++ b/src/Tests/OldOctTree.cpp @@ -0,0 +1,328 @@ +#include +#include +#include + +#include "OldOctTree.h" +#include "Core/Collision.h" +#include "Core/World.h" +#include "Rendering/Camera.h" + +namespace Old +{ + +namespace +{ +//To be able to sort nodes based on distance to ray origin. +struct ChildInfo +{ + int Index; + float Distance; +}; + +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() + : OctTree(AABB(), 0) +{} + +OctTree::OctTree(const AABB& octTreeBounds, int subDivisions) + : m_Box(octTreeBounds) + , m_UpdatedOnce(false) +{ + if (subDivisions == 0) { + for (OctTree*& c : m_Children) { + c = nullptr; + } + } else { + --subDivisions; + for (int i = 0; i < 8; ++i) { + glm::vec3 minPos, maxPos; + const glm::vec3& parentMin = m_Box.MinCorner(); + const glm::vec3& parentMax = m_Box.MaxCorner(); + const glm::vec3& parentCenter = m_Box.Center(); + std::bitset<3> bits(i); + //If child is 4,5,6,7. + if (bits.test(2)) { + minPos.x = parentCenter.x; + maxPos.x = parentMax.x; + } else { + minPos.x = parentMin.x; + maxPos.x = parentCenter.x; + } + + //If child is 2,3,6,7 + if (bits.test(1)) { + minPos.y = parentCenter.y; + maxPos.y = parentMax.y; + } else { + minPos.y = parentMin.y; + maxPos.y = parentCenter.y; + } + //If child is 1,3,5,7 + if (bits.test(0)) { + minPos.z = parentCenter.z; + maxPos.z = parentMax.z; + } else { + minPos.z = parentMin.z; + maxPos.z = parentCenter.z; + } + m_Children[i] = new OctTree(AABB(minPos, maxPos), subDivisions); + } + } +} + +OctTree::~OctTree() +{ + for (OctTree*& c : m_Children) { + if (c != nullptr) { + delete c; + c = nullptr; + } + } +} + +void OctTree::Update(float dt, World* world, Camera* cam) +{ + AABB aabb; + for (ComponentWrapper& c : *world->GetComponents("Collision")) { + aabb.CreateFromCenter(c["BoxCenter"], c["BoxSize"]); + AddStaticObject(aabb); + } + const glm::vec4 redCol = glm::vec4(1, 0.2f, 0, 1); + const glm::vec4 greenCol = glm::vec4(0.1f, 1.0f, 0.25f, 1); + const glm::vec3 boxSize = 0.1f*glm::vec3(1.0f, 1.0f, 1.0f); + + if (!m_UpdatedOnce) { + m_BoxID = world->CreateEntity(); + ComponentWrapper transform = world->AttachComponent(m_BoxID, "Transform"); + transform["Scale"] = boxSize; + ComponentWrapper model = world->AttachComponent(m_BoxID, "Model"); + model["Resource"] = "Models/Core/UnitBox.obj"; + m_UpdatedOnce = true; + } + + AABB box; + auto boxPos = cam->Position() + 1.2f*cam->Forward(); + box.CreateFromCenter(boxPos, boxSize); + ComponentWrapper transform = world->GetComponent(m_BoxID, "Transform"); + transform["Position"] = boxPos; + ComponentWrapper model = world->GetComponent(m_BoxID, "Model"); + //if (BoxCollides(box, AABB())) { + if (Collision::AABBVsAABB(box, aabb)) { + cam->SetPosition(m_PrevPos); + cam->SetOrientation(m_PrevOri); + model["Color"] = greenCol; + } else { + model["Color"] = redCol; + } + + m_PrevPos = cam->Position(); + m_PrevOri = cam->Orientation(); + ClearObjects(); +} + +bool OctTree::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const +{ + if (hasChildren()) { + for (int i : childIndicesContainingBox(boxToTest)) { + if (m_Children[i]->BoxCollides(boxToTest, outBoxIntersected)) + return true; + } + } else { + for (const auto& obj : m_StaticObjects) { + if (Collision::AABBVsAABB(boxToTest, obj)) { + outBoxIntersected = obj; + return true; + } + } + for (const auto& obj : m_DynamicObjects) { + //If there is a collision and it is not testing against itself. + if (!isSameBoxProbably(boxToTest, obj) && + Collision::AABBVsAABB(boxToTest, obj)) { + outBoxIntersected = obj; + return true; + } + } + } + return false; +} + +bool OctTree::RayCollides(const Ray& ray, Output& data) const +{ + //If the node AABB is missed, everything it contains is missed. + if (Collision::RayAABBIntr(ray, m_Box)) { + //If the ray shoots the tree, and it is a parent to 8 children :o + if (hasChildren()) { + //Sort children according to their distance from the ray origin. + std::vector childInfos; + childInfos.reserve(8); + for (int i = 0; i < 8; ++i) { + childInfos.push_back({ i, glm::distance(ray.Origin, m_Children[i]->m_Box.Center()) }); + } + std::sort(childInfos.begin(), childInfos.end(), isFirstLower); + //Loop through the children, starting with the one closest to the ray origin. I.e the first to be hit. + for (const ChildInfo& info : childInfos) { + if (m_Children[info.Index]->RayCollides(ray, data)) { + return true; + } + } + } else { + //Check against boxes in the node. + float minDist = INFINITY; + bool intersected = false; + for (const auto& obj : m_StaticObjects) { + float dist; + if (Collision::RayVsAABB(ray, obj, dist)) { + minDist = std::min(dist, minDist); + intersected = true; + } + } + for (const auto& obj : m_DynamicObjects) { + float dist; + if (Collision::RayVsAABB(ray, obj, dist)) { + minDist = std::min(dist, minDist); + intersected = true; + } + } + + data.CollideDistance = minDist; + return intersected; + } + } + return false; +} + + +void OctTree::AddDynamicObject(const AABB& box) +{ + if (hasChildren()) { + for (auto i : childIndicesContainingBox(box)) { + m_Children[i]->AddDynamicObject(box); + } + } else { + m_DynamicObjects.push_back(box); + } +} + +void OctTree::AddStaticObject(const AABB& box) +{ + if (hasChildren()) { + for (auto i : childIndicesContainingBox(box)) { + m_Children[i]->AddStaticObject(box); + } + } else { + m_StaticObjects.push_back(box); + } +} + +void OctTree::BoxesInSameRegion(const AABB& box, std::vector& outBoxes) const +{ + if (hasChildren()) { + for (auto i : childIndicesContainingBox(box)) { + m_Children[i]->BoxesInSameRegion(box, outBoxes); + } + } else { + outBoxes.insert(outBoxes.end(), m_StaticObjects.begin(), m_StaticObjects.end()); + outBoxes.insert(outBoxes.end(), m_DynamicObjects.begin(), m_DynamicObjects.end()); + } +} + +void OctTree::ClearObjects() +{ + if (hasChildren()) { + for (OctTree*& c : m_Children) { + c->ClearObjects(); + } + } else { + m_DynamicObjects.clear(); + m_StaticObjects.clear(); + } +} + +void OctTree::ClearDynamicObjects() +{ + if (hasChildren()) { + for (OctTree*& c : m_Children) { + c->ClearObjects(); + } + } else { + m_DynamicObjects.clear(); + } +} + +//: 3 7 +//: +//: 2 6 +//: | +//: 1 5 \ y +//: z +//: 0 4 0 x--> +// +// child: 0 1 2 3 4 5 6 7 +// x : - - - - + + + + +// y : - - + + - - + + +// z : - + - + - + - + +int OctTree::childIndexContainingPoint(const glm::vec3& point) const +{ + const glm::vec3& c = m_Box.Center(); + return (1 << 2) * (point.x >= c.x) | (1 << 1) * (point.y >= c.y) | (point.z >= c.z); +} + +std::vector OctTree::childIndicesContainingBox(const AABB& box) const +{ + int minInd = childIndexContainingPoint(box.MinCorner()); + int maxInd = childIndexContainingPoint(box.MaxCorner()); + //Because of the predictable ordering of the child indices, + //the number of bits set when xor:ing the indices will determine the number of children containing the box. + std::bitset<3> bits(minInd ^ maxInd); + switch (bits.count()) { + //Box contained completely in one child. + case 0: + return{ minInd }; + //Two children. + case 1: + return{ minInd, maxInd }; + //Four children. + case 2: + { + std::vector ret; + //Bit-hax to calculate the correct 4 children containing the box. + //This works because of the childrens index determine what part of + //the dimensions they are responsible for (which octant). + bits.flip(); + //At this point the bits necessarily have exactly one bit set. + for (int c = 0; c < 8; ++c) { + //If the child index have the same bit set as the bits, add box to it. + if (bits.to_ulong() & c) { + ret.push_back(c); + } + } + return ret; + } + case 3: //Eight children. + return{ 0,1,2,3,4,5,6,7 }; + default: + return std::vector(); + } +} + +inline bool OctTree::hasChildren() const +{ + return m_Children[0] != nullptr; +} +} \ No newline at end of file diff --git a/src/Tests/OldOctTree.h b/src/Tests/OldOctTree.h new file mode 100644 index 00000000..2e37b739 --- /dev/null +++ b/src/Tests/OldOctTree.h @@ -0,0 +1,69 @@ +#ifndef OldOctTree_h__ +#define OldOctTree_h__ + +#include "Core/AABB.h" + +struct Ray; +class World; +class Camera; + +namespace Old +{ + +class OctTree +{ +public: + struct Output + { + float CollideDistance; + }; + OctTree(); + ~OctTree(); + //For the root OctTree, [octTreeBounds] should be a box containing the entire level. + OctTree(const AABB& octTreeBounds, int subDivisions); + + //We should only ever need one OctTree in the game, and it should not need to be copied. + //Define these if the OctTree suddenly needs to be copied, think of the children OctTree* ptrs. + OctTree(const OctTree& other) = delete; + OctTree(const OctTree&& other) = delete; + OctTree& operator= (const OctTree& other) = delete; + + void AddDynamicObject(const AABB& box); + void AddStaticObject(const AABB& box); + + void BoxesInSameRegion(const AABB& box, std::vector& outBoxes) const; + + void ClearObjects(); + void ClearDynamicObjects(); + + //Collision test function. WTODO: Probably remove or relocate elsewhere, Collision system? + void Update(float dt, World* world, Camera* cam); + //Returns true if the ray collides with something in the tree. Result is written to [data]. + bool RayCollides(const Ray& ray, Output& data) const; + //Returns true if the box collides with something in the tree. + //On collision with a box, that box is written to [outBoxIntersected]. + //Note: More efficient than calling BoxesInSameRegion from outside and testing there. + bool BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const; + +private: + OctTree* m_Children[8]; + //WTODO: Do -derived class from AABB- struct containing AABB, with a bool Tested, falsify at + //start of Collision test, set on check, don't check if set already. Solves duplicate boxes in tree. + //Store indices in the struct, pointing to grand ancestor list of boxes, need the same AABB not copies to save Tested. + std::vector m_StaticObjects; + std::vector m_DynamicObjects; + AABB m_Box; + + bool m_UpdatedOnce; + unsigned int m_BoxID; + glm::vec3 m_PrevPos; + glm::quat m_PrevOri; + + inline bool hasChildren() const; + int childIndexContainingPoint(const glm::vec3& point) const; + std::vector childIndicesContainingBox(const AABB& box) const; +}; + +} + +#endif \ No newline at end of file From 9a7d77f29d400a405c885ab1ce16183f97ee6a56 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Fri, 11 Dec 2015 16:44:49 +0100 Subject: [PATCH 07/28] Removed notes in OldOctTree. --- src/Tests/OldOctTree.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Tests/OldOctTree.h b/src/Tests/OldOctTree.h index 2e37b739..316b9545 100644 --- a/src/Tests/OldOctTree.h +++ b/src/Tests/OldOctTree.h @@ -36,7 +36,7 @@ public: void ClearObjects(); void ClearDynamicObjects(); - //Collision test function. WTODO: Probably remove or relocate elsewhere, Collision system? + //Collision test function. void Update(float dt, World* world, Camera* cam); //Returns true if the ray collides with something in the tree. Result is written to [data]. bool RayCollides(const Ray& ray, Output& data) const; @@ -47,9 +47,6 @@ public: private: OctTree* m_Children[8]; - //WTODO: Do -derived class from AABB- struct containing AABB, with a bool Tested, falsify at - //start of Collision test, set on check, don't check if set already. Solves duplicate boxes in tree. - //Store indices in the struct, pointing to grand ancestor list of boxes, need the same AABB not copies to save Tested. std::vector m_StaticObjects; std::vector m_DynamicObjects; AABB m_Box; From 55aad493ff5d009740e0d2013028b3b805ac4630 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Fri, 11 Dec 2015 16:45:38 +0100 Subject: [PATCH 08/28] Fixed warnings in OctTree. Cleaned unit tests. --- src/Engine/Core/OctTree.cpp | 6 +++--- src/Tests/OctTreeTest.cpp | 9 +++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index aae9762a..e41a1692 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -300,7 +300,7 @@ void OctTree::OctChild::AddDynamicObject(const AABB& box) } } else { //Since it hasn't been added yet to the real object list, the index is after the last =size. - m_DynamicObjIndices.push_back(m_DynamicObjectsRef.size()); + m_DynamicObjIndices.push_back((int)m_DynamicObjectsRef.size()); } } @@ -312,7 +312,7 @@ void OctTree::OctChild::AddStaticObject(const AABB& box) } } else { //Since it hasn't been added yet to the real object list, the index is after the last =size. - m_StaticObjIndices.push_back(m_StaticObjectsRef.size()); + m_StaticObjIndices.push_back((int)m_StaticObjectsRef.size()); } } @@ -323,7 +323,7 @@ void OctTree::OctChild::BoxesInSameRegion(const AABB& box, std::vector& ou m_Children[i]->BoxesInSameRegion(box, outBoxes); } } else { - int startIndex = outBoxes.size(); + size_t startIndex = outBoxes.size(); int numDuplicates = 0; outBoxes.resize(outBoxes.size() + m_StaticObjIndices.size() + m_DynamicObjIndices.size()); for (size_t i = 0; i < m_StaticObjIndices.size(); ++i){ diff --git a/src/Tests/OctTreeTest.cpp b/src/Tests/OctTreeTest.cpp index d9ba2e86..efd2d109 100644 --- a/src/Tests/OctTreeTest.cpp +++ b/src/Tests/OctTreeTest.cpp @@ -44,9 +44,9 @@ const int BOXES = 400; const int NUM_DYNAMICS = 0; const int NUM_STATICS = BOXES - NUM_DYNAMICS; const int SEED = 6548; -const int TEST_FRAMES = 1; //300 -const int NUM_FUNCTION_LOOPS = 1; //25 -const int TESTS = 1; //10 +const int TEST_FRAMES = 300; +const int NUM_FUNCTION_LOOPS = 25; +const int TESTS = 0; //10 template void RegionTest(Tree& tree) @@ -116,9 +116,6 @@ void TestLoop(TestFunction xTest) } tree.ClearObjects(); } - int asda = 0; - asda = 123; - BOOST_CHECK(asda == 123); } BOOST_AUTO_TEST_CASE(octRegionPerfTestWithDuplicates) From 11e37ca8981258dd6ae4eee58f738f6ebe06f63a Mon Sep 17 00:00:00 2001 From: William Moberg Date: Mon, 14 Dec 2015 10:42:45 +0100 Subject: [PATCH 09/28] Added functions to test ray vs triangle- and model-intersections. --- include/Engine/Core/Collision.h | 28 +++++++++++- src/Engine/Core/Collision.cpp | 77 ++++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/include/Engine/Core/Collision.h b/include/Engine/Core/Collision.h index b23b33cc..87f521bd 100644 --- a/include/Engine/Core/Collision.h +++ b/include/Engine/Core/Collision.h @@ -1,17 +1,43 @@ #ifndef Collision_h__ #define Collision_h__ +#include + #include "Core/Ray.h" #include "Core/AABB.h" +#include "Engine/Rendering/RawModel.h" namespace Collision { - +//Return true if the ray hits the box. bool RayAABBIntr(const Ray& ray, const AABB& box); bool RayVsAABB(const Ray& ray, const AABB& box); +//Return true if the ray hits the box, also outputs distance from ray origin to intersection point in [outDistance]. bool RayVsAABB(const Ray& ray, const AABB& box, float& outDistance); +//Return true if the ray hits any of the triangles in the model. Stops checking when a hit is detected. +bool RayVsModel(const Ray& ray, + const std::vector& modelVertices, + const std::vector& modelIndices); +//Return true if the ray hits any of the triangles in the model. +//Also returns the position of the intersection point. Will loop through all the whole model indices. +bool RayVsModel(const Ray& ray, + const std::vector& modelVertices, + const std::vector& modelIndices, + glm::vec3& outHitPosition); +//Return true if the ray hits any of the triangles in the model. +//Also returns the distance from the ray origin to the closest +//intersection point, and the barycentric u,v-coordinates. Will loop through all the whole model indices. +bool RayVsModel(const Ray& ray, + const std::vector& modelVertices, + const std::vector& modelIndices, + float& outDistance, + float& outUCoord, + float& outVCoord); + +//Return true if the boxes are intersecting. bool AABBVsAABB(const AABB& a, const AABB& b); + } #endif \ No newline at end of file diff --git a/src/Engine/Core/Collision.cpp b/src/Engine/Core/Collision.cpp index 07e37085..dee567e0 100644 --- a/src/Engine/Core/Collision.cpp +++ b/src/Engine/Core/Collision.cpp @@ -1,6 +1,7 @@ +#include + #include "Core/Collision.h" #include "Engine/GLM.h" -#include namespace Collision { @@ -74,4 +75,76 @@ bool AABBVsAABB(const AABB& a, const AABB& b) return (abs(aCenter[1] - bCenter[1]) <= (aHSize[1] + bHSize[1])); } -} \ No newline at end of file +bool RayVsModel(const Ray& ray, + const std::vector& modelVertices, + const std::vector& modelIndices) +{ + for (int i = 0; i < modelIndices.size(); ++i) { + glm::vec3 v0 = modelVertices[modelIndices[i]].Position; + glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0 + glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0 + glm::vec3 m = ray.Origin - v0; + glm::vec3 MxE1 = glm::cross(m, e1); + glm::vec3 DxE2 = glm::cross(ray.Direction, e2); + float DetInv = 1.0f / glm::dot(e1, DxE2); + float u = glm::dot(m, DxE2) * DetInv; + float v = glm::dot(ray.Direction, MxE1) * DetInv; + if (u < 0 && v < 0 && 1 < u + v) { + continue; + } + //Here, u and v are positive, u+v <= 1, and if distance is positive - triangle is hit. + if (0 <= glm::dot(e2, MxE1) * DetInv) { + return true; + } + } + return false; +} + +bool RayVsModel(const Ray& ray, + const std::vector& modelVertices, + const std::vector& modelIndices, + float& outDistance, + float& outUCoord, + float& outVCoord) +{ + outDistance = INFINITY; + bool hit = false; + for (int i = 0; i < modelIndices.size(); ++i) { + glm::vec3 v0 = modelVertices[modelIndices[i]].Position; + glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0 + glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0 + glm::vec3 m = ray.Origin - v0; + glm::vec3 MxE1 = glm::cross(m, e1); + glm::vec3 DxE2 = glm::cross(ray.Direction, e2); + float DetInv = 1.0f / glm::dot(e1, DxE2); + float dist = glm::dot(e2, MxE1) * DetInv; + if (dist >= outDistance) { + continue; + } + float u = glm::dot(m, DxE2) * DetInv; + float v = glm::dot(ray.Direction, MxE1) * DetInv; + //If u and v are positive, u+v <= 1, dist is positive, and less than closest. + if (0 <= u && 0 <= v && u + v <= 1 && 0 <= dist) { + outDistance = dist; + outUCoord = u; + outVCoord = v; + hit = true; + } + } + return hit; +} + +bool RayVsModel(const Ray& ray, + const std::vector& modelVertices, + const std::vector& modelIndices, + glm::vec3& outHitPosition) +{ + float u; + float v; + float dist; + bool hit = RayVsModel(ray, modelVertices, modelIndices, dist, u, v); + outHitPosition = ray.Origin + dist * ray.Direction; + return hit; +} + +} From 5fce7490a9d547e37d673d78f1c714ea5e6dc718 Mon Sep 17 00:00:00 2001 From: verysecrethero Date: Mon, 14 Dec 2015 11:57:18 +0100 Subject: [PATCH 10/28] MergeFix OctTree williambranch and andersbranch --- src/Tests/OctTreeTest.cpp | 45 ++----------------- src/Tests/OctTreeTestAnders.cpp | 53 +++++++++++++++++++++++ src/Tests/OctTreeTestGameClass.h | 1 + src/Tests/OctTreeTestHardCodedTestWorld.h | 1 + 4 files changed, 58 insertions(+), 42 deletions(-) create mode 100644 src/Tests/OctTreeTestAnders.cpp diff --git a/src/Tests/OctTreeTest.cpp b/src/Tests/OctTreeTest.cpp index 68aa4c7a..0206ce50 100644 --- a/src/Tests/OctTreeTest.cpp +++ b/src/Tests/OctTreeTest.cpp @@ -2,51 +2,12 @@ using boost::unit_test_framework::test_suite; using boost::unit_test_framework::test_case; #include //srand -#include "OctTreeTestGameClass.h" -//HACK! Needed for white box testing -//else we would have to "open up" the octTree class more with get/sets, public methods, etc. which is not good encapsulation-wise -//#include "Engine/Core/OctTree.h" + +#include "Engine/Core/OctTree.h" #include "Engine/Core/Ray.h" #include "OldOctTree.h" -//friend class and refactoringIntoNewClass is some extra work and needs to be updated when the original class is updated, and can contain bugs that -//isnt in the original class -//Reflection-inspection seems to be only available for C# -//http://stackoverflow.com/questions/6778496/how-to-do-unit-testing-on-private-members-and-methods-of-c-classes -//http://stackoverflow.com/questions/3676664/unit-testing-of-private-methods -#define private public -#include -BOOST_AUTO_TEST_SUITE(octTreeTests) - -BOOST_AUTO_TEST_CASE(octTreeTest) -{ - //white box testing - //http://softwaretestingfundamentals.com/differences-between-black-box-testing-and-white-box-testing/ - //http://technologyconversations.com/2013/12/11/black-box-vs-white-box-testing/ - - //simple AABB constructor check - auto minCorner = glm::vec3(0.0f, 0.0f, 0.0f); - auto maxCorner = glm::vec3(1.0f, 1.0f, 1.0f); - auto someAABB = AABB(minCorner,maxCorner); - BOOST_CHECK(someAABB.MinCorner() == minCorner); - BOOST_CHECK(someAABB.MaxCorner() == maxCorner); - BOOST_CHECK(someAABB.Center() == 0.5f * (minCorner + maxCorner)); - - //simple OctTree constructor check - //OctTree someOctTree(someAABB, 5); - //BOOST_CHECK(someOctTree.m_Children[0] != nullptr); - - //simple destructor check in the end, just look for memleaks, then it didnt clear the AABB structure -} - -BOOST_AUTO_TEST_CASE(octTreeTest2) -{ - //octtree ritningen osv - Game game(0, nullptr); - while (game.Running()) { - game.Tick(); - } -} +BOOST_AUTO_TEST_SUITE(octTreeTestsW) BOOST_AUTO_TEST_CASE(octSameRegionTest) { diff --git a/src/Tests/OctTreeTestAnders.cpp b/src/Tests/OctTreeTestAnders.cpp new file mode 100644 index 00000000..12a122ea --- /dev/null +++ b/src/Tests/OctTreeTestAnders.cpp @@ -0,0 +1,53 @@ +#include +using boost::unit_test_framework::test_suite; +using boost::unit_test_framework::test_case; +#include //srand + +//#define private public//HACK! Needed for white box testing +//#include "Engine/Core/OctTree.h" +//#include "OldOctTree.h" +//friend class and refactoringIntoNewClass is some extra work and needs to be updated when the original class is updated, and can contain bugs that +//isnt in the original class +//Reflection-inspection seems to be only available for C# +//http://stackoverflow.com/questions/6778496/how-to-do-unit-testing-on-private-members-and-methods-of-c-classes +//http://stackoverflow.com/questions/3676664/unit-testing-of-private-methods + +#include "OctTreeTestGameClass.h" + +#define private public//HACK! Needed for white box testing +#include +//else we would have to "open up" the octTree class more with get/sets, public methods, etc. which is not good encapsulation-wise + +BOOST_AUTO_TEST_SUITE(octTreeTestsA) + +BOOST_AUTO_TEST_CASE(octTreeTest) +{ + //white box testing + //http://softwaretestingfundamentals.com/differences-between-black-box-testing-and-white-box-testing/ + //http://technologyconversations.com/2013/12/11/black-box-vs-white-box-testing/ + + //simple AABB constructor check + auto minCorner = glm::vec3(0.0f, 0.0f, 0.0f); + auto maxCorner = glm::vec3(1.0f, 1.0f, 1.0f); + auto someAABB = AABB(minCorner, maxCorner); + BOOST_CHECK(someAABB.MinCorner() == minCorner); + BOOST_CHECK(someAABB.MaxCorner() == maxCorner); + BOOST_CHECK(someAABB.Center() == 0.5f * (minCorner + maxCorner)); + + //simple OctTree constructor check + //OctTree someOctTree(someAABB, 5); + //BOOST_CHECK(someOctTree.m_Children[0] != nullptr); + + //simple destructor check in the end, just look for memleaks, then it didnt clear the AABB structure +} + +BOOST_AUTO_TEST_CASE(octTreeTest2) +{ + //octtree ritningen osv + Game game(0, nullptr); + while (game.Running()) { + game.Tick(); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/Tests/OctTreeTestGameClass.h b/src/Tests/OctTreeTestGameClass.h index 8ca34510..38aaab66 100644 --- a/src/Tests/OctTreeTestGameClass.h +++ b/src/Tests/OctTreeTestGameClass.h @@ -13,6 +13,7 @@ #include "OctTreeTestHardCodedTestWorld.h" #include "Core\Collision.h" + class Game { public: diff --git a/src/Tests/OctTreeTestHardCodedTestWorld.h b/src/Tests/OctTreeTestHardCodedTestWorld.h index 46b1e00b..512716df 100644 --- a/src/Tests/OctTreeTestHardCodedTestWorld.h +++ b/src/Tests/OctTreeTestHardCodedTestWorld.h @@ -7,6 +7,7 @@ #include //last! +//#include "OldOctTree.h" #define private public #include From 7f8e78c8cdeedcfb794b75b4cba3b00f583941ea Mon Sep 17 00:00:00 2001 From: verysecrethero Date: Mon, 14 Dec 2015 17:04:35 +0100 Subject: [PATCH 11/28] RayVsModel tests in progress --- src/Engine/Core/Collision.cpp | 13 +++-- src/Tests/CMakeLists.txt | 1 + src/Tests/CollisionTest.cpp | 95 ++++++++++++++++++++++++++++++++++- 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/Engine/Core/Collision.cpp b/src/Engine/Core/Collision.cpp index dee567e0..44875aa4 100644 --- a/src/Engine/Core/Collision.cpp +++ b/src/Engine/Core/Collision.cpp @@ -86,10 +86,14 @@ bool RayVsModel(const Ray& ray, glm::vec3 m = ray.Origin - v0; glm::vec3 MxE1 = glm::cross(m, e1); glm::vec3 DxE2 = glm::cross(ray.Direction, e2); - float DetInv = 1.0f / glm::dot(e1, DxE2); + float DetInv = glm::dot(e1, DxE2); + if (std::abs(DetInv) < FLT_EPSILON) { + continue; + } + DetInv = 1.0f / DetInv; float u = glm::dot(m, DxE2) * DetInv; float v = glm::dot(ray.Direction, MxE1) * DetInv; - if (u < 0 && v < 0 && 1 < u + v) { + if (u < 0 || v < 0 || 1 < u + v) { continue; } //Here, u and v are positive, u+v <= 1, and if distance is positive - triangle is hit. @@ -116,7 +120,10 @@ bool RayVsModel(const Ray& ray, glm::vec3 m = ray.Origin - v0; glm::vec3 MxE1 = glm::cross(m, e1); glm::vec3 DxE2 = glm::cross(ray.Direction, e2); - float DetInv = 1.0f / glm::dot(e1, DxE2); + float DetInv = glm::dot(e1, DxE2); + if (std::abs(DetInv) < FLT_EPSILON) { + continue; + } float dist = glm::dot(e2, MxE1) * DetInv; if (dist >= outDistance) { continue; diff --git a/src/Tests/CMakeLists.txt b/src/Tests/CMakeLists.txt index 697dea29..a3f95a37 100644 --- a/src/Tests/CMakeLists.txt +++ b/src/Tests/CMakeLists.txt @@ -12,6 +12,7 @@ include_directories( ) file(GLOB SOURCE_FILES + "*.h" "*.cpp" ) diff --git a/src/Tests/CollisionTest.cpp b/src/Tests/CollisionTest.cpp index eef24e61..497812a3 100644 --- a/src/Tests/CollisionTest.cpp +++ b/src/Tests/CollisionTest.cpp @@ -3,11 +3,18 @@ #include using boost::unit_test_framework::test_suite; using boost::unit_test_framework::test_case; -#include +#include "Engine\Core\Collision.h" #include "Engine/Core/AABB.h" #include "Engine/Core/Ray.h" #include //srand #include "Engine/Core/OctTree.h" +//vs model +#include + +//ray vs model +#include "Engine\Core\ResourceManager.h" +#include "Engine\Rendering\Model.h" +#include "Engine\Core\Ray.h" //vs memleaks //#define _CRTDBG_MAP_ALLOC @@ -87,6 +94,92 @@ BOOST_AUTO_TEST_CASE(collisionTest2) BOOST_CHECK(test >= 0); } +BOOST_AUTO_TEST_CASE(rayVsModelTest) +{ + //simple test + + Ray ray; + ray.Origin = glm::vec3(-50, 0, 0); + //ray.Direction = glm::vec3(-1, 0, 0); + ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); + //inte model, det kräver renderar grejs tydligen + ResourceManager::RegisterType("RawModel"); + auto unitBox = ResourceManager::Load("Models/Core/UnitBox.obj"); + BOOST_CHECK(unitBox != nullptr); + bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(hit); + ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); + hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(!hit); +} + +BOOST_AUTO_TEST_CASE(rayVsModelTest2) +{ + //advanced test, based on ray vs AABB + srand(7676762); + Ray ray; + AABB someAABB; + glm::vec3 minPos; + glm::vec3 maxPos; + bool z; + int test = 0; + minPos = glm::vec3(-0.5f, -0.5f, -0.5f); + maxPos = glm::vec3(0.5f, 0.5f, 0.5f); + someAABB = AABB(minPos, maxPos); + ResourceManager::RegisterType("RawModel"); + auto unitBox = ResourceManager::Load("Models/Core/UnitBox.obj"); + BOOST_CHECK(unitBox != nullptr); + + for (size_t i = 0; i < 1000000; i++) + { + ray.Origin.x = rand() % 100; + ray.Origin.y = rand() % 100; + ray.Origin.z = rand() % 100; + ray.Direction.x = rand() % 100; + ray.Direction.y = rand() % 100; + ray.Direction.z = rand() % 100; + ray.Origin /= 100; + ray.Origin = glm::vec3(-2, 0, 0); + ray.Direction /= 100; + ray.Direction = glm::normalize(ray.Direction); + + z = Collision::RayVsAABB(ray, someAABB); + if (z) { + //hit + bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + if (!hit) { + hit = hit; + glm::vec3 outtttttttt; + hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices, outtttttttt); + hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + } + else { + hit = hit; + } + BOOST_CHECK(hit); + } + // + bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + if (hit) { + //hit + z = Collision::RayVsAABB(ray, someAABB); + if (!z) { + z = z; + z = Collision::RayVsAABB(ray, someAABB); + glm::vec3 outtttttttt; + hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices, outtttttttt); + hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + } + else { + z = z; + } + BOOST_CHECK(hit); + } + + } +} + + BOOST_AUTO_TEST_CASE(octTest) { glm::vec3 mini = glm::vec3(-1, -1, -1); From 59095c2ce50b85f864d57eedb38d0a5da0224cde Mon Sep 17 00:00:00 2001 From: William Moberg Date: Mon, 14 Dec 2015 17:08:00 +0100 Subject: [PATCH 12/28] Started on CollisionSystem and TriggerSystem. Moved collision code to Collision folder. --- .../Engine/{Core => Collision}/Collision.h | 0 include/Engine/Collision/CollisionSystem.h | 30 ++++++++++++++ include/Engine/Collision/ETrigger.h | 39 +++++++++++++++++++ include/Engine/Collision/TriggerSystem.h | 23 +++++++++++ resources/Schema/Components.xsd | 2 + resources/Schema/Components/AABB.xml | 4 ++ resources/Schema/Components/AABB.xsd | 14 +++++++ resources/Schema/Components/Trigger.xml | 2 + resources/Schema/Components/Trigger.xsd | 8 ++++ src/Engine/{Core => Collision}/Collision.cpp | 0 src/Engine/Collision/CollisionSystem.cpp | 16 ++++++++ src/Engine/Collision/TriggerSystem.cpp | 22 +++++++++++ src/Game/Game.cpp | 4 +- 13 files changed, 162 insertions(+), 2 deletions(-) rename include/Engine/{Core => Collision}/Collision.h (100%) create mode 100644 include/Engine/Collision/CollisionSystem.h create mode 100644 include/Engine/Collision/ETrigger.h create mode 100644 include/Engine/Collision/TriggerSystem.h create mode 100644 resources/Schema/Components/AABB.xml create mode 100644 resources/Schema/Components/AABB.xsd create mode 100644 resources/Schema/Components/Trigger.xml create mode 100644 resources/Schema/Components/Trigger.xsd rename src/Engine/{Core => Collision}/Collision.cpp (100%) create mode 100644 src/Engine/Collision/CollisionSystem.cpp create mode 100644 src/Engine/Collision/TriggerSystem.cpp diff --git a/include/Engine/Core/Collision.h b/include/Engine/Collision/Collision.h similarity index 100% rename from include/Engine/Core/Collision.h rename to include/Engine/Collision/Collision.h diff --git a/include/Engine/Collision/CollisionSystem.h b/include/Engine/Collision/CollisionSystem.h new file mode 100644 index 00000000..1aecfacc --- /dev/null +++ b/include/Engine/Collision/CollisionSystem.h @@ -0,0 +1,30 @@ +#ifndef CollisionSystem_h__ +#define CollisionSystem_h__ + +#include +#include + +#include "Common.h" +#include "Core/System.h" +#include "Core/EventBroker.h" +#include "Core/EKeyUp.h" + +class CollisionSystem : public System +{ +public: + CollisionSystem(EventBroker* eventBroker) + : System(eventBroker, "AABB") + { + //TODO: Debug stuff, remove later. + EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &CollisionSystem::OnKeyUp); + } + + virtual void Update(World* world, ComponentWrapper& collision, double dt) override; + +private: + + EventRelay m_EKeyUp; + bool OnKeyUp(const Events::KeyUp &event); +}; + +#endif \ No newline at end of file diff --git a/include/Engine/Collision/ETrigger.h b/include/Engine/Collision/ETrigger.h new file mode 100644 index 00000000..aaa5ef06 --- /dev/null +++ b/include/Engine/Collision/ETrigger.h @@ -0,0 +1,39 @@ +#ifndef Events_TriggerEnter_h__ +#define Events_TriggerEnter_h__ + +#include "../Core/EventBroker.h" +#include "../Core/Entity.h" + +namespace Events +{ + +/** Thrown once, when an entity is completely inside a trigger. */ +struct TriggerTouch : Event +{ + /** The id of the entity that touches the trigger. */ + EntityID Entity; + /** The id of the trigger entity. */ + EntityID Trigger; +}; + +/** Thrown once, when an entity has completely left a trigger. */ +struct TriggerLeave : Event +{ + /** The id of the entity that left the trigger. */ + EntityID Entity; + /** The id of the trigger entity. */ + EntityID Trigger; +}; + +/** Thrown when an entity is completely inside a trigger. */ +struct TriggerEnter : Event +{ + /** The id of the entity that entered the trigger. */ + EntityID Entity; + /** The id of the trigger entity. */ + EntityID Trigger; +}; + +} + +#endif diff --git a/include/Engine/Collision/TriggerSystem.h b/include/Engine/Collision/TriggerSystem.h new file mode 100644 index 00000000..94084ee3 --- /dev/null +++ b/include/Engine/Collision/TriggerSystem.h @@ -0,0 +1,23 @@ +#ifndef PlayerSystem_h__ +#define PlayerSystem_h__ + +#include + +#include "Core/System.h" +#include "Core/EventBroker.h" +#include "ETrigger.h" + +class TriggerSystem : public System +{ +public: + TriggerSystem(EventBroker* eventBroker) + : System(eventBroker, "Trigger") + {} + + virtual void Update(World* world, ComponentWrapper& collision, double dt) override; + +private: + +}; + +#endif \ No newline at end of file diff --git a/resources/Schema/Components.xsd b/resources/Schema/Components.xsd index 12fb870e..fd04fd39 100644 --- a/resources/Schema/Components.xsd +++ b/resources/Schema/Components.xsd @@ -6,4 +6,6 @@ + + \ No newline at end of file diff --git a/resources/Schema/Components/AABB.xml b/resources/Schema/Components/AABB.xml new file mode 100644 index 00000000..341d1d0d --- /dev/null +++ b/resources/Schema/Components/AABB.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/Schema/Components/AABB.xsd b/resources/Schema/Components/AABB.xsd new file mode 100644 index 00000000..8fac860e --- /dev/null +++ b/resources/Schema/Components/AABB.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/Schema/Components/Trigger.xml b/resources/Schema/Components/Trigger.xml new file mode 100644 index 00000000..4c8aad58 --- /dev/null +++ b/resources/Schema/Components/Trigger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/Schema/Components/Trigger.xsd b/resources/Schema/Components/Trigger.xsd new file mode 100644 index 00000000..a8bc8865 --- /dev/null +++ b/resources/Schema/Components/Trigger.xsd @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Engine/Core/Collision.cpp b/src/Engine/Collision/Collision.cpp similarity index 100% rename from src/Engine/Core/Collision.cpp rename to src/Engine/Collision/Collision.cpp diff --git a/src/Engine/Collision/CollisionSystem.cpp b/src/Engine/Collision/CollisionSystem.cpp new file mode 100644 index 00000000..d1657361 --- /dev/null +++ b/src/Engine/Collision/CollisionSystem.cpp @@ -0,0 +1,16 @@ +#include "Collision/CollisionSystem.h" + + +void CollisionSystem::Update(World * world, ComponentWrapper & collision, double dt) +{ + m_EventBroker->Process(); + (glm::vec3&)collision["Center"] = (glm::vec3)world->GetComponent(collision.EntityID, "Transform")["Position"]; +} + +bool CollisionSystem::OnKeyUp(const Events::KeyUp & event) +{ + if (event.KeyCode == GLFW_KEY_Z) { + + } + return false; +} diff --git a/src/Engine/Collision/TriggerSystem.cpp b/src/Engine/Collision/TriggerSystem.cpp new file mode 100644 index 00000000..4b6c0de4 --- /dev/null +++ b/src/Engine/Collision/TriggerSystem.cpp @@ -0,0 +1,22 @@ +#include "Collision/TriggerSystem.h" +#include "Core/AABB.h" + +void TriggerSystem::Update(World* world, ComponentWrapper& trigger, double dt) +{ + 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); + } +} + diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index fa12bbc6..1070d2fa 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -1,4 +1,5 @@ #include "Game.h" +#include "Collision/TriggerSystem.h" Game::Game(int argc, char* argv[]) { @@ -46,8 +47,7 @@ Game::Game(int argc, char* argv[]) m_SystemPipeline = new SystemPipeline(m_EventBroker); m_SystemPipeline->AddSystem(); m_SystemPipeline->AddSystem(); - - + m_SystemPipeline->AddSystem(); m_LastTime = glfwGetTime(); From ae7d4b90979bf901160da16bfce4cdea3fa0aa16 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Mon, 14 Dec 2015 17:35:39 +0100 Subject: [PATCH 13/28] Put Collision folder in CMakeLists.txt and altered paths. --- include/Engine/Collision/TriggerSystem.h | 7 ++++--- src/Engine/CMakeLists.txt | 7 +++++++ src/Engine/Collision/Collision.cpp | 2 +- src/Engine/Core/OctTree.cpp | 2 +- src/Tests/CollisionTest.cpp | 2 +- src/Tests/OctTreeTestGameClass.cpp | 2 +- src/Tests/OctTreeTestGameClass.h | 2 +- src/Tests/OctTreeTestGameMain.cpp | 2 +- src/Tests/OldOctTree.cpp | 2 +- 9 files changed, 18 insertions(+), 10 deletions(-) diff --git a/include/Engine/Collision/TriggerSystem.h b/include/Engine/Collision/TriggerSystem.h index 94084ee3..a7b2122f 100644 --- a/include/Engine/Collision/TriggerSystem.h +++ b/include/Engine/Collision/TriggerSystem.h @@ -1,7 +1,8 @@ -#ifndef PlayerSystem_h__ -#define PlayerSystem_h__ +#ifndef TriggerSystem_h__ +#define TriggerSystem_h__ #include +#include #include "Core/System.h" #include "Core/EventBroker.h" @@ -17,7 +18,7 @@ public: virtual void Update(World* world, ComponentWrapper& collision, double dt) override; private: - + std::unordered_map> m_EntitiesInTrigger; }; #endif \ No newline at end of file diff --git a/src/Engine/CMakeLists.txt b/src/Engine/CMakeLists.txt index de24b9cf..915cdee2 100644 --- a/src/Engine/CMakeLists.txt +++ b/src/Engine/CMakeLists.txt @@ -70,6 +70,12 @@ file(GLOB SOURCE_FILES_GUI ) source_group(GUI FILES ${SOURCE_FILES_GUI}) +file(GLOB SOURCE_FILES_Collision + "${INCLUDE_PATH}/Collision/*.h" + "Collision/*.cpp" +) +source_group(Collision FILES ${SOURCE_FILES_Collision}) + set(SOURCE_FILES ${SOURCE_FILES_Core} ${SOURCE_FILES_Core_Util} @@ -78,6 +84,7 @@ set(SOURCE_FILES ${SOURCE_FILES_GUI} ${SOURCE_FILES_Rendering} ${SOURCE_FILES_Rendering_Util} + ${SOURCE_FILES_Collision} ) set(LIBRARIES diff --git a/src/Engine/Collision/Collision.cpp b/src/Engine/Collision/Collision.cpp index 44875aa4..f8dfedcb 100644 --- a/src/Engine/Collision/Collision.cpp +++ b/src/Engine/Collision/Collision.cpp @@ -1,6 +1,6 @@ #include -#include "Core/Collision.h" +#include "Collision/Collision.h" #include "Engine/GLM.h" namespace Collision diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index e41a1692..b5fed86b 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -3,7 +3,7 @@ #include #include "Core/OctTree.h" -#include "Core/Collision.h" +#include "Collision/Collision.h" #include "Core/World.h" #include "Rendering/Camera.h" diff --git a/src/Tests/CollisionTest.cpp b/src/Tests/CollisionTest.cpp index 497812a3..21b2ef20 100644 --- a/src/Tests/CollisionTest.cpp +++ b/src/Tests/CollisionTest.cpp @@ -3,7 +3,7 @@ #include using boost::unit_test_framework::test_suite; using boost::unit_test_framework::test_case; -#include "Engine\Core\Collision.h" +#include "Engine/Collision/Collision.h" #include "Engine/Core/AABB.h" #include "Engine/Core/Ray.h" #include //srand diff --git a/src/Tests/OctTreeTestGameClass.cpp b/src/Tests/OctTreeTestGameClass.cpp index 735d3ee8..1a7deb3f 100644 --- a/src/Tests/OctTreeTestGameClass.cpp +++ b/src/Tests/OctTreeTestGameClass.cpp @@ -15,7 +15,7 @@ Game::Game(int argc, char* argv[]) : someOctTree(AABB(-0.5f*worldSize, 0.5f*worl m_RenderQueueFactory = new RenderQueueFactory(); // Create the renderer - m_Renderer = new Renderer(); + m_Renderer = new Renderer(m_EventBroker); m_Renderer->SetFullscreen(m_Config->Get("Video.Fullscreen", false)); m_Renderer->SetVSYNC(m_Config->Get("Video.VSYNC", false)); m_Renderer->SetResolution(Rectangle( diff --git a/src/Tests/OctTreeTestGameClass.h b/src/Tests/OctTreeTestGameClass.h index 38aaab66..985d34d4 100644 --- a/src/Tests/OctTreeTestGameClass.h +++ b/src/Tests/OctTreeTestGameClass.h @@ -11,7 +11,7 @@ #include "Rendering/RenderQueueFactory.h" #include "OctTreeTestHardCodedTestWorld.h" -#include "Core\Collision.h" +#include "Collision/Collision.h" class Game diff --git a/src/Tests/OctTreeTestGameMain.cpp b/src/Tests/OctTreeTestGameMain.cpp index 5dd23cf2..43789cea 100644 --- a/src/Tests/OctTreeTestGameMain.cpp +++ b/src/Tests/OctTreeTestGameMain.cpp @@ -3,7 +3,7 @@ #include using boost::unit_test_framework::test_suite; using boost::unit_test_framework::test_case; -#include +#include "Engine/Collision/Collision.h" #include "Engine/Core/AABB.h" #include "Engine/Core/Ray.h" #include //srand diff --git a/src/Tests/OldOctTree.cpp b/src/Tests/OldOctTree.cpp index 72decd4d..4aabf9ed 100644 --- a/src/Tests/OldOctTree.cpp +++ b/src/Tests/OldOctTree.cpp @@ -3,7 +3,7 @@ #include #include "OldOctTree.h" -#include "Core/Collision.h" +#include "Collision/Collision.h" #include "Core/World.h" #include "Rendering/Camera.h" From 948f6d5de74d967f931fee04b737989930268b57 Mon Sep 17 00:00:00 2001 From: verysecrethero Date: Mon, 14 Dec 2015 17:53:11 +0100 Subject: [PATCH 14/28] RayVsModel & RayVsAABB comparison continued --- src/Engine/Core/Collision.cpp | 3 ++- src/Tests/CollisionTest.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Engine/Core/Collision.cpp b/src/Engine/Core/Collision.cpp index 44875aa4..f5381c08 100644 --- a/src/Engine/Core/Collision.cpp +++ b/src/Engine/Core/Collision.cpp @@ -119,11 +119,12 @@ bool RayVsModel(const Ray& ray, glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0 glm::vec3 m = ray.Origin - v0; glm::vec3 MxE1 = glm::cross(m, e1); - glm::vec3 DxE2 = glm::cross(ray.Direction, e2); + glm::vec3 DxE2 = glm::cross(ray.Direction, e2);//pVec float DetInv = glm::dot(e1, DxE2); if (std::abs(DetInv) < FLT_EPSILON) { continue; } + DetInv = 1.0f / DetInv; float dist = glm::dot(e2, MxE1) * DetInv; if (dist >= outDistance) { continue; diff --git a/src/Tests/CollisionTest.cpp b/src/Tests/CollisionTest.cpp index 497812a3..3395d505 100644 --- a/src/Tests/CollisionTest.cpp +++ b/src/Tests/CollisionTest.cpp @@ -127,7 +127,8 @@ BOOST_AUTO_TEST_CASE(rayVsModelTest2) maxPos = glm::vec3(0.5f, 0.5f, 0.5f); someAABB = AABB(minPos, maxPos); ResourceManager::RegisterType("RawModel"); - auto unitBox = ResourceManager::Load("Models/Core/UnitBox.obj"); + //auto unitBox = ResourceManager::Load("Models/Core/UnitBox.obj"); + auto unitBox = ResourceManager::Load("Models/Core/UnitCube.obj"); BOOST_CHECK(unitBox != nullptr); for (size_t i = 0; i < 1000000; i++) From 8e431c8edd2f0c04b45a1a53940d0201356b1ba6 Mon Sep 17 00:00:00 2001 From: verysecrethero Date: Tue, 15 Dec 2015 11:07:09 +0100 Subject: [PATCH 15/28] RayVsModel && RayVsAABB tests added and working. Also adjusted RayVsModel,RayVsAABB with deltas when values are very close to each other (floatingprecision problems) --- src/Engine/Core/Collision.cpp | 287 +++++++++++++++++----------------- src/Tests/CollisionTest.cpp | 86 +++++++++- 2 files changed, 225 insertions(+), 148 deletions(-) diff --git a/src/Engine/Core/Collision.cpp b/src/Engine/Core/Collision.cpp index f5381c08..151a7a28 100644 --- a/src/Engine/Core/Collision.cpp +++ b/src/Engine/Core/Collision.cpp @@ -6,153 +6,160 @@ namespace Collision { -bool RayAABBIntr(const Ray& ray, const AABB& box) -{ - glm::vec3 w = 75.0f * ray.Direction; - glm::vec3 v = glm::abs(w); - glm::vec3 c = ray.Origin - box.Center() + w; - glm::vec3 half = box.HalfSize(); - - if (abs(c.x) > v.x + half.x) { - return false; - } - if (abs(c.y) > v.y + half.y) { - return false; - } - if (abs(c.z) > v.z + half.z) { - return false; - } + //note: this one hasnt been delta adjusted like RayVsAABB has + bool RayAABBIntr(const Ray& ray, const AABB& box) + { + glm::vec3 w = 75.0f * ray.Direction; + glm::vec3 v = glm::abs(w); + glm::vec3 c = ray.Origin - box.Center() + w; + glm::vec3 half = box.HalfSize(); - if (abs(c.y*w.z - c.z*w.y) > half.y*v.z + half.z*v.y) { - return false; - } - if (abs(c.x*w.z - c.z*w.x) > half.x*v.z + half.z*v.x) { - return false; - } - return !(abs(c.x*w.y - c.y*w.x) > half.x*v.y + half.y*v.x); -} - -bool RayVsAABB(const Ray& ray, const AABB& box) -{ - float dummy; - return RayVsAABB(ray, box, dummy); -} - -bool RayVsAABB(const Ray& ray, const AABB& box, float& outDistance) -{ - glm::vec3 invdir = 1.0f / ray.Direction; - - float t1 = (box.MinCorner().x - ray.Origin.x)*invdir.x; - float t2 = (box.MaxCorner().x - ray.Origin.x)*invdir.x; - float t3 = (box.MinCorner().y - ray.Origin.y)*invdir.y; - float t4 = (box.MaxCorner().y - ray.Origin.y)*invdir.y; - float t5 = (box.MinCorner().z - ray.Origin.z)*invdir.z; - float t6 = (box.MaxCorner().z - ray.Origin.z)*invdir.z; - - float tmin = std::max(std::max(std::min(t1, t2), std::min(t3, t4)), std::min(t5, t6)); - float tmax = std::min(std::min(std::max(t1, t2), std::max(t3, t4)), std::max(t5, t6)); - - if (tmax < 0 || tmin > tmax) - return false; - - outDistance = (tmin > 0) ? tmin : tmax; - return true; -} - -bool AABBVsAABB(const AABB& a, const AABB& b) -{ - const glm::vec3& aCenter = a.Center(); - const glm::vec3& bCenter = b.Center(); - const glm::vec3& aHSize = a.HalfSize(); - const glm::vec3& bHSize = b.HalfSize(); - //Test will probably exit because of the X and Z axes more often, so test them first. - if (abs(aCenter[0] - bCenter[0]) > (aHSize[0] + bHSize[0])) { - return false; - } - if (abs(aCenter[2] - bCenter[2]) > (aHSize[2] + bHSize[2])) { - return false; - } - return (abs(aCenter[1] - bCenter[1]) <= (aHSize[1] + bHSize[1])); -} - -bool RayVsModel(const Ray& ray, - const std::vector& modelVertices, - const std::vector& modelIndices) -{ - for (int i = 0; i < modelIndices.size(); ++i) { - glm::vec3 v0 = modelVertices[modelIndices[i]].Position; - glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0 - glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0 - glm::vec3 m = ray.Origin - v0; - glm::vec3 MxE1 = glm::cross(m, e1); - glm::vec3 DxE2 = glm::cross(ray.Direction, e2); - float DetInv = glm::dot(e1, DxE2); - if (std::abs(DetInv) < FLT_EPSILON) { - continue; + if (abs(c.x) > v.x + half.x) { + return false; } - DetInv = 1.0f / DetInv; - float u = glm::dot(m, DxE2) * DetInv; - float v = glm::dot(ray.Direction, MxE1) * DetInv; - if (u < 0 || v < 0 || 1 < u + v) { - continue; + if (abs(c.y) > v.y + half.y) { + return false; } - //Here, u and v are positive, u+v <= 1, and if distance is positive - triangle is hit. - if (0 <= glm::dot(e2, MxE1) * DetInv) { - return true; + if (abs(c.z) > v.z + half.z) { + return false; } + + if (abs(c.y*w.z - c.z*w.y) > half.y*v.z + half.z*v.y) { + return false; + } + if (abs(c.x*w.z - c.z*w.x) > half.x*v.z + half.z*v.x) { + return false; + } + return !(abs(c.x*w.y - c.y*w.x) > half.x*v.y + half.y*v.x); } - return false; -} -bool RayVsModel(const Ray& ray, - const std::vector& modelVertices, - const std::vector& modelIndices, - float& outDistance, - float& outUCoord, - float& outVCoord) -{ - outDistance = INFINITY; - bool hit = false; - for (int i = 0; i < modelIndices.size(); ++i) { - glm::vec3 v0 = modelVertices[modelIndices[i]].Position; - glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0 - glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0 - glm::vec3 m = ray.Origin - v0; - glm::vec3 MxE1 = glm::cross(m, e1); - glm::vec3 DxE2 = glm::cross(ray.Direction, e2);//pVec - float DetInv = glm::dot(e1, DxE2); - if (std::abs(DetInv) < FLT_EPSILON) { - continue; - } - DetInv = 1.0f / DetInv; - float dist = glm::dot(e2, MxE1) * DetInv; - if (dist >= outDistance) { - continue; - } - float u = glm::dot(m, DxE2) * DetInv; - float v = glm::dot(ray.Direction, MxE1) * DetInv; - //If u and v are positive, u+v <= 1, dist is positive, and less than closest. - if (0 <= u && 0 <= v && u + v <= 1 && 0 <= dist) { - outDistance = dist; - outUCoord = u; - outVCoord = v; - hit = true; - } + bool RayVsAABB(const Ray& ray, const AABB& box) + { + float dummy; + return RayVsAABB(ray, box, dummy); } - return hit; -} -bool RayVsModel(const Ray& ray, - const std::vector& modelVertices, - const std::vector& modelIndices, - glm::vec3& outHitPosition) -{ - float u; - float v; - float dist; - bool hit = RayVsModel(ray, modelVertices, modelIndices, dist, u, v); - outHitPosition = ray.Origin + dist * ray.Direction; - return hit; -} + bool RayVsAABB(const Ray& ray, const AABB& box, float& outDistance) + { + glm::vec3 invdir = 1.0f / ray.Direction; + + float t1 = (box.MinCorner().x - ray.Origin.x)*invdir.x; + float t2 = (box.MaxCorner().x - ray.Origin.x)*invdir.x; + float t3 = (box.MinCorner().y - ray.Origin.y)*invdir.y; + float t4 = (box.MaxCorner().y - ray.Origin.y)*invdir.y; + float t5 = (box.MinCorner().z - ray.Origin.z)*invdir.z; + float t6 = (box.MaxCorner().z - ray.Origin.z)*invdir.z; + + float tmin = std::max(std::max(std::min(t1, t2), std::min(t3, t4)), std::min(t5, t6)); + float tmax = std::min(std::min(std::max(t1, t2), std::max(t3, t4)), std::max(t5, t6)); + + //if (tmax < 0 || tmin > tmax) + //if tmin,tmax are almost the same (i.e. hitting exactly in the corner) then tmin might be slightly + //greater than tmax becuase of floating-precision problems. fixed by adding a small delta to tmax + if (tmax < 0 || tmin>(tmax + 0.0001f)) + return false; + + outDistance = (tmin > 0) ? tmin : tmax; + return true; + } + + bool AABBVsAABB(const AABB& a, const AABB& b) + { + const glm::vec3& aCenter = a.Center(); + const glm::vec3& bCenter = b.Center(); + const glm::vec3& aHSize = a.HalfSize(); + const glm::vec3& bHSize = b.HalfSize(); + //Test will probably exit because of the X and Z axes more often, so test them first. + if (abs(aCenter[0] - bCenter[0]) > (aHSize[0] + bHSize[0])) { + return false; + } + if (abs(aCenter[2] - bCenter[2]) > (aHSize[2] + bHSize[2])) { + return false; + } + return (abs(aCenter[1] - bCenter[1]) <= (aHSize[1] + bHSize[1])); + } + + bool RayVsModel(const Ray& ray, + const std::vector& modelVertices, + const std::vector& modelIndices) + { + for (int i = 0; i < modelIndices.size(); ++i) { + glm::vec3 v0 = modelVertices[modelIndices[i]].Position; + glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0 + glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0 + glm::vec3 m = ray.Origin - v0; + glm::vec3 MxE1 = glm::cross(m, e1); + glm::vec3 DxE2 = glm::cross(ray.Direction, e2); + float DetInv = glm::dot(e1, DxE2); + if (std::abs(DetInv) < FLT_EPSILON) { + continue; + } + DetInv = 1.0f / DetInv; + float u = glm::dot(m, DxE2) * DetInv; + float v = glm::dot(ray.Direction, MxE1) * DetInv; + //u,v can be very close to 0 but still negative sometimes. added a deltafactor to compensate for that problem + if ((u + 0.001f) < 0 || (v + 0.001f) < 0 || 1 < u + v) { + continue; + } + //Here, u and v are positive, u+v <= 1, and if distance is positive - triangle is hit. + if (0 <= glm::dot(e2, MxE1) * DetInv) { + return true; + } + } + return false; + } + + bool RayVsModel(const Ray& ray, + const std::vector& modelVertices, + const std::vector& modelIndices, + float& outDistance, + float& outUCoord, + float& outVCoord) + { + outDistance = INFINITY; + bool hit = false; + for (int i = 0; i < modelIndices.size(); ++i) { + glm::vec3 v0 = modelVertices[modelIndices[i]].Position; + glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0 + glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0 + glm::vec3 m = ray.Origin - v0; + glm::vec3 MxE1 = glm::cross(m, e1); + glm::vec3 DxE2 = glm::cross(ray.Direction, e2);//pVec + float DetInv = glm::dot(e1, DxE2); + if (std::abs(DetInv) < FLT_EPSILON) { + continue; + } + DetInv = 1.0f / DetInv; + float dist = glm::dot(e2, MxE1) * DetInv; + if (dist >= outDistance) { + continue; + } + float u = glm::dot(m, DxE2) * DetInv; + float v = glm::dot(ray.Direction, MxE1) * DetInv; + + //u,v can be very close to 0 but still negative sometimes. added a deltafactor to compensate for that problem + //If u and v are positive, u+v <= 1, dist is positive, and less than closest. + if (0 <= (u + 0.001f) && 0 <= (v + 0.001f) && u + v <= 1 && 0 <= dist) { + outDistance = dist; + outUCoord = u; + outVCoord = v; + hit = true; + } + } + return hit; + } + + bool RayVsModel(const Ray& ray, + const std::vector& modelVertices, + const std::vector& modelIndices, + glm::vec3& outHitPosition) + { + float u; + float v; + float dist; + bool hit = RayVsModel(ray, modelVertices, modelIndices, dist, u, v); + outHitPosition = ray.Origin + dist * ray.Direction; + return hit; + } } diff --git a/src/Tests/CollisionTest.cpp b/src/Tests/CollisionTest.cpp index 3395d505..d5f7e0ed 100644 --- a/src/Tests/CollisionTest.cpp +++ b/src/Tests/CollisionTest.cpp @@ -96,13 +96,12 @@ BOOST_AUTO_TEST_CASE(collisionTest2) BOOST_AUTO_TEST_CASE(rayVsModelTest) { - //simple test + //simple box test Ray ray; ray.Origin = glm::vec3(-50, 0, 0); - //ray.Direction = glm::vec3(-1, 0, 0); ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); - //inte model, det kräver renderar grejs tydligen + //using a rawmodel here, else we have to init the renderingsystem ResourceManager::RegisterType("RawModel"); auto unitBox = ResourceManager::Load("Models/Core/UnitBox.obj"); BOOST_CHECK(unitBox != nullptr); @@ -115,19 +114,24 @@ BOOST_AUTO_TEST_CASE(rayVsModelTest) BOOST_AUTO_TEST_CASE(rayVsModelTest2) { - //advanced test, based on ray vs AABB - srand(7676762); + //advanced test, this will check so rayVSAABB and rayVsModel(with boxmodel) gives the same result (hit/miss) + //testing with different seeds +// srand(7676762); +// srand(7676462); +// srand(7462); + srand(72); Ray ray; AABB someAABB; glm::vec3 minPos; glm::vec3 maxPos; bool z; int test = 0; + //min/max is the same as the rawmodels boundaries ofcourse minPos = glm::vec3(-0.5f, -0.5f, -0.5f); maxPos = glm::vec3(0.5f, 0.5f, 0.5f); someAABB = AABB(minPos, maxPos); + //using a rawmodel here, else we have to init the renderingsystem ResourceManager::RegisterType("RawModel"); - //auto unitBox = ResourceManager::Load("Models/Core/UnitBox.obj"); auto unitBox = ResourceManager::Load("Models/Core/UnitCube.obj"); BOOST_CHECK(unitBox != nullptr); @@ -142,6 +146,9 @@ BOOST_AUTO_TEST_CASE(rayVsModelTest2) ray.Origin /= 100; ray.Origin = glm::vec3(-2, 0, 0); ray.Direction /= 100; + //if we normalize the ray.direction when its 0,0,0 then we get nan,nan,nan - thus we have this check to prevent that + if (ray.Direction.x < 0.0001f && ray.Direction.y < 0.0001f && ray.Direction.z < 0.0001f) + continue; ray.Direction = glm::normalize(ray.Direction); z = Collision::RayVsAABB(ray, someAABB); @@ -149,7 +156,7 @@ BOOST_AUTO_TEST_CASE(rayVsModelTest2) //hit bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); if (!hit) { - hit = hit; + //if rayvsaabb hit but rayvvmodel didnt hit, we get to here glm::vec3 outtttttttt; hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices, outtttttttt); hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); @@ -159,13 +166,21 @@ BOOST_AUTO_TEST_CASE(rayVsModelTest2) } BOOST_CHECK(hit); } + ////breakpoint test + //if (!z) { + // z = z; + //} // bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + ////breakpoint test + //if (!hit) { + // hit = hit; + //} if (hit) { //hit z = Collision::RayVsAABB(ray, someAABB); if (!z) { - z = z; + //if rayvsmodel hit but rayvsaabb didnt hit then we get to here z = Collision::RayVsAABB(ray, someAABB); glm::vec3 outtttttttt; hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices, outtttttttt); @@ -180,7 +195,62 @@ BOOST_AUTO_TEST_CASE(rayVsModelTest2) } } +BOOST_AUTO_TEST_CASE(rayVsModelTest3) +{ + //simple test + Ray ray; + ray.Origin = glm::vec3(-50, 0, 0); + //ray.Direction = glm::vec3(-1, 0, 0); + ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); + //using a rawmodel here, else we have to init the renderingsystem + ResourceManager::RegisterType("RawModel"); + auto unitBox = ResourceManager::Load("Models/Core/UnitSphere.obj"); + BOOST_CHECK(unitBox != nullptr); + bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(hit); + ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); + hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(!hit); +} + +BOOST_AUTO_TEST_CASE(rayVsModelTest4) +{ + //simple test + + Ray ray; + ray.Origin = glm::vec3(-50, 0, 0); + //ray.Direction = glm::vec3(-1, 0, 0); + ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); + //using a rawmodel here, else we have to init the renderingsystem + ResourceManager::RegisterType("RawModel"); + auto unitBox = ResourceManager::Load("Models/Core/UnitCylinder.obj"); + BOOST_CHECK(unitBox != nullptr); + bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(hit); + ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); + hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(!hit); +} + +BOOST_AUTO_TEST_CASE(rayVsModelTest5) +{ + //simple test + + Ray ray; + ray.Origin = glm::vec3(-50, 0, 0); + //ray.Direction = glm::vec3(-1, 0, 0); + ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); + //using a rawmodel here, else we have to init the renderingsystem + ResourceManager::RegisterType("RawModel"); + auto unitBox = ResourceManager::Load("Models/Core/UnitRaptor.obj"); + BOOST_CHECK(unitBox != nullptr); + bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(hit); + ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); + hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(!hit); +} BOOST_AUTO_TEST_CASE(octTest) { glm::vec3 mini = glm::vec3(-1, -1, -1); From 454ed0032b45e391fd3803ec3b6dee682db77e36 Mon Sep 17 00:00:00 2001 From: verysecrethero Date: Tue, 15 Dec 2015 14:01:49 +0100 Subject: [PATCH 16/28] Refactored some RayVsModel tests --- src/Tests/CollisionTest.cpp | 77 ++++++++++--------------------------- 1 file changed, 21 insertions(+), 56 deletions(-) diff --git a/src/Tests/CollisionTest.cpp b/src/Tests/CollisionTest.cpp index eb048a79..82e2e800 100644 --- a/src/Tests/CollisionTest.cpp +++ b/src/Tests/CollisionTest.cpp @@ -10,6 +10,7 @@ using boost::unit_test_framework::test_case; #include "Engine/Core/OctTree.h" //vs model #include +#include //ray vs model #include "Engine\Core\ResourceManager.h" @@ -23,6 +24,22 @@ using boost::unit_test_framework::test_case; //#define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__) //#define new DEBUG_CLIENTBLOCK +void RayTest(std::string fileName) { + //simple box test + Ray ray; + ray.Origin = glm::vec3(-50, 0, 0); + ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); + //using a rawmodel here, else we have to init the renderingsystem + ResourceManager::RegisterType("RawModel"); + auto unitBox = ResourceManager::Load(fileName); + BOOST_CHECK(unitBox != nullptr); + bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(hit); + ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); + hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); + BOOST_CHECK(!hit); +} + BOOST_AUTO_TEST_SUITE(collisionTests) BOOST_AUTO_TEST_CASE(collisionTest) @@ -97,19 +114,7 @@ BOOST_AUTO_TEST_CASE(collisionTest2) BOOST_AUTO_TEST_CASE(rayVsModelTest) { //simple box test - - Ray ray; - ray.Origin = glm::vec3(-50, 0, 0); - ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); - //using a rawmodel here, else we have to init the renderingsystem - ResourceManager::RegisterType("RawModel"); - auto unitBox = ResourceManager::Load("Models/Core/UnitBox.obj"); - BOOST_CHECK(unitBox != nullptr); - bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); - BOOST_CHECK(hit); - ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); - hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); - BOOST_CHECK(!hit); + RayTest("Models/Core/UnitBox.obj"); } BOOST_AUTO_TEST_CASE(rayVsModelTest2) @@ -194,62 +199,22 @@ BOOST_AUTO_TEST_CASE(rayVsModelTest2) } } - BOOST_AUTO_TEST_CASE(rayVsModelTest3) { //simple test - - Ray ray; - ray.Origin = glm::vec3(-50, 0, 0); - //ray.Direction = glm::vec3(-1, 0, 0); - ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); - //using a rawmodel here, else we have to init the renderingsystem - ResourceManager::RegisterType("RawModel"); - auto unitBox = ResourceManager::Load("Models/Core/UnitSphere.obj"); - BOOST_CHECK(unitBox != nullptr); - bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); - BOOST_CHECK(hit); - ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); - hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); - BOOST_CHECK(!hit); + RayTest("Models/Core/UnitSphere.obj"); } BOOST_AUTO_TEST_CASE(rayVsModelTest4) { //simple test - - Ray ray; - ray.Origin = glm::vec3(-50, 0, 0); - //ray.Direction = glm::vec3(-1, 0, 0); - ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); - //using a rawmodel here, else we have to init the renderingsystem - ResourceManager::RegisterType("RawModel"); - auto unitBox = ResourceManager::Load("Models/Core/UnitCylinder.obj"); - BOOST_CHECK(unitBox != nullptr); - bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); - BOOST_CHECK(hit); - ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); - hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); - BOOST_CHECK(!hit); + RayTest("Models/Core/UnitCylinder.obj"); } BOOST_AUTO_TEST_CASE(rayVsModelTest5) { //simple test - - Ray ray; - ray.Origin = glm::vec3(-50, 0, 0); - //ray.Direction = glm::vec3(-1, 0, 0); - ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); - //using a rawmodel here, else we have to init the renderingsystem - ResourceManager::RegisterType("RawModel"); - auto unitBox = ResourceManager::Load("Models/Core/UnitRaptor.obj"); - BOOST_CHECK(unitBox != nullptr); - bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); - BOOST_CHECK(hit); - ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); - hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); - BOOST_CHECK(!hit); + RayTest("Models/Core/UnitRaptor.obj"); } BOOST_AUTO_TEST_CASE(octTest) { From 170610bcb1c1b24b031beca0935087135a436b88 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Tue, 15 Dec 2015 14:49:52 +0100 Subject: [PATCH 17/28] 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 From e2417be8c726043f94542d17552d9770af77226d Mon Sep 17 00:00:00 2001 From: William Moberg Date: Tue, 15 Dec 2015 15:36:28 +0100 Subject: [PATCH 18/28] Very simple CollisionSystem made, 'Z','Y' to toggle on/off. --- include/Engine/Collision/CollisionSystem.h | 5 +-- resources/Schema/Entities/Test.xml | 7 ++-- src/Engine/Collision/CollisionSystem.cpp | 38 +++++++++++++++++++--- src/Game/Game.cpp | 2 ++ src/Game/PlayerSystem.cpp | 6 ++-- 5 files changed, 46 insertions(+), 12 deletions(-) diff --git a/include/Engine/Collision/CollisionSystem.h b/include/Engine/Collision/CollisionSystem.h index 1aecfacc..e7a862f7 100644 --- a/include/Engine/Collision/CollisionSystem.h +++ b/include/Engine/Collision/CollisionSystem.h @@ -14,15 +14,16 @@ class CollisionSystem : public System public: CollisionSystem(EventBroker* eventBroker) : System(eventBroker, "AABB") + , zPress(false) { //TODO: Debug stuff, remove later. EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &CollisionSystem::OnKeyUp); } - virtual void Update(World* world, ComponentWrapper& collision, double dt) override; + virtual void Update(World* world, ComponentWrapper& cAABB, double dt) override; private: - + bool zPress; EventRelay m_EKeyUp; bool OnKeyUp(const Events::KeyUp &event); }; diff --git a/resources/Schema/Entities/Test.xml b/resources/Schema/Entities/Test.xml index ef194ad0..0233ff95 100644 --- a/resources/Schema/Entities/Test.xml +++ b/resources/Schema/Entities/Test.xml @@ -14,23 +14,24 @@ - + Models/ScaleWidget.obj - - + Models/RotationWidget.obj + + diff --git a/src/Engine/Collision/CollisionSystem.cpp b/src/Engine/Collision/CollisionSystem.cpp index d1657361..e1fcb0a2 100644 --- a/src/Engine/Collision/CollisionSystem.cpp +++ b/src/Engine/Collision/CollisionSystem.cpp @@ -1,16 +1,46 @@ +#include "Collision/Collision.h" #include "Collision/CollisionSystem.h" +#include "Core/AABB.h" - -void CollisionSystem::Update(World * world, ComponentWrapper & collision, double dt) +void CollisionSystem::Update(World * world, ComponentWrapper & cAABB, double dt) { + //cAABB is any entity that should be collideable. m_EventBroker->Process(); - (glm::vec3&)collision["Center"] = (glm::vec3)world->GetComponent(collision.EntityID, "Transform")["Position"]; + //Currently the box is translated according to the models Transform matrix, + //but not scaled (or rotated since it's AABB). This should be fine, if we don't + //want to shrink or scale up an collideable object after creation. + cAABB["BoxCenter"] = (glm::vec3)world->GetComponent(cAABB.EntityID, "Transform")["Position"]; + AABB thisBox; + thisBox.CreateFromCenter(cAABB["BoxCenter"], cAABB["BoxSize"]); + //Here c should be an object that moves, currently only players. + if (zPress) { + return; + } + for (auto& mover : *world->GetComponents("Player")) { + if (cAABB.EntityID == mover.EntityID) { + continue; + } + AABB otherBox; + ComponentWrapper& aabbComp = world->GetComponent(mover.EntityID, "AABB"); + otherBox.CreateFromCenter(aabbComp["BoxCenter"], aabbComp["BoxSize"]); + if (Collision::AABBVsAABB(thisBox, otherBox)) { + ComponentWrapper& trans = world->GetComponent(mover.EntityID, "Transform"); + //TODO: Move entity to correct position on collision instead of this. Special treatment if both are movers. + glm::vec3 newPos = trans["Position"]; + float moveSpeed = 0.12f; + newPos += moveSpeed * glm::normalize(newPos - thisBox.Center()); + trans["Position"] = newPos; + aabbComp["BoxCenter"] = newPos; + } + } } bool CollisionSystem::OnKeyUp(const Events::KeyUp & event) { if (event.KeyCode == GLFW_KEY_Z) { - + zPress = true; + } else if (event.KeyCode == GLFW_KEY_X) { + zPress = false; } return false; } diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index 1070d2fa..ee5a29b0 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -1,5 +1,6 @@ #include "Game.h" #include "Collision/TriggerSystem.h" +#include "Collision/CollisionSystem.h" Game::Game(int argc, char* argv[]) { @@ -47,6 +48,7 @@ Game::Game(int argc, char* argv[]) m_SystemPipeline = new SystemPipeline(m_EventBroker); m_SystemPipeline->AddSystem(); m_SystemPipeline->AddSystem(); + m_SystemPipeline->AddSystem(); m_SystemPipeline->AddSystem(); m_LastTime = glfwGetTime(); diff --git a/src/Game/PlayerSystem.cpp b/src/Game/PlayerSystem.cpp index 4f86a2de..727ddea3 100644 --- a/src/Game/PlayerSystem.cpp +++ b/src/Game/PlayerSystem.cpp @@ -59,18 +59,18 @@ bool PlayerSystem::OnKeyUp(const Events::KeyUp & event) bool PlayerSystem::OnTouch(const Events::TriggerTouch &event) { - LOG_INFO("Player %i touched widget (entity %i).", event.Entity, event.Trigger); + LOG_INFO("Player entity %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); + LOG_INFO("Player entity %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); + LOG_INFO("Player entity %i left widget (entity %i).", event.Entity, event.Trigger); return false; } \ No newline at end of file From 99b76d7ae1780fdc3022ae097581e10fdfff1ec7 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Wed, 16 Dec 2015 12:19:43 +0100 Subject: [PATCH 19/28] Fixed returning address to temporary in functions. AABB also constructible from glm::vec4s. --- include/Engine/Core/AABB.h | 3 ++- include/Engine/Core/ComponentPool.h | 2 +- include/Engine/Core/MemoryPool.h | 2 +- src/Engine/Core/AABB.cpp | 4 ++++ src/Engine/Core/ComponentPool.cpp | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/Engine/Core/AABB.h b/include/Engine/Core/AABB.h index 0a0adafa..c8e05248 100644 --- a/include/Engine/Core/AABB.h +++ b/include/Engine/Core/AABB.h @@ -9,6 +9,7 @@ public: AABB() = default; //No checks are made. Values in minPos must be less than values in maxPos, i.e. min.x < max.x, etc. AABB(const glm::vec3& minPos, const glm::vec3& maxPos); + AABB(const glm::vec4& minPos, const glm::vec4& maxPos); //No checks are made. Size must consist of non-negative numbers. virtual void CreateFromCenter(const glm::vec3& center, const glm::vec3& size); virtual ~AABB(); @@ -16,7 +17,7 @@ public: const glm::vec3& MinCorner() const { return m_MinCorner; } const glm::vec3& MaxCorner() const { return m_MaxCorner; } const glm::vec3& Center() const { return m_Center; } - const glm::vec3& Size() const { return 2.0f * m_HalfSize; } + const glm::vec3 Size() const { return 2.0f * m_HalfSize; } const glm::vec3& HalfSize() const { return m_HalfSize; } private: glm::vec3 m_MinCorner; diff --git a/include/Engine/Core/ComponentPool.h b/include/Engine/Core/ComponentPool.h index 8dd8dc29..619aade8 100644 --- a/include/Engine/Core/ComponentPool.h +++ b/include/Engine/Core/ComponentPool.h @@ -20,7 +20,7 @@ public: ~ComponentPoolForwardIterator() = default; ComponentPoolForwardIterator& operator=(const ComponentPoolForwardIterator& other) = default; ComponentPoolForwardIterator& operator++(); - ComponentPoolForwardIterator& operator++(int); + ComponentPoolForwardIterator operator++(int); bool operator!=(const ComponentPoolForwardIterator& other) const; bool operator==(const ComponentPoolForwardIterator& other) const; ComponentWrapper operator*() const; diff --git a/include/Engine/Core/MemoryPool.h b/include/Engine/Core/MemoryPool.h index 034e6dc4..ff24ac80 100644 --- a/include/Engine/Core/MemoryPool.h +++ b/include/Engine/Core/MemoryPool.h @@ -253,7 +253,7 @@ public: } //Postfix increment i.e. iter++. Prefer pre-increment (++iter) for efficiency. - MemoryPoolForwardIterator& operator++(int) + MemoryPoolForwardIterator operator++(int) { MemoryPoolForwardIterator copyIter(*this); operator++(); diff --git a/src/Engine/Core/AABB.cpp b/src/Engine/Core/AABB.cpp index 9da2bdb0..05403022 100644 --- a/src/Engine/Core/AABB.cpp +++ b/src/Engine/Core/AABB.cpp @@ -7,6 +7,10 @@ AABB::AABB(const glm::vec3& minPos, const glm::vec3& maxPos) , m_HalfSize(0.5f * (maxPos - minPos)) {} +AABB::AABB(const glm::vec4& minPos, const glm::vec4& maxPos) + : AABB(glm::vec3(minPos), glm::vec3(maxPos)) +{} + void AABB::CreateFromCenter(const glm::vec3& center, const glm::vec3& size) { m_Center = center; diff --git a/src/Engine/Core/ComponentPool.cpp b/src/Engine/Core/ComponentPool.cpp index ca9cc801..ce24c1f7 100644 --- a/src/Engine/Core/ComponentPool.cpp +++ b/src/Engine/Core/ComponentPool.cpp @@ -19,7 +19,7 @@ bool ComponentPoolForwardIterator::operator!=(const ComponentPoolForwardIterator return m_MemoryPoolIterator != other.m_MemoryPoolIterator; } -ComponentPoolForwardIterator& ComponentPoolForwardIterator::operator++(int) +ComponentPoolForwardIterator ComponentPoolForwardIterator::operator++(int) { ComponentPoolForwardIterator copyIter(*this); operator++(); From d10df6ae095b3589b2dc3b7d29b06d7320c04f2e Mon Sep 17 00:00:00 2001 From: William Moberg Date: Wed, 16 Dec 2015 12:22:15 +0100 Subject: [PATCH 20/28] TriggerSystem implemented and working. --- include/Engine/Collision/Collision.h | 8 ++++ include/Engine/Collision/TriggerSystem.h | 8 ++-- src/Engine/Collision/Collision.cpp | 57 ++++++++++++++++++++++++ src/Engine/Collision/TriggerSystem.cpp | 54 +++------------------- src/Game/PlayerSystem.cpp | 2 +- 5 files changed, 75 insertions(+), 54 deletions(-) diff --git a/include/Engine/Collision/Collision.h b/include/Engine/Collision/Collision.h index 8b1f96b2..1fcb910e 100644 --- a/include/Engine/Collision/Collision.h +++ b/include/Engine/Collision/Collision.h @@ -6,6 +6,10 @@ #include "Core/Ray.h" #include "Core/AABB.h" #include "Engine/Rendering/RawModel.h" +#include "Core/Entity.h" + +class World; +struct ComponentWrapper; namespace Collision { @@ -39,6 +43,10 @@ bool RayVsModel(const Ray& ray, bool AABBVsAABB(const AABB& a, const AABB& b); bool IsSameBoxProbably(const AABB& first, const AABB& second, const float epsilon = 0.0001f); +//Returns true if the entity has a boundingbox. Outputs the aabb in [outBox]. +bool GetEntityBox(World* world, EntityID entity, AABB& outBox, bool forceBoxFromModel = false); +bool GetEntityBox(World* world, ComponentWrapper& AABBComponent, AABB& outBox); + } #endif \ No newline at end of file diff --git a/include/Engine/Collision/TriggerSystem.h b/include/Engine/Collision/TriggerSystem.h index 59718018..7e6ef008 100644 --- a/include/Engine/Collision/TriggerSystem.h +++ b/include/Engine/Collision/TriggerSystem.h @@ -10,23 +10,21 @@ class AABB; -class TriggerSystem : public System +class TriggerSystem : public PureSystem { public: TriggerSystem(EventBroker* eventBroker) - : System(eventBroker, "Trigger") + : PureSystem(eventBroker, "Trigger") {} - virtual void Update(World* world, ComponentWrapper& collision, double dt) override; + virtual void UpdateComponent(World* world, ComponentWrapper& collision, double dt) override; private: 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) { diff --git a/src/Engine/Collision/Collision.cpp b/src/Engine/Collision/Collision.cpp index 69bffa1a..500d59bc 100644 --- a/src/Engine/Collision/Collision.cpp +++ b/src/Engine/Collision/Collision.cpp @@ -2,6 +2,8 @@ #include "Collision/Collision.h" #include "Engine/GLM.h" +#include "Core/World.h" +#include "Rendering/Model.h" namespace Collision { @@ -176,4 +178,59 @@ bool IsSameBoxProbably(const AABB& first, const AABB& second, const float epsilo (std::abs(mi1.y - mi2.y) < epsilon); } +void attachAABBComponentFromModel(World* world, EntityID id) +{ + ComponentWrapper model = world->GetComponent(id, "Model"); + ComponentWrapper collision = world->AttachComponent(id, "AABB"); + Model* modelRes = ResourceManager::Load(model["Resource"]); + + glm::mat4 modelMatrix = modelRes->m_Matrix; + + 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; +} + +bool GetEntityBox(World* world, ComponentWrapper& AABBComponent, AABB& outBox) +{ + ComponentWrapper& cTrans = world->GetComponent(AABBComponent.EntityID, "Transform"); + ComponentWrapper model = world->GetComponent(AABBComponent.EntityID, "Model"); + Model* modelRes = ResourceManager::Load(model["Resource"]); + outBox.CreateFromCenter(AABBComponent["BoxCenter"], AABBComponent["BoxSize"]); + glm::vec3 mini = outBox.MinCorner(); + glm::vec3 maxi = outBox.MaxCorner(); + + glm::mat4 modelMatrix = modelRes->m_Matrix * + glm::translate(glm::mat4(), (glm::vec3)cTrans["Position"]) * + glm::scale((glm::vec3)cTrans["Scale"]); + + outBox = AABB(modelMatrix * glm::vec4(mini.x, mini.y, mini.z, 1), + modelMatrix * glm::vec4(maxi.x, maxi.y, maxi.z, 1)); + return true; +} + +bool GetEntityBox(World* world, EntityID entity, AABB& outBox, bool forceBoxFromModel) +{ + if (!world->HasComponent(entity, "AABB")) { + if (forceBoxFromModel) { + attachAABBComponentFromModel(world, entity); + } else { + return false; + } + } + + ComponentWrapper& cBox = world->GetComponent(entity, "AABB"); + return GetEntityBox(world, cBox, outBox); +} + } diff --git a/src/Engine/Collision/TriggerSystem.cpp b/src/Engine/Collision/TriggerSystem.cpp index 38a8fac4..09092461 100644 --- a/src/Engine/Collision/TriggerSystem.cpp +++ b/src/Engine/Collision/TriggerSystem.cpp @@ -3,7 +3,7 @@ #include "Core/AABB.h" #include "Rendering/Model.h" -void TriggerSystem::Update(World* world, ComponentWrapper& trigger, double dt) +void TriggerSystem::UpdateComponent(World* world, ComponentWrapper& trigger, double dt) { //Currently only players can trigger things. auto players = world->GetComponents("Player"); @@ -13,14 +13,14 @@ void TriggerSystem::Update(World* world, ComponentWrapper& trigger, double dt) 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)) { + if (!Collision::GetEntityBox(world, tId, triggerBox, true)) { 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)) { + if (!Collision::GetEntityBox(world, pId, playerBox, true)) { continue; } if (!Collision::AABBVsAABB(triggerBox, playerBox)) { @@ -35,8 +35,9 @@ void TriggerSystem::Update(World* world, ComponentWrapper& trigger, double dt) } else { //Entity is at least touching the trigger. AABB completelyInsideBox; - completelyInsideBox.CreateFromCenter(triggerBox.Center(), triggerBox.Size() - playerBox.Size()); - if (Collision::AABBVsAABB(completelyInsideBox, playerBox)) { + completelyInsideBox.CreateFromCenter(triggerBox.Center(), triggerBox.Size() - 2.0f * playerBox.Size()); + if (Collision::AABBVsAABB(completelyInsideBox, playerBox) && + glm::all(glm::greaterThan(triggerBox.Size(), playerBox.Size()))) { //Entity is completely inside the trigger. //If it was only touching before, it is erased. m_EntitiesTouchingTrigger[tId].erase(pId); @@ -66,23 +67,6 @@ void TriggerSystem::Update(World* world, ComponentWrapper& trigger, double dt) } } -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); @@ -95,29 +79,3 @@ bool TriggerSystem::throwLeaveIfWasInTrigger(std::unordered_set& trigg 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/Game/PlayerSystem.cpp b/src/Game/PlayerSystem.cpp index 41d5602c..9f7a14cf 100644 --- a/src/Game/PlayerSystem.cpp +++ b/src/Game/PlayerSystem.cpp @@ -16,7 +16,7 @@ void PlayerSystem::UpdateComponent(World * world, ComponentWrapper & player, dou } else { m_Direction.x = 0; } - m_EventBroker->Process(); + ComponentWrapper& transform = world->GetComponent(player.EntityID, "Transform"); (glm::vec3&)player["Velocity"] = m_Speed * float(dt) * m_Direction; (glm::vec3&)transform["Position"] += (glm::vec3)player["Velocity"]; From cf0ff9bfe54e0971f0de9981a1aef920db411474 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Wed, 16 Dec 2015 12:23:08 +0100 Subject: [PATCH 21/28] Simple quickly made CollisionSystem. --- include/Engine/Collision/CollisionSystem.h | 6 +++--- src/Engine/Collision/CollisionSystem.cpp | 21 ++++++++------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/include/Engine/Collision/CollisionSystem.h b/include/Engine/Collision/CollisionSystem.h index e7a862f7..254a2461 100644 --- a/include/Engine/Collision/CollisionSystem.h +++ b/include/Engine/Collision/CollisionSystem.h @@ -9,18 +9,18 @@ #include "Core/EventBroker.h" #include "Core/EKeyUp.h" -class CollisionSystem : public System +class CollisionSystem : public PureSystem { public: CollisionSystem(EventBroker* eventBroker) - : System(eventBroker, "AABB") + : PureSystem(eventBroker, "AABB") , zPress(false) { //TODO: Debug stuff, remove later. EVENT_SUBSCRIBE_MEMBER(m_EKeyUp, &CollisionSystem::OnKeyUp); } - virtual void Update(World* world, ComponentWrapper& cAABB, double dt) override; + virtual void UpdateComponent(World* world, ComponentWrapper& cAABB, double dt) override; private: bool zPress; diff --git a/src/Engine/Collision/CollisionSystem.cpp b/src/Engine/Collision/CollisionSystem.cpp index e1fcb0a2..ee5d4d29 100644 --- a/src/Engine/Collision/CollisionSystem.cpp +++ b/src/Engine/Collision/CollisionSystem.cpp @@ -2,16 +2,13 @@ #include "Collision/CollisionSystem.h" #include "Core/AABB.h" -void CollisionSystem::Update(World * world, ComponentWrapper & cAABB, double dt) +void CollisionSystem::UpdateComponent(World * world, ComponentWrapper & cAABB, double dt) { //cAABB is any entity that should be collideable. - m_EventBroker->Process(); - //Currently the box is translated according to the models Transform matrix, - //but not scaled (or rotated since it's AABB). This should be fine, if we don't - //want to shrink or scale up an collideable object after creation. - cAABB["BoxCenter"] = (glm::vec3)world->GetComponent(cAABB.EntityID, "Transform")["Position"]; AABB thisBox; - thisBox.CreateFromCenter(cAABB["BoxCenter"], cAABB["BoxSize"]); + if (!Collision::GetEntityBox(world, cAABB, thisBox)) { + return; + } //Here c should be an object that moves, currently only players. if (zPress) { return; @@ -21,8 +18,9 @@ void CollisionSystem::Update(World * world, ComponentWrapper & cAABB, double dt) continue; } AABB otherBox; - ComponentWrapper& aabbComp = world->GetComponent(mover.EntityID, "AABB"); - otherBox.CreateFromCenter(aabbComp["BoxCenter"], aabbComp["BoxSize"]); + if (!Collision::GetEntityBox(world, mover.EntityID, otherBox)) { + continue; + } if (Collision::AABBVsAABB(thisBox, otherBox)) { ComponentWrapper& trans = world->GetComponent(mover.EntityID, "Transform"); //TODO: Move entity to correct position on collision instead of this. Special treatment if both are movers. @@ -30,7 +28,6 @@ void CollisionSystem::Update(World * world, ComponentWrapper & cAABB, double dt) float moveSpeed = 0.12f; newPos += moveSpeed * glm::normalize(newPos - thisBox.Center()); trans["Position"] = newPos; - aabbComp["BoxCenter"] = newPos; } } } @@ -38,9 +35,7 @@ void CollisionSystem::Update(World * world, ComponentWrapper & cAABB, double dt) bool CollisionSystem::OnKeyUp(const Events::KeyUp & event) { if (event.KeyCode == GLFW_KEY_Z) { - zPress = true; - } else if (event.KeyCode == GLFW_KEY_X) { - zPress = false; + zPress = !zPress; } return false; } From 6d1cbd928ed2f84445d9390efdfcb992e0beb493 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Wed, 16 Dec 2015 16:34:27 +0100 Subject: [PATCH 22/28] Improved collision logic, still not perfect since it updates before PlayerSystem. --- include/Engine/Collision/Collision.h | 3 +++ src/Engine/Collision/Collision.cpp | 34 ++++++++++++++++++++++++ src/Engine/Collision/CollisionSystem.cpp | 17 ++++++------ 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/include/Engine/Collision/Collision.h b/include/Engine/Collision/Collision.h index 1fcb910e..64bf2d2c 100644 --- a/include/Engine/Collision/Collision.h +++ b/include/Engine/Collision/Collision.h @@ -41,6 +41,9 @@ bool RayVsModel(const Ray& ray, //Return true if the boxes are intersecting. bool AABBVsAABB(const AABB& a, const AABB& b); +//Return true if the boxes are intersecting. +//Also outputs the minimum translation that box [a] would need in order to resolve collision. +bool AABBVsAABB(const AABB& a, const AABB& b, glm::vec3& minimumTranslation); bool IsSameBoxProbably(const AABB& first, const AABB& second, const float epsilon = 0.0001f); //Returns true if the entity has a boundingbox. Outputs the aabb in [outBox]. diff --git a/src/Engine/Collision/Collision.cpp b/src/Engine/Collision/Collision.cpp index 500d59bc..d9cf8a0e 100644 --- a/src/Engine/Collision/Collision.cpp +++ b/src/Engine/Collision/Collision.cpp @@ -81,6 +81,40 @@ namespace Collision return (abs(aCenter[1] - bCenter[1]) <= (aHSize[1] + bHSize[1])); } + bool AABBVsAABB(const AABB& a, const AABB& b, glm::vec3& minimumTranslation) + { + minimumTranslation = glm::vec3(0, 0, 0); + const glm::vec3& aMax = a.MaxCorner(); + const glm::vec3& bMax = b.MaxCorner(); + const glm::vec3& aMin = a.MinCorner(); + const glm::vec3& bMin = b.MinCorner(); + const glm::vec3& bSize = b.Size(); + const glm::vec3& aSize = a.Size(); + float minOffset = INFINITY; + float off; + auto axisesIntersecting = glm::tvec3(false, false, false); + for (int i = 0; i < 3; ++i) { + off = bMax[i] - aMin[i]; + if (off > 0 && off < bSize[i] + aSize[i]) { + if (off < minOffset) { + minimumTranslation = glm::vec3(); + minimumTranslation[i] = minOffset = off; + } + axisesIntersecting[i] = true; + } + off = aMax[i] - bMin[i]; + if (off > 0 && off < bSize[i] + aSize[i]) { + if (off < minOffset) { + minOffset = off; + minimumTranslation = glm::vec3(); + minimumTranslation[i] = -off; + } + axisesIntersecting[i] = true; + } + } + return glm::all(axisesIntersecting); + } + bool RayVsModel(const Ray& ray, const std::vector& modelVertices, const std::vector& modelIndices) diff --git a/src/Engine/Collision/CollisionSystem.cpp b/src/Engine/Collision/CollisionSystem.cpp index ee5d4d29..dec33a20 100644 --- a/src/Engine/Collision/CollisionSystem.cpp +++ b/src/Engine/Collision/CollisionSystem.cpp @@ -4,15 +4,18 @@ void CollisionSystem::UpdateComponent(World * world, ComponentWrapper & cAABB, double dt) { - //cAABB is any entity that should be collideable. + //TODO: Update CollisionSystem system after PlayerSystem. + + //Right now, cAABB is a component attached to any entity that should be collideable. AABB thisBox; if (!Collision::GetEntityBox(world, cAABB, thisBox)) { return; } - //Here c should be an object that moves, currently only players. + //Press 'Z' to enable/disable collision. if (zPress) { return; } + //Here, mover should be an object that moves, currently only players. for (auto& mover : *world->GetComponents("Player")) { if (cAABB.EntityID == mover.EntityID) { continue; @@ -21,13 +24,11 @@ void CollisionSystem::UpdateComponent(World * world, ComponentWrapper & cAABB, d if (!Collision::GetEntityBox(world, mover.EntityID, otherBox)) { continue; } - if (Collision::AABBVsAABB(thisBox, otherBox)) { + glm::vec3 resolveTranslation; + if (Collision::AABBVsAABB(otherBox, thisBox, resolveTranslation)) { ComponentWrapper& trans = world->GetComponent(mover.EntityID, "Transform"); - //TODO: Move entity to correct position on collision instead of this. Special treatment if both are movers. - glm::vec3 newPos = trans["Position"]; - float moveSpeed = 0.12f; - newPos += moveSpeed * glm::normalize(newPos - thisBox.Center()); - trans["Position"] = newPos; + //TODO: Special treatment if both are movers. + trans["Position"] = (glm::vec3)trans["Position"] + resolveTranslation; } } } From a23d69af9a884ef0839e09fc5827e882aeb4dee1 Mon Sep 17 00:00:00 2001 From: William Moberg Date: Wed, 16 Dec 2015 17:08:55 +0100 Subject: [PATCH 23/28] Removed old relics from OctTree testing in OctTree. --- include/Engine/Core/OctTree.h | 8 ++---- src/Engine/Core/OctTree.cpp | 48 ----------------------------------- 2 files changed, 2 insertions(+), 54 deletions(-) diff --git a/include/Engine/Core/OctTree.h b/include/Engine/Core/OctTree.h index a3715480..c09ab63e 100644 --- a/include/Engine/Core/OctTree.h +++ b/include/Engine/Core/OctTree.h @@ -4,8 +4,6 @@ #include "Core/AABB.h" struct Ray; -class World; -class Camera; class OctTree { @@ -20,8 +18,8 @@ public: //For the root OctTree, [octTreeBounds] should be a box containing the entire level. OctTree(const AABB& octTreeBounds, int subDivisions); - //We should only ever need one OctTree in the game, and it should not need to be copied. - //Define these if the OctTree suddenly needs to be copied, think of the children OctTree* ptrs. + //We cannot copy the OctTree as of now, because of the recursive dynamic allocation. + //Define these if the OctTree suddenly needs to be copied, think of the children OctChild* ptrs. OctTree(const OctTree& other) = delete; OctTree(const OctTree&& other) = delete; OctTree& operator= (const OctTree& other) = delete; @@ -36,8 +34,6 @@ public: //Empty the tree of all dynamic objects. Static objects remain in the tree. void ClearDynamicObjects(); - //Collision test function. WTODO: Probably remove or relocate elsewhere, Collision system? - void Update(float dt, World* world, Camera* cam); //Returns true if the ray collides with something in the tree. Result is written to [data]. bool RayCollides(const Ray& ray, Output& data); //Returns true if the box collides with something in the tree. diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index 8c262faf..31b32cec 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -4,8 +4,6 @@ #include "Core/OctTree.h" #include "Collision/Collision.h" -#include "Core/World.h" -#include "Rendering/Camera.h" namespace { @@ -151,52 +149,6 @@ OctTree::OctChild::~OctChild() } } -void OctTree::Update(float dt, World* world, Camera* cam) -{ - for (ComponentWrapper& c : *world->GetComponents("Collision")) { - AABB aabb; - aabb.CreateFromCenter(c["BoxCenter"], c["BoxSize"]); - AddDynamicObject(aabb); - } - const glm::vec4 redCol = glm::vec4(1, 0.2f, 0, 1); - const glm::vec4 greenCol = glm::vec4(0.1f, 1.0f, 0.25f, 1); - const glm::vec4 blueCol = glm::vec4(0.1f, 0.05f, 0.95f, 1); - const glm::vec4 cyanCol = glm::vec4(0.1f, 0.9f, 0.85f, 1); - const glm::vec3 boxSize = 0.05f*glm::vec3(1.0f, 1.0f, 1.0f); - - if (!m_UpdatedOnce) { - m_BoxID = world->CreateEntity(); - ComponentWrapper transform = world->AttachComponent(m_BoxID, "Transform"); - transform["Scale"] = boxSize; - - ComponentWrapper model = world->AttachComponent(m_BoxID, "Model"); - model["Resource"] = "Models/Core/UnitBox.obj"; - m_UpdatedOnce = true; - } - - AABB box; - auto boxPos = cam->Position() + 1.2f*cam->Forward(); - box.CreateFromCenter(boxPos, boxSize); - ComponentWrapper transform = world->GetComponent(m_BoxID, "Transform"); - transform["Position"] = boxPos; - ComponentWrapper model = world->GetComponent(m_BoxID, "Model"); - bool collBox = BoxCollides(box, AABB()); - if (collBox) { - cam->SetPosition(m_PrevPos); - cam->SetOrientation(m_PrevOri); - bool collRay = RayCollides({ cam->Position(), cam->Forward() }, Output()); - model["Color"] = collRay ? cyanCol : greenCol; - } else if (RayCollides({ cam->Position(), cam->Forward() }, Output())) { - model["Color"] = blueCol; - } else { - model["Color"] = redCol; - } - - m_PrevPos = cam->Position(); - m_PrevOri = cam->Orientation(); - ClearDynamicObjects(); -} - bool OctTree::OctChild::BoxCollides(const AABB& boxToTest, AABB& outBoxIntersected) const { if (hasChildren()) { From 03d5f0dd674e486dfdbcbabced4f48875731285d Mon Sep 17 00:00:00 2001 From: William Moberg Date: Thu, 17 Dec 2015 13:31:54 +0100 Subject: [PATCH 24/28] Added an macro IF_DEBUG_IS(x) that executes if run in debug mode, AABB has a check so max and min corners are set properly in debug mode. --- include/Engine/Common.h | 3 ++- include/Engine/Core/Util/IfDebug.h | 17 +++++++++++++++++ src/Engine/Core/AABB.cpp | 15 ++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 include/Engine/Core/Util/IfDebug.h diff --git a/include/Engine/Common.h b/include/Engine/Common.h index 2469a11d..ebdc90d0 100644 --- a/include/Engine/Common.h +++ b/include/Engine/Common.h @@ -4,4 +4,5 @@ #include #include -#include "Core/Util/Logging.h" \ No newline at end of file +#include "Core/Util/Logging.h" +#include "Core/Util/IfDebug.h" \ No newline at end of file diff --git a/include/Engine/Core/Util/IfDebug.h b/include/Engine/Core/Util/IfDebug.h new file mode 100644 index 00000000..159162c5 --- /dev/null +++ b/include/Engine/Core/Util/IfDebug.h @@ -0,0 +1,17 @@ +//Example: +//IF_DEBUG_IS(true) { +// //Stuff done only in debug mode. +//} +// +//IF_DEBUG_IS(false) { +// //Stuff done only in release mode. +//} else { +// //Stuff only in debug. +//} +#ifndef IF_DEBUG_IS +#ifndef DEBUG +#define IF_DEBUG_IS(c) if(c) +#else +#define IF_DEBUG_IS(c) if(!c) +#endif +#endif \ No newline at end of file diff --git a/src/Engine/Core/AABB.cpp b/src/Engine/Core/AABB.cpp index 05403022..b1da6b26 100644 --- a/src/Engine/Core/AABB.cpp +++ b/src/Engine/Core/AABB.cpp @@ -1,11 +1,24 @@ #include "Core/AABB.h" +#include "Common.h" AABB::AABB(const glm::vec3& minPos, const glm::vec3& maxPos) : m_MinCorner(minPos) , m_MaxCorner(maxPos) , m_Center(0.5f * (maxPos + minPos)) , m_HalfSize(0.5f * (maxPos - minPos)) -{} +{ + IF_DEBUG_IS(true) { + if (glm::any(glm::lessThan(m_MaxCorner, m_MinCorner))) { + LOG_WARNING("AABB maxCorner coordinates are not greater than minCorner"); + m_MaxCorner.x = glm::max(m_MaxCorner.x, m_MinCorner.x); + m_MinCorner.x = glm::min(m_MaxCorner.x, m_MinCorner.x); + m_MaxCorner.y = glm::max(m_MaxCorner.y, m_MinCorner.y); + m_MinCorner.y = glm::min(m_MaxCorner.y, m_MinCorner.y); + m_MaxCorner.z = glm::max(m_MaxCorner.z, m_MinCorner.z); + m_MinCorner.z = glm::min(m_MaxCorner.z, m_MinCorner.z); + } + } +} AABB::AABB(const glm::vec4& minPos, const glm::vec4& maxPos) : AABB(glm::vec3(minPos), glm::vec3(maxPos)) From 5160a45b446a9b30a85e35161e0442010b27fcfd Mon Sep 17 00:00:00 2001 From: William Moberg Date: Thu, 17 Dec 2015 13:41:34 +0100 Subject: [PATCH 25/28] Made Ray into a class with checks so the direction will always be normalized. --- include/Engine/Collision/Collision.h | 4 +++ include/Engine/Core/OctTree.h | 2 +- include/Engine/Core/Ray.h | 25 +++++++++++-- src/Engine/Collision/Collision.cpp | 33 ++++++++--------- src/Engine/Core/OctTree.cpp | 2 +- src/Tests/CollisionTest.cpp | 53 ++++++++++------------------ src/Tests/OldOctTree.cpp | 2 +- src/Tests/OldOctTree.h | 2 +- 8 files changed, 66 insertions(+), 57 deletions(-) diff --git a/include/Engine/Collision/Collision.h b/include/Engine/Collision/Collision.h index 64bf2d2c..714cee3f 100644 --- a/include/Engine/Collision/Collision.h +++ b/include/Engine/Collision/Collision.h @@ -1,6 +1,10 @@ #ifndef Collision_h__ #define Collision_h__ +//NOTE: Collision.h needs to be #included before , +//because Collision #includes "RawModel.h", which has "Texture.h", which has "OpenGL.h" which must be #included first +//or you will get "fatal error C1189: #error: gl.h included before glew.h" + #include #include "Core/Ray.h" diff --git a/include/Engine/Core/OctTree.h b/include/Engine/Core/OctTree.h index c09ab63e..cdb21fbb 100644 --- a/include/Engine/Core/OctTree.h +++ b/include/Engine/Core/OctTree.h @@ -3,7 +3,7 @@ #include "Core/AABB.h" -struct Ray; +class Ray; class OctTree { diff --git a/include/Engine/Core/Ray.h b/include/Engine/Core/Ray.h index 64392cbc..2ed192a2 100644 --- a/include/Engine/Core/Ray.h +++ b/include/Engine/Core/Ray.h @@ -2,11 +2,30 @@ #define Ray_h__ #include "../GLM.h" +#include "Common.h" -struct Ray +class Ray { - glm::vec3 Origin; - glm::vec3 Direction; +public: + Ray(const glm::vec3& origin, const glm::vec3& dir) + : m_Origin(origin) + , m_Direction(glm::normalize(dir)) + { + IF_DEBUG_IS(true) { + if (glm::any(glm::isnan(m_Direction))) { + LOG_WARNING("Ray Direction was set to the zero-vector, expect unknown side effects and/or crashes."); + } + } + } + const glm::vec3& Origin() const { return m_Origin; } + const glm::vec3& Direction() const { return m_Direction; } + //Sets the ray origin at parameter. + void SetOrigin(const glm::vec3& origin) { m_Origin = origin; } + //Normalizes the parameter and sets direction to it. + void SetDirection(const glm::vec3& direction) { m_Direction = glm::normalize(direction); } +private: + glm::vec3 m_Origin; + glm::vec3 m_Direction; }; #endif // Ray_h__ diff --git a/src/Engine/Collision/Collision.cpp b/src/Engine/Collision/Collision.cpp index d9cf8a0e..ec6c471c 100644 --- a/src/Engine/Collision/Collision.cpp +++ b/src/Engine/Collision/Collision.cpp @@ -11,9 +11,9 @@ namespace Collision //note: this one hasnt been delta adjusted like RayVsAABB has bool RayAABBIntr(const Ray& ray, const AABB& box) { - glm::vec3 w = 75.0f * ray.Direction; + glm::vec3 w = 75.0f * ray.Direction(); glm::vec3 v = glm::abs(w); - glm::vec3 c = ray.Origin - box.Center() + w; + glm::vec3 c = ray.Origin() - box.Center() + w; glm::vec3 half = box.HalfSize(); if (abs(c.x) > v.x + half.x) { @@ -43,14 +43,15 @@ namespace Collision bool RayVsAABB(const Ray& ray, const AABB& box, float& outDistance) { - glm::vec3 invdir = 1.0f / ray.Direction; + glm::vec3 invdir = 1.0f / ray.Direction(); + glm::vec3 origin = ray.Origin(); - float t1 = (box.MinCorner().x - ray.Origin.x)*invdir.x; - float t2 = (box.MaxCorner().x - ray.Origin.x)*invdir.x; - float t3 = (box.MinCorner().y - ray.Origin.y)*invdir.y; - float t4 = (box.MaxCorner().y - ray.Origin.y)*invdir.y; - float t5 = (box.MinCorner().z - ray.Origin.z)*invdir.z; - float t6 = (box.MaxCorner().z - ray.Origin.z)*invdir.z; + float t1 = (box.MinCorner().x - origin.x)*invdir.x; + float t2 = (box.MaxCorner().x - origin.x)*invdir.x; + float t3 = (box.MinCorner().y - origin.y)*invdir.y; + float t4 = (box.MaxCorner().y - origin.y)*invdir.y; + float t5 = (box.MinCorner().z - origin.z)*invdir.z; + float t6 = (box.MaxCorner().z - origin.z)*invdir.z; float tmin = std::max(std::max(std::min(t1, t2), std::min(t3, t4)), std::min(t5, t6)); float tmax = std::min(std::min(std::max(t1, t2), std::max(t3, t4)), std::max(t5, t6)); @@ -123,16 +124,16 @@ namespace Collision glm::vec3 v0 = modelVertices[modelIndices[i]].Position; glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0 glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0 - glm::vec3 m = ray.Origin - v0; + glm::vec3 m = ray.Origin() - v0; glm::vec3 MxE1 = glm::cross(m, e1); - glm::vec3 DxE2 = glm::cross(ray.Direction, e2); + glm::vec3 DxE2 = glm::cross(ray.Direction(), e2); float DetInv = glm::dot(e1, DxE2); if (std::abs(DetInv) < FLT_EPSILON) { continue; } DetInv = 1.0f / DetInv; float u = glm::dot(m, DxE2) * DetInv; - float v = glm::dot(ray.Direction, MxE1) * DetInv; + float v = glm::dot(ray.Direction(), MxE1) * DetInv; //u,v can be very close to 0 but still negative sometimes. added a deltafactor to compensate for that problem if ((u + 0.001f) < 0 || (v + 0.001f) < 0 || 1 < u + v) { continue; @@ -158,9 +159,9 @@ namespace Collision glm::vec3 v0 = modelVertices[modelIndices[i]].Position; glm::vec3 e1 = modelVertices[modelIndices[++i]].Position - v0; //v1 - v0 glm::vec3 e2 = modelVertices[modelIndices[++i]].Position - v0; //v2 - v0 - glm::vec3 m = ray.Origin - v0; + glm::vec3 m = ray.Origin() - v0; glm::vec3 MxE1 = glm::cross(m, e1); - glm::vec3 DxE2 = glm::cross(ray.Direction, e2);//pVec + glm::vec3 DxE2 = glm::cross(ray.Direction(), e2);//pVec float DetInv = glm::dot(e1, DxE2); if (std::abs(DetInv) < FLT_EPSILON) { continue; @@ -171,7 +172,7 @@ namespace Collision continue; } float u = glm::dot(m, DxE2) * DetInv; - float v = glm::dot(ray.Direction, MxE1) * DetInv; + float v = glm::dot(ray.Direction(), MxE1) * DetInv; //u,v can be very close to 0 but still negative sometimes. added a deltafactor to compensate for that problem //If u and v are positive, u+v <= 1, dist is positive, and less than closest. @@ -194,7 +195,7 @@ namespace Collision float v; float dist; bool hit = RayVsModel(ray, modelVertices, modelIndices, dist, u, v); - outHitPosition = ray.Origin + dist * ray.Direction; + outHitPosition = ray.Origin() + dist * ray.Direction(); return hit; } diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index 31b32cec..c1d8c64e 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -192,7 +192,7 @@ bool OctTree::OctChild::RayCollides(const Ray& ray, Output& data) const std::vector childInfos; childInfos.reserve(8); for (int i = 0; i < 8; ++i) { - childInfos.push_back({ i, glm::distance(ray.Origin, m_Children[i]->m_Box.Center()) }); + childInfos.push_back({ i, glm::distance(ray.Origin(), m_Children[i]->m_Box.Center()) }); } std::sort(childInfos.begin(), childInfos.end(), isFirstLower); //Loop through the children, starting with the one closest to the ray origin. I.e the first to be hit. diff --git a/src/Tests/CollisionTest.cpp b/src/Tests/CollisionTest.cpp index 82e2e800..e6a29298 100644 --- a/src/Tests/CollisionTest.cpp +++ b/src/Tests/CollisionTest.cpp @@ -26,16 +26,14 @@ using boost::unit_test_framework::test_case; void RayTest(std::string fileName) { //simple box test - Ray ray; - ray.Origin = glm::vec3(-50, 0, 0); - ray.Direction = glm::normalize(glm::vec3(1, 0, 0)); + Ray ray(glm::vec3(-50, 0, 0), glm::vec3(1, 0, 0)); //using a rawmodel here, else we have to init the renderingsystem ResourceManager::RegisterType("RawModel"); auto unitBox = ResourceManager::Load(fileName); - BOOST_CHECK(unitBox != nullptr); + BOOST_REQUIRE(unitBox != nullptr); bool hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); BOOST_CHECK(hit); - ray.Direction = glm::normalize(glm::vec3(-1, 0, 0)); + ray.SetDirection(glm::vec3(-1, 0, 0)); hit = Collision::RayVsModel(ray, unitBox->m_Vertices, unitBox->m_Indices); BOOST_CHECK(!hit); } @@ -49,7 +47,6 @@ BOOST_AUTO_TEST_CASE(collisionTest) //fixed seed srand(2); - Ray ray; AABB someAABB; glm::vec3 minPos; glm::vec3 maxPos; @@ -57,12 +54,10 @@ BOOST_AUTO_TEST_CASE(collisionTest) int test = 0; for (size_t i = 0; i < 10; i++) { - ray.Origin.x = rand() % 100; - ray.Origin.y = rand() % 100; - ray.Origin.z = rand() % 100; - ray.Direction.x = rand() % 100; - ray.Direction.y = rand() % 100; - ray.Direction.z = rand() % 100; + Ray ray( + glm::vec3(rand() % 100, rand() % 100, rand() % 100), + glm::vec3(rand() % 100, rand() % 100, rand() % 100) + ); minPos.x = rand() % 100; minPos.y = rand() % 100; minPos.z = rand() % 100; @@ -83,7 +78,6 @@ BOOST_AUTO_TEST_CASE(collisionTest2) { //fixed seed srand(2); - Ray ray; AABB someAABB; glm::vec3 minPos; glm::vec3 maxPos; @@ -91,12 +85,10 @@ BOOST_AUTO_TEST_CASE(collisionTest2) int test = 0; for (size_t i = 0; i < 1000000; i++) { - ray.Origin.x = rand() % 100; - ray.Origin.y = rand() % 100; - ray.Origin.z = rand() % 100; - ray.Direction.x = rand() % 100; - ray.Direction.y = rand() % 100; - ray.Direction.z = rand() % 100; + Ray ray( + glm::vec3(rand() % 100, rand() % 100, rand() % 100), + glm::vec3(rand() % 100, rand() % 100, rand() % 100) + ); minPos.x = rand() % 100; minPos.y = rand() % 100; minPos.z = rand() % 100; @@ -114,7 +106,7 @@ BOOST_AUTO_TEST_CASE(collisionTest2) BOOST_AUTO_TEST_CASE(rayVsModelTest) { //simple box test - RayTest("Models/Core/UnitBox.obj"); + RayTest("Models/Core/UnitCube.obj"); } BOOST_AUTO_TEST_CASE(rayVsModelTest2) @@ -125,7 +117,6 @@ BOOST_AUTO_TEST_CASE(rayVsModelTest2) // srand(7676462); // srand(7462); srand(72); - Ray ray; AABB someAABB; glm::vec3 minPos; glm::vec3 maxPos; @@ -142,19 +133,13 @@ BOOST_AUTO_TEST_CASE(rayVsModelTest2) for (size_t i = 0; i < 1000000; i++) { - ray.Origin.x = rand() % 100; - ray.Origin.y = rand() % 100; - ray.Origin.z = rand() % 100; - ray.Direction.x = rand() % 100; - ray.Direction.y = rand() % 100; - ray.Direction.z = rand() % 100; - ray.Origin /= 100; - ray.Origin = glm::vec3(-2, 0, 0); - ray.Direction /= 100; + Ray ray( + glm::vec3(-2, 0, 0), + glm::vec3(rand() % 100, rand() % 100, rand() % 100) + ); //if we normalize the ray.direction when its 0,0,0 then we get nan,nan,nan - thus we have this check to prevent that - if (ray.Direction.x < 0.0001f && ray.Direction.y < 0.0001f && ray.Direction.z < 0.0001f) + if (glm::any(glm::isnan(ray.Direction()))) continue; - ray.Direction = glm::normalize(ray.Direction); z = Collision::RayVsAABB(ray, someAABB); if (z) { @@ -224,10 +209,10 @@ BOOST_AUTO_TEST_CASE(octTest) tree.AddDynamicObject(AABB(mini, -0.9f*maxi)); OctTree::Output data; glm::vec3 origin = 3.0f * mini; - bool rayIntersected = tree.RayCollides({ origin , glm::normalize(mini - origin) }, data); + bool rayIntersected = tree.RayCollides(Ray(origin , mini - origin), data); BOOST_CHECK(rayIntersected); tree.ClearDynamicObjects(); - rayIntersected = tree.RayCollides({ origin , glm::normalize(mini - origin) }, data); + rayIntersected = tree.RayCollides(Ray(origin, mini - origin), data); BOOST_CHECK(!rayIntersected); } diff --git a/src/Tests/OldOctTree.cpp b/src/Tests/OldOctTree.cpp index 4aabf9ed..d3738ee6 100644 --- a/src/Tests/OldOctTree.cpp +++ b/src/Tests/OldOctTree.cpp @@ -172,7 +172,7 @@ bool OctTree::RayCollides(const Ray& ray, Output& data) const std::vector childInfos; childInfos.reserve(8); for (int i = 0; i < 8; ++i) { - childInfos.push_back({ i, glm::distance(ray.Origin, m_Children[i]->m_Box.Center()) }); + childInfos.push_back({ i, glm::distance(ray.Origin(), m_Children[i]->m_Box.Center()) }); } std::sort(childInfos.begin(), childInfos.end(), isFirstLower); //Loop through the children, starting with the one closest to the ray origin. I.e the first to be hit. diff --git a/src/Tests/OldOctTree.h b/src/Tests/OldOctTree.h index 316b9545..3b0eefdc 100644 --- a/src/Tests/OldOctTree.h +++ b/src/Tests/OldOctTree.h @@ -3,7 +3,7 @@ #include "Core/AABB.h" -struct Ray; +class Ray; class World; class Camera; From 3e2ffdb85bae2742f75abc9ce857d195b408cc8b Mon Sep 17 00:00:00 2001 From: William Moberg Date: Thu, 17 Dec 2015 13:48:35 +0100 Subject: [PATCH 26/28] Fixed comment on Trigger events. --- include/Engine/Collision/ETrigger.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/Engine/Collision/ETrigger.h b/include/Engine/Collision/ETrigger.h index 8852ee8c..687c73fa 100644 --- a/include/Engine/Collision/ETrigger.h +++ b/include/Engine/Collision/ETrigger.h @@ -7,7 +7,7 @@ namespace Events { -/** Thrown once, when an entity is completely inside a trigger. */ +/** Thrown once, when an entity is only touching a trigger. */ struct TriggerTouch : Event { /** The id of the entity that touches the trigger. */ @@ -25,7 +25,7 @@ struct TriggerLeave : Event EntityID Trigger; }; -/** Thrown once, when an entity is completely inside a trigger. */ +/** Thrown once, when an entity is completely contained inside a trigger. */ struct TriggerEnter : Event { /** The id of the entity that entered the trigger. */ From 0f59b863eedb9f154c01de26c5df8aecc1cb123d Mon Sep 17 00:00:00 2001 From: William Moberg Date: Thu, 17 Dec 2015 17:08:47 +0100 Subject: [PATCH 27/28] Renamed IF_DEBUG_IS to DEBUG_IF, slightly different behavior and added some safety checking for auto creating AABBs from models. --- include/Engine/Core/Ray.h | 2 +- include/Engine/Core/Util/IfDebug.h | 21 ++- .../Schema/Entities/CollisionTestLevel.xml | 122 ++++++++++++++++++ src/Engine/Collision/Collision.cpp | 9 +- src/Engine/Core/AABB.cpp | 18 ++- 5 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 resources/Schema/Entities/CollisionTestLevel.xml diff --git a/include/Engine/Core/Ray.h b/include/Engine/Core/Ray.h index 2ed192a2..0fcef01e 100644 --- a/include/Engine/Core/Ray.h +++ b/include/Engine/Core/Ray.h @@ -11,7 +11,7 @@ public: : m_Origin(origin) , m_Direction(glm::normalize(dir)) { - IF_DEBUG_IS(true) { + DEBUG_IF(true) { if (glm::any(glm::isnan(m_Direction))) { LOG_WARNING("Ray Direction was set to the zero-vector, expect unknown side effects and/or crashes."); } diff --git a/include/Engine/Core/Util/IfDebug.h b/include/Engine/Core/Util/IfDebug.h index 159162c5..79cb3a4c 100644 --- a/include/Engine/Core/Util/IfDebug.h +++ b/include/Engine/Core/Util/IfDebug.h @@ -1,17 +1,12 @@ -//Example: -//IF_DEBUG_IS(true) { -// //Stuff done only in debug mode. -//} -// -//IF_DEBUG_IS(false) { -// //Stuff done only in release mode. -//} else { -// //Stuff only in debug. -//} -#ifndef IF_DEBUG_IS +// Example: +// DEBUG_IF(condition) { +// // This code is executed only in debug mode and if condition is true. +// } +// NOTE: condition statement is not executed at all in release mode. +#ifndef DEBUG_IF #ifndef DEBUG -#define IF_DEBUG_IS(c) if(c) +#define DEBUG_IF(c) if(c) #else -#define IF_DEBUG_IS(c) if(!c) +#define DEBUG_IF(c) if(false) #endif #endif \ No newline at end of file diff --git a/resources/Schema/Entities/CollisionTestLevel.xml b/resources/Schema/Entities/CollisionTestLevel.xml new file mode 100644 index 00000000..99842b88 --- /dev/null +++ b/resources/Schema/Entities/CollisionTestLevel.xml @@ -0,0 +1,122 @@ + + + + + + + + + Models/DummyScene.obj + + + + + + + + + + + Models/ScaleWidget.obj + + + + + + + + + + + Models/RotationWidgetX.obj + + + + + + + + + + + + + + + Models/Core/UnitCube.obj + + + + + + + + + + + + + + + + + + + + + + Models/Core/UnitRaptor.obj + + + + + + + + + + + + 20 + + + + + + + + + + + + + Models/Core/UnitCube.obj + + + + + + + + + + + + + Models/Core/UnitCube.obj + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Engine/Collision/Collision.cpp b/src/Engine/Collision/Collision.cpp index ec6c471c..dd2adec8 100644 --- a/src/Engine/Collision/Collision.cpp +++ b/src/Engine/Collision/Collision.cpp @@ -213,8 +213,11 @@ bool IsSameBoxProbably(const AABB& first, const AABB& second, const float epsilo (std::abs(mi1.y - mi2.y) < epsilon); } -void attachAABBComponentFromModel(World* world, EntityID id) +bool attachAABBComponentFromModel(World* world, EntityID id) { + if (!world->HasComponent(id, "Model")) { + return false; + } ComponentWrapper model = world->GetComponent(id, "Model"); ComponentWrapper collision = world->AttachComponent(id, "AABB"); Model* modelRes = ResourceManager::Load(model["Resource"]); @@ -234,6 +237,7 @@ void attachAABBComponentFromModel(World* world, EntityID id) } collision["BoxCenter"] = 0.5f * (maxi + mini); collision["BoxSize"] = maxi - mini; + return true; } bool GetEntityBox(World* world, ComponentWrapper& AABBComponent, AABB& outBox) @@ -258,7 +262,8 @@ bool GetEntityBox(World* world, EntityID entity, AABB& outBox, bool forceBoxFrom { if (!world->HasComponent(entity, "AABB")) { if (forceBoxFromModel) { - attachAABBComponentFromModel(world, entity); + if (!attachAABBComponentFromModel(world, entity)) + return false; } else { return false; } diff --git a/src/Engine/Core/AABB.cpp b/src/Engine/Core/AABB.cpp index b1da6b26..0df56229 100644 --- a/src/Engine/Core/AABB.cpp +++ b/src/Engine/Core/AABB.cpp @@ -7,16 +7,14 @@ AABB::AABB(const glm::vec3& minPos, const glm::vec3& maxPos) , m_Center(0.5f * (maxPos + minPos)) , m_HalfSize(0.5f * (maxPos - minPos)) { - IF_DEBUG_IS(true) { - if (glm::any(glm::lessThan(m_MaxCorner, m_MinCorner))) { - LOG_WARNING("AABB maxCorner coordinates are not greater than minCorner"); - m_MaxCorner.x = glm::max(m_MaxCorner.x, m_MinCorner.x); - m_MinCorner.x = glm::min(m_MaxCorner.x, m_MinCorner.x); - m_MaxCorner.y = glm::max(m_MaxCorner.y, m_MinCorner.y); - m_MinCorner.y = glm::min(m_MaxCorner.y, m_MinCorner.y); - m_MaxCorner.z = glm::max(m_MaxCorner.z, m_MinCorner.z); - m_MinCorner.z = glm::min(m_MaxCorner.z, m_MinCorner.z); - } + DEBUG_IF(glm::any(glm::lessThan(m_MaxCorner, m_MinCorner))) { + LOG_WARNING("AABB maxCorner coordinates are not greater than minCorner"); + m_MaxCorner.x = glm::max(m_MaxCorner.x, m_MinCorner.x); + m_MinCorner.x = glm::min(m_MaxCorner.x, m_MinCorner.x); + m_MaxCorner.y = glm::max(m_MaxCorner.y, m_MinCorner.y); + m_MinCorner.y = glm::min(m_MaxCorner.y, m_MinCorner.y); + m_MaxCorner.z = glm::max(m_MaxCorner.z, m_MinCorner.z); + m_MinCorner.z = glm::min(m_MaxCorner.z, m_MinCorner.z); } } From 5e0fe975f36f62b253b1f935f70cd092aba42d6d Mon Sep 17 00:00:00 2001 From: William Moberg Date: Thu, 17 Dec 2015 17:48:00 +0100 Subject: [PATCH 28/28] Small error handling check. --- src/Engine/Collision/Collision.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Engine/Collision/Collision.cpp b/src/Engine/Collision/Collision.cpp index dd2adec8..d076d1c2 100644 --- a/src/Engine/Collision/Collision.cpp +++ b/src/Engine/Collision/Collision.cpp @@ -221,6 +221,9 @@ bool attachAABBComponentFromModel(World* world, EntityID id) ComponentWrapper model = world->GetComponent(id, "Model"); ComponentWrapper collision = world->AttachComponent(id, "AABB"); Model* modelRes = ResourceManager::Load(model["Resource"]); + if (modelRes == nullptr) { + return false; + } glm::mat4 modelMatrix = modelRes->m_Matrix;