forked from townforge/townforge
quadtree fixes and unit tests
This commit is contained in:
parent
31f1ccade8
commit
5e9c11a642
@ -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;
|
||||
|
@ -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}
|
||||
|
185
tests/unit_tests/quadtree.cpp
Normal file
185
tests/unit_tests/quadtree.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user