quadtree fixes and unit tests

This commit is contained in:
Crypto City 2019-08-06 23:23:45 +00:00
parent 31f1ccade8
commit 5e9c11a642
3 changed files with 265 additions and 31 deletions

View File

@ -2,20 +2,26 @@
#define QUADTREE_H
#include <stdint.h>
#include <set>
#include <deque>
#include <algorithm>
#include <Urho3D/Container/Ptr.h>
#include <Urho3D/Container/RefCounted.h>
#define SPLIT_THRESHOLD 64
#ifndef QUADRANT_SPLIT_THRESHOLD
#define QUADRANT_SPLIT_THRESHOLD 8
#endif
#ifndef MIN_QUADRANT_SIZE
#define MIN_QUADRANT_SIZE 64
#endif
static inline bool intersection(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t qx0, uint32_t qy0, uint32_t qx1, uint32_t qy1)
{
return x0 <= qx1 && x1 >= qx0 && y0 <= qy1 && y1 >= qy0;
}
template<typename T>
template<typename T, typename Res = std::set<Urho3D::SharedPtr<T>>>
class Quadtree
{
private:
@ -41,7 +47,7 @@ private:
if (intersection(c->x0, c->y0, c->x1, c->y1, qx0, qy0, qx1, qy1))
return c;
}
for (auto &q: quadrants)
for (const auto &q: quadrants)
{
if (q && intersection(q->x0, q->y0, q->x1, q->y1, qx0, qy0, qx1, qy1))
{
@ -53,14 +59,15 @@ private:
return {};
}
void query(uint32_t qx0, uint32_t qy0, uint32_t qx1, uint32_t qy1, std::deque<Urho3D::SharedPtr<T>> &results) const
void query(uint32_t qx0, uint32_t qy0, uint32_t qx1, uint32_t qy1, Res &results) const
{
//printf("quadrant %u %u %u %u, query %u %u %u %u with %zu contents\n", x0, y0, x1, y1, qx0, qy0, qx1, qy1, contents.size());
for (const Urho3D::SharedPtr<T> &c: contents)
{
if (intersection(c->x0, c->y0, c->x1, c->y1, qx0, qy0, qx1, qy1))
results.push_back(c);
results.insert(c);
}
for (auto &q: quadrants)
for (const auto &q: quadrants)
{
if (q && intersection(q->x0, q->y0, q->x1, q->y1, qx0, qy0, qx1, qy1))
q->query(qx0, qy0, qx1, qy1, results);
@ -71,10 +78,10 @@ private:
{
// 0 1
// 2 3
const uint32_t mx = (x1 + x0) / 2;
const uint32_t my = (y1 + y0) / 2;
const uint32_t tmx = (t->x1 + t->x0) / 2;
const uint32_t tmy = (t->y1 + t->y0) / 2;
const uint32_t mx = x0 + (x1 - x0) / 2;
const uint32_t my = y0 + (y1 - y0) / 2;
const uint32_t tmx = t->x0 + (t->x1 - t->x0) / 2;
const uint32_t tmy = t->y0 + (t->y1 - t->y0) / 2;
return (tmy > my) * 2 + (tmx > mx);
}
@ -83,48 +90,83 @@ private:
//printf("Quadrant at %u %u %u %u: insert %u %u %u %u\n", x0, y0, x1, y1, t->x0, t->y0, t->x1, t->y1);
if (!intersection(t->x0, t->y0, t->x1, t->y1, x0, y0, x1, y1))
{
printf("Oops, outwith our extents!\n");
//printf("Oops, outwith our extents!\n");
return false;
}
if (contents.size() < SPLIT_THRESHOLD || x1 - x0 + 1 <= MIN_QUADRANT_SIZE || y1 - y0 + 1 <= MIN_QUADRANT_SIZE)
if (contents.size() < QUADRANT_SPLIT_THRESHOLD || x1 - x0 <= MIN_QUADRANT_SIZE-1 || y1 - y0 <= MIN_QUADRANT_SIZE-1)
{
//printf("Adding\n");
contents.push_back(t);
//printf("Adding, now %zu\n", contents.size());
return true;
}
const uint32_t idx = getQuadrantIndex(t);
if (!quadrants[idx])
for (int idx = 0; idx < 4; ++idx)
{
const bool left = (t->x0 + t->x1) / 2 <= (x0 + x1) / 2;
const bool top = (t->y0 + t->y1) / 2 <= (y0 + y1) / 2;
const uint32_t nx0 = left ? x0 : ((x0 + x1) / 2);
const uint32_t nx1 = left ? ((x0 + x1) / 2 + 1) : x1;
const uint32_t ny0 = top ? y0 : ((y0 + y1) / 2);
const uint32_t ny1 = top ? ((y0 + y1) / 2 + 1) : y1;
printf("Creating new quadrant with extents %u %u %u %u\n", nx0, ny0, nx1, ny1);
quadrants[idx] = new Quadrant(nx0, ny0, nx1, ny1);
const bool left = ~idx & 1;
const bool top = idx < 2;
const uint32_t nx0 = left ? x0 : (x0 + (x1 - x0) / 2 + 1);
const uint32_t nx1 = left ? (x0 + (x1 - x0) / 2) : x1;
const uint32_t ny0 = top ? y0 : (y0 + (y1 - y0) / 2 + 1);
const uint32_t ny1 = top ? (y0 + (y1 - y0) / 2) : y1;
if (intersection(nx0, ny0, nx1, ny1, t->x0, t->y0, t->x1, t->y1))
{
if (!quadrants[idx])
quadrants[idx] = new Quadrant(nx0, ny0, nx1, ny1);
if (!quadrants[idx]->insert(t))
return false;
}
}
return quadrants[idx]->insert(t);
return true;
}
bool remove(const Urho3D::SharedPtr<T> &t)
{
bool ret = false;
for (auto i = contents.begin(); i != contents.end(); ++i)
{
if (*i == t)
{
contents.erase(i);
return true;
ret = true;
break;
}
}
const uint32_t idx = getQuadrantIndex(t);
if (quadrants[idx])
return quadrants[idx]->remove(t);
return false;
for (int idx = 0; idx < 4; ++idx)
{
const bool left = ~idx & 1;
const bool top = idx < 2;
const uint32_t nx0 = left ? x0 : (x0 + (x1 - x0) / 2 + 1);
const uint32_t nx1 = left ? (x0 + (x1 - x0) / 2) : x1;
const uint32_t ny0 = top ? y0 : (y0 + (y1 - y0) / 2 + 1);
const uint32_t ny1 = top ? (y0 + (y1 - y0) / 2) : y1;
if (intersection(nx0, ny0, nx1, ny1, t->x0, t->y0, t->x1, t->y1))
{
if (quadrants[idx] && quadrants[idx]->remove(t))
ret = true;
}
}
return ret;
}
size_t size() const
{
size_t s = contents.size();
for (int idx = 0; idx < 4; ++idx)
if (quadrants[idx])
s += quadrants[idx]->size();
return s;
}
size_t depth() const
{
size_t d = 0;
for (int idx = 0; idx < 4; ++idx)
if (quadrants[idx])
d = std::max(d, quadrants[idx]->depth());
return d + 1;
}
private:
@ -138,13 +180,16 @@ printf("Creating new quadrant with extents %u %u %u %u\n", nx0, ny0, nx1, ny1);
public:
Quadtree(): x0(0), y0(0), x1(std::numeric_limits<uint32_t>::max()), y1(std::numeric_limits<uint32_t>::max()), root(x0, y0, x1, y1) {}
Quadtree(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1): x0(x0), y0(y0), x1(x1), y1(y1), root(x0, y0, x1, y1) {}
~Quadtree() {}
bool insert(Urho3D::SharedPtr<T> t) { return root.insert(t); }
bool remove(Urho3D::SharedPtr<T> t) { return root.remove(t); }
Urho3D::SharedPtr<T> query_any(uint32_t qx0, uint32_t qy0, uint32_t qx1, uint32_t qy1) const { return root.query_any(qx0, qy0, qx1, qy1); }
void query(uint32_t qx0, uint32_t qy0, uint32_t qx1, uint32_t qy1, std::deque<Urho3D::SharedPtr<T>> &results) const { return root.query(qx0, qy0, qx1, qy1, results); }
void query(uint32_t qx0, uint32_t qy0, uint32_t qx1, uint32_t qy1, Res &results) const { return root.query(qx0, qy0, qx1, qy1, results); }
void clear() { root = Quadrant(x0, y0, x1, y1); }
size_t size() const { return root.size(); }
size_t depth() const { return root.depth(); }
private:
uint32_t x0, y0, x1, y1;

View File

@ -73,6 +73,7 @@ set(unit_tests_sources
output_distribution.cpp
parse_amount.cpp
pruning.cpp
quadtree.cpp
random.cpp
rolling_median.cpp
serialization.cpp
@ -99,6 +100,8 @@ set(unit_tests_sources
set(unit_tests_headers
unit_tests_utils.h)
include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/external/urho3d/Source")
set(URHO3D_LIBRARY "${CMAKE_BINARY_DIR}/external/urho3d/lib/${CMAKE_STATIC_LIBRARY_PREFIX}Urho3D${CMAKE_SHARED_LIBRARY_SUFFIX}")
add_executable(unit_tests
${unit_tests_sources}
${unit_tests_headers})
@ -116,6 +119,7 @@ target_link_libraries(unit_tests
cc
p2p
version
${URHO3D_LIBRARY}
${Boost_CHRONO_LIBRARY}
${Boost_THREAD_LIBRARY}
${GTEST_LIBRARIES}

View File

@ -0,0 +1,185 @@
// Copyright (c) 2019, Crypto City
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer 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.
#include <limits>
#include <deque>
#include <set>
#include "gtest/gtest.h"
#define QUADRANT_SPLIT_THREHOLD 2
#define MIN_QUADRANT_SIZE 2
#include "game/quadtree.h"
using namespace Urho3D;
struct T: public RefCounted
{
uint32_t x0, y0, x1, y1;
T(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1): x0(x0), y0(y0), x1(x1), y1(y1) {}
bool operator==(const T &t) const { return x0 == t.x0 && y0 == t.y0 && x1 == t.x1 && y1 == t.y1; }
};
#define MAXXY std::numeric_limits<uint32_t>::max()
#define NULLT SharedPtr<T>((T*)NULL)
TEST(quadtree, empty)
{
Quadtree<T> qt;
EXPECT_EQ(qt.query_any(0, 0, MAXXY, MAXXY), NULLT);
std::set<SharedPtr<T>> res;
qt.query(0, 0, MAXXY, MAXXY, res);
ASSERT_EQ(res.size(), 0);
}
TEST(quadtree, lrtb)
{
// . . . . . . .
// . . . . . . .
// . . x x x . .
// . . x x x . .
// . . x x x . .
// . . . . . . .
// . . . . . . .
Quadtree<T> qt;
qt.insert(SharedPtr<T>(new T{2, 2, 4, 4}));
ASSERT_EQ(qt.query_any(0, 0, 1, 1), NULLT);
ASSERT_EQ(qt.query_any(2, 0, 4, 1), NULLT);
ASSERT_EQ(qt.query_any(5, 0, 6, 1), NULLT);
ASSERT_EQ(qt.query_any(0, 2, 1, 4), NULLT);
ASSERT_EQ(qt.query_any(5, 2, 6, 4), NULLT);
ASSERT_EQ(qt.query_any(0, 5, 1, 6), NULLT);
ASSERT_EQ(qt.query_any(2, 5, 4, 6), NULLT);
ASSERT_EQ(qt.query_any(5, 5, 6, 6), NULLT);
}
TEST(quadtree, corners_intersect)
{
// . . . . . . .
// . . . . . . .
// . . x x x . .
// . . x x x . .
// . . x x x . .
// . . . . . . .
// . . . . . . .
Quadtree<T> qt;
qt.insert(SharedPtr<T>(new T{2, 2, 4, 4}));
ASSERT_NE(qt.query_any(0, 0, 2, 2), NULLT);
ASSERT_NE(qt.query_any(4, 0, 6, 2), NULLT);
ASSERT_NE(qt.query_any(0, 4, 2, 6), NULLT);
ASSERT_NE(qt.query_any(4, 4, 6, 6), NULLT);
}
TEST(quadtree, inside)
{
// . . . . . . .
// . . . . . . .
// . . x x x . .
// . . x x x . .
// . . x x x . .
// . . . . . . .
// . . . . . . .
Quadtree<T> qt;
qt.insert(SharedPtr<T>(new T{2, 2, 4, 4}));
ASSERT_NE(qt.query_any(3, 3, 3, 3), NULLT);
}
TEST(quadtree, equal)
{
// . . . . . . .
// . . . . . . .
// . . x x x . .
// . . x x x . .
// . . x x x . .
// . . . . . . .
// . . . . . . .
Quadtree<T> qt;
qt.insert(SharedPtr<T>(new T{2, 2, 4, 4}));
ASSERT_NE(qt.query_any(2, 2, 4, 4), NULLT);
}
TEST(quadtree, encompassing)
{
// . . . . . . .
// . . . . . . .
// . . x x x . .
// . . x x x . .
// . . x x x . .
// . . . . . . .
// . . . . . . .
Quadtree<T> qt;
qt.insert(SharedPtr<T>(new T{2, 2, 4, 4}));
ASSERT_NE(qt.query_any(1, 1, 4, 5), NULLT);
}
TEST(quadtree, point_query)
{
// . . . . . . .
// . . . . . . .
// . . x x x . .
// . . x x x . .
// . . x x x . .
// . . . . . . .
// . . . . . . .
Quadtree<T> qt;
qt.insert(SharedPtr<T>(new T{2, 2, 4, 4}));
for (uint32_t y = 0; y <= 6; ++y)
for (uint32_t x = 0; x <= 6; ++x)
ASSERT_EQ(qt.query_any(x, y, x, y) == NULLT, x < 2 || y < 2 || x > 4 || y > 4);
}
TEST(quadtree, point_results)
{
Quadtree<T> qt;
for (uint32_t y = 0; y <= 6; ++y)
for (uint32_t x = 0; x <= 6; ++x)
qt.insert(SharedPtr<T>(new T{x, y, x, y}));
for (uint32_t y = 0; y <= 6; ++y)
{
for (uint32_t x = 0; x <= 6; ++x)
{
std::set<SharedPtr<T>> res;
qt.query(x, y, x, y, res);
ASSERT_EQ(res.size(), 1);
ASSERT_EQ(**res.begin(), (T{x, y, x, y}));
}
}
for (uint32_t y = 0; y <= 5; ++y)
{
for (uint32_t x = 0; x <= 5; ++x)
{
std::set<SharedPtr<T>> res;
qt.query(x, y, x+1, y+1, res);
ASSERT_EQ(res.size(), 4);
}
}
std::set<SharedPtr<T>> res;
qt.query(0, 0, MAXXY, MAXXY, res);
ASSERT_EQ(res.size(), 49);
res.clear();
qt.query(1, 0, 3, 49, res);
ASSERT_EQ(res.size(), 21);
}