neoGFX
Cross-platform C++ app/game engine
Loading...
Searching...
No Matches
aabb_quadtree.hpp
Go to the documentation of this file.
1// aabb_quadree.hpp
2/*
3 neogfx C++ App/Game Engine
4 Copyright (c) 2015, 2020 Leigh Johnston. All Rights Reserved.
5
6 This program is free software: you can redistribute it and / or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#pragma once
21
22#include <neogfx/neogfx.hpp>
23#include <boost/pool/pool_alloc.hpp>
27#include <neogfx/game/i_ecs.hpp>
29
30namespace neogfx::game
31{
32 template <typename Collider, std::size_t BucketSize = 16, typename Allocator = boost::fast_pool_allocator<Collider>>
34 {
35 public:
36 typedef Collider collider_type;
37 typedef Allocator allocator_type;
38 typedef typename allocator_type::pointer pointer;
39 typedef typename allocator_type::const_pointer const_pointer;
40 typedef typename allocator_type::reference reference;
41 typedef typename allocator_type::const_reference const_reference;
42 public:
43 typedef const void* const_iterator; // todo
44 typedef void* iterator; // todo
45 private:
46 class node : public neolib::lifetime<>
47 {
48 private:
49 typedef neolib::vecarray<entity_id, BucketSize, -1> entity_list;
50 typedef std::array<std::array<aabb_2d, 2>, 2> quadrants;
51 typedef std::array<std::array<node*, 2>, 2> children;
52 private:
53 struct no_parent : std::logic_error { no_parent() : std::logic_error{ "neogfx::aabb_quadtree::node::no_parent" } {} };
54 struct no_children : std::logic_error { no_children() : std::logic_error{ "neogfx::aabb_quadtree::node::no_children" } {} };
55 public:
56 node(aabb_quadtree& aTree, const aabb_2d& aAabb) : iTree{ aTree }, iParent{ nullptr }, iDepth{ 1 }, iAabb { aAabb }, iChildren{}
57 {
58 iTree.iDepth = std::max(iTree.iDepth, iDepth);
59 populate_quadrants();
60 }
61 node(const node& aParent, const aabb_2d& aAabb) : iTree{ aParent.iTree }, iParent{ &aParent }, iDepth{ aParent.iDepth + 1 }, iAabb { aAabb }, iChildren{}
62 {
63 iTree.iDepth = std::max(iTree.iDepth, iDepth);
64 populate_quadrants();
65 }
66 ~node()
67 {
69 if (has_child<0, 0>())
70 remove_child<0, 0>();
71 if (has_child<0, 1>())
72 remove_child<0, 1>();
73 if (has_child<1, 0>())
74 remove_child<1, 0>();
75 if (has_child<1, 1>())
76 remove_child<1, 1>();
77 if (has_parent())
78 parent().unsplit(this);
79 }
80 public:
81 bool has_parent() const
82 {
83 return iParent != nullptr;
84 }
85 const node& parent() const
86 {
87 if (has_parent())
88 return *iParent;
89 throw no_parent();
90 }
91 node& parent()
92 {
93 return const_cast<node&>(to_const(*this).parent());
94 }
95 uint32_t depth() const
96 {
97 return iDepth;
98 }
99 const aabb_2d& aabb() const
100 {
101 return iAabb;
102 }
103 void add_entity(entity_id aEntity, const collider_type& aCollider)
104 {
105 iTree.iDepth = std::max(iTree.iDepth, iDepth);
106 if (is_split())
107 {
108 if (aabb_intersects(iQuadrants[0][0], aCollider.currentAabb))
109 child<0, 0>().add_entity(aEntity, aCollider);
110 if (aabb_intersects(iQuadrants[0][1], aCollider.currentAabb))
111 child<0, 1>().add_entity(aEntity, aCollider);
112 if (aabb_intersects(iQuadrants[1][0], aCollider.currentAabb))
113 child<1, 0>().add_entity(aEntity, aCollider);
114 if (aabb_intersects(iQuadrants[1][1], aCollider.currentAabb))
115 child<1, 1>().add_entity(aEntity, aCollider);
116 }
117 else
118 {
119 iEntities.push_back(aEntity);
120 if (iEntities.size() > BucketSize && (iAabb.max - iAabb.min).min() > iTree.minimum_quadrant_size())
121 split();
122 }
123 }
124 void remove_entity(entity_id aEntity, const collider_type& aCollider)
125 {
126 if (aCollider.previousAabb && aCollider.currentAabb)
127 remove_entity(aEntity, aCollider, aabb_union(*aCollider.previousAabb, *aCollider.currentAabb));
128 }
129 void remove_entity(entity_id aEntity, const collider_type& aCollider, const aabb_2d& aAabb)
130 {
131 if (!aabb_intersects(aCollider.currentAabb, iAabb))
132 {
133 auto existing = std::find(iEntities.begin(), iEntities.end(), aEntity);
134 if (existing != iEntities.end())
135 iEntities.erase(existing);
136 }
137 if (has_child<0, 0>() && aabb_intersects(iQuadrants[0][0], aAabb))
138 child<0, 0>().remove_entity(aEntity, aCollider, aAabb);
139 if (has_child<0, 1>() && aabb_intersects(iQuadrants[0][1], aAabb))
140 child<0, 1>().remove_entity(aEntity, aCollider, aAabb);
141 if (has_child<1, 0>() && aabb_intersects(iQuadrants[1][0], aAabb))
142 child<1, 0>().remove_entity(aEntity, aCollider, aAabb);
143 if (has_child<1, 1>() && aabb_intersects(iQuadrants[1][1], aAabb))
144 child<1, 1>().remove_entity(aEntity, aCollider, aAabb);
145 if (empty())
146 iTree.destroy_node(*this);
147 }
148 void update_entity(entity_id aEntity, const collider_type& aCollider)
149 {
150 iTree.iDepth = std::max(iTree.iDepth, iDepth);
151 auto const& currentAabb = aCollider.currentAabb;
152 auto const& previousAabb = aCollider.previousAabb;
153 if (currentAabb == previousAabb)
154 return;
155 if (aabb_intersects(currentAabb, iAabb))
156 add_entity(aEntity, aCollider);
157 if (aabb_intersects(previousAabb, iAabb))
158 remove_entity(aEntity, aCollider, previousAabb);
159 }
160 bool empty() const
161 {
162 bool result = iEntities.empty();
163 if (has_child<0, 0>())
164 result = child<0, 0>().empty() && result;
165 if (has_child<0, 1>())
166 result = child<0, 1>().empty() && result;
167 if (has_child<1, 0>())
168 result = child<1, 0>().empty() && result;
169 if (has_child<1, 1>())
170 result = child<1, 1>().empty() && result;
171 return result;
172 }
173 const entity_list& entities() const
174 {
175 return iEntities;
176 }
177 template <typename Visitor>
178 void visit(const collider_type& aCandidate, const Visitor& aVisitor) const
179 {
180 if (aCandidate.currentAabb)
181 visit(*aCandidate.currentAabb, aVisitor);
182 }
183 template <typename Visitor>
184 void visit(const vec2& aPoint, const Visitor& aVisitor) const
185 {
186 visit(aabb_2d{ aPoint, aPoint }, aVisitor);
187 }
188 template <typename Visitor>
189 void visit(const aabb_2d& aAabb, const Visitor& aVisitor) const
190 {
191 for (auto e : entities())
192 if (aabb_intersects(aAabb, iTree.iEcs.component<collider_type>().entity_record(e).currentAabb))
193 aVisitor(e);
194 if (has_child<0, 0>() && aabb_intersects(iQuadrants[0][0], aAabb))
195 child<0, 0>().visit(aAabb, aVisitor);
196 if (has_child<0, 1>() && aabb_intersects(iQuadrants[0][1], aAabb))
197 child<0, 1>().visit(aAabb, aVisitor);
198 if (has_child<1, 0>() && aabb_intersects(iQuadrants[1][0], aAabb))
199 child<1, 0>().visit(aAabb, aVisitor);
200 if (has_child<1, 1>() && aabb_intersects(iQuadrants[1][1], aAabb))
201 child<1, 1>().visit(aAabb, aVisitor);
202 }
203 template <typename Visitor>
204 void visit_entities(const Visitor& aVisitor) const
205 {
206 for (auto e : entities())
207 aVisitor(e);
208 if (has_child<0, 0>())
209 child<0, 0>().visit_entities(aVisitor);
210 if (has_child<0, 1>())
211 child<0, 1>().visit_entities(aVisitor);
212 if (has_child<1, 0>())
213 child<1, 0>().visit_entities(aVisitor);
214 if (has_child<1, 1>())
215 child<1, 1>().visit_entities(aVisitor);
216 }
217 template <typename Visitor>
218 void visit_aabbs(const Visitor& aVisitor) const
219 {
220 aVisitor(aabb());
221 if (has_child<0, 0>())
222 child<0, 0>().visit_aabbs(aVisitor);
223 if (has_child<0, 1>())
224 child<0, 1>().visit_aabbs(aVisitor);
225 if (has_child<1, 0>())
226 child<1, 0>().visit_aabbs(aVisitor);
227 if (has_child<1, 1>())
228 child<1, 1>().visit_aabbs(aVisitor);
229 }
230 private:
231 void populate_quadrants()
232 {
233 auto const& min = iAabb.min;
234 auto const& max = iAabb.max;
235 auto const& center = (min + max) / 2.0;
236 iQuadrants[0][0] = aabb_2d{ min, center };
237 iQuadrants[0][1] = aabb_2d{ vec2{ min.x, center.y }, vec2{ center.x, max.y } };
238 iQuadrants[1][0] = aabb_2d{ vec2{ center.x, min.y }, vec2{ max.x, center.y } };
239 iQuadrants[1][1] = aabb_2d{ center, max };
240 }
241 template <std::size_t X, std::size_t Y>
242 node& child() const
243 {
244 if (iChildren == std::nullopt)
245 iChildren.emplace();
246 if ((*iChildren)[X][Y] == nullptr)
247 (*iChildren)[X][Y] = iTree.create_node(*this, iQuadrants[X][Y]);
248 return *(*iChildren)[X][Y];
249 }
250 template <std::size_t X, std::size_t Y>
251 bool has_child() const
252 {
253 if (iChildren == std::nullopt)
254 return false;
255 return (*iChildren)[X][Y] != nullptr;
256 }
257 template <std::size_t X, std::size_t Y>
258 bool remove_child(node* aDestroyedNode = nullptr) const
259 {
260 if (iChildren == std::nullopt)
261 return true;
262 if ((*iChildren)[X][Y] == nullptr)
263 return true;
264 if ((*iChildren)[X][Y] == aDestroyedNode || aDestroyedNode == nullptr)
265 {
266 auto n = (*iChildren)[X][Y];
267 (*iChildren)[X][Y] = nullptr;
268 iTree.destroy_node(*n);
269 return true;
270 }
271 return false;
272 }
273 bool is_split() const
274 {
275 return iChildren != std::nullopt;
276 }
277 void split()
278 {
279 for (auto e : entities())
280 {
281 auto const& collider = iTree.iEcs.component<collider_type>().entity_record(e);
282 if (aabb_intersects(iQuadrants[0][0], collider.currentAabb))
283 child<0, 0>().add_entity(e, collider);
284 if (aabb_intersects(iQuadrants[0][1], collider.currentAabb))
285 child<0, 1>().add_entity(e, collider);
286 if (aabb_intersects(iQuadrants[1][0], collider.currentAabb))
287 child<1, 0>().add_entity(e, collider);
288 if (aabb_intersects(iQuadrants[1][1], collider.currentAabb))
289 child<1, 1>().add_entity(e, collider);
290 }
291 iEntities.clear();
292 }
293 void unsplit(node* aDestroyedNode)
294 {
295 bool haveChildren = false;
296 if (!remove_child<0, 0>(aDestroyedNode))
297 haveChildren = true;
298 if (!remove_child<0, 1>(aDestroyedNode))
299 haveChildren = true;
300 if (!remove_child<1, 0>(aDestroyedNode))
301 haveChildren = true;
302 if (!remove_child<1, 1>(aDestroyedNode))
303 haveChildren = true;
304 if (!haveChildren)
305 iChildren = std::nullopt;
306 if (empty())
307 iTree.destroy_node(*this);
308 }
309 private:
310 aabb_quadtree& iTree;
311 const node* iParent;
312 uint32_t iDepth;
313 aabb_2d iAabb;
314 quadrants iQuadrants;
315 entity_list iEntities;
316 mutable std::optional<children> iChildren;
317 };
318 typedef typename allocator_type::template rebind<node>::other node_allocator;
319 public:
320 aabb_quadtree(i_ecs& aEcs, const aabb_2d& aRootAabb = aabb_2d{ vec2{-4096.0, -4096.0}, vec2{4096.0, 4096.0} }, scalar aMinimumQuadrantSize = 16.0, const allocator_type& aAllocator = allocator_type{}) :
321 iAllocator{ aAllocator },
322 iEcs{ aEcs },
323 iRootAabb{ aRootAabb },
324 iCount{ 0 },
325 iDepth{ 0 },
326 iRootNode{ *this, aRootAabb },
327 iMinimumQuadrantSize{ aMinimumQuadrantSize },
328 iCollisionUpdateId{ 0 }
329 {
330 }
331 public:
333 {
334 return iMinimumQuadrantSize;
335 }
337 {
338 iDepth = 0;
339 iRootNode.~node();
340 new(&iRootNode) node{ *this, iRootAabb };
341 for (auto entity : iEcs.component<collider_type>().entities())
342 {
343 auto& collider = iEcs.component<collider_type>().entity_record(entity);
344 iRootNode.add_entity(entity, collider);
345 }
346 }
348 {
349 iDepth = 0;
350 for (auto entity : iEcs.component<collider_type>().entities())
351 {
352 auto& collider = iEcs.component<collider_type>().entity_record(entity);
353 iRootNode.update_entity(entity, collider);
354 }
355 }
356 template <typename CollisionAction>
357 void collisions(CollisionAction aCollisionAction) const
358 {
359 for (auto candidate : iEcs.component<collider_type>().entities())
360 {
361 auto const& candidateInfo = iEcs.component<entity_info>().entity_record(candidate);
362 if (candidateInfo.destroyed)
363 continue;
364 auto& candidateCollider = iEcs.component<collider_type>().entity_record(candidate);
365 if (++iCollisionUpdateId == 0)
366 iCollisionUpdateId = 1;
367 iRootNode.visit(candidateCollider, [&](entity_id aHit)
368 {
369 if (candidateInfo.destroyed)
370 return;
371 if (candidate < aHit)
372 {
373 auto const& hitInfo = iEcs.component<entity_info>().entity_record(aHit);
374 if (hitInfo.destroyed)
375 return;
376 auto& hitCollider = iEcs.component<collider_type>().entity_record(aHit);
377 if ((candidateCollider.mask & hitCollider.mask) == 0 && hitCollider.collisionEventId != iCollisionUpdateId)
378 {
379 hitCollider.collisionEventId = iCollisionUpdateId;
380 aCollisionAction(candidate, aHit);
381 }
382 }
383 });
384 }
385 }
386 template <typename ResultContainer>
387 void pick(const vec2& aPoint, ResultContainer& aResult, std::function<bool(entity_id aMatch, const vec2& aPoint)> aColliderPredicate = [](entity_id, const vec2&) { return true; }) const
388 {
389 iRootNode.visit(aPoint, [&](entity_id aMatch)
390 {
391 auto const& matchInfo = iEcs.component<entity_info>().entity_record(aMatch);
392 if (!matchInfo.destroyed && aColliderPredicate(aMatch, aPoint))
393 aResult.insert(aResult.end(), aMatch);
394 });
395 }
396 template <typename Visitor>
397 void visit_aabbs(const Visitor& aVisitor) const
398 {
399 iRootNode.visit_aabbs(aVisitor);
400 }
401 public:
402 void insert(reference aItem)
403 {
404 iRootNode.add_entity(aItem);
405 }
406 void remove(reference aItem)
407 {
408 iRootNode.remove_entity(aItem);
409 }
410 public:
411 uint32_t count() const
412 {
413 return iCount;
414 }
415 uint32_t depth() const
416 {
417 return iDepth;
418 }
419 public:
420 const node& root_node() const
421 {
422 return iRootNode;
423 }
424 private:
425 node* create_node(const node& aParent, const aabb_2d& aAabb)
426 {
427 ++iCount;
428 node* newNode = iAllocator.allocate(1);
429 iAllocator.construct(newNode, aParent, aAabb);
430 return newNode;
431 }
432 void destroy_node(node& aNode)
433 {
434 if (&aNode != &iRootNode && aNode.is_alive())
435 {
436 --iCount;
437 iAllocator.destroy(&aNode);
438 iAllocator.deallocate(&aNode, 1);
439 }
440 }
441 private:
442 node_allocator iAllocator;
443 i_ecs& iEcs;
444 aabb_2d iRootAabb;
445 scalar iMinimumQuadrantSize;
446 uint32_t iCount;
447 mutable uint32_t iDepth;
448 node iRootNode;
449 mutable uint32_t iCollisionUpdateId;
450 };
451}
void pick(const vec2 &aPoint, ResultContainer &aResult, std::function< bool(entity_id aMatch, const vec2 &aPoint)> aColliderPredicate=[](entity_id, const vec2 &) { return true;}) const
allocator_type::const_reference const_reference
void collisions(CollisionAction aCollisionAction) const
allocator_type::pointer pointer
void remove(reference aItem)
aabb_quadtree(i_ecs &aEcs, const aabb_2d &aRootAabb=aabb_2d{ vec2{-4096.0, -4096.0}, vec2{4096.0, 4096.0} }, scalar aMinimumQuadrantSize=16.0, const allocator_type &aAllocator=allocator_type{})
allocator_type::const_pointer const_pointer
void visit_aabbs(const Visitor &aVisitor) const
void insert(reference aItem)
allocator_type::reference reference
const node & root_node() const
virtual const i_component & component(component_id aComponentId) const =0
void set_destroying() override
Definition lifetime.hpp:165
id_t entity_id
Definition ecs_ids.hpp:51
aabb aabb_union(const aabb &left, const aabb &right)
bool aabb_intersects(const aabb &first, const aabb &second)
double scalar
Definition numerical.hpp:63