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