sandbox
Loading...
Searching...
No Matches
octree.hpp
1// SPDX-License-Identifier: MIT
2#ifndef LIBSBX_CONTAINERS_OCTREE_HPP_
3#define LIBSBX_CONTAINERS_OCTREE_HPP_
4
5#include <range/v3/all.hpp>
6
7#include <libsbx/utility/enum.hpp>
8#include <libsbx/utility/iterator.hpp>
9
10#include <libsbx/memory/concepts.hpp>
11
12#include <libsbx/math/vector2.hpp>
13#include <libsbx/math/vector3.hpp>
14#include <libsbx/math/uuid.hpp>
15#include <libsbx/math/volume.hpp>
16#include <libsbx/math/box.hpp>
17
18#include <libsbx/containers/static_vector.hpp>
19
20namespace sbx::containers {
21
22template<typename Type, std::size_t Threshold = 16u, std::size_t Depth = 8u>
23class octree {
24
25 inline static constexpr auto threshold = Threshold;
26 inline static constexpr auto depth = Depth;
27
28 struct node {
29
30 using id = std::uint32_t;
31
32 inline static constexpr auto children_count = std::size_t{8u};
33
34 struct value_type {
35 Type value;
36 math::volume bounds;
37 }; // struct value_type
38
39 inline constexpr static auto null = static_cast<id>(-1);
40
41 node()
42 : values{utility::make_reserved_vector<value_type>(threshold)},
43 children{null, null, null, null, null, null, null, null} { }
44
45 auto is_leaf() const noexcept -> bool {
46 return children[0u] == null;
47 }
48
49 auto child_at(const std::size_t index) const noexcept -> id {
50 return children[index];
51 }
52
53 auto push_back(const value_type& value) noexcept -> void {
54 values.push_back(value);
55 }
56
57 auto push_back(value_type&& value) noexcept -> void {
58 values.push_back(std::move(value));
59 }
60
61 static auto find_volume(const math::volume& outer, const math::volume& inner) noexcept -> std::optional<std::uint32_t> {
62 auto center = outer.center();
63
64 if (!outer.contains(inner)) {
65 return std::nullopt;
66 }
67
68 if (inner.max().x() <= center.x() && inner.max().y() <= center.y() && inner.max().z() <= center.z()) {
69 return 0u;
70 }
71
72 if (inner.min().x() >= center.x() && inner.max().y() <= center.y() && inner.max().z() <= center.z()) {
73 return 1u;
74 }
75
76 if (inner.max().x() <= center.x() && inner.min().y() >= center.y() && inner.max().z() <= center.z()) {
77 return 2u;
78 }
79
80 if (inner.min().x() >= center.x() && inner.min().y() >= center.y() && inner.max().z() <= center.z()) {
81 return 3u;
82 }
83
84 if (inner.max().x() <= center.x() && inner.max().y() <= center.y() && inner.min().z() >= center.z()) {
85 return 4u;
86 }
87
88 if (inner.min().x() >= center.x() && inner.max().y() <= center.y() && inner.min().z() >= center.z()) {
89 return 5u;
90 }
91
92 if (inner.max().x() <= center.x() && inner.min().y() >= center.y() && inner.min().z() >= center.z()) {
93 return 6u;
94 }
95
96 if (inner.min().x() >= center.x() && inner.min().y() >= center.y() && inner.min().z() >= center.z()) {
97 return 7u;
98 }
99
100 return std::nullopt;
101 }
102
103 static auto child_bounds(const math::volume& outer, const std::uint32_t index) -> math::volume {
104 const auto min = outer.min();
105 const auto max = outer.max();
106 const auto center = (min + max) * 0.5f;
107
108 switch (index) {
109 case 0: return math::volume{min, center};
110 case 1: return math::volume{math::vector3{center.x(), min.y(), min.z()}, math::vector3{max.x(), center.y(), center.z()}};
111 case 2: return math::volume{math::vector3{min.x(), center.y(), min.z()}, math::vector3{center.x(), max.y(), center.z()}};
112 case 3: return math::volume{math::vector3{center.x(), center.y(), min.z()}, math::vector3{max.x(), max.y(), center.z()}};
113 case 4: return math::volume{math::vector3{min.x(), min.y(), center.z()}, math::vector3{center.x(), center.y(), max.z()}};
114 case 5: return math::volume{math::vector3{center.x(), min.y(), center.z()}, math::vector3{max.x(), center.y(), max.z()}};
115 case 6: return math::volume{math::vector3{min.x(), center.y(), center.z()}, math::vector3{center.x(), max.y(), max.z()}};
116 case 7: return math::volume{center, max};
117 }
118
119 throw std::runtime_error("Invalid index");
120 }
121
122 std::vector<value_type> values;
123 std::array<id, children_count> children;
124
125 }; // struct node
126
127public:
128
129 using value_type = Type;
130 using reference = value_type&;
131 using const_reference = const value_type&;
132
134 value_type first;
135 value_type second;
136 }; // struct intersection
137
139 value_type value;
140 const math::volume& bounds;
141 }; // struct inside_result
142
143 octree(const math::volume& bounds, const std::size_t estimated_elements) noexcept
144 : _bounds{bounds},
145 _root{0u} {
146 _nodes.reserve(estimated_elements / 2);
147 _nodes.push_back(node{});
148 }
149
150 ~octree() {
151
152 }
153
154 auto insert(const value_type& value, const math::volume& bounds) noexcept -> void {
155 _insert(_root, _bounds, value, bounds, 0u);
156 }
157
158 auto intersections() -> std::vector<intersection> {
159 auto intersections = utility::make_reserved_vector<intersection>(_nodes.size());
160
161 _intersections(_root, _bounds, intersections);
162
163 intersections.shrink_to_fit();
164
165 return intersections;
166 }
167
168 auto inside(const math::box& box) -> std::vector<inside_result> {
169 auto inside = std::vector<inside_result>{};
170
171 _inside(_root, _bounds, box, inside);
172
173 return inside;
174 }
175
176 auto clear() -> void {
177 _nodes.resize(1);
178
179 _nodes[0].values.clear();
180 _nodes[0].children.fill(node::null);
181 }
182
183 template<typename Fn>
184 requires (std::is_invocable_v<Fn, const math::volume&>)
185 auto for_each_volume(Fn&& fn) -> void {
186 _for_each_volume(_root, _bounds, std::forward<Fn>(fn));
187 }
188
189private:
190
191 auto _insert(const node::id node_id, const math::volume& bounds, const value_type& value, const math::volume& value_bounds, const std::size_t current_depth) noexcept -> void {
192 if (!bounds.contains(value_bounds)) {
193 return;
194 }
195
196 if (_nodes[node_id].is_leaf()) {
197 if (_nodes[node_id].values.size() < threshold || current_depth >= depth) {
198 _nodes[node_id].push_back({value, value_bounds});
199 } else {
200 _split(node_id, bounds);
201 _insert(node_id, bounds, value, value_bounds, current_depth);
202 }
203 } else {
204 const auto quadrant = node::find_volume(bounds, value_bounds);
205
206 if (quadrant) {
207 _insert(_nodes[node_id].child_at(*quadrant), node::child_bounds(bounds, *quadrant), value, value_bounds, current_depth + 1u);
208 } else {
209 _nodes[node_id].values.push_back({value, value_bounds});
210 }
211 }
212 }
213
214 auto _split(node::id node_id, const math::volume& bounds) -> void {
215 const auto first_child_id = static_cast<node::id>(_nodes.size());
216
217 _nodes.resize(_nodes.size() + node::children_count);
218
219 for (auto i = 0u; i < node::children_count; ++i) {
220 _nodes[node_id].children[i] = first_child_id + i;
221 }
222
223 auto current_node_values = std::move(_nodes[node_id].values);
224
225 _nodes[node_id].values.clear();
226
227 for (auto&& value : current_node_values) {
228 const auto quadrant = node::find_volume(bounds, value.bounds);
229
230 if (quadrant) {
231 _nodes[first_child_id + *quadrant].values.push_back(std::move(value));
232 } else {
233 _nodes[node_id].values.push_back(std::move(value));
234 }
235 }
236 }
237
238 auto _intersections(node::id node_id, const math::volume& bounds, std::vector<intersection>& intersections) -> void {
239 for (auto i : std::views::iota(0u, _nodes[node_id].values.size())) {
240 for (auto j : std::views::iota(0u, i)) {
241 if (_nodes[node_id].values[i].bounds.intersects(_nodes[node_id].values[j].bounds)) {
242 intersections.push_back({_nodes[node_id].values[i].value, _nodes[node_id].values[j].value});
243 }
244 }
245 }
246
247 if (!_nodes[node_id].is_leaf()) {
248 for (const auto& child_id : _nodes[node_id].children) {
249 for (const auto& [value, bounds] : _nodes[node_id].values) {
250 _intersections_with_descendants(child_id, value, bounds, intersections);
251 }
252 }
253
254 for (auto i : std::views::iota(0u, _nodes[node_id].children.size())) {
255 auto child_bounds = node::child_bounds(bounds, i);
256
257 _intersections(_nodes[node_id].child_at(i), child_bounds, intersections);
258 }
259 }
260 }
261
262 auto _intersections_with_descendants(node::id node_id, const value_type& value, const math::volume& value_bounds, std::vector<intersection>& intersections) -> void {
263 for (const auto& [node_value, bound] : _nodes[node_id].values) {
264 if (bound.intersects(value_bounds)) {
265 intersections.push_back({value, node_value});
266 }
267 }
268
269 if (!_nodes[node_id].is_leaf()) {
270 for (const auto& child : _nodes[node_id].children) {
271 _intersections_with_descendants(child, value, value_bounds, intersections);
272 }
273 }
274 }
275
276 template<typename Fn>
277 auto _for_each_volume(node::id node_id, const math::volume& bounds, Fn&& fn) -> void {
278 std::invoke(fn, bounds);
279
280 if (!_nodes[node_id].is_leaf()) {
281 for (auto i = 0u; i < _nodes[node_id].children.size(); ++i) {
282 const auto child_id = _nodes[node_id].child_at(i);
283 const auto child_volume = node::child_bounds(bounds, i);
284
285 _for_each_volume(child_id, child_volume, std::forward<Fn>(fn));
286 }
287 }
288 }
289
290 auto _inside(node::id node_id, const math::volume& bounds, const math::box& box, std::vector<inside_result>& inside) -> void {
291 // Early-out if node's bounding volume doesn't intersect the box
292 if (!box.intersects(bounds)) {
293 return;
294 }
295
296 // Check all values in this node
297 for (const auto& [value, value_bounds] : _nodes[node_id].values) {
298 if (box.intersects(value_bounds)) {
299 inside.push_back({value, value_bounds});
300 }
301 }
302
303 // Recurse into children if not a leaf
304 if (!_nodes[node_id].is_leaf()) {
305 for (auto i : std::views::iota(0u, _nodes[node_id].children.size())) {
306 auto child_id = _nodes[node_id].child_at(i);
307 auto child_bounds = node::child_bounds(bounds, i);
308
309 _inside(child_id, child_bounds, box, inside);
310 }
311 }
312 }
313
314 math::volume _bounds;
315 node::id _root;
316 std::vector<node> _nodes;
317
318}; // class octree
319
320} // namespace sbx::containers
321
322#endif // LIBSBX_CONTAINERS_OCTREE_HPP_
Box / frustum-style volume intersection helpers.
boxf box
Default box alias.
Definition: box.hpp:131
Definition: tests.cpp:6
Definition: octree.hpp:23
auto intersects(const volume_type &volume) const -> bool
Tests whether this box intersects a volume.
Definition: box.ipp:15
Definition: volume.hpp:14
Definition: octree.hpp:138
Definition: octree.hpp:133