Added QuadTree Part1 Code

pull/281/head
Javidx9 3 years ago committed by GitHub
parent dbeabb8ffc
commit 51900bb3af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 493
      Videos/OneLoneCoder_PGE_QuadTree1.cpp

@ -0,0 +1,493 @@
/*
Quirky Quad Trees Part #1 - Static Quad Tree Implementation
"War... huh... What is it good for? Absolutely nothin..." - javidx9
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2018 - 2022 OneLoneCoder.com
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions or derivations of source code must retain the above
copyright notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce
the above copyright notice. This list of conditions and the following
disclaimer must be reproduced in the documentation and/or other
materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Video:
~~~~~~
https://youtu.be/ASAowY6yJII
Pan & Zoom with middle mouse, TAB to switch between methods
Links
~~~~~
YouTube: https://www.youtube.com/javidx9
https://www.youtube.com/javidx9extra
Discord: https://discord.gg/WhwHUMV
Twitter: https://www.twitter.com/javidx9
Twitch: https://www.twitch.tv/javidx9
GitHub: https://www.github.com/onelonecoder
Homepage: https://www.onelonecoder.com
Author
~~~~~~
David Barr, aka javidx9, ©OneLoneCoder 2019, 2020, 2021, 2022
*/
#define OLC_PGE_APPLICATION
#include "olcPixelGameEngine.h"
#define OLC_PGEX_TRANSFORMEDVIEW
#include "olcPGEX_TransformedView.h"
namespace olc
{
struct rect
{
olc::vf2d pos;
olc::vf2d size;
rect(const olc::vf2d& p = { 0.0f, 0.0f }, const olc::vf2d& s = { 1.0f, 1.0f }) : pos(p), size(s)
{
}
constexpr bool contains(const olc::vf2d& p) const
{
return !(p.x < pos.x || p.y < pos.y || p.x >= (pos.x + size.x) || p.y >= (pos.y + size.y));
}
constexpr bool contains(const olc::rect& r) const
{
return (r.pos.x >= pos.x) && (r.pos.x + r.size.x < pos.x + size.x) &&
(r.pos.y >= pos.y) && (r.pos.y + r.size.y < pos.y + size.y);
}
constexpr bool overlaps(const olc::rect& r) const
{
return (pos.x < r.pos.x + r.size.x && pos.x + size.x >= r.pos.x && pos.y < r.pos.y + r.size.y && pos.y + size.y >= r.pos.y);
}
};
};
// Constrain depth of Quad Tree. Since its floating point, it could in principle sub-divide for
// a very long time, consuming far more time and memory than is sensible
constexpr size_t MAX_DEPTH = 8;
template <typename OBJECT_TYPE>
class StaticQuadTree
{
public:
StaticQuadTree(const olc::rect& size = { {0.0f, 0.0f}, {100.0f, 100.0f} }, const size_t nDepth = 0)
{
m_depth = nDepth;
resize(size);
}
// Force area change on Tree, invalidates this and all child layers
void resize(const olc::rect& rArea)
{
// Erase this layer
clear();
// Recalculate area of children
m_rect = rArea;
olc::vf2d vChildSize = m_rect.size / 2.0f;
// Cache child areas local to this layer
m_rChild =
{
// Top Left
olc::rect(m_rect.pos, vChildSize),
// Top Right
olc::rect({m_rect.pos.x + vChildSize.x, m_rect.pos.y}, vChildSize),
// Bottom Left
olc::rect({m_rect.pos.x, m_rect.pos.y + vChildSize.y}, vChildSize),
// Bottom Right
olc::rect(m_rect.pos + vChildSize, vChildSize)
};
}
// Clears the contents of this layer, and all child layers
void clear()
{
// Erase any items stored in this layer
m_pItems.clear();
// Iterate through children, erase them too
for (int i = 0; i < 4; i++)
{
if (m_pChild[i])
m_pChild[i]->clear();
m_pChild[i].reset();
}
}
// Returns a count of how many items are stored in this layer, and all children of this layer
size_t size() const
{
size_t nCount = m_pItems.size();
for (int i = 0; i < 4; i++)
if (m_pChild[i]) nCount += m_pChild[i]->size();
return nCount;
}
// Inserts an object into this layer (or appropriate child layer), given the area the item occupies
void insert(const OBJECT_TYPE& item, const olc::rect& itemsize)
{
// Check each child
for (int i = 0; i < 4; i++)
{
// If the child can wholly contain the item being inserted
if (m_rChild[i].contains(itemsize))
{
// Have we reached depth limit?
if (m_depth + 1 < MAX_DEPTH)
{
// No, so does child exist?
if (!m_pChild[i])
{
// No, so create it
m_pChild[i] = std::make_shared<StaticQuadTree<OBJECT_TYPE>>(m_rChild[i], m_depth + 1);
}
// Yes, so add item to it
m_pChild[i]->insert(item, itemsize);
return;
}
}
}
// It didnt fit, so item must belong to this quad
m_pItems.push_back({ itemsize, item });
}
// Returns a list of objects in the given search area
std::list<OBJECT_TYPE> search(const olc::rect& rArea) const
{
std::list<OBJECT_TYPE> listItems;
search(rArea, listItems);
return listItems;
}
// Returns the objects in the given search area, by adding to supplied list
void search(const olc::rect& rArea, std::list<OBJECT_TYPE>& listItems) const
{
// First, check for items belonging to this area, add them to the list
// if there is overlap
for (const auto& p : m_pItems)
{
if (rArea.overlaps(p.first))
listItems.push_back(p.second);
}
// Second, recurse through children and see if they can
// add to the list
for (int i = 0; i < 4; i++)
{
if (m_pChild[i])
{
// If child is entirely contained within area, recursively
// add all of its children, no need to check boundaries
if (rArea.contains(m_rChild[i]))
m_pChild[i]->items(listItems);
// If child overlaps with search area then checks need
// to be made
else if (m_rChild[i].overlaps(rArea))
m_pChild[i]->search(rArea, listItems);
}
}
}
void items(std::list<OBJECT_TYPE>& listItems) const
{
// No questions asked, just return child items
for (const auto& p : m_pItems)
listItems.push_back(p.second);
// Now add children of this layer's items
for (int i = 0; i < 4; i++)
if (m_pChild[i]) m_pChild[i]->items(listItems);
}
std::list<OBJECT_TYPE> items() const
{
// No questions asked, just return child items
std::list<OBJECT_TYPE> listItems;
items(listItems);
return listItems;
}
// Returns area of this layer
const olc::rect& area()
{
return m_rect;
}
protected:
// Depth of this StaticQuadTree layer
size_t m_depth = 0;
// Area of this StaticQuadTree
olc::rect m_rect;
// 4 child areas of this StaticQuadTree
std::array<olc::rect, 4> m_rChild{};
// 4 potential children of this StaticQuadTree
std::array<std::shared_ptr<StaticQuadTree<OBJECT_TYPE>>, 4> m_pChild{};
// Items which belong to this StaticQuadTree
std::vector<std::pair<olc::rect, OBJECT_TYPE>> m_pItems;
};
template <typename OBJECT_TYPE>
class StaticQuadTreeContainer
{
// Using a std::list as we dont want pointers to be invalidated to objects stored in the
// tree should the contents of the tree change
using QuadTreeContainer = std::list<OBJECT_TYPE>;
protected:
// The actual container
QuadTreeContainer m_allItems;
// Use our StaticQuadTree to store "pointers" instead of objects - this reduces
// overheads when moving or copying objects
StaticQuadTree<typename QuadTreeContainer::iterator> root;
public:
StaticQuadTreeContainer(const olc::rect& size = { {0.0f, 0.0f}, { 100.0f, 100.0f } }, const size_t nDepth = 0) : root(size, nDepth)
{
}
// Sets the spatial coverage area of the quadtree
// Invalidates tree
void resize(const olc::rect& rArea)
{
root.resize(rArea);
}
// Returns number of items within tree
size_t size() const
{
return m_allItems.size();
}
// Returns true if tree is empty
bool empty() const
{
return m_allItems.empty();
}
// Removes all items from tree
void clear()
{
root.clear();
m_allItems.clear();
}
// Convenience functions for ranged for loop
typename QuadTreeContainer::iterator begin()
{
return m_allItems.begin();
}
typename QuadTreeContainer::iterator end()
{
return m_allItems.end();
}
typename QuadTreeContainer::const_iterator cbegin()
{
return m_allItems.cbegin();
}
typename QuadTreeContainer::const_iterator cend()
{
return m_allItems.cend();
}
// Insert item into tree in specified area
void insert(const OBJECT_TYPE& item, const olc::rect& itemsize)
{
// Item is stored in container
m_allItems.push_back(item);
// Pointer/Area of item is stored in quad tree
root.insert(std::prev(m_allItems.end()), itemsize);
}
// Returns a std::list of pointers to items within the search area
std::list<typename QuadTreeContainer::iterator> search(const olc::rect& rArea) const
{
std::list<typename QuadTreeContainer::iterator> listItemPointers;
root.search(rArea, listItemPointers);
return listItemPointers;
}
};
// The Example!
class Example_StaticQuadTree : public olc::PixelGameEngine
{
public:
Example_StaticQuadTree()
{
sAppName = "Static QuadTree";
}
protected:
olc::TransformedView tv;
// An example object of something in 2D space
struct SomeObjectWithArea
{
olc::vf2d vPos;
olc::vf2d vVel;
olc::vf2d vSize;
olc::Pixel colour;
};
// A regular list of the objects
std::list<SomeObjectWithArea> vecObjects;
// An equivalent quad tree of the objects
StaticQuadTreeContainer<SomeObjectWithArea> treeObjects;
// The "length" of one side of the "world" the objects reside in
float fArea = 100000.0f;
bool bUseQuadTree = true;
public:
bool OnUserCreate() override
{
// Transform View - enables Pan & Zoom
tv.Initialise({ ScreenWidth(), ScreenHeight() });
// Create the tree, and size it to the world
treeObjects.resize(olc::rect({ 0.0f, 0.0f }, { fArea, fArea }));
// Dirty random float generator
auto rand_float = [](const float a, const float b)
{
return float(rand()) / float(RAND_MAX) * (b - a) + a;
};
// Create 1,000,000 objects, push into both containers (so 2,000,000 I suppose :P )
for (int i = 0; i < 1000000; i++)
{
SomeObjectWithArea ob;
ob.vPos = { rand_float(0.0f, fArea), rand_float(0.0f, fArea) };
ob.vSize = { rand_float(0.1f, 100.0f), rand_float(0.1f, 100.0f) };
ob.colour = olc::Pixel(rand() % 256, rand() % 256, rand() % 256);
treeObjects.insert(ob, olc::rect(ob.vPos, ob.vSize));
vecObjects.push_back(ob);
}
return true;
}
bool OnUserUpdate(float fElapsedTime) override
{
// Tab switches between modes
if (GetKey(olc::Key::TAB).bPressed)
bUseQuadTree = !bUseQuadTree;
tv.HandlePanAndZoom();
// Get rectangle that equates to screen in world space
olc::rect rScreen = { tv.GetWorldTL(), tv.GetWorldBR() - tv.GetWorldTL() };
size_t nObjectCount = 0;
if (bUseQuadTree)
{
// QUAD TREE MODE
auto tpStart = std::chrono::system_clock::now();
// Use search function to return list of pointers to objects in that area
for (const auto& object : treeObjects.search(rScreen))
{
tv.FillRectDecal(object->vPos, object->vSize, object->colour);
nObjectCount++;
}
std::chrono::duration<float> duration = std::chrono::system_clock::now() - tpStart;
std::string sOutput = "Quadtree " + std::to_string(nObjectCount) + "/" + std::to_string(vecObjects.size()) + " in " + std::to_string(duration.count());
DrawStringDecal({ 4, 4 }, sOutput, olc::BLACK, { 4.0f, 8.0f });
DrawStringDecal({ 2, 2 }, sOutput, olc::WHITE, { 4.0f, 8.0f });
}
else
{
// LINEAR SEARCH MODE
auto tpStart = std::chrono::system_clock::now();
// Blindly check all objects to see if they overlap with screen
for (const auto& object : vecObjects)
{
if (rScreen.overlaps({ object.vPos, object.vSize }))
{
tv.FillRectDecal(object.vPos, object.vSize, object.colour);
nObjectCount++;
}
}
std::chrono::duration<float> duration = std::chrono::system_clock::now() - tpStart;
std::string sOutput = "Linear " + std::to_string(nObjectCount) + "/" + std::to_string(vecObjects.size()) + " in " + std::to_string(duration.count());
DrawStringDecal({ 4, 4 }, sOutput, olc::BLACK, { 4.0f, 8.0f });
DrawStringDecal({ 2, 2 }, sOutput, olc::WHITE, { 4.0f, 8.0f });
}
return true;
}
};
int main()
{
Example_StaticQuadTree demo;
if (demo.Construct(1280, 960, 1, 1, false, false))
demo.Start();
return 0;
}
Loading…
Cancel
Save