Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
shapetree.hpp
Go to the documentation of this file.
1#pragma once
2
4
23
24#include <algorithm>
25#include <cmath>
26#include <cstddef>
27#include <cstdint>
28#include <type_traits>
29#include <utility>
30#include <vector>
31
32
33namespace pgl {
34
35namespace detail {
36
37// Default weight: an empty type that is its own additive identity, so the
38// per-node weight sum occupies no storage (via [[no_unique_address]]) and costs
39// nothing unless a real weight function is supplied.
40struct EmptyWeight {
41 constexpr EmptyWeight operator+(const EmptyWeight&) const { return {}; }
42};
43struct EmptyWeightFn {
44 template <class Shape>
45 constexpr EmptyWeight operator()(const Shape&) const { return {}; }
46};
47
48// Invokes a visitor on one element and reports whether traversal should stop.
49// A visitor returning bool stops the traversal as soon as it returns true; a
50// visitor returning void never stops. This lets the visit* methods accept both
51// plain side-effecting callbacks and early-exit callbacks.
52template <class Fn, class Arg>
53[[nodiscard]] bool invokeVisitor(Fn& fn, const Arg& arg) {
54 if constexpr (std::is_same_v<std::invoke_result_t<Fn&, const Arg&>, bool>) {
55 return fn(arg);
56 } else {
57 fn(arg);
58 return false;
59 }
60}
61
62// Squared distance from q to other, for use in the nearest-neighbor
63// branch-and-bound. The requested result type is passed through to exact
64// shape pairs. A Disk-related overload may necessarily compute in double; its
65// result is then converted to the tree's comparison type.
66template <class ResultNumber, class Q, class Other>
67[[nodiscard]] ResultNumber nearestSquaredDistance(const Q& q, const Other& other) {
68 if constexpr (requires { q.template squaredDistance<ResultNumber>(other); }) {
69 return static_cast<ResultNumber>(q.template squaredDistance<ResultNumber>(other));
70 } else {
71 return static_cast<ResultNumber>(q.squaredDistance(other));
72 }
73}
74
75// Same fallback dance as nearestSquaredDistance, for the L1 (Manhattan) metric.
76template <class ResultNumber, class Q, class Other>
77[[nodiscard]] ResultNumber nearestDistanceL1(const Q& q, const Other& other) {
78 if constexpr (requires { q.template distanceL1<ResultNumber>(other); }) {
79 return static_cast<ResultNumber>(q.template distanceL1<ResultNumber>(other));
80 } else {
81 return static_cast<ResultNumber>(q.distanceL1(other));
82 }
83}
84
85// Same fallback dance as nearestSquaredDistance, for the LInf (Chebyshev) metric.
86template <class ResultNumber, class Q, class Other>
87[[nodiscard]] ResultNumber nearestDistanceLInf(const Q& q, const Other& other) {
88 if constexpr (requires { q.template distanceLInf<ResultNumber>(other); }) {
89 return static_cast<ResultNumber>(q.template distanceLInf<ResultNumber>(other));
90 } else {
91 return static_cast<ResultNumber>(q.distanceLInf(other));
92 }
93}
94
95// Metric tags selecting which detail::nearest*Distance is used by Node::nearest,
96// so the branch-and-bound traversal is written once and shared by squared-L2,
97// L1 and LInf nearest-neighbor queries.
98struct SquaredMetric {
99 template <class ResultNumber, class Q, class Other>
100 [[nodiscard]] static ResultNumber distance(const Q& q, const Other& other) {
101 return nearestSquaredDistance<ResultNumber>(q, other);
102 }
103};
104struct L1Metric {
105 template <class ResultNumber, class Q, class Other>
106 [[nodiscard]] static ResultNumber distance(const Q& q, const Other& other) {
107 return nearestDistanceL1<ResultNumber>(q, other);
108 }
109};
110struct LInfMetric {
111 template <class ResultNumber, class Q, class Other>
112 [[nodiscard]] static ResultNumber distance(const Q& q, const Other& other) {
113 return nearestDistanceLInf<ResultNumber>(q, other);
114 }
115};
116
117} // namespace detail
118
130template <class S, class WeightFn = detail::EmptyWeightFn>
132 public:
133 using ShapeType = S;
134 using WeightFunction = WeightFn;
135 using Rect = std::remove_cvref_t<decltype(std::declval<const S&>().bbox())>;
136 using PointType = typename Rect::PointType;
137 using NumberType = typename PointType::NumberType;
138 using WeightType = std::remove_cvref_t<std::invoke_result_t<const WeightFn&, const ShapeType&>>;
139
140 // Container-like aliases so a ShapeTree can be iterated and passed where a
141 // container of shapes is expected.
143 using size_type = std::size_t;
144 using const_iterator = typename std::vector<ShapeType>::const_iterator;
146
147 private:
148 // ----------------------------------------------------------------------
149 // Cheap box tests
150 //
151 // Every traversal below prunes with bounding boxes before it reaches an
152 // exact predicate. Over arbitrary-precision coordinates the box test is
153 // itself expensive -- comparing two rationals cross-multiplies big integers
154 // -- so each exact box is shadowed by an outward-rounded `double` one,
155 // cached beside the node or element it belongs to. A `double` box that
156 // misses the query proves the exact box does too, rounding having only
157 // grown it; anything else falls through to the exact test unchanged.
158 // Fixed-width coordinates compare in a few machine instructions and carry
159 // no shadow.
160 // ----------------------------------------------------------------------
161
162 using FilterBox = Rectangle<Point<double>>;
163
164 static constexpr bool usesFilter = detail::arbitraryPrecision<NumberType>;
165
166 // Shapes that *are* their own bounding box. A box test in front of an exact
167 // predicate against one of these would just run the same test twice.
168 template <class T>
169 static constexpr bool boxShaped = PointConcept<T> || RectangleConcept<T>;
170
171 // Query shapes whose `bbox()` is defined for every value. An unbounded
172 // convex region has no finite box (@ref HalfplaneIntersection::bbox throws
173 // for one), and a runtime @ref Shape forwards to whatever it holds, so
174 // neither is filtered.
175 template <class T>
176 static constexpr bool hasTotalBoundingBox =
177 requires(const T& t) { t.bbox(); } &&
179
180 // The boxes one query is filtered through, computed once per query.
181 template <class QueryRect>
182 struct QueryBoxes {
183 QueryRect box;
184 FilterBox filter;
185 };
186
187 // Stands in for @ref QueryBoxes when the query has no box to filter
188 // through; every test then goes straight to the exact predicate.
189 struct NoQueryBoxes {};
190
191 // One coordinate of a filter box: `below` picks which end of the interval
192 // the exact value is known to lie in. That interval is the one
193 // @ref detail::approximate charges for reaching a double, so a coordinate
194 // costs one conversion and a few flops. @ref Rectangle::fbox would give a
195 // *tight* bound instead, at two long-double divisions and an exact
196 // comparison against the rational apiece -- accuracy a filter cannot spend
197 // on the box it is only trying to reject.
198 template <class Coordinate>
199 static double filterBound(const Coordinate& value, bool below) {
200 const detail::Approximate a = detail::approximate(value);
201 const double slack = a.error * detail::approximateMargin + 0x1p-1000;
202 return below ? a.value - slack : a.value + slack;
203 }
204
205 // The outward-rounded `double` box of `r`. A coordinate too large to reach
206 // a finite double leaves a NaN behind, and a NaN bound would answer
207 // "disjoint" to everything and prune a subtree that does meet the query;
208 // such a box is widened to the whole plane instead, which prunes nothing.
209 template <class OtherRect>
210 static FilterBox filterBoxOf(const OtherRect& r) {
211 const double xmin = filterBound(r.min().x(), true);
212 const double ymin = filterBound(r.min().y(), true);
213 const double xmax = filterBound(r.max().x(), false);
214 const double ymax = filterBound(r.max().y(), false);
215 if (std::isnan(xmin) || std::isnan(ymin) || std::isnan(xmax) || std::isnan(ymax)) {
216 const double lo = -detail::numeric_limits<double>::infinity();
217 const double hi = detail::numeric_limits<double>::infinity();
218 return FilterBox(lo, lo, hi, hi, true);
219 }
220 return FilterBox(xmin, ymin, xmax, ymax, true);
221 }
222
223 template <class Q>
224 static auto queryBoxesOf(const Q& q) {
225 if constexpr (hasTotalBoundingBox<Q>) {
226 auto box = q.bbox();
227 FilterBox filter{};
228 if constexpr (usesFilter) {
229 filter = filterBoxOf(box);
230 }
231 return QueryBoxes<decltype(box)>{std::move(box), filter};
232 } else {
233 return NoQueryBoxes{};
234 }
235 }
236
237 struct Node {
238 Rect box; // Union bounding box of the whole subtree.
239 std::ptrdiff_t left = -1, right = -1;
240 std::size_t count = 0; // Number of elements in the whole subtree.
241 [[no_unique_address]] WeightType weightSum{}; // Sum of subtree weights.
242 std::vector<std::size_t> elementIndices; // Elements owned by this node.
243
244 // This node's index in `tree.nodes_`, which is what addresses its cached
245 // filter box. Both are elements of that one array, so the difference is
246 // exact; carrying the index down every traversal instead would put a
247 // parameter on each of them for it.
248 [[nodiscard]] std::size_t index(const ShapeTree& tree) const {
249 return static_cast<std::size_t>(this - tree.nodes_.data());
250 }
251
252 // Whether the cheap box tests alone already prove this subtree misses
253 // `q`. False means undecided, not that the subtree meets the query.
254 template <class Q, class QB>
255 [[nodiscard]] bool boxMisses(const ShapeTree& tree, const Q&, const QB& qb) const {
256 if constexpr (std::is_same_v<QB, NoQueryBoxes> || boxShaped<Q>) {
257 return false; // The exact node test that follows *is* this test.
258 } else {
259 if constexpr (usesFilter) {
260 if (!qb.filter.intersects(tree.nodeFilterBoxes_[index(tree)])) {
261 return true;
262 }
263 }
264 return !qb.box.intersects(box);
265 }
266 }
267
268 // Whether the cheap box tests alone already prove element `i` misses
269 // `q` -- and so that it neither meets `q` nor lies inside it.
270 template <class Q, class QB>
271 [[nodiscard]] bool elementBoxMisses(const ShapeTree& tree, const Q&, const QB& qb,
272 std::size_t i) const {
273 if constexpr (std::is_same_v<QB, NoQueryBoxes> ||
274 (boxShaped<Q> && boxShaped<ShapeType>)) {
275 return false; // The exact element test that follows *is* this test.
276 } else if constexpr (usesFilter) {
277 return !qb.filter.intersects(tree.filterBoxes_[i]);
278 } else {
279 return !qb.box.intersects(tree.elements_[i].bbox());
280 }
281 }
282
283 template <class Q, class QB>
284 [[nodiscard]] std::size_t countIntersecting(const ShapeTree& tree, const Q& q,
285 const QB& qb) const {
286 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
287 return 0;
288 }
289 if (q.contains(box)) {
290 // The whole subtree lies inside q, so every element intersects it.
291 return count;
292 }
293 std::size_t ret = 0;
294 for (std::size_t i : elementIndices) {
295 if (!elementBoxMisses(tree, q, qb, i) && tree.elements_[i].intersects(q)) {
296 ret++;
297 }
298 }
299 if (left != -1) {
300 ret += tree.nodes_[left].countIntersecting(tree, q, qb);
301 }
302 if (right != -1) {
303 ret += tree.nodes_[right].countIntersecting(tree, q, qb);
304 }
305 return ret;
306 }
307
308 template <class Q, class QB>
309 [[nodiscard]] WeightType sumIntersecting(const ShapeTree& tree, const Q& q,
310 const QB& qb) const {
311 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
312 return WeightType{};
313 }
314 if (q.contains(box)) {
315 // The whole subtree lies inside q, so it contributes its full sum.
316 return weightSum;
317 }
318 WeightType ret{};
319 for (std::size_t i : elementIndices) {
320 if (!elementBoxMisses(tree, q, qb, i) && tree.elements_[i].intersects(q)) {
321 ret = ret + tree.weight_(tree.elements_[i]);
322 }
323 }
324 if (left != -1) {
325 ret = ret + tree.nodes_[left].sumIntersecting(tree, q, qb);
326 }
327 if (right != -1) {
328 ret = ret + tree.nodes_[right].sumIntersecting(tree, q, qb);
329 }
330 return ret;
331 }
332
333 // Appends every element in this subtree, with no intersection test.
334 void collectAll(const ShapeTree& tree, std::vector<ShapeType>& out) const {
335 for (std::size_t i : elementIndices) {
336 out.push_back(tree.elements_[i]);
337 }
338 if (left != -1) {
339 tree.nodes_[left].collectAll(tree, out);
340 }
341 if (right != -1) {
342 tree.nodes_[right].collectAll(tree, out);
343 }
344 }
345
346 template <class Q, class QB>
347 void reportIntersecting(const ShapeTree& tree, const Q& q, const QB& qb,
348 std::vector<ShapeType>& out) const {
349 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
350 return;
351 }
352 if (q.contains(box)) {
353 // The whole subtree lies inside q, so every element intersects it.
354 collectAll(tree, out);
355 return;
356 }
357 for (std::size_t i : elementIndices) {
358 if (!elementBoxMisses(tree, q, qb, i) && tree.elements_[i].intersects(q)) {
359 out.push_back(tree.elements_[i]);
360 }
361 }
362 if (left != -1) {
363 tree.nodes_[left].reportIntersecting(tree, q, qb, out);
364 }
365 if (right != -1) {
366 tree.nodes_[right].reportIntersecting(tree, q, qb, out);
367 }
368 }
369
370 // Calls fn on every element in this subtree, with no intersection test.
371 // Returns true as soon as fn requests a stop (see detail::invokeVisitor).
372 template <class Fn>
373 [[nodiscard]] bool visitAll(const ShapeTree& tree, Fn& fn) const {
374 for (std::size_t i : elementIndices) {
375 if (detail::invokeVisitor(fn, tree.elements_[i])) {
376 return true;
377 }
378 }
379 if (left != -1 && tree.nodes_[left].visitAll(tree, fn)) {
380 return true;
381 }
382 if (right != -1 && tree.nodes_[right].visitAll(tree, fn)) {
383 return true;
384 }
385 return false;
386 }
387
388 template <class Q, class QB, class Fn>
389 [[nodiscard]] bool visitIntersecting(const ShapeTree& tree, const Q& q, const QB& qb,
390 Fn& fn) const {
391 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
392 return false;
393 }
394 if (q.contains(box)) {
395 // The whole subtree lies inside q, so every element intersects it.
396 return visitAll(tree, fn);
397 }
398 for (std::size_t i : elementIndices) {
399 if (!elementBoxMisses(tree, q, qb, i) && tree.elements_[i].intersects(q) &&
400 detail::invokeVisitor(fn, tree.elements_[i])) {
401 return true;
402 }
403 }
404 if (left != -1 && tree.nodes_[left].visitIntersecting(tree, q, qb, fn)) {
405 return true;
406 }
407 if (right != -1 && tree.nodes_[right].visitIntersecting(tree, q, qb, fn)) {
408 return true;
409 }
410 return false;
411 }
412
413 template <class Q, class QB>
414 [[nodiscard]] bool anyIntersecting(const ShapeTree& tree, const Q& q,
415 const QB& qb) const {
416 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
417 return false;
418 }
419 if (q.contains(box)) {
420 // The whole (non-empty) subtree lies inside q.
421 return true;
422 }
423 for (std::size_t i : elementIndices) {
424 if (!elementBoxMisses(tree, q, qb, i) && tree.elements_[i].intersects(q)) {
425 return true;
426 }
427 }
428 if (left != -1 && tree.nodes_[left].anyIntersecting(tree, q, qb)) {
429 return true;
430 }
431 if (right != -1 && tree.nodes_[right].anyIntersecting(tree, q, qb)) {
432 return true;
433 }
434 return false;
435 }
436
437 // --- containment: stored element contained in the query (element ⊆ q) ---
438
439 template <class Q, class QB>
440 [[nodiscard]] std::size_t countContainedIn(const ShapeTree& tree, const Q& q,
441 const QB& qb) const {
442 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
443 return 0;
444 }
445 if (q.contains(box)) {
446 // Every element ⊆ box ⊆ q.
447 return count;
448 }
449 std::size_t ret = 0;
450 for (std::size_t i : elementIndices) {
451 if (!elementBoxMisses(tree, q, qb, i) && q.contains(tree.elements_[i])) {
452 ret++;
453 }
454 }
455 if (left != -1) {
456 ret += tree.nodes_[left].countContainedIn(tree, q, qb);
457 }
458 if (right != -1) {
459 ret += tree.nodes_[right].countContainedIn(tree, q, qb);
460 }
461 return ret;
462 }
463
464 template <class Q, class QB>
465 [[nodiscard]] WeightType sumContainedIn(const ShapeTree& tree, const Q& q,
466 const QB& qb) const {
467 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
468 return WeightType{};
469 }
470 if (q.contains(box)) {
471 return weightSum;
472 }
473 WeightType ret{};
474 for (std::size_t i : elementIndices) {
475 if (!elementBoxMisses(tree, q, qb, i) && q.contains(tree.elements_[i])) {
476 ret = ret + tree.weight_(tree.elements_[i]);
477 }
478 }
479 if (left != -1) {
480 ret = ret + tree.nodes_[left].sumContainedIn(tree, q, qb);
481 }
482 if (right != -1) {
483 ret = ret + tree.nodes_[right].sumContainedIn(tree, q, qb);
484 }
485 return ret;
486 }
487
488 template <class Q, class QB>
489 void reportContainedIn(const ShapeTree& tree, const Q& q, const QB& qb,
490 std::vector<ShapeType>& out) const {
491 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
492 return;
493 }
494 if (q.contains(box)) {
495 collectAll(tree, out);
496 return;
497 }
498 for (std::size_t i : elementIndices) {
499 if (!elementBoxMisses(tree, q, qb, i) && q.contains(tree.elements_[i])) {
500 out.push_back(tree.elements_[i]);
501 }
502 }
503 if (left != -1) {
504 tree.nodes_[left].reportContainedIn(tree, q, qb, out);
505 }
506 if (right != -1) {
507 tree.nodes_[right].reportContainedIn(tree, q, qb, out);
508 }
509 }
510
511 template <class Q, class QB, class Fn>
512 [[nodiscard]] bool visitContainedIn(const ShapeTree& tree, const Q& q, const QB& qb,
513 Fn& fn) const {
514 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
515 return false;
516 }
517 if (q.contains(box)) {
518 return visitAll(tree, fn);
519 }
520 for (std::size_t i : elementIndices) {
521 if (!elementBoxMisses(tree, q, qb, i) && q.contains(tree.elements_[i]) &&
522 detail::invokeVisitor(fn, tree.elements_[i])) {
523 return true;
524 }
525 }
526 if (left != -1 && tree.nodes_[left].visitContainedIn(tree, q, qb, fn)) {
527 return true;
528 }
529 if (right != -1 && tree.nodes_[right].visitContainedIn(tree, q, qb, fn)) {
530 return true;
531 }
532 return false;
533 }
534
535 template <class Q, class QB>
536 [[nodiscard]] bool anyContainedIn(const ShapeTree& tree, const Q& q,
537 const QB& qb) const {
538 if (boxMisses(tree, q, qb) || !q.intersects(box)) {
539 return false;
540 }
541 if (q.contains(box)) {
542 return true;
543 }
544 for (std::size_t i : elementIndices) {
545 if (!elementBoxMisses(tree, q, qb, i) && q.contains(tree.elements_[i])) {
546 return true;
547 }
548 }
549 if (left != -1 && tree.nodes_[left].anyContainedIn(tree, q, qb)) {
550 return true;
551 }
552 if (right != -1 && tree.nodes_[right].anyContainedIn(tree, q, qb)) {
553 return true;
554 }
555 return false;
556 }
557
558 // --- membership: a stored element equal to a given shape ---
559
560 // Searches for a stored element equal to `shape`, pruning any subtree
561 // whose box does not contain the shape's box `sb` (an equal element has
562 // exactly that box, so it cannot lie in such a subtree).
563 [[nodiscard]] bool containsShape(const ShapeTree& tree, const ShapeType& shape,
564 const Rect& sb) const {
565 if (!box.contains(sb)) {
566 return false;
567 }
568 for (std::size_t i : elementIndices) {
569 if (tree.elements_[i] == shape) {
570 return true;
571 }
572 }
573 if (left != -1 && tree.nodes_[left].containsShape(tree, shape, sb)) {
574 return true;
575 }
576 if (right != -1 && tree.nodes_[right].containsShape(tree, shape, sb)) {
577 return true;
578 }
579 return false;
580 }
581
582 // --- nearest neighbor: branch-and-bound on squared distance ---
583
584 // Refines (bestDist, bestIndex) with the closest element in this subtree,
585 // under the distance metric selected by `Metric` (squared-L2, L1 or LInf;
586 // see detail::SquaredMetric / L1Metric / LInfMetric). The subtree box
587 // gives a lower bound on the distance from q to anything it stores, so a
588 // subtree whose box is no nearer than the current best is pruned
589 // entirely. Children are descended nearest-box-first so the bound
590 // tightens before the farther child is considered.
591 template <class ResultNumber, class Metric, class Q>
592 void nearest(const ShapeTree& tree, const Q& q, ResultNumber& bestDist,
593 std::ptrdiff_t& bestIndex) const {
594 if (bestIndex != -1) {
595 const ResultNumber lowerBound = Metric::template distance<ResultNumber>(q, box);
596 if (!(lowerBound < bestDist)) {
597 return; // Nothing here can beat the current best.
598 }
599 }
600
601 for (std::size_t i : elementIndices) {
602 const ResultNumber d = Metric::template distance<ResultNumber>(q, tree.elements_[i]);
603 if (bestIndex == -1 || d < bestDist) {
604 bestDist = d;
605 bestIndex = static_cast<std::ptrdiff_t>(i);
606 if (d == 0) {
607 return; // Exact hit: nothing can be nearer than zero.
608 }
609 }
610 }
611
612 if (left == -1) {
613 if (right != -1) {
614 tree.nodes_[right].template nearest<ResultNumber, Metric>(tree, q, bestDist, bestIndex);
615 }
616 return;
617 }
618 if (right == -1) {
619 tree.nodes_[left].template nearest<ResultNumber, Metric>(tree, q, bestDist, bestIndex);
620 return;
621 }
622
623 const ResultNumber leftBound = Metric::template distance<ResultNumber>(q, tree.nodes_[left].box);
624 const ResultNumber rightBound = Metric::template distance<ResultNumber>(q, tree.nodes_[right].box);
625 const std::ptrdiff_t nearChild = leftBound <= rightBound ? left : right;
626 const std::ptrdiff_t farChild = leftBound <= rightBound ? right : left;
627 tree.nodes_[nearChild].template nearest<ResultNumber, Metric>(tree, q, bestDist, bestIndex);
628 tree.nodes_[farChild].template nearest<ResultNumber, Metric>(tree, q, bestDist, bestIndex);
629 }
630
631 // Maintains a max-heap containing the k closest elements seen so far.
632 // Once the heap is full, its front is the current kth-smallest distance
633 // and therefore supplies the pruning bound for whole subtrees.
634 template <class ResultNumber, class Metric, class Q>
635 void nearest(const ShapeTree& tree, const Q& q, std::size_t k,
636 std::vector<std::pair<ResultNumber, std::size_t>>& best) const {
637 const auto nearer = [](const auto& a, const auto& b) {
638 return a.first < b.first;
639 };
640
641 if (best.size() == k) {
642 const ResultNumber lowerBound = Metric::template distance<ResultNumber>(q, box);
643 if (!(lowerBound < best.front().first)) {
644 return; // Nothing here can enter the current top k.
645 }
646 }
647
648 for (std::size_t i : elementIndices) {
649 const ResultNumber d = Metric::template distance<ResultNumber>(q, tree.elements_[i]);
650 if (best.size() < k) {
651 best.emplace_back(d, i);
652 std::push_heap(best.begin(), best.end(), nearer);
653 } else if (d < best.front().first) {
654 std::pop_heap(best.begin(), best.end(), nearer);
655 best.back() = {d, i};
656 std::push_heap(best.begin(), best.end(), nearer);
657 }
658 }
659
660 if (left == -1) {
661 if (right != -1) {
662 tree.nodes_[right].template nearest<ResultNumber, Metric>(tree, q, k, best);
663 }
664 return;
665 }
666 if (right == -1) {
667 tree.nodes_[left].template nearest<ResultNumber, Metric>(tree, q, k, best);
668 return;
669 }
670
671 const ResultNumber leftBound = Metric::template distance<ResultNumber>(q, tree.nodes_[left].box);
672 const ResultNumber rightBound = Metric::template distance<ResultNumber>(q, tree.nodes_[right].box);
673 const std::ptrdiff_t nearChild = leftBound <= rightBound ? left : right;
674 const std::ptrdiff_t farChild = leftBound <= rightBound ? right : left;
675 tree.nodes_[nearChild].template nearest<ResultNumber, Metric>(tree, q, k, best);
676 tree.nodes_[farChild].template nearest<ResultNumber, Metric>(tree, q, k, best);
677 }
678 };
679
680 static constexpr std::size_t defaultLeafSize = 6;
681
682 std::vector<ShapeType> elements_;
683 std::vector<Node> nodes_;
684 // Parallel to elements_ and to nodes_, and empty unless usesFilter.
685 std::vector<FilterBox> filterBoxes_;
686 std::vector<FilterBox> nodeFilterBoxes_;
687 std::ptrdiff_t root_ = -1;
688 std::size_t leafSize_ = defaultLeafSize;
689 [[no_unique_address]] WeightFn weight_{};
690
691 // Appends a fresh node and returns its index, centralizing the "the index is
692 // the current size, then push" pattern shared by build and insert.
693 std::ptrdiff_t allocNode() {
694 const std::ptrdiff_t id = static_cast<std::ptrdiff_t>(nodes_.size());
695 nodes_.push_back(Node{});
696 if constexpr (usesFilter) {
697 nodeFilterBoxes_.emplace_back();
698 }
699 return id;
700 }
701
702 // Records the filter box of node `id`, after its box was set or changed.
703 void refreshNodeFilterBox(std::ptrdiff_t id) {
704 if constexpr (usesFilter) {
705 nodeFilterBoxes_[static_cast<std::size_t>(id)] = filterBoxOf(nodes_[id].box);
706 }
707 }
708
709 // The best split found on a single axis.
710 struct Split {
711 bool found = false;
712 int axis = 0;
713 NumberType value{};
714 std::size_t straddlers = 0;
715 std::size_t maxChild = 0;
716 std::size_t score = 0; // maxChild + straddlers; lower is better.
717 };
718
719 // One end of one element's bounding box on one axis, carrying the element
720 // it came from. The scan below wants these in ascending order and wants
721 // them contiguous, which is why the coordinate is copied here rather than
722 // reached through the element every time.
723 struct EndPoint {
724 NumberType value;
725 std::size_t index;
726 };
727
728 // A subtree's box ends: per axis, every element's lower end and every
729 // element's upper end, each list ascending. Splitting a node preserves the
730 // relative order of whatever it hands to a child, so a child's lists are
731 // its parent's with the elements that went elsewhere dropped -- one linear
732 // pass, no comparison. Inheriting them is what keeps the whole build to one
733 // sort per axis instead of one per node.
734 struct SortedEnds {
735 std::vector<EndPoint> lo[2], hi[2];
736 };
737
738 // Fills `lo` and `hi` with the elements' box ends on `axis`, ascending.
739 void sortEndsOnAxis(const std::vector<std::size_t>& indices, std::size_t axis,
740 std::vector<EndPoint>& lo, std::vector<EndPoint>& hi) const {
741 lo.reserve(indices.size());
742 hi.reserve(indices.size());
743 for (std::size_t i : indices) {
744 const auto box = elements_[i].bbox();
745 lo.push_back(EndPoint{box.min()[axis], i});
746 hi.push_back(EndPoint{box.max()[axis], i});
747 }
748 const auto byValue = [](const EndPoint& a, const EndPoint& b) { return a.value < b.value; };
749 std::sort(lo.begin(), lo.end(), byValue);
750 std::sort(hi.begin(), hi.end(), byValue);
751 }
752
753 // Finds the split on `axis` minimizing maxChild + straddlers, which balances
754 // the children while keeping few elements stuck at the node; ties are broken
755 // toward fewer straddlers. The two lists are that axis's box ends, ascending.
756 Split bestSplitOnEnds(const std::vector<EndPoint>& los, const std::vector<EndPoint>& his,
757 std::size_t axis) const {
758 const std::size_t n = los.size();
759 Split best;
760 best.axis = static_cast<int>(axis);
761 std::size_t loPos = 0;
762 std::size_t hiPos = 0;
763 while (loPos < n || hiPos < n) {
764 // Merge the distinct lo/hi coordinates in order. Advancing both
765 // cursors through v gives the two side counts directly, avoiding a
766 // third sort and two binary searches per candidate coordinate.
767 const NumberType& v =
768 hiPos == n || (loPos < n && los[loPos].value < his[hiPos].value)
769 ? los[loPos].value
770 : his[hiPos].value;
771 while (loPos < n && !(v < los[loPos].value)) {
772 ++loPos;
773 }
774 while (hiPos < n && !(v < his[hiPos].value)) {
775 ++hiPos;
776 }
777
778 // left: boxes entirely <= v (hi <= v); right: entirely > v (lo > v).
779 const std::size_t leftCount = hiPos;
780 const std::size_t rightCount = n - loPos;
781 if (leftCount >= n || rightCount >= n || leftCount + rightCount == 0) {
782 continue; // No progress: a child would hold every element.
783 }
784 const std::size_t straddlers = n - leftCount - rightCount;
785 const std::size_t maxChild = std::max(leftCount, rightCount);
786 const std::size_t score = maxChild + straddlers;
787 if (!best.found || score < best.score ||
788 (score == best.score && straddlers < best.straddlers)) {
789 best.found = true;
790 best.value = v;
791 best.straddlers = straddlers;
792 best.maxChild = maxChild;
793 best.score = score;
794 }
795 }
796 return best;
797 }
798
799 // @ref bestSplitOnEnds for a node whose ends were not inherited: the
800 // incremental path splits one overflowing leaf, whose elements are few, so
801 // it sorts them here rather than carrying lists through every insertion.
802 Split bestSplitOnAxis(const std::vector<std::size_t>& indices, std::size_t axis) const {
803 // Points have no straddlers: an optimum is attained immediately before
804 // or after the coordinate group containing the median. Selecting that
805 // group is linear on average and avoids the endpoint sorts needed for
806 // shapes with non-degenerate bounding boxes.
807 if constexpr (PointConcept<ShapeType>) {
808 const std::size_t n = indices.size();
809 std::vector<std::size_t> ordered = indices;
810 const auto middle = ordered.begin() + static_cast<std::ptrdiff_t>(n / 2);
811 std::nth_element(
812 ordered.begin(), middle, ordered.end(),
813 [&](std::size_t a, std::size_t b) {
814 return elements_[a][axis] < elements_[b][axis];
815 });
816 const NumberType& median = elements_[*middle][axis];
817
818 std::size_t less = 0;
819 std::size_t equal = 0;
820 std::size_t predecessor = 0;
821 bool hasPredecessor = false;
822 for (std::size_t i : indices) {
823 const NumberType& coordinate = elements_[i][axis];
824 if (coordinate < median) {
825 ++less;
826 if (!hasPredecessor || elements_[predecessor][axis] < coordinate) {
827 predecessor = i;
828 hasPredecessor = true;
829 }
830 } else if (!(median < coordinate)) {
831 ++equal;
832 }
833 }
834
835 Split best;
836 best.axis = static_cast<int>(axis);
837 const auto consider = [&](const NumberType& value, std::size_t leftCount) {
838 const std::size_t rightCount = n - leftCount;
839 if (leftCount >= n || rightCount >= n) {
840 return;
841 }
842 const std::size_t score = std::max(leftCount, rightCount);
843 if (!best.found || score < best.score) {
844 best.found = true;
845 best.value = value;
846 best.maxChild = score;
847 best.score = score;
848 }
849 };
850 if (hasPredecessor) {
851 consider(elements_[predecessor][axis], less);
852 }
853 consider(median, less + equal);
854 return best;
855 }
856
857 std::vector<EndPoint> los, his;
858 sortEndsOnAxis(indices, axis, los, his);
859 return bestSplitOnEnds(los, his, axis);
860 }
861
862 // Chooses the best split over `n` elements, trying the depth-parity axis
863 // first so equal-scoring splits alternate direction. `splitOnAxis` answers
864 // for one axis; where it reads the coordinates from is the caller's.
865 template <class SplitOnAxis>
866 static Split chooseSplitOverAxes(std::size_t n, int level, SplitOnAxis&& splitOnAxis) {
867 Split best;
868 for (int k = 0; k < 2; ++k) {
869 const std::size_t axis = static_cast<std::size_t>((level + k) % 2);
870 const Split candidate = splitOnAxis(axis);
871 if (!candidate.found) {
872 continue;
873 }
874 if (!best.found || candidate.score < best.score ||
875 (candidate.score == best.score && candidate.straddlers < best.straddlers)) {
876 best = candidate;
877 }
878 // This is the absolute lower bound: the elements are split evenly
879 // and none stays at the node, so the other axis cannot improve it.
880 if (candidate.straddlers == 0 && candidate.score == (n + 1) / 2) {
881 return candidate;
882 }
883 }
884 return best;
885 }
886
887 // Chooses the best split over `indices`, sorting each axis's ends here.
888 Split chooseSplit(const std::vector<std::size_t>& indices, int level) const {
889 return chooseSplitOverAxes(indices.size(), level, [&](std::size_t axis) {
890 return bestSplitOnAxis(indices, axis);
891 });
892 }
893
894 // Chooses the best split over a node whose ends came down from its parent.
895 Split chooseSplit(const SortedEnds& ends, int level) const {
896 return chooseSplitOverAxes(ends.lo[0].size(), level, [&](std::size_t axis) {
897 return bestSplitOnEnds(ends.lo[axis], ends.hi[axis], axis);
898 });
899 }
900
901 // Partitions indices by a split: strictly-left (hi <= value), strictly-right
902 // (lo > value), and straddlers (kept at the node).
903 void partitionBySplit(const std::vector<std::size_t>& indices, const Split& split,
904 std::vector<std::size_t>& leftIndices,
905 std::vector<std::size_t>& rightIndices,
906 std::vector<std::size_t>& straddlers) const {
907 const auto a = static_cast<std::size_t>(split.axis);
908 for (std::size_t i : indices) {
909 const NumberType lo = elements_[i].bbox().min()[a];
910 const NumberType hi = elements_[i].bbox().max()[a];
911 if (hi <= split.value) {
912 leftIndices.push_back(i);
913 } else if (lo > split.value) {
914 rightIndices.push_back(i);
915 } else {
916 straddlers.push_back(i);
917 }
918 }
919 }
920
921 // Appends the node covering `indices`, with its bounding box, its element
922 // count and its weight sum, and returns its index. Whether it keeps them as
923 // a leaf or splits them further is the caller's to decide.
924 std::ptrdiff_t makeNode(const std::vector<std::size_t>& indices) {
925 Rect box = Rect(elements_[indices[0]].bbox());
926 WeightType weightSum = weight_(elements_[indices[0]]);
927 // The subtree's filter box is unioned from the elements' rather than
928 // converted from `box` once it is known: a union of outward boxes is
929 // outward too, and it rides the loop already running instead of paying
930 // eight directed conversions out of the exact coordinate type.
931 FilterBox filter{};
932 if constexpr (usesFilter) {
933 filter = filterBoxes_[indices[0]];
934 }
935 for (std::size_t k = 1; k < indices.size(); ++k) {
936 box.insert(elements_[indices[k]].bbox());
937 weightSum = weightSum + weight_(elements_[indices[k]]);
938 if constexpr (usesFilter) {
939 filter.insert(filterBoxes_[indices[k]]);
940 }
941 }
942
943 // Reserve this node's slot now; recursion may reallocate nodes_, so the
944 // node is always addressed by index, never by a dangling reference.
945 const std::ptrdiff_t id = allocNode();
946 nodes_[id].box = box;
947 if constexpr (usesFilter) {
948 nodeFilterBoxes_[static_cast<std::size_t>(id)] = filter;
949 }
950 nodes_[id].count = indices.size();
951 nodes_[id].weightSum = weightSum;
952 return id;
953 }
954
955 // Builds a subtree from the given element indices and returns its node index.
956 // `level` is the depth, used only to break ties between equally good axes so
957 // the split direction alternates (e.g. for points, where both axes always
958 // score the same).
959 std::ptrdiff_t build(const std::vector<std::size_t>& indices, int level) {
960 const std::ptrdiff_t id = makeNode(indices);
961
962 if (indices.size() <= leafSize_) {
963 nodes_[id].elementIndices = indices;
964 return id;
965 }
966
967 const Split best = chooseSplit(indices, level);
968 if (!best.found) {
969 // No axis can separate the elements (e.g. many identical boxes):
970 // keep them all here as a leaf.
971 nodes_[id].elementIndices = indices;
972 return id;
973 }
974
975 std::vector<std::size_t> leftIndices, rightIndices, straddlers;
976 partitionBySplit(indices, best, leftIndices, rightIndices, straddlers);
977
978 const std::ptrdiff_t leftChild = leftIndices.empty() ? -1 : build(leftIndices, level + 1);
979 const std::ptrdiff_t rightChild = rightIndices.empty() ? -1 : build(rightIndices, level + 1);
980 nodes_[id].left = leftChild;
981 nodes_[id].right = rightChild;
982 nodes_[id].elementIndices = std::move(straddlers);
983 return id;
984 }
985
986 // @ref build for a node whose box ends came down from its parent already in
987 // order. It picks the same split the sorting path would and so produces the
988 // same tree; what it saves is the sort, which the other path pays at every
989 // node and on both axes. `side` is scratch indexed by element, sized once
990 // by the caller and reused down the whole recursion.
991 std::ptrdiff_t buildFromEnds(SortedEnds& ends, int level, std::vector<std::uint8_t>& side) {
992 // The elements this subtree holds, in the order the first axis's lower
993 // ends put them. A node's own list is read in whatever order it is
994 // stored, and nothing depends on which order that is: it decides only
995 // which of several equally good answers a query gives back -- which
996 // stored shape is returned when two are the same distance away.
997 std::vector<std::size_t> indices;
998 indices.reserve(ends.lo[0].size());
999 for (const EndPoint& end : ends.lo[0]) {
1000 indices.push_back(end.index);
1001 }
1002
1003 const std::ptrdiff_t id = makeNode(indices);
1004
1005 if (indices.size() <= leafSize_) {
1006 nodes_[id].elementIndices = std::move(indices);
1007 return id;
1008 }
1009
1010 const Split best = chooseSplit(ends, level);
1011 if (!best.found) {
1012 nodes_[id].elementIndices = std::move(indices);
1013 return id;
1014 }
1015
1016 // Tag each element with the side it goes to, then deal every end list
1017 // out by the tags. A filtered subsequence of an ordered list is ordered,
1018 // so the children get their lists sorted without a comparison.
1019 static constexpr std::uint8_t toLeft = 0, toRight = 1, stays = 2;
1020 const auto a = static_cast<std::size_t>(best.axis);
1021 std::vector<std::size_t> straddlers;
1022 std::size_t leftCount = 0, rightCount = 0;
1023 for (std::size_t i : indices) {
1024 const auto box = elements_[i].bbox();
1025 const std::uint8_t which = box.max()[a] <= best.value ? toLeft
1026 : box.min()[a] > best.value ? toRight
1027 : stays;
1028 side[i] = which;
1029 if (which == toLeft) {
1030 ++leftCount;
1031 } else if (which == toRight) {
1032 ++rightCount;
1033 } else {
1034 straddlers.push_back(i);
1035 }
1036 }
1037
1038 SortedEnds left, right;
1039 const auto deal = [&](std::vector<EndPoint>& source, std::vector<EndPoint>& toTheLeft,
1040 std::vector<EndPoint>& toTheRight) {
1041 toTheLeft.reserve(leftCount);
1042 toTheRight.reserve(rightCount);
1043 for (const EndPoint& end : source) {
1044 if (side[end.index] == toLeft) {
1045 toTheLeft.push_back(end);
1046 } else if (side[end.index] == toRight) {
1047 toTheRight.push_back(end);
1048 }
1049 }
1050 // The parent's copy is dead the moment its children have theirs;
1051 // releasing it here is what keeps the live lists linear in total
1052 // rather than linear per level of the recursion.
1053 source.clear();
1054 source.shrink_to_fit();
1055 };
1056 for (int axis = 0; axis < 2; ++axis) {
1057 deal(ends.lo[axis], left.lo[axis], right.lo[axis]);
1058 deal(ends.hi[axis], left.hi[axis], right.hi[axis]);
1059 }
1060
1061 const std::ptrdiff_t leftChild =
1062 leftCount == 0 ? -1 : buildFromEnds(left, level + 1, side);
1063 const std::ptrdiff_t rightChild =
1064 rightCount == 0 ? -1 : buildFromEnds(right, level + 1, side);
1065 nodes_[id].left = leftChild;
1066 nodes_[id].right = rightChild;
1067 nodes_[id].elementIndices = std::move(straddlers);
1068 return id;
1069 }
1070
1071 // Splits an overflowing leaf in place with the best split. Its box, count
1072 // and weight sum are unchanged since the same elements stay in the subtree.
1073 void splitNode(std::ptrdiff_t id, int level) {
1074 std::vector<std::size_t> indices = std::move(nodes_[id].elementIndices);
1075 nodes_[id].elementIndices.clear();
1076
1077 const Split best = chooseSplit(indices, level);
1078 if (!best.found) {
1079 // Cannot separate (e.g. identical boxes): stays an oversized leaf.
1080 nodes_[id].elementIndices = std::move(indices);
1081 return;
1082 }
1083
1084 std::vector<std::size_t> leftIndices, rightIndices, straddlers;
1085 partitionBySplit(indices, best, leftIndices, rightIndices, straddlers);
1086
1087 const std::ptrdiff_t leftChild = leftIndices.empty() ? -1 : build(leftIndices, level + 1);
1088 const std::ptrdiff_t rightChild = rightIndices.empty() ? -1 : build(rightIndices, level + 1);
1089 nodes_[id].left = leftChild;
1090 nodes_[id].right = rightChild;
1091 nodes_[id].elementIndices = std::move(straddlers);
1092 }
1093
1094 // Area increase of `box` when grown to also include `other`.
1095 static auto enlargement(const Rect& box, const Rect& other) {
1096 Rect grown = box;
1097 grown.insert(other);
1098 return grown.area() - box.area();
1099 }
1100
1101 // Routes element i (bounding box eb) down from node id, maintaining every
1102 // visited node's box, count and weight sum, and keeping sibling boxes
1103 // disjoint.
1104 void insertInto(std::ptrdiff_t id, std::size_t i, const Rect& eb, int level) {
1105 nodes_[id].box.insert(eb);
1106 refreshNodeFilterBox(id);
1107 nodes_[id].count += 1;
1108 nodes_[id].weightSum = nodes_[id].weightSum + weight_(elements_[i]);
1109
1110 if (nodes_[id].left == -1 && nodes_[id].right == -1) {
1111 nodes_[id].elementIndices.push_back(i);
1112 if (nodes_[id].elementIndices.size() > leafSize_) {
1113 splitNode(id, level);
1114 }
1115 return;
1116 }
1117
1118 const std::ptrdiff_t L = nodes_[id].left;
1119 const std::ptrdiff_t R = nodes_[id].right;
1120
1121 // A child may take the element only if its grown box stays disjoint from
1122 // its sibling's box.
1123 bool leftOk = false;
1124 bool rightOk = false;
1125 if (L != -1) {
1126 Rect grown = nodes_[L].box;
1127 grown.insert(eb);
1128 leftOk = (R == -1) || !grown.intersects(nodes_[R].box);
1129 }
1130 if (R != -1) {
1131 Rect grown = nodes_[R].box;
1132 grown.insert(eb);
1133 rightOk = (L == -1) || !grown.intersects(nodes_[L].box);
1134 }
1135
1136 std::ptrdiff_t target = -1;
1137 if (leftOk && rightOk) {
1138 // Both keep disjointness: descend into the one enlarged least.
1139 target = enlargement(nodes_[L].box, eb) <= enlargement(nodes_[R].box, eb) ? L : R;
1140 } else if (leftOk) {
1141 target = L;
1142 } else if (rightOk) {
1143 target = R;
1144 }
1145
1146 if (target == -1) {
1147 // Neither child can stay disjoint: keep the element at this node.
1148 nodes_[id].elementIndices.push_back(i);
1149 return;
1150 }
1151 insertInto(target, i, eb, level + 1);
1152 }
1153
1154 // True when the weight function actually produces weights (i.e. the user
1155 // supplied one), so weight bookkeeping can be skipped entirely otherwise.
1156 static constexpr bool hasWeight = !std::is_same_v<WeightType, detail::EmptyWeight>;
1157
1158 // A node holds nothing once it has neither elements nor children; such a
1159 // node is detached from its parent by erase.
1160 static bool nodeIsEmpty(const Node& node) {
1161 return node.elementIndices.empty() && node.left == -1 && node.right == -1;
1162 }
1163
1164 // Recomputes node id's box as the union of its children boxes and its own
1165 // elements' boxes, and returns whether the box actually changed. The node
1166 // must be non-empty (so the union is well defined).
1167 bool recomputeBox(std::ptrdiff_t id) {
1168 Node& node = nodes_[id];
1169 Rect newBox{};
1170 bool init = false;
1171 for (std::size_t i : node.elementIndices) {
1172 const Rect eb = Rect(elements_[i].bbox());
1173 if (!init) {
1174 newBox = eb;
1175 init = true;
1176 } else {
1177 newBox.insert(eb);
1178 }
1179 }
1180 if (node.left != -1) {
1181 if (!init) {
1182 newBox = nodes_[node.left].box;
1183 init = true;
1184 } else {
1185 newBox.insert(nodes_[node.left].box);
1186 }
1187 }
1188 if (node.right != -1) {
1189 if (!init) {
1190 newBox = nodes_[node.right].box;
1191 init = true;
1192 } else {
1193 newBox.insert(nodes_[node.right].box);
1194 }
1195 }
1196 const bool changed = !(newBox == node.box);
1197 node.box = newBox;
1198 refreshNodeFilterBox(id);
1199 return changed;
1200 }
1201
1202 // Removes one stored element equal to `shape` from the subtree rooted at
1203 // `id`. On success the element is removed from its owning node, every node on
1204 // the path has its count decremented and its weight sum reduced by the
1205 // removed weight, boxes are recomputed from the removal point upward and stop
1206 // propagating once a box is unchanged, and any node left empty is detached
1207 // from its parent. `removedIdx`/`removedWeight` receive the removed element's
1208 // index and weight; `boxChanged` reports whether this node's box changed, so
1209 // the parent only recomputes when it has to.
1210 bool eraseFrom(std::ptrdiff_t id, const ShapeType& shape, const Rect& sb,
1211 std::size_t& removedIdx, WeightType& removedWeight, bool& boxChanged,
1212 std::vector<std::ptrdiff_t>& dead) {
1213 boxChanged = false;
1214 if (!nodes_[id].box.contains(sb)) {
1215 return false; // An equal element has box sb, so it cannot be here.
1216 }
1217
1218 bool removed = false;
1219 bool needBoxRecompute = false;
1220
1221 // Look among this node's own elements first.
1222 auto& elems = nodes_[id].elementIndices;
1223 for (std::size_t k = 0; k < elems.size(); ++k) {
1224 if (elements_[elems[k]] == shape) {
1225 removedIdx = elems[k];
1226 if constexpr (hasWeight) {
1227 removedWeight = weight_(elements_[removedIdx]);
1228 }
1229 elems.erase(elems.begin() + static_cast<std::ptrdiff_t>(k));
1230 removed = true;
1231 needBoxRecompute = true;
1232 break;
1233 }
1234 }
1235
1236 // Otherwise descend into the children, detaching one that empties out.
1237 for (std::ptrdiff_t side = 0; !removed && side < 2; ++side) {
1238 std::ptrdiff_t& child = side == 0 ? nodes_[id].left : nodes_[id].right;
1239 if (child == -1) {
1240 continue;
1241 }
1242 bool childBoxChanged = false;
1243 if (eraseFrom(child, shape, sb, removedIdx, removedWeight, childBoxChanged, dead)) {
1244 removed = true;
1245 if (nodeIsEmpty(nodes_[child])) {
1246 dead.push_back(child); // Reclaimed after the recursion unwinds.
1247 child = -1;
1248 needBoxRecompute = true;
1249 } else if (childBoxChanged) {
1250 needBoxRecompute = true;
1251 }
1252 }
1253 }
1254
1255 if (!removed) {
1256 return false;
1257 }
1258
1259 nodes_[id].count -= 1;
1260 if constexpr (hasWeight) {
1261 nodes_[id].weightSum = nodes_[id].weightSum - removedWeight;
1262 }
1263 if (nodeIsEmpty(nodes_[id])) {
1264 boxChanged = true; // The whole subtree is gone; the parent detaches it.
1265 return true;
1266 }
1267 if (needBoxRecompute) {
1268 boxChanged = recomputeBox(id);
1269 }
1270 return true;
1271 }
1272
1273 // After an element is swap-removed in elements_, the element formerly at
1274 // `oldIdx` now lives at `newIdx`; this fixes the single node reference to it.
1275 // The element is stored in exactly one node, whose box (and every ancestor's)
1276 // contains the element box `eb`; since sibling boxes are disjoint, at most one
1277 // child can contain `eb`, so the owning node is reached on a single path down.
1278 void remapElementIndex(std::size_t oldIdx, std::size_t newIdx, const Rect& eb) {
1279 for (std::ptrdiff_t id = root_; id != -1;) {
1280 for (std::size_t& ref : nodes_[id].elementIndices) {
1281 if (ref == oldIdx) {
1282 ref = newIdx;
1283 return;
1284 }
1285 }
1286 const std::ptrdiff_t left = nodes_[id].left;
1287 // Descend into the unique child whose box contains eb (the right one
1288 // when the left does not), since the element lies in one subtree.
1289 id = (left != -1 && nodes_[left].box.contains(eb)) ? left : nodes_[id].right;
1290 }
1291 }
1292
1293 // Repoints the single reference to node `oldId` (a parent's child link, or
1294 // the root) to `newId`, used when that node is relocated within nodes_. `b`
1295 // is the relocated node's box; its parent is reached on a single path down,
1296 // descending into the unique child whose box contains `b` (sibling boxes are
1297 // disjoint, so only one can).
1298 void repointNodeRef(std::ptrdiff_t oldId, std::ptrdiff_t newId, const Rect& b) {
1299 if (root_ == oldId) {
1300 root_ = newId;
1301 return;
1302 }
1303 for (std::ptrdiff_t id = root_; id != -1;) {
1304 if (nodes_[id].left == oldId) {
1305 nodes_[id].left = newId;
1306 return;
1307 }
1308 if (nodes_[id].right == oldId) {
1309 nodes_[id].right = newId;
1310 return;
1311 }
1312 const std::ptrdiff_t left = nodes_[id].left;
1313 id = (left != -1 && nodes_[left].box.contains(b)) ? left : nodes_[id].right;
1314 }
1315 }
1316
1317 // Removes the detached node slots in `dead` from nodes_ by swap-removing each
1318 // with the last node, keeping the array compact so interleaved insert/erase
1319 // does not grow it without bound. Processing the dead indices in descending
1320 // order guarantees the last slot is always a live node (every index past the
1321 // largest remaining dead index is live), so the node moved into the hole is
1322 // real and its one inbound reference can be repointed.
1323 void compactNodes(std::vector<std::ptrdiff_t>& dead) {
1324 std::sort(dead.begin(), dead.end());
1325 for (auto it = dead.rbegin(); it != dead.rend(); ++it) {
1326 const std::ptrdiff_t hole = *it;
1327 const std::ptrdiff_t last = static_cast<std::ptrdiff_t>(nodes_.size()) - 1;
1328 if (hole != last) {
1329 nodes_[hole] = std::move(nodes_[last]);
1330 repointNodeRef(last, hole, nodes_[hole].box);
1331 if constexpr (usesFilter) {
1332 nodeFilterBoxes_[static_cast<std::size_t>(hole)] =
1333 nodeFilterBoxes_[static_cast<std::size_t>(last)];
1334 }
1335 }
1336 nodes_.pop_back();
1337 if constexpr (usesFilter) {
1338 nodeFilterBoxes_.pop_back();
1339 }
1340 }
1341 }
1342
1343 // Shared implementation behind nearestNeighbor/nearestNeighborL1/
1344 // nearestNeighborLInf: runs the branch-and-bound traversal under `Metric`
1345 // and returns the winning element (or a default-constructed one when empty).
1346 template <class Metric, class ResultNumber, class Q>
1347 [[nodiscard]] const ShapeType& nearestNeighborByMetric(const Q& q) const {
1348 if (root_ == -1) {
1349 static const ShapeType empty{};
1350 return empty;
1351 }
1352 ResultNumber bestDist{};
1353 std::ptrdiff_t bestIndex = -1;
1354 nodes_[root_].template nearest<ResultNumber, Metric>(*this, q, bestDist, bestIndex);
1355 return elements_[static_cast<std::size_t>(bestIndex)];
1356 }
1357
1358 // Shared implementation for the k-nearest-neighbor overloads. The heap is
1359 // sorted before its indices are mapped back to copies of the stored shapes.
1360 template <class Metric, class ResultNumber, class Q>
1361 [[nodiscard]] std::vector<ShapeType> nearestNeighborsByMetric(const Q& q, int k) const {
1362 if (root_ == -1 || k <= 0) {
1363 return {};
1364 }
1365 const std::size_t count = std::min(static_cast<std::size_t>(k), elements_.size());
1366 std::vector<std::pair<ResultNumber, std::size_t>> best;
1367 best.reserve(count);
1368 nodes_[root_].template nearest<ResultNumber, Metric>(*this, q, count, best);
1369 std::sort(best.begin(), best.end(), [](const auto& a, const auto& b) {
1370 return a.first < b.first;
1371 });
1372
1373 std::vector<ShapeType> result;
1374 result.reserve(best.size());
1375 for (const auto& [distance, index] : best) {
1376 (void)distance;
1377 result.push_back(elements_[index]);
1378 }
1379 return result;
1380 }
1381
1382 // Discards the current node structure and rebuilds it from elements_.
1383 void buildFromElements() {
1384 nodes_.clear();
1385 nodeFilterBoxes_.clear();
1386 if constexpr (usesFilter) {
1387 filterBoxes_.clear();
1388 filterBoxes_.reserve(elements_.size());
1389 for (const ShapeType& e : elements_) {
1390 filterBoxes_.push_back(filterBoxOf(e.bbox()));
1391 }
1392 }
1393 root_ = -1;
1394 if (elements_.empty()) {
1395 return;
1396 }
1397 std::vector<std::size_t> indices(elements_.size());
1398 for (std::size_t i = 0; i < indices.size(); ++i) {
1399 indices[i] = i;
1400 }
1401 nodes_.reserve(2 * elements_.size() / leafSize_ + 1);
1402 if constexpr (PointConcept<ShapeType>) {
1403 // A point's split needs no ordered ends: the median it splits at is
1404 // selected in linear time, so there is nothing to inherit.
1405 root_ = build(indices, 0);
1406 } else {
1407 SortedEnds ends;
1408 for (int axis = 0; axis < 2; ++axis) {
1409 sortEndsOnAxis(indices, static_cast<std::size_t>(axis), ends.lo[axis],
1410 ends.hi[axis]);
1411 }
1412 std::vector<std::uint8_t> side(elements_.size());
1413 root_ = buildFromEnds(ends, 0, side);
1414 }
1415 }
1416
1417 // Appends the subtree bounding boxes to `out` in pre-order.
1418 void collectBoundingBoxes(std::ptrdiff_t id, std::vector<Rect>& out) const {
1419 if (id == -1) {
1420 return;
1421 }
1422 out.push_back(nodes_[id].box);
1423 collectBoundingBoxes(nodes_[id].left, out);
1424 collectBoundingBoxes(nodes_[id].right, out);
1425 }
1426
1427 public:
1428 ShapeTree() = default;
1429
1438 template <class Container>
1439 explicit ShapeTree(const Container& shapes, std::size_t leafSize = defaultLeafSize,
1440 WeightFn weight = WeightFn{})
1441 : leafSize_(leafSize > 0 ? leafSize : 1), weight_(std::move(weight)) {
1442 for (const auto& s : shapes) {
1443 elements_.push_back(s);
1444 }
1445 buildFromElements();
1446 }
1447
1457 template <class Container>
1458 explicit ShapeTree(const Container& shapes, WeightFn weight)
1459 : ShapeTree(shapes, defaultLeafSize, std::move(weight)) {}
1460
1462 [[nodiscard]] std::size_t size() const {
1463 return elements_.size();
1464 }
1465
1467 [[nodiscard]] bool empty() const {
1468 return elements_.empty();
1469 }
1470
1472 [[nodiscard]] const std::vector<ShapeType>& shapes() const {
1473 return elements_;
1474 }
1475
1477 [[nodiscard]] const_iterator begin() const {
1478 return elements_.begin();
1479 }
1480
1482 [[nodiscard]] const_iterator end() const {
1483 return elements_.end();
1484 }
1485
1487 [[nodiscard]] const_iterator cbegin() const {
1488 return elements_.cbegin();
1489 }
1490
1492 [[nodiscard]] const_iterator cend() const {
1493 return elements_.cend();
1494 }
1495
1512 void insert(const ShapeType& shape) {
1513 const Rect eb = Rect(shape.bbox());
1514 const std::size_t i = elements_.size();
1515 elements_.push_back(shape);
1516 if constexpr (usesFilter) {
1517 filterBoxes_.push_back(filterBoxOf(eb));
1518 }
1519
1520 if (root_ == -1) {
1521 root_ = allocNode();
1522 nodes_[root_].box = eb;
1523 refreshNodeFilterBox(root_);
1524 nodes_[root_].count = 1;
1525 nodes_[root_].weightSum = weight_(elements_[i]);
1526 nodes_[root_].elementIndices.push_back(i);
1527 return;
1528 }
1529 insertInto(root_, i, eb, 0);
1530 }
1531
1543 void rebuild(std::size_t leafSize = 0) {
1544 if (leafSize > 0) {
1545 leafSize_ = leafSize;
1546 }
1547 buildFromElements();
1548 }
1549
1569 bool erase(const ShapeType& shape) {
1570 if (root_ == -1) {
1571 return false;
1572 }
1573 const Rect sb = Rect(shape.bbox());
1574 std::size_t removedIdx = 0;
1575 WeightType removedWeight{};
1576 bool boxChanged = false;
1577 std::vector<std::ptrdiff_t> dead;
1578 if (!eraseFrom(root_, shape, sb, removedIdx, removedWeight, boxChanged, dead)) {
1579 return false;
1580 }
1581 if (nodeIsEmpty(nodes_[root_])) {
1582 dead.push_back(root_);
1583 root_ = -1;
1584 }
1585
1586 // Swap-remove the element from storage, repointing the moved element.
1587 const std::size_t last = elements_.size() - 1;
1588 if (removedIdx != last) {
1589 elements_[removedIdx] = std::move(elements_[last]);
1590 remapElementIndex(last, removedIdx, Rect(elements_[removedIdx].bbox()));
1591 if constexpr (usesFilter) {
1592 filterBoxes_[removedIdx] = filterBoxes_[last];
1593 }
1594 }
1595 elements_.pop_back();
1596 if constexpr (usesFilter) {
1597 filterBoxes_.pop_back();
1598 }
1599
1600 // Reclaim the detached node slots, keeping the node array compact.
1601 compactNodes(dead);
1602 return true;
1603 }
1604
1615 template <class Q>
1616 [[nodiscard]] std::size_t countIntersecting(const Q& q) const {
1617 if (root_ == -1) {
1618 return 0;
1619 }
1620 return nodes_[root_].countIntersecting(*this, q, queryBoxesOf(q));
1621 }
1622
1634 template <class Q>
1635 [[nodiscard]] WeightType sumIntersecting(const Q& q) const {
1636 if (root_ == -1) {
1637 return WeightType{};
1638 }
1639 return nodes_[root_].sumIntersecting(*this, q, queryBoxesOf(q));
1640 }
1641
1652 template <class Q>
1653 [[nodiscard]] std::vector<ShapeType> reportIntersecting(const Q& q) const {
1654 std::vector<ShapeType> out;
1655 if (root_ != -1) {
1656 nodes_[root_].reportIntersecting(*this, q, queryBoxesOf(q), out);
1657 }
1658 return out;
1659 }
1660
1678 template <class Q, class Fn>
1679 bool visitIntersecting(const Q& q, Fn fn) const {
1680 return root_ == -1 ? false : nodes_[root_].visitIntersecting(*this, q, queryBoxesOf(q), fn);
1681 }
1682
1692 template <class Q>
1693 [[nodiscard]] bool emptyIntersecting(const Q& q) const {
1694 return root_ == -1 ? true : !nodes_[root_].anyIntersecting(*this, q, queryBoxesOf(q));
1695 }
1696
1708 template <class Q>
1709 [[nodiscard]] std::size_t countContainedIn(const Q& q) const {
1710 if (root_ == -1) {
1711 return 0;
1712 }
1713 return nodes_[root_].countContainedIn(*this, q, queryBoxesOf(q));
1714 }
1715
1723 template <class Q>
1724 [[nodiscard]] WeightType sumContainedIn(const Q& q) const {
1725 if (root_ == -1) {
1726 return WeightType{};
1727 }
1728 return nodes_[root_].sumContainedIn(*this, q, queryBoxesOf(q));
1729 }
1730
1738 template <class Q>
1739 [[nodiscard]] std::vector<ShapeType> reportContainedIn(const Q& q) const {
1740 std::vector<ShapeType> out;
1741 if (root_ != -1) {
1742 nodes_[root_].reportContainedIn(*this, q, queryBoxesOf(q), out);
1743 }
1744 return out;
1745 }
1746
1760 template <class Q, class Fn>
1761 bool visitContainedIn(const Q& q, Fn fn) const {
1762 return root_ == -1 ? false : nodes_[root_].visitContainedIn(*this, q, queryBoxesOf(q), fn);
1763 }
1764
1774 template <class Q>
1775 [[nodiscard]] bool emptyContainedIn(const Q& q) const {
1776 return root_ == -1 ? true : !nodes_[root_].anyContainedIn(*this, q, queryBoxesOf(q));
1777 }
1778
1790 [[nodiscard]] bool has(const ShapeType& shape) const {
1791 return root_ != -1 && nodes_[root_].containsShape(*this, shape, Rect(shape.bbox()));
1792 }
1793
1826 template <class Q>
1827 [[nodiscard]] const ShapeType& nearestNeighbor(const Q& q) const {
1828 using ResultNumber = std::remove_cvref_t<decltype(
1829 q.squaredDistance(std::declval<const ShapeType&>()))>;
1830 return nearestNeighborByMetric<detail::SquaredMetric, ResultNumber>(q);
1831 }
1832
1833 template <class ResultNumber, class Q>
1834 [[nodiscard]] const ShapeType& nearestNeighbor(const Q& q) const {
1835 return nearestNeighborByMetric<detail::SquaredMetric, ResultNumber>(q);
1836 }
1837
1851 template <class Q>
1852 [[nodiscard]] std::vector<ShapeType> kNearestNeighbors(const Q& q, int k) const {
1853 using ResultNumber = std::remove_cvref_t<decltype(
1854 q.squaredDistance(std::declval<const ShapeType&>()))>;
1855 return nearestNeighborsByMetric<detail::SquaredMetric, ResultNumber>(q, k);
1856 }
1857
1858 template <class ResultNumber, class Q>
1859 [[nodiscard]] std::vector<ShapeType> kNearestNeighbors(const Q& q, int k) const {
1860 return nearestNeighborsByMetric<detail::SquaredMetric, ResultNumber>(q, k);
1861 }
1862
1878 template <class Q>
1879 [[nodiscard]] const ShapeType& nearestNeighborL1(const Q& q) const {
1880 using ResultNumber = std::remove_cvref_t<decltype(
1881 q.distanceL1(std::declval<const ShapeType&>()))>;
1882 return nearestNeighborByMetric<detail::L1Metric, ResultNumber>(q);
1883 }
1884
1885 template <class ResultNumber, class Q>
1886 [[nodiscard]] const ShapeType& nearestNeighborL1(const Q& q) const {
1887 return nearestNeighborByMetric<detail::L1Metric, ResultNumber>(q);
1888 }
1889
1905 template <class Q>
1906 [[nodiscard]] const ShapeType& nearestNeighborLInf(const Q& q) const {
1907 using ResultNumber = std::remove_cvref_t<decltype(
1908 q.distanceLInf(std::declval<const ShapeType&>()))>;
1909 return nearestNeighborByMetric<detail::LInfMetric, ResultNumber>(q);
1910 }
1911
1912 template <class ResultNumber, class Q>
1913 [[nodiscard]] const ShapeType& nearestNeighborLInf(const Q& q) const {
1914 return nearestNeighborByMetric<detail::LInfMetric, ResultNumber>(q);
1915 }
1916
1925 [[nodiscard]] std::vector<Rect> boundingBoxes() const {
1926 std::vector<Rect> out;
1927 out.reserve(nodes_.size());
1928 collectBoundingBoxes(root_, out);
1929 return out;
1930 }
1931
1942 friend Canvas& operator<<(Canvas& canvas, const ShapeTree& tree) {
1943 for (const Rect& box : tree.boundingBoxes()) {
1944 canvas << box;
1945 }
1946 return canvas;
1947 }
1948};
1949
1950// Deduction guides: the shape type S is not deducible from the templated
1951// constructors on their own (the constructor is templated on the container,
1952// not the element), so without these CTAD fails and S must always be named.
1953// These deduce S from the container's value type, preserving its label.
1954template <class Container>
1956
1957template <class Container>
1959
1960template <class Container, class WeightFn>
1961ShapeTree(const Container&, std::size_t, WeightFn)
1963
1964// A weight given without a leaf size: constrained off the integral overload so
1965// `ShapeTree(shapes, leafSize)` still deduces the default weight.
1966template <class Container, class WeightFn>
1967 requires(!std::is_integral_v<WeightFn>)
1968ShapeTree(const Container&, WeightFn)
1970
1971} // namespace pgl
Stores drawable objects and exports them as an SVG image.
Definition canvas.hpp:128
Static shape tree of bounded shapes.
Definition shapetree.hpp:131
bool empty() const
Returns whether the tree is empty.
Definition shapetree.hpp:1467
void insert(const ShapeType &shape)
Inserts a shape without rebalancing the existing tree.
Definition shapetree.hpp:1512
const_iterator cbegin() const
Returns an iterator to the first stored shape.
Definition shapetree.hpp:1487
const_iterator cend() const
Returns an iterator past the last stored shape.
Definition shapetree.hpp:1492
const_iterator end() const
Returns an iterator past the last stored shape.
Definition shapetree.hpp:1482
const ShapeType & nearestNeighborLInf(const Q &q) const
Returns the stored shape nearest to a query shape under the LInf (Chebyshev) metric.
Definition shapetree.hpp:1906
const ShapeType & nearestNeighborL1(const Q &q) const
Returns the stored shape nearest to a query shape under the L1 (Manhattan) metric.
Definition shapetree.hpp:1879
WeightFn WeightFunction
Definition shapetree.hpp:134
std::size_t size() const
Returns the number of stored shapes.
Definition shapetree.hpp:1462
const ShapeType & const_reference
Definition shapetree.hpp:145
bool erase(const ShapeType &shape)
Removes one stored shape equal to shape.
Definition shapetree.hpp:1569
std::vector< Rect > boundingBoxes() const
Returns every node's subtree bounding box in pre-order.
Definition shapetree.hpp:1925
S ShapeType
Definition shapetree.hpp:133
ShapeTree(const Container &shapes, WeightFn weight)
Builds the tree from a container of shapes with a weight function.
Definition shapetree.hpp:1458
ShapeTree(const Container &shapes, std::size_t leafSize=defaultLeafSize, WeightFn weight=WeightFn{})
Builds the tree from a container of shapes.
Definition shapetree.hpp:1439
ShapeTree()=default
bool visitIntersecting(const Q &q, Fn fn) const
Calls fn on each stored shape intersecting a query shape.
Definition shapetree.hpp:1679
std::vector< ShapeType > reportContainedIn(const Q &q) const
Returns copies of the stored shapes contained in a query shape.
Definition shapetree.hpp:1739
const ShapeType & nearestNeighborLInf(const Q &q) const
Definition shapetree.hpp:1913
bool has(const ShapeType &shape) const
Returns whether a shape equal to shape is stored in the tree.
Definition shapetree.hpp:1790
std::vector< ShapeType > kNearestNeighbors(const Q &q, int k) const
Returns up to k stored shapes nearest to a query shape.
Definition shapetree.hpp:1852
WeightType sumContainedIn(const Q &q) const
Sums the weights of the stored shapes contained in a query shape.
Definition shapetree.hpp:1724
bool visitContainedIn(const Q &q, Fn fn) const
Calls fn on each stored shape contained in a query shape.
Definition shapetree.hpp:1761
bool emptyIntersecting(const Q &q) const
Returns whether no stored shape intersects a query shape.
Definition shapetree.hpp:1693
typename Rect::PointType PointType
Definition shapetree.hpp:136
const ShapeType & nearestNeighborL1(const Q &q) const
Definition shapetree.hpp:1886
typename std::vector< ShapeType >::const_iterator const_iterator
Definition shapetree.hpp:144
bool emptyContainedIn(const Q &q) const
Returns whether no stored shape is contained in a query shape.
Definition shapetree.hpp:1775
std::size_t countContainedIn(const Q &q) const
Counts the stored shapes contained in a query shape.
Definition shapetree.hpp:1709
const ShapeType & nearestNeighbor(const Q &q) const
Returns the stored shape nearest to a query shape.
Definition shapetree.hpp:1827
ShapeType value_type
Definition shapetree.hpp:142
typename PointType::NumberType NumberType
Definition shapetree.hpp:137
std::remove_cvref_t< decltype(std::declval< const S & >().bbox())> Rect
Definition shapetree.hpp:135
std::size_t countIntersecting(const Q &q) const
Counts the stored shapes intersecting a query shape.
Definition shapetree.hpp:1616
const ShapeType & nearestNeighbor(const Q &q) const
Definition shapetree.hpp:1834
const_iterator begin() const
Returns an iterator to the first stored shape.
Definition shapetree.hpp:1477
const std::vector< ShapeType > & shapes() const
Returns the stored shapes in their internal order.
Definition shapetree.hpp:1472
friend Canvas & operator<<(Canvas &canvas, const ShapeTree &tree)
Draws every node's subtree bounding box to a canvas in pre-order.
Definition shapetree.hpp:1942
void rebuild(std::size_t leafSize=0)
Rebuilds the tree from the stored shapes, restoring its quality.
Definition shapetree.hpp:1543
std::vector< ShapeType > reportIntersecting(const Q &q) const
Returns copies of the stored shapes intersecting a query shape.
Definition shapetree.hpp:1653
std::size_t size_type
Definition shapetree.hpp:143
WeightType sumIntersecting(const Q &q) const
Sums the weights of the stored shapes intersecting a query shape.
Definition shapetree.hpp:1635
std::vector< ShapeType > kNearestNeighbors(const Q &q, int k) const
Definition shapetree.hpp:1859
std::remove_cvref_t< std::invoke_result_t< const WeightFn &, const ShapeType & > > WeightType
Definition shapetree.hpp:138
Closest pair of points by divide and conquer.
Definition forward.hpp:306
Definition forward.hpp:313
Definition forward.hpp:323
Unbounded convex polyhedral primitives.
Definition forward.hpp:358
Definition arrangement.hpp:67
ShapeTree(const Container &) -> ShapeTree< typename Container::value_type >
Shape(const std::variant< T, Ts... > &) -> Shape< detail::shape_point_type_t< T > >
Axis-aligned rectangle stored by minimum and maximum corners.
Definition rectangle.hpp:75