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