Skip to content

Commit 6aa5452

Browse files
authored
Merge pull request #3 from hit9/refactor-coordinates
refactor coordinates
2 parents b25c47a + f2044ef commit 6aa5452

File tree

5 files changed

+212
-200
lines changed

5 files changed

+212
-200
lines changed

quadtree.hpp

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
// Optimized quadtrees on grid rectangles in C++.
22
// https://github.com/hit9/quadtree-hpp
33
//
4-
// BSD license. Chao Wang, Version: 0.2.2
4+
// BSD license. Chao Wang, Version: 0.3.0
55
//
66
// Coordinate conventions:
77
//
88
// w
9-
// +---------------> y
9+
// +---------------> x
1010
// |
1111
// h |
1212
// |
1313
// v
14-
// x
14+
// y
1515
//
1616

1717
// changes
1818
// ~~~~~~~
19+
// 0.3.0: **Breaking change**: inverts the coordinates conventions.
1920
// 0.2.2: Add `RemoveObjects` and `BatchAddToLeafNode`.
2021

2122
#ifndef HIT9_QUADTREE_HPP
@@ -42,14 +43,14 @@ using std::uint8_t;
4243
// NodeId is the unique identifier of a tree node, composed of:
4344
//
4445
// +----- 6bit -----+------- 29bit ----+----- 29bit -----+
45-
// | depth d (6bit) | floor(x*(2^d)/h) | floor(y*(2^d)/w |
46+
// | depth d (6bit) | floor(x*(2^d)/w) | floor(y*(2^d)/h |
4647
// +----------------+------------------+-----------------+
4748
//
4849
// Properties:
4950
// 1. Substituting this formula into any position (x,y) inside the node always give the same ID.
5051
// 2. The id of tree root is always 0.
5152
// 3. The deeper the node, the larger the id.
52-
// 4. For nodes at the same depth, the id changes with the size of x*w+y.
53+
// 4. For nodes at the same depth, the id changes with the size of x*h+y.
5354
using NodeId = uint64_t;
5455

5556
// pack caculates the id of a node.
@@ -63,8 +64,8 @@ inline NodeId pack(uint64_t d, uint64_t x, uint64_t y, uint64_t w, uint64_t h) {
6364
// other bits are all 0.
6465
// 0x1fffffff : the lowest 29 bits are all 1, the other bits are all 0.
6566
return ((d << 58) & 0xfc00000000000000ULL) |
66-
((((1 << d) * x / h) << 29) & 0x3ffffffe0000000ULL) |
67-
(((1 << d) * y / w) & 0x1fffffffULL);
67+
((((1 << d) * x / w) << 29) & 0x3ffffffe0000000ULL) |
68+
(((1 << d) * y / h) & 0x1fffffffULL);
6869
}
6970

7071
template <typename Object>
@@ -280,7 +281,7 @@ class Quadtree {
280281
// For diagonal directions(4,5,6,7), it simply returns the single diagonal leaf neighbour.
281282
// For non-diagonal directions (0,1,2,3), there're two steps.
282283
// Explaination for direction=0 (North), supposing the depth of given node is d:
283-
// 1. Take 2 neighbour positions p1(x1-1,y1), p2(x1-1,y2) find the smallest node containing p1
284+
// 1. Take 2 neighbour positions p1(x1,y1-1), p2(x1,y2-1) find the smallest node containing p1
284285
// and p2. This node's size should be equal or greater than current node.
285286
// This step could be done by a binary-search in time complexity O(log Depth).
286287
// 2. Find the sourth children(No. 2,3) downward recursively from the node found in step1, until
@@ -450,7 +451,7 @@ bool Quadtree<Object, ObjectHasher>::splitable(int x1, int y1, int x2, int y2, i
450451
return true;
451452
}
452453
// ssf v1
453-
if (ssf != nullptr && ssf(y2 - y1 + 1, x2 - x1 + 1, n)) return false;
454+
if (ssf != nullptr && ssf(x2 - x1 + 1, y2 - y1 + 1, n)) return false;
454455
return true;
455456
}
456457

@@ -501,8 +502,8 @@ Node<Object, ObjectHasher>* Quadtree<Object, ObjectHasher>::splitHelper1(
501502
uint8_t d, int x1, int y1, int x2, int y2, ObjectsT& upstreamObjects,
502503
NodeSet& createdLeafNodes) {
503504
// boundary checks.
504-
if (!(x1 >= 0 && x1 < h && y1 >= 0 && y1 < w)) return nullptr;
505-
if (!(x2 >= 0 && x2 < h && y2 >= 0 && y2 < w)) return nullptr;
505+
if (!(x1 >= 0 && x1 < w && y1 >= 0 && y1 < h)) return nullptr;
506+
if (!(x2 >= 0 && x2 < w && y2 >= 0 && y2 < h)) return nullptr;
506507
if (!(x1 <= x2 && y1 <= y2)) return nullptr;
507508
// steal objects inside this rectangle from upstream.
508509
ObjectsT objs;
@@ -545,30 +546,30 @@ void Quadtree<Object, ObjectHasher>::splitHelper2(NodeT* node, NodeSet& createdL
545546
auto d = node->d;
546547
// the following (x3,y3) is the middle point*:
547548
//
548-
// y1 y3 y2
549-
// x1 -+------+------+-
549+
// x1 x3 x2
550+
// y1 -+------+------+-
550551
// | 0 | 1 |
551-
// x3 | * | |
552+
// y3 | * | |
552553
// -+------+------+-
553554
// | 2 | 3 |
554555
// | | |
555-
// x2 -+------+------+-
556+
// y2 -+------+------+-
556557
int x3 = x1 + (x2 - x1) / 2, y3 = y1 + (y2 - y1) / 2;
557558

558-
// determines which side each axis x3 and y3 belongs, take y axis for instance:
559-
// by default, we assume y3 belongs to the left side.
560-
// but if the ids of y1 and y3 are going to dismatch, which means the y3 should belong to the
561-
// right side, that is we should minus y3 by 1.
562-
// And minus by 1 should be enough, because y3-2 always equals to y3-4, y3-8,.. until y1.
559+
// determines which side each axis x3 and y3 belongs, take x axis for instance:
560+
// by default, we assume x3 belongs to the left side.
561+
// but if the ids of x1 and x3 are going to dismatch, which means the x3 should belong to the
562+
// right side, that is we should minus x3 by 1.
563+
// And minus by 1 should be enough, because x3-2 always equals to x3-4, x3-8,.. until x1.
563564
// Potential optimization: how to avoid the division here?
564565
uint64_t k = 1 << (d + 1);
565-
if ((k * x3 / h) != (k * x1 / h)) --x3;
566-
if ((k * y3 / w) != (k * y1 / w)) --y3;
566+
if ((k * x3 / w) != (k * x1 / w)) --x3;
567+
if ((k * y3 / h) != (k * y1 / h)) --y3;
567568

568569
// clang-format off
569570
node->children[0] = splitHelper1(d + 1, x1, y1, x3, y3, node->objects, createdLeafNodes);
570-
node->children[1] = splitHelper1(d + 1, x1, y3 + 1, x3, y2, node->objects, createdLeafNodes);
571-
node->children[2] = splitHelper1(d + 1, x3 + 1, y1, x2, y3, node->objects, createdLeafNodes);
571+
node->children[1] = splitHelper1(d + 1, x3 + 1, y1, x2, y3, node->objects, createdLeafNodes);
572+
node->children[2] = splitHelper1(d + 1, x1, y3 + 1, x3, y2, node->objects, createdLeafNodes);
572573
node->children[3] = splitHelper1(d + 1, x3 + 1, y3 + 1, x2, y2, node->objects, createdLeafNodes);
573574
// clang-format on
574575

@@ -702,16 +703,16 @@ inline bool isOverlap(int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int
702703
//
703704
// Ref: https://silentmatt.com/rectangle-intersection/
704705
//
705-
// ax1 < bx2 => A's upper boundary is above B's bottom boundary.
706-
// ax2 > bx1 => A's bottom boundary is below B's upper boundary.
706+
// ay1 < by2 => A's left boundary is above B's bottom boundary.
707+
// ay2 > by1 => A's bottom boundary is below B's upper boundary.
707708
//
708709
// *********** B's upper A's upper -----------
709710
// A's upper ----------- OR *********** B's upper
710711
// *********** B's bottom A's bottom -----------
711712
// A's bottom ----------- *********** B's bottom
712713
//
713-
// ay1 < by2 => A's left boundary is on the left of B's right boundary.
714-
// ay2 > by1 => A's right boundary is on the right of B's left boundary.
714+
// ax1 < bx2 => A's left boundary is on the left of B's right boundary.
715+
// ax2 > bx1 => A's right boundary is on the right of B's left boundary.
715716
//
716717
// A's left A's right A's left A's right
717718
//
@@ -759,7 +760,7 @@ void Quadtree<Object, ObjectHasher>::queryRange(NodeT* node, CollectorT& objects
759760
}
760761

761762
// Using binary search to guess the depth of the target node.
762-
// Reason: the id = (d, x*2^d/h, y*2^d/w), it's the same for all (x,y) inside the same
763+
// Reason: the id = (d, x*2^d/w, y*2^d/h), it's the same for all (x,y) inside the same
763764
// node. If id(d,x,y) is not found in the map m, the guessed depth is too large, we should
764765
// shrink the upper bound. Otherwise, if we found a node, but it's not a leaf, the answer
765766
// is too small, we should make the lower bound larger, And finally, if we found a leaf node,
@@ -787,7 +788,7 @@ Node<Object, ObjectHasher>* Quadtree<Object, ObjectHasher>::Find(int x, int y) c
787788
template <typename Object, typename ObjectHasher>
788789
void Quadtree<Object, ObjectHasher>::Add(int x, int y, Object o) {
789790
// boundary checks.
790-
if (!(x >= 0 && x < h && y >= 0 && y < w)) return;
791+
if (!(x >= 0 && x < w && y >= 0 && y < h)) return;
791792
// find the leaf node.
792793
auto node = Find(x, y);
793794
if (node == nullptr) return;
@@ -803,7 +804,7 @@ void Quadtree<Object, ObjectHasher>::Add(int x, int y, Object o) {
803804
template <typename Object, typename ObjectHasher>
804805
void Quadtree<Object, ObjectHasher>::Remove(int x, int y, Object o) {
805806
// boundary checks.
806-
if (!(x >= 0 && x < h && y >= 0 && y < w)) return;
807+
if (!(x >= 0 && x < w && y >= 0 && y < h)) return;
807808
// find the leaf node.
808809
auto node = Find(x, y);
809810
if (node == nullptr) return;
@@ -818,7 +819,7 @@ void Quadtree<Object, ObjectHasher>::Remove(int x, int y, Object o) {
818819
template <typename Object, typename ObjectHasher>
819820
void Quadtree<Object, ObjectHasher>::RemoveObjects(int x, int y) {
820821
// boundary checks.
821-
if (!(x >= 0 && x < h && y >= 0 && y < w)) return;
822+
if (!(x >= 0 && x < w && y >= 0 && y < h)) return;
822823
// find the leaf node.
823824
auto node = Find(x, y);
824825
if (node == nullptr) return;
@@ -833,7 +834,7 @@ void Quadtree<Object, ObjectHasher>::RemoveObjects(int x, int y) {
833834

834835
template <typename Object, typename ObjectHasher>
835836
void Quadtree<Object, ObjectHasher>::Build() {
836-
root = createNode(true, 0, 0, 0, h - 1, w - 1);
837+
root = createNode(true, 0, 0, 0, w - 1, h - 1);
837838
if (!trySplitDown(root)) {
838839
// If the root is not splited, it's finally a new-created leaf node.
839840
if (afterLeafCreated != nullptr) afterLeafCreated(root);
@@ -853,8 +854,8 @@ template <typename Object, typename ObjectKeyHasher>
853854
Node<Object, ObjectKeyHasher>* Quadtree<Object, ObjectKeyHasher>::findSmallestNodeCoveringRange(
854855
int x1, int y1, int x2, int y2, int dma) const {
855856
// boundary checks
856-
if (!(x1 >= 0 && x1 < h && y1 >= 0 && y1 < w)) return nullptr;
857-
if (!(x2 >= 0 && x2 < h && y2 >= 0 && y2 < w)) return nullptr;
857+
if (!(x1 >= 0 && x1 < w && y1 >= 0 && y1 < h)) return nullptr;
858+
if (!(x2 >= 0 && x2 < w && y2 >= 0 && y2 < h)) return nullptr;
858859
// Find the target
859860
int l = 0, r = dma;
860861
NodeT* node = root;
@@ -921,11 +922,11 @@ void Quadtree<Object, ObjectHasher>::QueryLeafNodesInRange(int x1, int y1, int x
921922
// Get the neighbour position (px,py) on given diagonal direction of given node.
922923
// The a,b,c,d is the target neighbour position for each direction:
923924
//
924-
// y1 y2
925+
// x1 x2
925926
// 4 a| |b 5
926-
// --+-----+-- x1
927+
// --+-----+-- y1
927928
// | |
928-
// --+-----+-- x2
929+
// --+-----+-- y2
929930
// 7 d| |c 6
930931
template <typename Object, typename ObjectKeyHasher>
931932
void Quadtree<Object, ObjectKeyHasher>::getNeighbourPositionDiagonal(NodeT* node, int direction,
@@ -936,13 +937,13 @@ void Quadtree<Object, ObjectKeyHasher>::getNeighbourPositionDiagonal(NodeT* node
936937
px = x1 - 1, py = y1 - 1;
937938
return;
938939
case 5: // b
939-
px = x1 - 1, py = y2 + 1;
940+
px = x2 + 1, py = y1 - 1;
940941
return;
941942
case 6: // c
942943
px = x2 + 1, py = y2 + 1;
943944
return;
944945
case 7: // d
945-
px = x2 + 1, py = y1 - 1;
946+
px = x1 - 1, py = y2 + 1;
946947
return;
947948
}
948949
}
@@ -964,13 +965,13 @@ void Quadtree<Object, ObjectKeyHasher>::findNeighbourLeafNodesDiagonal(NodeT* no
964965
// The ab,cd,ef,gh are the target neighbour positions at each direction:
965966
//
966967
// N:0
967-
// y1 y2
968+
// x1 x2
968969
// | |
969970
// a b
970-
// -g+-----+c- x1
971+
// -g+-----+c- y1
971972
// W:3 | | E:1
972973
// | |
973-
// -h+-----+d- x2
974+
// -h+-----+d- y2
974975
// e f
975976
// | |
976977
// S:2
@@ -981,20 +982,20 @@ void Quadtree<Object, ObjectKeyHasher>::getNeighbourPositionsHV(NodeT* node, int
981982
int x1 = node->x1, y1 = node->y1, x2 = node->x2, y2 = node->y2;
982983
switch (direction) {
983984
case 0: // N
984-
px1 = x1 - 1, py1 = y1; // a
985-
px2 = x1 - 1, py2 = y2; // b
985+
px1 = x1, py1 = y1 - 1; // a
986+
px2 = x2, py2 = y1 - 1; // b
986987
return;
987988
case 1: // E
988-
px1 = x1, py1 = y2 + 1; // c
989-
px2 = x2, py2 = y2 + 1; // d
989+
px1 = x2 + 1, py1 = y1; // c
990+
px2 = x2 + 1, py2 = y2; // d
990991
return;
991992
case 2: // S
992-
px1 = x2 + 1, py1 = y1; // e
993-
px2 = x2 + 1, py2 = y2; // f
993+
px1 = x1, py1 = y2 + 1; // e
994+
px2 = x2, py2 = y2 + 1; // f
994995
return;
995996
case 3: // W
996-
px1 = x1, py1 = y1 - 1; // g
997-
px2 = x2, py2 = y1 - 1; // h
997+
px1 = x1 - 1, py1 = y1; // g
998+
px2 = x1 - 1, py2 = y2; // h
998999
return;
9991000
}
10001001
}

tests/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ install:
44
conan install . --output-folder=build --build=missing -s compiler.cppstd=20 -s build_type=Debug
55

66
cmake:
7+
@if [ ! -d build ]; then \
8+
$(MAKE) install; \
9+
fi
710
cd build && cmake .. \
811
-DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake \
912
-DCMAKE_BUILD_TYPE=Debug \
1013
-DCMAKE_EXPORT_COMPILE_COMMANDS=1
1114

1215
build: cmake
16+
@if [ ! -d build ]; then \
17+
$(MAKE) cmake; \
18+
fi
1319
cd build && make
1420

1521
run:

0 commit comments

Comments
 (0)