libdonut  2.3.2
Application framework for cross-platform game development in C++20
LooseQuadtree.hpp
Go to the documentation of this file.
1 #ifndef DONUT_LOOSE_QUADTREE_HPP
2 #define DONUT_LOOSE_QUADTREE_HPP
3 
4 #include <donut/math.hpp>
5 #include <donut/shapes.hpp>
6 
7 #include <algorithm> // std::all_of
8 #include <array> // std::array
9 #include <cassert> // assert
10 #include <cstddef> // std::size_t, std::ptrdiff_t
11 #include <cstdint> // std::uint..._t
12 #include <iterator> // std::iterator_traits
13 #include <memory> // std::unique_ptr, std::make_unique
14 #include <optional> // std::optional
15 #include <type_traits> // std::conditional_t, std::is_convertible_v, std::invoke_result_t
16 #include <utility> // std::pair, std::forward, std::move, std::exchange
17 #include <vector> // std::vector
18 
19 namespace donut {
20 
27 template <typename T>
29 public:
30  using value_type = T;
31  using reference = T&;
32  using const_reference = const T&;
33  using pointer = T*;
34  using const_pointer = const T*;
35  using size_type = std::size_t;
36 
37 private:
38  using TreeIndex = std::uint32_t;
39  using QuadrantIndexArray = std::array<TreeIndex, 4>;
40 
41  struct Quadrant {
42  QuadrantIndexArray subQuadrantIndices{};
43  TreeIndex parentIndex = 0;
44  std::optional<T> element{};
45  };
46 
47  template <bool Const>
48  class Iterator {
49  public:
50  using reference = std::conditional_t<Const, LooseQuadtree::const_reference, LooseQuadtree::reference>;
51  using pointer = std::conditional_t<Const, LooseQuadtree::const_pointer, LooseQuadtree::pointer>;
52  using difference_type = std::ptrdiff_t;
54 
55  constexpr Iterator() noexcept = default;
56 
57  [[nodiscard]] constexpr reference operator*() const {
58  assert(element);
59  assert(*element);
60  return **element;
61  }
62 
63  [[nodiscard]] constexpr pointer operator->() const {
64  return &**this;
65  }
66 
67  [[nodiscard]] constexpr bool operator==(const Iterator& other) const noexcept {
68  return element == other.element;
69  }
70 
71  Iterator& operator++() {
72  ++element;
73  return *this;
74  }
75 
76  Iterator operator++(int) {
77  Iterator old = *this;
78  ++*this;
79  return old;
80  }
81 
82  private:
83  friend LooseQuadtree;
84 
85  Iterator(std::optional<T>* element, TreeIndex treeIndex) noexcept
86  : element(element)
87  , treeIndex(treeIndex) {}
88 
89  std::optional<T>* element = nullptr;
90  TreeIndex treeIndex{};
91  };
92 
93 public:
94  using iterator = Iterator<false>;
95  using const_iterator = Iterator<true>;
96  using difference_type = typename std::iterator_traits<iterator>::difference_type;
97 
108  LooseQuadtree(const Box<2, float>& worldBoundingBox, vec2 typicalBoxSize) noexcept {
109  reset(worldBoundingBox, typicalBoxSize);
110  }
111 
124  void reset(const Box<2, float>& worldBoundingBox, vec2 typicalBoxSize) noexcept {
125  clear();
126  minimumQuadrantSize = max(typicalBoxSize.x, typicalBoxSize.y);
127  rootCenter = (worldBoundingBox.min + worldBoundingBox.max) * 0.5f;
128  const vec2 worldMaxExtents = max(worldBoundingBox.max - rootCenter, rootCenter - worldBoundingBox.min);
129  const float worldMaxExtent = max(worldMaxExtents.x, worldMaxExtents.y);
130  // Double the root size until it fits the entire world.
131  halfRootSize = minimumQuadrantSize;
132  while (halfRootSize < worldMaxExtent) {
133  halfRootSize *= 2.0f;
134  }
135  }
136 
143  void clear() noexcept {
144  tree.clear();
145  firstFreeIndex = 0;
146  }
147 
172  template <typename... Args>
173  std::pair<iterator, bool> emplace(const Box<2, float>& elementBoundingBox, Args&&... args) {
174  // Make sure the tree has a root.
175  if (tree.empty()) {
176  tree.emplace_back();
177  }
178 
179  // Find the center of the AABB.
180  const vec2 aabbDiagonal = elementBoundingBox.max - elementBoundingBox.min;
181  const vec2 aabbCenter = elementBoundingBox.min + aabbDiagonal * 0.5f;
182 
183  // Find the largest extent of the AABB.
184  const float aabbSize = max(aabbDiagonal.x, aabbDiagonal.y);
185 
186  // Start at the root of the tree and search for the smallest quadrant that contains the entire AABB within its loose bounds.
187  // The loose bounds is a box around the quadrant that is twice as big as the quadrant in every direction and shares the same center.
188  // Since the loose bounds of adjacent quadrants overlap, it could happen that the AABB is contained within multiple quadrants' loose bounds at the same time.
189  // In that case, the closest quadrant, i.e. the one which contains the center of the AABB, is chosen.
190  // The loop stops going lower in the tree when the AABB can no longer fit in a smaller quadrant, or when we reach the minimum quadrant size.
191  float quadrantSize = halfRootSize;
192  vec2 center = rootCenter;
193  TreeIndex treeIndex = 0;
194  while (quadrantSize >= aabbSize && quadrantSize >= minimumQuadrantSize) {
195  quadrantSize *= 0.5f;
196 
197  // Determine which quadrant the AABB belongs to. This updates the center.
198  const std::size_t quadrantIndexArrayIndex = chooseQuadrant(aabbCenter, center, quadrantSize);
199 
200  // Go to the quadrant.
201  if (TreeIndex& quadrantIndex = tree[treeIndex].subQuadrantIndices[quadrantIndexArrayIndex]) {
202  // The quadrant already exists in the tree. Go directly to it.
203  treeIndex = quadrantIndex;
204  } else {
205  // The quadrant does not exist in the tree yet.
206  // Acquire a space in the tree for the new quadrant.
207  if (firstFreeIndex != 0) {
208  // Re-use the first free quadrant.
209  Quadrant& quadrant = tree[firstFreeIndex];
210  // Set the new quadrant's parent index.
211  quadrant.parentIndex = treeIndex;
212  // Update the quadrant index to point to the new quadrant.
213  quadrantIndex = firstFreeIndex;
214  // Go to the new quadrant.
215  treeIndex = firstFreeIndex;
216  // Update the free index. The first sub-quadrant index in the quadrant leads to the next free quadrant.
217  firstFreeIndex = std::exchange(quadrant.subQuadrantIndices.front(), 0);
218  } else {
219  // No free quadrants available for re-use. We need to allocate a new quadrant.
220  // Save the new index. Don't change quadrantIndex before emplace_back() since the allocation might throw.
221  const TreeIndex newQuadrantIndex = static_cast<TreeIndex>(tree.size());
222  // Allocate the new quadrant.
223  Quadrant& newQuadrant = tree.emplace_back();
224  // Set the new quadrant's parent index.
225  newQuadrant.parentIndex = treeIndex;
226  // Update the quadrant index to point to the new quadrant.
227  // Don't access through the quadrantIndex reference since it might have been invalidated by emplace_back().
228  tree[treeIndex].subQuadrantIndices[quadrantIndexArrayIndex] = newQuadrantIndex;
229  // Go to the new quadrant.
230  treeIndex = newQuadrantIndex;
231  }
232  }
233  }
234 
235  // Try to insert the new element into the selected quadrant.
236  try {
237  std::optional<T>& element = tree[treeIndex].element;
238  if (element) {
239  return {iterator{&element, treeIndex}, false};
240  }
241  element.emplace(std::forward<Args>(args)...);
242  return {iterator{&element, treeIndex}, true};
243  } catch (...) {
244  cleanup(treeIndex);
245  throw;
246  }
247  }
248 
273  std::pair<iterator, bool> insert(const Box<2, float>& elementBoundingBox, const T& value) {
274  return emplace(elementBoundingBox, value);
275  }
276 
301  std::pair<iterator, bool> insert(const Box<2, float>& elementBoundingBox, T&& value) {
302  return emplace(elementBoundingBox, std::move(value));
303  }
304 
324  [[nodiscard]] T& operator[](const Box<2, float>& elementBoundingBox) {
325  return *emplace(elementBoundingBox).first;
326  }
327 
335  void erase(const_iterator pos) noexcept {
336  assert(pos.element);
337  pos.element->reset();
338  cleanup(pos.treeIndex);
339  }
340 
388  template <typename Callback, typename Predicate>
389  constexpr auto traverseActiveNodes(Callback&& callback, Predicate&& predicate) const { // NOLINT(cppcoreguidelines-missing-std-forward)
390  return traverseNodesImpl(
391  [callback = std::forward<Callback>(callback)](const Box<2, float>& looseBounds, const Quadrant& node) mutable {
392  constexpr bool CALLBACK_RETURNS_BOOL = std::is_convertible_v<std::invoke_result_t<Callback, const Box<2, float>&, const T* const&>, bool>;
393  const T* const element = (node.element) ? &*node.element : nullptr;
394  if constexpr (CALLBACK_RETURNS_BOOL) {
395  return callback(looseBounds, element);
396  } else {
397  callback(looseBounds, element);
398  }
399  },
400  std::forward<Predicate>(predicate));
401  }
402 
439  template <typename Callback>
440  constexpr auto traverseActiveNodes(Callback&& callback) const {
441  return traverseActiveNodes(std::forward<Callback>(callback), [](const Box<2, float>&) -> bool { return true; });
442  }
443 
491  template <typename Callback, typename Predicate>
492  constexpr auto traverseElementNodes(Callback&& callback, Predicate&& predicate) const { // NOLINT(cppcoreguidelines-missing-std-forward)
493  return traverseNodesImpl(
494  [callback = std::forward<Callback>(callback)](const Box<2, float>& looseBounds, const Quadrant& node) mutable {
495  constexpr bool CALLBACK_RETURNS_BOOL = std::is_convertible_v<std::invoke_result_t<Callback, const Box<2, float>&, const T&>, bool>;
496  if (node.element) {
497  if constexpr (CALLBACK_RETURNS_BOOL) {
498  if (callback(looseBounds, *node.element)) {
499  return true; // Callback requested early return.
500  }
501  } else {
502  callback(looseBounds, *node.element);
503  }
504  }
505  if constexpr (CALLBACK_RETURNS_BOOL) {
506  return false;
507  }
508  },
509  std::forward<Predicate>(predicate));
510  }
511 
548  template <typename Callback>
549  constexpr auto traverseElementNodes(Callback&& callback) const {
550  return traverseElementNodes(std::forward<Callback>(callback), [](const Box<2, float>&) -> bool { return true; });
551  }
552 
594  template <typename Callback, typename Predicate>
595  constexpr auto traverseElements(Callback&& callback, Predicate&& predicate) const { // NOLINT(cppcoreguidelines-missing-std-forward)
596  return traverseNodesImpl(
597  [callback = std::forward<Callback>(callback)](const Box<2, float>&, const Quadrant& node) mutable {
598  constexpr bool CALLBACK_RETURNS_BOOL = std::is_convertible_v<std::invoke_result_t<Callback, const T&>, bool>;
599  if (node.element) {
600  if constexpr (CALLBACK_RETURNS_BOOL) {
601  if (callback(*node.element)) {
602  return true; // Callback requested early return.
603  }
604  } else {
605  callback(*node.element);
606  }
607  }
608  if constexpr (CALLBACK_RETURNS_BOOL) {
609  return false;
610  }
611  },
612  std::forward<Predicate>(predicate));
613  }
614 
646  template <typename Callback>
647  constexpr auto traverseElements(Callback&& callback) const {
648  return traverseElements(std::forward<Callback>(callback), [](const Box<2, float>&) -> bool { return true; });
649  }
650 
684  template <typename Callback>
685  auto test(vec2 point, Callback&& callback) const {
686  return traverseElements(std::forward<Callback>(callback), [&point](const Box<2, float>& looseBounds) -> bool { return looseBounds.contains(point); });
687  }
688 
707  [[nodiscard]] bool test(vec2 point) const noexcept {
708  return traverseElements([](const T&) -> bool { return true; }, [&point](const Box<2, float>& looseBounds) -> bool { return looseBounds.contains(point); });
709  }
710 
744  template <typename Callback>
745  auto test(const Box<2, float>& box, Callback&& callback) const {
746  return traverseElements(std::forward<Callback>(callback), [&box](const Box<2, float>& looseBounds) -> bool { return intersects(looseBounds, box); });
747  }
748 
768  [[nodiscard]] bool test(const Box<2, float>& box) const noexcept {
769  return traverseElements([](const T&) -> bool { return true; }, [&box](const Box<2, float>& looseBounds) -> bool { return intersects(looseBounds, box); });
770  }
771 
772 private:
773  struct IterationState {
774  vec2 center;
775  float quadrantSize;
776  TreeIndex treeIndex;
777  };
778 
779  [[nodiscard]] static constexpr std::size_t chooseQuadrant(vec2 aabbCenter, vec2& center, float halfQuadrantSize) {
780  std::size_t index{};
781  if (aabbCenter.x < center.x) {
782  center.x -= halfQuadrantSize;
783  if (aabbCenter.y < center.y) {
784  center.y -= halfQuadrantSize;
785  index = 0;
786  } else {
787  center.y += halfQuadrantSize;
788  index = 1;
789  }
790  } else {
791  center.x += halfQuadrantSize;
792  if (aabbCenter.y < center.y) {
793  center.y -= halfQuadrantSize;
794  index = 2;
795  } else {
796  center.y += halfQuadrantSize;
797  index = 3;
798  }
799  }
800  return index;
801  }
802 
803  template <typename Callback>
804  static constexpr void forEachActiveQuadrant(const QuadrantIndexArray& subQuadrantIndices, vec2 center, float halfQuadrantSize,
805  Callback&& callback) { // NOLINT(cppcoreguidelines-missing-std-forward)
806  if (const TreeIndex quadrantIndex = subQuadrantIndices[0]) {
807  callback(quadrantIndex, vec2{center.x - halfQuadrantSize, center.y - halfQuadrantSize});
808  }
809  if (const TreeIndex quadrantIndex = subQuadrantIndices[1]) {
810  callback(quadrantIndex, vec2{center.x - halfQuadrantSize, center.y + halfQuadrantSize});
811  }
812  if (const TreeIndex quadrantIndex = subQuadrantIndices[2]) {
813  callback(quadrantIndex, vec2{center.x + halfQuadrantSize, center.y - halfQuadrantSize});
814  }
815  if (const TreeIndex quadrantIndex = subQuadrantIndices[3]) {
816  callback(quadrantIndex, vec2{center.x + halfQuadrantSize, center.y + halfQuadrantSize});
817  }
818  }
819 
820  template <typename Callback, typename Predicate>
821  constexpr auto traverseNodesImpl(Callback&& callback, Predicate&& predicate) const { // NOLINT(cppcoreguidelines-missing-std-forward)
822  constexpr bool CALLBACK_RETURNS_BOOL = std::is_convertible_v<std::invoke_result_t<Callback, const Box<2, float>&, const Quadrant&>, bool>;
823  if (!tree.empty()) {
824  iterationStack.clear();
825  iterationStack.push_back(IterationState{
826  .center = rootCenter,
827  .quadrantSize = halfRootSize,
828  .treeIndex = 0,
829  });
830  do {
831  const auto [center, quadrantSize, treeIndex] = iterationStack.back();
832  iterationStack.pop_back();
833 
834  const float size = quadrantSize * 2.0f;
835  const Box<2, float> looseBounds{center - vec2{size}, center + vec2{size}};
836  const Quadrant& node = tree[treeIndex];
837  if constexpr (CALLBACK_RETURNS_BOOL) {
838  if (callback(looseBounds, node)) {
839  return true; // Callback requested early return.
840  }
841  } else {
842  callback(looseBounds, node);
843  }
844 
845  const float halfQuadrantSize = quadrantSize * 0.5f;
846  forEachActiveQuadrant(node.subQuadrantIndices, center, halfQuadrantSize,
847  [this, &predicate, quadrantSize = quadrantSize, halfQuadrantSize](TreeIndex quadrantIndex, vec2 quadrantCenter) {
848  const Box<2, float> looseBounds{quadrantCenter - vec2{quadrantSize}, quadrantCenter + vec2{quadrantSize}};
849  if (predicate(looseBounds)) {
850  iterationStack.push_back(IterationState{
851  .center = quadrantCenter,
852  .quadrantSize = halfQuadrantSize,
853  .treeIndex = quadrantIndex,
854  });
855  }
856  });
857  } while (!iterationStack.empty());
858  }
859  if constexpr (CALLBACK_RETURNS_BOOL) {
860  return false;
861  }
862  }
863 
864  void cleanup(TreeIndex treeIndex) noexcept {
865  Quadrant* node = &tree[treeIndex];
866  while (!node->element &&
867  std::all_of(node->subQuadrantIndices.begin(), node->subQuadrantIndices.end(), [](TreeIndex quadrantIndex) -> bool { return quadrantIndex == 0; })) {
868  if (treeIndex == 0) {
869  clear();
870  break;
871  }
872  node->subQuadrantIndices.front() = firstFreeIndex;
873  firstFreeIndex = treeIndex;
874  treeIndex = node->parentIndex;
875  node = &tree[treeIndex];
876  for (TreeIndex& quadrantIndex : node->subQuadrantIndices) {
877  if (quadrantIndex == firstFreeIndex) {
878  quadrantIndex = 0;
879  break;
880  }
881  }
882  }
883  }
884 
885  std::vector<Quadrant> tree{};
886  float minimumQuadrantSize = 0.0f;
887  float halfRootSize = 0.0f;
888  vec2 rootCenter{0.0f, 0.0f};
889  TreeIndex firstFreeIndex = 0;
890  mutable std::vector<IterationState> iterationStack{};
891 };
892 
893 } // namespace donut
894 
895 #endif
Quadtree-based space subdivision container, optimized for intersection queries between 2D axis-aligne...
Definition: LooseQuadtree.hpp:28
auto test(vec2 point, Callback &&callback) const
Execute a callback function for each element in the tree that might contain a given point.
Definition: LooseQuadtree.hpp:685
std::pair< iterator, bool > emplace(const Box< 2, float > &elementBoundingBox, Args &&... args)
Try to construct a new element in the tree.
Definition: LooseQuadtree.hpp:173
T value_type
Definition: LooseQuadtree.hpp:30
auto test(const Box< 2, float > &box, Callback &&callback) const
Execute a callback function for each element in the tree that might be intersecting with a given axis...
Definition: LooseQuadtree.hpp:745
constexpr auto traverseElementNodes(Callback &&callback, Predicate &&predicate) const
Execute a callback function for each active node of the tree that has an element.
Definition: LooseQuadtree.hpp:492
std::size_t size_type
Definition: LooseQuadtree.hpp:35
void erase(const_iterator pos) noexcept
Remove an element from the tree.
Definition: LooseQuadtree.hpp:335
std::pair< iterator, bool > insert(const Box< 2, float > &elementBoundingBox, T &&value)
Try to move an element into the tree.
Definition: LooseQuadtree.hpp:301
std::pair< iterator, bool > insert(const Box< 2, float > &elementBoundingBox, const T &value)
Try to copy an element into the tree.
Definition: LooseQuadtree.hpp:273
constexpr auto traverseActiveNodes(Callback &&callback) const
Execute a callback function for each active node of the tree, including empty branch nodes without an...
Definition: LooseQuadtree.hpp:440
T & reference
Definition: LooseQuadtree.hpp:31
Iterator< false > iterator
Definition: LooseQuadtree.hpp:94
bool test(vec2 point) const noexcept
Check if it is possible that some element in the tree contains a given point.
Definition: LooseQuadtree.hpp:707
constexpr auto traverseElements(Callback &&callback, Predicate &&predicate) const
Execute a callback function for each element in the tree.
Definition: LooseQuadtree.hpp:595
LooseQuadtree(const Box< 2, float > &worldBoundingBox, vec2 typicalBoxSize) noexcept
Construct an empty tree.
Definition: LooseQuadtree.hpp:108
typename std::iterator_traits< iterator >::difference_type difference_type
Definition: LooseQuadtree.hpp:96
Iterator< true > const_iterator
Definition: LooseQuadtree.hpp:95
constexpr auto traverseElementNodes(Callback &&callback) const
Execute a callback function for each active node of the tree that has an element.
Definition: LooseQuadtree.hpp:549
void clear() noexcept
Erase all inserted elements from the tree.
Definition: LooseQuadtree.hpp:143
bool test(const Box< 2, float > &box) const noexcept
Check if it is possible that some element in the tree is intersecting with a given axis-aligned box.
Definition: LooseQuadtree.hpp:768
constexpr auto traverseActiveNodes(Callback &&callback, Predicate &&predicate) const
Execute a callback function for each active node of the tree, including empty branch nodes without an...
Definition: LooseQuadtree.hpp:389
T * pointer
Definition: LooseQuadtree.hpp:33
T & operator[](const Box< 2, float > &elementBoundingBox)
Try to default-construct a new element in the tree and get a reference to it.
Definition: LooseQuadtree.hpp:324
constexpr auto traverseElements(Callback &&callback) const
Execute a callback function for each element in the tree.
Definition: LooseQuadtree.hpp:647
const T & const_reference
Definition: LooseQuadtree.hpp:32
const T * const_pointer
Definition: LooseQuadtree.hpp:34
void reset(const Box< 2, float > &worldBoundingBox, vec2 typicalBoxSize) noexcept
Reset the tree to an empty state with new world parameters.
Definition: LooseQuadtree.hpp:124
Definition: Application.hpp:9
constexpr bool operator==(Monostate, Monostate) noexcept
Compare two monostates for equality.
Definition: Variant.hpp:246
constexpr bool intersects(const Sphere< L, T > &a, const Sphere< L, T > &b) noexcept
Check if two spheres intersect.
Definition: shapes.hpp:243
Generic axis-aligned box shape with minimum and maximum extents.
Definition: shapes.hpp:110
constexpr bool contains(const Point< L, T > &point) const noexcept
Check if a given point is contained within the extents of this box.
Definition: shapes.hpp:603
Point< L, T > max
Position with the maximum coordinates of the box extents on each coordinate axis.
Definition: shapes.hpp:112
Point< L, T > min
Position with the minimum coordinates of the box extents on each coordinate axis.
Definition: shapes.hpp:111