Luanti 5.18.0-dev
Loading...
Searching...
No Matches
k_d_tree.h
Go to the documentation of this file.
1// Copyright (C) 2024 Lars Müller
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#pragma once
5
6#include <algorithm>
7#include <cassert>
8#include <cstdint>
9#include <unordered_map>
10#include <vector>
11#include <memory>
12#include "debug.h"
13
14/*
15This implements a dynamic forest of static k-d-trees.
16
17A k-d-tree is a k-dimensional binary search tree.
18On the i-th level of the tree, you split by the (i mod k)-th coordinate.
19
20Building a balanced k-d-tree for n points is done in O(n log n) time:
21Points are stored in a matrix, identified by indices.
22These indices are presorted by all k axes.
23To split, you simply pick the pivot index in the appropriate index array,
24and mark all points left to it by index in a bitset.
25This lets you then split the indices sorted by the other axes,
26while preserving the sorted order.
27
28This however only gives us a static spatial index.
29To make it dynamic, we keep a "forest" of k-d-trees of sizes of successive powers of two.
30When we insert a new tree, we check whether there already is a k-d-tree of the same size.
31If this is the case, we merge with that tree, giving us a tree of twice the size,
32and so on, until we find a free size.
33
34This means our "forest" corresponds to a bit pattern,
35where a set bit means a non-empty tree.
36Inserting a point is equivalent to incrementing this bit pattern.
37
38To handle deletions, we simply mark the appropriate point as deleted using another bitset.
39When more than half the points have been deleted,
40we shrink the structure by removing all deleted points.
41This is equivalent to shifting down the "bit pattern" by one.
42
43There are plenty variations that could be explored:
44
45* Keeping a small amount of points in a small pool to make updates faster -
46 avoid building and querying small k-d-trees.
47 This might be useful if the overhead for small sizes hurts performance.
48* Keeping fewer trees to make queries faster, at the expense of updates.
49* More eagerly removing entries marked as deleted (for example, on merge).
50* Replacing the array-backed structure with a structure of dynamically allocated nodes.
51 This would make it possible to "let trees get out of shape".
52* Shrinking the structure currently sorts the live points by all axes,
53 not leveraging the existing presorting of the subsets.
54 Cleverly done filtering followed by sorted merges should enable linear time.
55* A special ray proximity query could be implemented. This is tricky however.
56*/
57
58namespace k_d_tree
59{
60
61using Idx = uint16_t;
62
63// We use size_t for sizes (but not for indices)
64// to make sure there are no wraparounds when we approach the limit.
65// This hardly affects performance or memory usage;
66// the core arrays still only store indices.
67
68template<uint8_t Dim, typename Component>
69class Points
70{
71public:
72 using Point = std::array<Component, Dim>;
74 Points() : n(0), coords(nullptr) {}
76 Points(size_t n) : n(n), coords(new Component[Dim * n]) {}
78 Points(size_t n, const std::array<Component const *, Dim> &coords)
79 : Points(n)
80 {
81 for (uint8_t d = 0; d < Dim; ++d)
82 std::copy(coords[d], coords[d] + n, begin(d));
83 }
84
85 size_t size() const { return n; }
86
87 void assign(Idx start, const Points &from)
88 {
89 for (uint8_t d = 0; d < Dim; ++d)
90 std::copy(from.begin(d), from.end(d), begin(d) + start);
91 }
92
94 {
95 Point point;
96 for (uint8_t d = 0; d < Dim; ++d)
97 point[d] = begin(d)[i];
98 return point;
99 }
100
101 void setPoint(Idx i, const Point &point)
102 {
103 for (uint8_t d = 0; d < Dim; ++d)
104 begin(d)[i] = point[d];
105 }
106
107 Component *begin(uint8_t d) { return coords.get() + d * n; }
108 Component *end(uint8_t d) { return begin(d) + n; }
109 const Component *begin(uint8_t d) const { return coords.get() + d * n; }
110 const Component *end(uint8_t d) const { return begin(d) + n; }
111
112private:
113 size_t n;
114 std::unique_ptr<Component[]> coords;
115};
116
117template<uint8_t Dim>
119{
120public:
123
126 {
128 }
129
132 : indices(n)
133 {
134 for (uint8_t d = 0; d < Dim; ++d) {
135 for (Idx i = 0; i < n; ++i) {
136 indices.begin(d)[i] = i;
137 }
138 }
139 }
140
141 size_t size() const { return indices.size(); }
142 bool empty() const { return size() == 0; }
143
148
151 SplitResult split(uint8_t axis, std::vector<bool> &markers) const
152 {
153 const auto begin = indices.begin(axis);
154 Idx left_n = indices.size() / 2;
155 const auto mid = begin + left_n;
156
157 // Mark all points to be partitioned left
158 for (auto it = begin; it != mid; ++it)
159 markers[*it] = true;
160
161 SortedIndices left(left_n);
162 std::copy(begin, mid, left.indices.begin(axis));
163 SortedIndices right(indices.size() - left_n - 1);
164 std::copy(mid + 1, indices.end(axis), right.indices.begin(axis));
165
166 for (uint8_t d = 0; d < Dim; ++d) {
167 if (d == axis)
168 continue;
169 auto left_ptr = left.indices.begin(d);
170 auto right_ptr = right.indices.begin(d);
171 for (auto it = indices.begin(d); it != indices.end(d); ++it) {
172 if (*it != *mid) { // ignore pivot
173 if (markers[*it])
174 *(left_ptr++) = *it;
175 else
176 *(right_ptr++) = *it;
177 }
178 }
179 }
180
181 // Unmark points, since we want to reuse the storage for markers
182 for (auto it = begin; it != mid; ++it)
183 markers[*it] = false;
184
185 return SplitResult{std::move(left), std::move(right), *mid};
186 }
187
188 Idx *begin(uint8_t d) { return indices.begin(d); }
189 Idx *end(uint8_t d) { return indices.end(d); }
190 const Idx *begin(uint8_t d) const { return indices.begin(d); }
191 const Idx *end(uint8_t d) const { return indices.end(d); }
192private:
195};
196
197template<uint8_t Dim, class Component>
199{
200public:
202
204 SortedPoints(const std::array<Component, Dim> &point)
205 : points(1), indices(1)
206 {
207 points.setPoint(0, point);
208 }
209
211 SortedPoints(size_t n, const std::array<Component const *, Dim> ptrs)
212 : points(n, ptrs), indices(n)
213 {
214 for (uint8_t d = 0; d < Dim; ++d) {
215 const auto coord = points.begin(d);
216 std::sort(indices.begin(d), indices.end(d), [&](auto i, auto j) {
217 return coord[i] < coord[j];
218 });
219 }
220 }
221
224 : points(a.size() + b.size())
225 {
226 const auto n = points.size();
228 for (uint8_t d = 0; d < Dim; ++d) {
229 points.assign(0, a.points);
230 points.assign(a.points.size(), b.points);
231 const auto coord = points.begin(d);
232 auto a_ptr = a.indices.begin(d);
233 auto b_ptr = b.indices.begin(d);
234 auto dst_ptr = indices.begin(d);
235 while (a_ptr != a.indices.end(d) && b_ptr != b.indices.end(d)) {
236 const auto i = *a_ptr;
237 const auto j = *b_ptr + a.size();
238 if (coord[i] <= coord[j]) {
239 *(dst_ptr++) = i;
240 ++a_ptr;
241 } else {
242 *(dst_ptr++) = j;
243 ++b_ptr;
244 }
245 }
246 while (a_ptr != a.indices.end(d))
247 *(dst_ptr++) = *(a_ptr++);
248 while (b_ptr != b.indices.end(d))
249 *(dst_ptr++) = a.size() + *(b_ptr++);
250 }
251 }
252
253 size_t size() const
254 {
255 // technically redundant with indices.size(),
256 // but that is irrelevant
257 return points.size();
258 }
259
262};
263
264template<uint8_t Dim, class Component, class Id>
266{
267public:
268 using Point = std::array<Component, Dim>;
269
272 : items()
273 , ids(nullptr)
274 , tree(nullptr)
275 , deleted()
276 {}
277
279 KdTree(const Point &point, const Id &id)
280 : items(point)
281 , ids(std::make_unique<Id[]>(1))
282 , tree(std::make_unique<Idx[]>(1))
283 , deleted(1)
284 {
285 tree[0] = 0;
286 ids[0] = id;
287 }
288
290 KdTree(size_t n, Id const *ids, std::array<Component const *, Dim> pts)
291 : items(n, pts)
292 , ids(std::make_unique<Id[]>(n))
293 , tree(std::make_unique<Idx[]>(n))
294 , deleted(n)
295 {
296 std::copy(ids, ids + n, this->ids.get());
297 init(0, 0, items.indices);
298 }
299
301 KdTree(const KdTree &a, const KdTree &b)
302 : items(a.items, b.items)
303 {
304 tree = std::make_unique<Idx[]>(cap());
305 ids = std::make_unique<Id[]>(cap());
306 std::copy(a.ids.get(), a.ids.get() + a.cap(), ids.get());
307 std::copy(b.ids.get(), b.ids.get() + b.cap(), ids.get() + a.cap());
308 // Note: Initialize `deleted` *before* calling `init`,
309 // since `init` abuses the `deleted` marks as left/right marks.
310 deleted = std::vector<bool>(cap());
311 init(0, 0, items.indices);
312 std::copy(a.deleted.begin(), a.deleted.end(), deleted.begin());
313 std::copy(b.deleted.begin(), b.deleted.end(), deleted.begin() + a.items.size());
314 }
315
316 template<typename F>
317 void rangeQuery(const Point &min, const Point &max,
318 const F &cb) const
319 {
320 rangeQuery(0, 0, min, max, cb);
321 }
322
323 void remove(Idx internalIdx)
324 {
325 assert(!deleted[internalIdx]);
326 deleted[internalIdx] = true;
327 }
328
329 template<class F>
330 void foreach(F cb) const
331 {
332 for (Idx i = 0; i < cap(); ++i) {
333 if (!deleted[i]) {
334 cb(i, items.points.getPoint(i), ids[i]);
335 }
336 }
337 }
338
340 size_t cap() const { return items.size(); }
341
342private:
343 void init(Idx root, uint8_t axis, const SortedIndices<Dim> &sorted)
344 {
345 // Temporarily abuse "deleted" marks as left/right marks
346 const auto split = sorted.split(axis, deleted);
347 tree[root] = split.pivot;
348 const auto next_axis = (axis + 1) % Dim;
349 if (!split.left.empty())
350 init(2 * root + 1, next_axis, split.left);
351 if (!split.right.empty())
352 init(2 * root + 2, next_axis, split.right);
353 }
354
355 template<typename F>
356 // Note: root is of type size_t to avoid issues with wraparound
357 void rangeQuery(size_t root, uint8_t split,
358 const Point &min, const Point &max,
359 const F &cb) const
360 {
361 if (root >= cap())
362 return;
363 const auto ptid = tree[root];
364 const auto coord = items.points.begin(split)[ptid];
365 const auto leftChild = 2*root + 1;
366 const auto rightChild = 2*root + 2;
367 const auto nextSplit = (split + 1) % Dim;
368 if (min[split] > coord) {
369 rangeQuery(rightChild, nextSplit, min, max, cb);
370 } else if (max[split] < coord) {
371 rangeQuery(leftChild, nextSplit, min, max, cb);
372 } else {
373 rangeQuery(rightChild, nextSplit, min, max, cb);
374 rangeQuery(leftChild, nextSplit, min, max, cb);
375 if (deleted[ptid])
376 return;
377 const auto point = items.points.getPoint(ptid);
378 for (uint8_t d = 0; d < Dim; ++d)
379 if (point[d] < min[d] || point[d] > max[d])
380 return;
381 cb(point, ids[ptid]);
382 }
383 }
385 std::unique_ptr<Id[]> ids;
386 std::unique_ptr<Idx[]> tree;
387 std::vector<bool> deleted;
388};
389
390template<uint8_t Dim, class Component, class Id>
392{
394
395public:
396 using Point = typename Tree::Point;
397
398 void insert(const std::array<Component, Dim> &point, Id id)
399 {
400 Tree tree(point, id);
401 for (uint8_t tree_idx = 0;; ++tree_idx) {
402 if (tree_idx == trees.size()) {
403 trees.push_back(std::move(tree));
404 updateDelEntries(tree_idx);
405 break;
406 }
407 // Can we use a free slot to "plant" the tree?
408 if (trees[tree_idx].cap() == 0) {
409 trees[tree_idx] = std::move(tree);
410 updateDelEntries(tree_idx);
411 break;
412 }
413 tree = Tree(tree, trees[tree_idx]);
414 trees[tree_idx] = std::move(Tree());
415 }
416 ++n_entries;
417 }
418
419 void remove(Id id)
420 {
421 {
422 auto node = del_entries.extract(id);
423 sanity_check(node);
424 const auto &entry = node.mapped();
425 trees.at(entry.tree_idx).remove(entry.in_tree);
426 }
427 ++deleted;
428 if (deleted >= (n_entries+1)/2) // "shift out" the last tree
430 }
431
432 void update(const Point &newPos, Id id)
433 {
434 remove(id);
435 insert(newPos, id);
436 }
437
438 template<typename F>
439 void rangeQuery(const Point &min, const Point &max,
440 const F &cb) const
441 {
442 for (const auto &tree : trees)
443 tree.rangeQuery(min, max, cb);
444 }
445
446 size_t size() const
447 {
448 return n_entries - deleted;
449 }
450
451private:
452
453 void updateDelEntries(uint8_t tree_idx)
454 {
455 trees[tree_idx].foreach([&](Idx in_tree_idx, auto _, Id id) {
456 del_entries[id] = {tree_idx, in_tree_idx};
457 });
458 }
459
460 // Shrink to half the size, equivalent to shifting down the "bit pattern".
462 {
463 assert(n_entries >= deleted);
464 assert(n_entries - deleted == (n_entries >> 1));
466 deleted = 0;
467 // Reset map, freeing memory (instead of clearing)
468 del_entries = std::unordered_map<Id, DelEntry>();
469
470 // Collect all live points and corresponding IDs.
471 const auto live_ids = std::make_unique<Id[]>(n_entries);
473 size_t i = 0;
474 for (const auto &tree : trees) {
475 tree.foreach([&](Idx _, auto point, Id id) {
476 assert(i < n_entries);
477 live_points.setPoint(static_cast<Idx>(i), point);
478 live_ids[i] = id;
479 ++i;
480 });
481 }
482 assert(i == n_entries);
483
484 // Construct a new forest.
485 // The "tree pattern" will effectively just be shifted down by one.
486 auto id_ptr = live_ids.get();
487 std::array<Component const *, Dim> point_ptrs;
488 size_t n = 1;
489 for (uint8_t d = 0; d < Dim; ++d)
490 point_ptrs[d] = live_points.begin(d);
491 for (uint8_t tree_idx = 0; tree_idx < trees.size() - 1; ++tree_idx, n *= 2) {
492 Tree tree;
493 // If there was a tree at the next position, there should be
494 // a tree at this position after shifting the pattern.
495 if (trees[tree_idx+1].cap() > 0) {
496 tree = std::move(Tree(n, id_ptr, point_ptrs));
497 id_ptr += n;
498 for (uint8_t d = 0; d < Dim; ++d)
499 point_ptrs[d] += n;
500 }
501 trees[tree_idx] = std::move(tree);
502 updateDelEntries(tree_idx);
503 }
504 trees.pop_back(); // "shift out" tree with the most elements
505 }
506 // This could even use an array instead of a vector,
507 // since the number of trees is guaranteed to be logarithmic in the max of Idx
508 std::vector<Tree> trees;
509 struct DelEntry {
510 uint8_t tree_idx;
512 };
513 std::unordered_map<Id, DelEntry> del_entries;
514 size_t n_entries = 0;
515 size_t deleted = 0;
516};
517
518} // end namespace k_d_tree
Definition k_d_tree.h:392
void updateDelEntries(uint8_t tree_idx)
Definition k_d_tree.h:453
void rangeQuery(const Point &min, const Point &max, const F &cb) const
Definition k_d_tree.h:439
std::vector< Tree > trees
Definition k_d_tree.h:508
void remove(Id id)
Definition k_d_tree.h:419
std::unordered_map< Id, DelEntry > del_entries
Definition k_d_tree.h:513
size_t n_entries
Definition k_d_tree.h:514
typename Tree::Point Point
Definition k_d_tree.h:396
KdTree< Dim, Component, Id > Tree
Definition k_d_tree.h:393
size_t size() const
Definition k_d_tree.h:446
void update(const Point &newPos, Id id)
Definition k_d_tree.h:432
size_t deleted
Definition k_d_tree.h:515
void insert(const std::array< Component, Dim > &point, Id id)
Definition k_d_tree.h:398
void shrink_to_half()
Definition k_d_tree.h:461
Definition k_d_tree.h:266
void remove(Idx internalIdx)
Definition k_d_tree.h:323
void init(Idx root, uint8_t axis, const SortedIndices< Dim > &sorted)
Definition k_d_tree.h:343
KdTree(const KdTree &a, const KdTree &b)
Merge two trees. Both trees are assumed to have a power of two size.
Definition k_d_tree.h:301
std::array< Component, Dim > Point
Definition k_d_tree.h:268
std::unique_ptr< Idx[]> tree
Definition k_d_tree.h:386
void rangeQuery(const Point &min, const Point &max, const F &cb) const
Definition k_d_tree.h:317
KdTree()
Empty tree.
Definition k_d_tree.h:271
KdTree(const Point &point, const Id &id)
Build a tree containing just a single point.
Definition k_d_tree.h:279
std::unique_ptr< Id[]> ids
Definition k_d_tree.h:385
size_t cap() const
Capacity, not size, since some items may be marked as deleted.
Definition k_d_tree.h:340
KdTree(size_t n, Id const *ids, std::array< Component const *, Dim > pts)
Build a tree.
Definition k_d_tree.h:290
std::vector< bool > deleted
Definition k_d_tree.h:387
SortedPoints< Dim, Component > items
Definition k_d_tree.h:384
void rangeQuery(size_t root, uint8_t split, const Point &min, const Point &max, const F &cb) const
Definition k_d_tree.h:357
Definition k_d_tree.h:70
size_t n
Definition k_d_tree.h:113
Points()
Empty.
Definition k_d_tree.h:74
Point getPoint(Idx i) const
Definition k_d_tree.h:93
Component * end(uint8_t d)
Definition k_d_tree.h:108
std::unique_ptr< Component[]> coords
Definition k_d_tree.h:114
std::array< Component, Dim > Point
Definition k_d_tree.h:72
Component * begin(uint8_t d)
Definition k_d_tree.h:107
Points(size_t n)
Allocating constructor; leaves coords uninitialized!
Definition k_d_tree.h:76
void setPoint(Idx i, const Point &point)
Definition k_d_tree.h:101
void assign(Idx start, const Points &from)
Definition k_d_tree.h:87
size_t size() const
Definition k_d_tree.h:85
Points(size_t n, const std::array< Component const *, Dim > &coords)
Copying constructor.
Definition k_d_tree.h:78
const Component * end(uint8_t d) const
Definition k_d_tree.h:110
const Component * begin(uint8_t d) const
Definition k_d_tree.h:109
Definition k_d_tree.h:119
static SortedIndices newUninitialized(size_t n)
uninitialized indices
Definition k_d_tree.h:125
Idx * end(uint8_t d)
Definition k_d_tree.h:189
Points< Dim, Idx > indices
Definition k_d_tree.h:194
SortedIndices(Points< Dim, Idx > &&indices)
Definition k_d_tree.h:193
Idx * begin(uint8_t d)
Definition k_d_tree.h:188
bool empty() const
Definition k_d_tree.h:142
const Idx * end(uint8_t d) const
Definition k_d_tree.h:191
const Idx * begin(uint8_t d) const
Definition k_d_tree.h:190
size_t size() const
Definition k_d_tree.h:141
SortedIndices()
empty
Definition k_d_tree.h:122
SplitResult split(uint8_t axis, std::vector< bool > &markers) const
Definition k_d_tree.h:151
SortedIndices(size_t n)
Identity permutation on all axes.
Definition k_d_tree.h:131
Definition k_d_tree.h:199
size_t size() const
Definition k_d_tree.h:253
SortedPoints(size_t n, const std::array< Component const *, Dim > ptrs)
Sort points.
Definition k_d_tree.h:211
Points< Dim, Component > points
Definition k_d_tree.h:260
SortedPoints()
Definition k_d_tree.h:201
SortedPoints(const SortedPoints &a, const SortedPoints &b)
Merge two sets of sorted points.
Definition k_d_tree.h:223
SortedIndices< Dim > indices
Definition k_d_tree.h:261
SortedPoints(const std::array< Component, Dim > &point)
Single point.
Definition k_d_tree.h:204
#define sanity_check(expr)
Definition debug.h:74
#define _(String)
Definition gettext.h:28
Definition k_d_tree.h:59
uint16_t Idx
Definition k_d_tree.h:61
std::vector< std::basic_string< T > > split(std::basic_string_view< T > s, T delim, T escape=static_cast< T >('\\'))
Splits a string into its component parts separated by the character delimiter.
Definition string.h:681
Definition k_d_tree.h:509
Idx in_tree
Definition k_d_tree.h:511
uint8_t tree_idx
Definition k_d_tree.h:510
Definition k_d_tree.h:144
SortedIndices right
Definition k_d_tree.h:145
SortedIndices left
Definition k_d_tree.h:145
Idx pivot
Definition k_d_tree.h:146