diff --git a/src/Engine/Core/OctTree.cpp b/src/Engine/Core/OctTree.cpp index af6cc3a8..4e5cdfaa 100644 --- a/src/Engine/Core/OctTree.cpp +++ b/src/Engine/Core/OctTree.cpp @@ -7,17 +7,17 @@ namespace { -//To be able to sort nodes based on distance to ray origin. -struct ChildInfo -{ - int Index; - float Distance; -}; + //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 isFirstLower(const ChildInfo& first, const ChildInfo& second) + { + return first.Distance < second.Distance; + } } @@ -32,28 +32,31 @@ OctTree::OctTree(const AABB& octTreeBounds, int subDivisions) for (OctTree*& c : m_Children) { c = nullptr; } - } else { + } + else { --subDivisions; + const glm::vec3& parentMin = m_Box.MinCorner(); + const glm::vec3& parentMax = m_Box.MaxCorner(); + const glm::vec3& parentCenter = m_Box.Center(); 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 { + } + 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 { + } + else { minPos.y = parentMin.y; maxPos.y = parentCenter.y; } @@ -61,7 +64,8 @@ OctTree::OctTree(const AABB& octTreeBounds, int subDivisions) if (bits.test(0)) { minPos.z = parentCenter.z; maxPos.z = parentMax.z; - } else { + } + else { minPos.z = parentMin.z; maxPos.z = parentCenter.z; } @@ -107,7 +111,8 @@ bool OctTree::rayCollides(const Ray& ray, Output& data) const return true; } } - } else { + } + else { //Check against boxes in the node. float minDist = INFINITY; bool intersected = false; @@ -162,7 +167,8 @@ void OctTree::AddBox(const AABB& box) default: break; } - } else { + } + else { m_ContainingBoxes.push_back(box); } } @@ -175,7 +181,8 @@ void OctTree::ClearBoxes() for (OctTree*& c : m_Children) { c->ClearBoxes(); } - } else { + } + else { m_ContainingBoxes.clear(); } } diff --git a/src/Tests/OctTreeTest.cpp b/src/Tests/OctTreeTest.cpp index aad95381..fee2932f 100644 --- a/src/Tests/OctTreeTest.cpp +++ b/src/Tests/OctTreeTest.cpp @@ -2,6 +2,7 @@ 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 //friend class and refactoringIntoNewClass is some extra work and needs to be updated when the original class is updated, and can contain bugs that @@ -10,7 +11,6 @@ using boost::unit_test_framework::test_case; //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) @@ -55,6 +55,10 @@ BOOST_AUTO_TEST_CASE(octTreeTest) BOOST_AUTO_TEST_CASE(octTreeTest2) { + Game game(0, nullptr); + while (game.Running()) { + game.Tick(); + } } diff --git a/src/Tests/OctTreeTestGameClass.cpp b/src/Tests/OctTreeTestGameClass.cpp new file mode 100644 index 00000000..4a6236c2 --- /dev/null +++ b/src/Tests/OctTreeTestGameClass.cpp @@ -0,0 +1,78 @@ +#include "OctTreeTestGameClass.h" + +Game::Game(int argc, char* argv[]) +{ + ResourceManager::RegisterType("ConfigFile"); + ResourceManager::RegisterType("Model"); + ResourceManager::RegisterType("Texture"); + + m_Config = ResourceManager::Load("Config.ini"); + LOG_LEVEL = static_cast<_LOG_LEVEL>(m_Config->Get("Debug.LogLevel", 1)); + + // Create the core event broker + m_EventBroker = new EventBroker(); + + m_RenderQueueFactory = new RenderQueueFactory(); + + // Create the renderer + m_Renderer = new Renderer(); + m_Renderer->SetFullscreen(m_Config->Get("Video.Fullscreen", false)); + m_Renderer->SetVSYNC(m_Config->Get("Video.VSYNC", false)); + m_Renderer->SetResolution(Rectangle( + 0, + 0, + m_Config->Get("Video.Width", 1280), + m_Config->Get("Video.Height", 720) + )); + m_Renderer->Initialize(); + + // Create input manager + m_InputManager = new InputManager(m_Renderer->Window(), m_EventBroker); + + // Create the root level GUI frame + m_FrameStack = new GUI::Frame(m_EventBroker); + m_FrameStack->Width = m_Renderer->Resolution().Width; + m_FrameStack->Height = m_Renderer->Resolution().Height; + + // Create a TEST WORLD + m_World = new HardcodedTestWorld(); + + m_LastTime = glfwGetTime(); +} + +Game::~Game() +{ + delete m_FrameStack; + delete m_EventBroker; +} + +void Game::Tick() +{ + double currentTime = glfwGetTime(); + double dt = currentTime - m_LastTime; + m_LastTime = currentTime; + + m_EventBroker->Swap(); + m_InputManager->Update(dt); + m_Renderer->Update(dt); + m_EventBroker->Swap(); + + //movement + //auto transf = m_World->GetComponent(m_World->OctTreeEntityIdSaved, "Transform"); + //((glm::vec3&)transf["Position"]).x += 0.001f; + + m_RenderQueueFactory->Update(m_World); + + //wireframe + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + m_Renderer->Draw(m_RenderQueueFactory->RenderQueues()); + + //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + + m_EventBroker->Swap(); + m_EventBroker->Clear(); + + glfwPollEvents(); +} diff --git a/src/Tests/OctTreeTestGameClass.h b/src/Tests/OctTreeTestGameClass.h new file mode 100644 index 00000000..9f4ae8c7 --- /dev/null +++ b/src/Tests/OctTreeTestGameClass.h @@ -0,0 +1,35 @@ +#ifndef Game_h__ +#define Game_h__ + +#include "Core/ResourceManager.h" +#include "Core/ConfigFile.h" +#include "Core/EventBroker.h" +#include "Rendering/Renderer.h" +#include "Core/InputManager.h" +#include "GUI/Frame.h" +#include "Core/World.h" +#include "Rendering/RenderQueueFactory.h" + +#include "OctTreeTestHardCodedTestWorld.h" + +class Game +{ +public: + Game(int argc, char* argv[]); + ~Game(); + + bool Running() const { return !glfwWindowShouldClose(m_Renderer->Window()); } + void Tick(); + +private: + double m_LastTime; + ConfigFile* m_Config = nullptr; + EventBroker* m_EventBroker; + IRenderer* m_Renderer; + InputManager* m_InputManager; + GUI::Frame* m_FrameStack; + HardcodedTestWorld* m_World; + RenderQueueFactory* m_RenderQueueFactory; +}; + +#endif diff --git a/src/Tests/OctTreeTestGameMain.cpp b/src/Tests/OctTreeTestGameMain.cpp new file mode 100644 index 00000000..ab13e930 --- /dev/null +++ b/src/Tests/OctTreeTestGameMain.cpp @@ -0,0 +1,104 @@ +//#define BOOST_TEST_MODULE collTest +#include +#include +using boost::unit_test_framework::test_suite; +using boost::unit_test_framework::test_case; +#include +#include "Engine/Core/AABB.h" +#include "Engine/Core/Ray.h" +#include //srand +#include "Engine/Core/OctTree.h" + +//vs memleaks +//#define _CRTDBG_MAP_ALLOC +//#include +//#include +//#define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__) +//#define new DEBUG_CLIENTBLOCK + +BOOST_AUTO_TEST_SUITE(collisionTests) + +BOOST_AUTO_TEST_CASE(collisionTest) +{ + //memleak + int* globalLeak = new int[5]; + + //fixed seed + srand(2); + Ray ray; + AABB someAABB; + glm::vec3 minPos; + glm::vec3 maxPos; + bool z; + 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; + minPos.x = rand() % 100; + minPos.y = rand() % 100; + minPos.z = rand() % 100; + maxPos.x = rand() % 100; + maxPos.y = rand() % 100; + maxPos.z = rand() % 100; + + someAABB = AABB(minPos, maxPos); + z = Collision::RayVsAABB(ray, someAABB); + if (z) ++test; + } + BOOST_CHECK(test >= 0); + + //_CrtDumpMemoryLeaks(); +} + +BOOST_AUTO_TEST_CASE(collisionTest2) +{ + //fixed seed + srand(2); + Ray ray; + AABB someAABB; + glm::vec3 minPos; + glm::vec3 maxPos; + bool z; + 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; + minPos.x = rand() % 100; + minPos.y = rand() % 100; + minPos.z = rand() % 100; + maxPos.x = rand() % 100; + maxPos.y = rand() % 100; + maxPos.z = rand() % 100; + + someAABB = AABB(minPos, maxPos); + z = Collision::RayAABBIntr(ray, someAABB); + if (z) ++test; + } + BOOST_CHECK(test >= 0); +} + +BOOST_AUTO_TEST_CASE(octTest) +{ + glm::vec3 mini = glm::vec3(-1, -1, -1); + glm::vec3 maxi = glm::vec3(1, 1, 1); + OctTree tree(AABB(mini, maxi), 2); + tree.AddBox(AABB(mini, -0.9f*maxi)); + OctTree::Output data; + glm::vec3 origin = 3.0f * mini; + BOOST_CHECK(tree.RayCollides({origin , glm::normalize(mini - origin) }, data)); + tree.ClearBoxes(); + BOOST_CHECK(!tree.RayCollides({ origin , glm::normalize(mini - origin) }, data)); +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/Tests/OctTreeTestHardCodedTestWorld.h b/src/Tests/OctTreeTestHardCodedTestWorld.h new file mode 100644 index 00000000..f5caf308 --- /dev/null +++ b/src/Tests/OctTreeTestHardCodedTestWorld.h @@ -0,0 +1,195 @@ +#include +#include +#include +#include "GLM.h" +#include "Core/World.h" +#include "Core/Util/Any.h" + +//octTree +//#include + +//last! +#define private public +#include + +class HardcodedTestWorld : public World +{ +public: + EntityID OctTreeEntityIdSaved; + + //constructor + HardcodedTestWorld() + : World() + { + registerTestComponents(); + createTestEntities(); + } + +private: + void registerTestComponents() + { + ComponentWrapperFactory f; + + + f = ComponentWrapperFactory("Test"); + f.AddProperty("TestInteger", 1337); + f.AddProperty("TestFloat", 13.37f); + f.AddProperty("TestString", std::string("Carlito")); + RegisterComponent(f); + + f = ComponentWrapperFactory("Debug"); + f.AddProperty("Name", std::string("Unnamed")); + RegisterComponent(f); + + f = ComponentWrapperFactory("Transform"); + f.AddProperty("Position", glm::vec3(0.f, 0.f, 0.f)); + f.AddProperty("Orientation", glm::quat()); + f.AddProperty("Scale", glm::vec3(1.f, 1.f, 1.f)); + RegisterComponent(f); + + f = ComponentWrapperFactory("Model"); + f.AddProperty("Resource", std::string()); + f.AddProperty("Color", glm::vec4(1.f, 1.f, 1.f, 1.f)); + f.AddProperty("Visible", true); + RegisterComponent(f); + } + + void createTestEntities() + { + World& world = *this; + + // Create an entity + EntityID e = world.CreateEntity(); + + // Attach a Debug component + ComponentWrapper debug = world.AttachComponent(e, "Debug"); + // Set the Name field of the Debug component using subscript operator + debug["Name"] = "Carlito"; + + // Attach a Transform component + world.AttachComponent(e, "Transform"); + // Fetch the component based on EntityID and component type + ComponentWrapper transform = world.GetComponent(e, "Transform"); + // Set the fields of the Transform component + transform["Position"] = glm::vec3(0.f, 0.f, 0.f); + transform["Scale"] = glm::vec3(1.f, 1.f, 1.f); + + // Move on the X axis by fetching field as reference + ((glm::vec3&)transform["Position"]).x += 10.f; + // Shrink by a factor of 100 + ((glm::vec3&)transform["Scale"]) /= 100.f; + + // Loop through all Transform components and print them + for (auto& transform : world.GetComponents("Transform")) { + glm::vec3 pos = transform["Position"]; + std::cout << "Position: " << pos.x << " " << pos.y << " " << pos.z << std::endl; + glm::vec3 scale = transform["Scale"]; + std::cout << "Scale: " << scale.x << " " << scale.y << " " << scale.z << std::endl; + + // Fetch the Debug component also present in this entity + ComponentWrapper debug = world.GetComponent(transform.EntityID, "Debug"); + std::cout << "Name: " << (std::string)debug["Name"] << std::endl; + } + + //Create some test widgets + //{ + // EntityID entityScaleWidget = world.CreateEntity(); + // ComponentWrapper transform = world.AttachComponent(entityScaleWidget, "Transform"); + // transform["Position"] = glm::vec3(-1.5f, 0.f, 0.f); + // ComponentWrapper model = world.AttachComponent(entityScaleWidget, "Model"); + // model["Resource"] = "Models/ScaleWidget.obj"; + //} + //{ + // EntityID entityRotationWidget = world.CreateEntity(); + // ComponentWrapper transform = world.AttachComponent(entityRotationWidget, "Transform"); + // transform["Position"] = glm::vec3(1.5f, 0.f, 0.f); + // ComponentWrapper model = world.AttachComponent(entityRotationWidget, "Model"); + // model["Resource"] = "Models/RotationWidget.obj"; + //} + //{ + // EntityID entityDummyScene = world.CreateEntity(); + // ComponentWrapper transform = world.AttachComponent(entityDummyScene, "Transform"); + // transform["Position"] = glm::vec3(0, 0.f, 0.f); + // ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model"); + // model["Resource"] = "Models/DummyScene.obj"; + //} + + //add octTree + { + //ComponentWrapper debug = world.AttachComponent(e, "Debug"); + //// Set the Name field of the Debug component using subscript operator + //debug["Name"] = "Blah"; + 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); + + auto someOctTree = OctTree(someAABB, 2); + + //auto min1 = someOctTree.m_Children[i]->m_Box.MinCorner(); + //auto max1 = someOctTree.m_Children[i]->m_Box.MaxCorner(); + + float boxDrawFactor = 1.05f; + auto halfSizeFactor = 0.0f; + halfSizeFactor = someAABB.HalfSize().x; + + //the first box first + EntityID entityDummyScene = world.CreateEntity(); + + ComponentWrapper transform = world.AttachComponent(entityDummyScene, "Transform"); + transform["Position"] = someAABB.Center()*1.0f; + transform["Scale"] = glm::vec3(1.0f, 1.0f, 1.0f)*halfSizeFactor*boxDrawFactor; + + ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model"); + model["Resource"] = "Models/Core/UnitBox.obj"; + model["Color"] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + + //just draw the first 8 children - looks nicer in code if i split it this way + for (size_t i = 0; i < 8; i++) + { + auto cen1 = someOctTree.m_Children[i]->m_Box.Center(); + halfSizeFactor = someOctTree.m_Children[i]->m_Box.HalfSize().x; + + EntityID entityDummyScene = world.CreateEntity(); + + ComponentWrapper transform = world.AttachComponent(entityDummyScene, "Transform"); + transform["Position"] = cen1*1.0f; + transform["Scale"] = glm::vec3(1.0f, 1.0f, 1.0f)*0.97f*halfSizeFactor*boxDrawFactor; + + ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model"); + model["Resource"] = "Models/Core/UnitBox.obj"; + model["Color"] = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + + //OutputDebugStringA((std::to_string(cen1.x) + std::to_string(cen1.y) + std::to_string(cen1.z)).c_str()); + //OctTreeEntityIdSaved = entityDummyScene; + } + + //then draw the childrens children + //for (size_t j = 0; j < 8; j++) + //{ + // auto someChild = someOctTree.m_Children[j]; + + // for (size_t i = 0; i < 8; i++) + // { + // auto cen1 = someChild->m_Children[i]->m_Box.Center(); + // auto boxScale = someChild->m_Children[i]->m_Box.HalfSize(); + + // EntityID entityDummyScene = world.CreateEntity(); + + // ComponentWrapper transform = world.AttachComponent(entityDummyScene, "Transform"); + // transform["Position"] = cen1*1.0f; + // transform["Scale"] = glm::vec3(1.0f, 1.0f, 1.0f)*0.97f*boxScale*boxDrawFactor; + + // ComponentWrapper model = world.AttachComponent(entityDummyScene, "Model"); + // model["Resource"] = "Models/Core/UnitBox.obj"; + // model["Color"] = glm::vec4(0.0f, 1.0f, 0.0f, 1.0f); + // //OctTreeEntityIdSaved = entityDummyScene; + // } + + //} + + + } + + + } +}; \ No newline at end of file