Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
intervaltree.hpp
Go to the documentation of this file.
1#pragma once
2
4
9
10#include <cassert>
11#include <cstddef>
12#include <cstdint>
13#include <limits>
14#include <stdexcept>
15#include <type_traits>
16#include <unordered_set>
17#include <utility>
18#include <vector>
19
20
21namespace pgl {
22
24enum class ProjectionAxis { x, y };
25
26namespace detail {
27
28// Calls a visitor and reports whether it requested traversal to stop. A bool
29// result stops on true; a void result always continues.
30template <class Fn, class Arg>
31[[nodiscard]] bool invokeIntervalTreeVisitor(Fn& fn, const Arg& arg) {
32 if constexpr (std::is_same_v<std::invoke_result_t<Fn&, const Arg&>, bool>) {
33 return fn(arg);
34 } else {
35 fn(arg);
36 return false;
37 }
38}
39
40} // namespace detail
41
72template <class S, ProjectionAxis Axis = ProjectionAxis::x>
74 public:
75 using ShapeType = S;
76 using BoxType = std::remove_cvref_t<decltype(std::declval<const S&>().bbox())>;
77 using NumberType = std::remove_cvref_t<decltype(std::declval<const BoxType&>().min().x())>;
78
80 using size_type = std::size_t;
81 using const_iterator = typename std::vector<ShapeType>::const_iterator;
82 using const_reference = const ShapeType&;
83
84 private:
85 using NodeId = std::uint32_t;
86 static constexpr NodeId invalidNode = std::numeric_limits<NodeId>::max();
87
88 enum class Color : unsigned char { red, black };
89
90 // Fields touched by range queries stay together in a compact array. Node
91 // IDs also index elements_, so no element index or insertion serial is
92 // stored per node; the IDs at least elements_.size() are the tombstones.
93 // `count` is the number of live nodes in the subtree, so a tombstone adds
94 // nothing to it and a subtree with `count == 0` can be skipped entirely.
95 struct QueryNode {
96 NumberType low{};
97 NumberType high{};
98 NumberType minLow{};
99 NumberType maxLow{};
100 NumberType minHigh{};
101 NumberType maxHigh{};
102 std::uint32_t count = 0;
103 NodeId left = invalidNode;
104 NodeId right = invalidNode;
105 };
106
107 // Insertion-only state is kept cold so queries do not pull it into cache.
108 struct MutationNode {
109 NodeId parent = invalidNode;
110 Color color = Color::black;
111 };
112
113 std::vector<ShapeType> elements_;
114 std::vector<QueryNode> nodes_;
115 std::vector<MutationNode> mutationNodes_;
116 NodeId root_ = invalidNode;
117
118 template <class T>
119 [[nodiscard]] static bool less(const T& a, const T& b) {
120 return a < b;
121 }
122
123 template <class A, class B>
124 [[nodiscard]] static bool equivalent(const A& a, const B& b) {
125 return !(a < b) && !(b < a);
126 }
127
128 template <class A, class B>
129 [[nodiscard]] static const auto& minimum(const A& a, const B& b) {
130 return b < a ? b : a;
131 }
132
133 template <class A, class B>
134 [[nodiscard]] static const auto& maximum(const A& a, const B& b) {
135 return a < b ? b : a;
136 }
137
138 [[nodiscard]] const QueryNode& node(NodeId id) const {
139 return nodes_[static_cast<std::size_t>(id)];
140 }
141
142 [[nodiscard]] QueryNode& node(NodeId id) {
143 return nodes_[static_cast<std::size_t>(id)];
144 }
145
146 [[nodiscard]] const MutationNode& mutationNode(NodeId id) const {
147 return mutationNodes_[static_cast<std::size_t>(id)];
148 }
149
150 [[nodiscard]] MutationNode& mutationNode(NodeId id) {
151 return mutationNodes_[static_cast<std::size_t>(id)];
152 }
153
154 // Live nodes own the shape stored at their own ID, and removals keep them
155 // packed in the first elements_.size() slots, so anything past that is a
156 // tombstone: still in the tree, matching nothing.
157 [[nodiscard]] bool live(NodeId id) const {
158 return static_cast<std::size_t>(id) < elements_.size();
159 }
160
161 [[nodiscard]] const ShapeType& shapeOf(NodeId id) const {
162 return elements_[static_cast<std::size_t>(id)];
163 }
164
165 // Every live node owns exactly one stored shape, so the nodes left over
166 // are the tombstones.
167 [[nodiscard]] std::size_t tombstones() const {
168 return nodes_.size() - elements_.size();
169 }
170
171 [[nodiscard]] static Color colorOf(const IntervalTree& tree, NodeId id) {
172 return id == invalidNode ? Color::black : tree.mutationNode(id).color;
173 }
174
175 template <class Shape>
176 [[nodiscard]] static auto project(const Shape& shape) {
177 const auto box = shape.bbox();
178 if constexpr (Axis == ProjectionAxis::x) {
179 return std::pair{box.min().x(), box.max().x()};
180 } else {
181 return std::pair{box.min().y(), box.max().y()};
182 }
183 }
184
185 [[nodiscard]] static bool intervalLess(const NumberType& lowA, const NumberType& highA,
186 const NumberType& lowB, const NumberType& highB) {
187 if (less(lowA, lowB)) {
188 return true;
189 }
190 if (less(lowB, lowA)) {
191 return false;
192 }
193 return less(highA, highB);
194 }
195
196 [[nodiscard]] bool keyLess(const NumberType& low, const NumberType& high,
197 NodeId id, NodeId otherId) const {
198 const QueryNode& other = node(otherId);
199 if (intervalLess(low, high, other.low, other.high)) {
200 return true;
201 }
202 if (intervalLess(other.low, other.high, low, high)) {
203 return false;
204 }
205 return id < otherId;
206 }
207
208 void update(NodeId id) {
209 if (id == invalidNode) {
210 return;
211 }
212 // A tombstone still contributes its own endpoints to the extrema.
213 // Keeping them is conservative: pruning stays correct and merely loses
214 // a little sharpness until the next rebuild.
215 QueryNode& n = node(id);
216 n.minLow = n.maxLow = n.low;
217 n.minHigh = n.maxHigh = n.high;
218 n.count = live(id) ? 1 : 0;
219 for (const NodeId child : {n.left, n.right}) {
220 if (child == invalidNode) {
221 continue;
222 }
223 const QueryNode& c = node(child);
224 n.minLow = minimum(n.minLow, c.minLow);
225 n.maxLow = maximum(n.maxLow, c.maxLow);
226 n.minHigh = minimum(n.minHigh, c.minHigh);
227 n.maxHigh = maximum(n.maxHigh, c.maxHigh);
228 n.count += c.count;
229 }
230 }
231
232 void updateUpward(NodeId id) {
233 while (id != invalidNode) {
234 update(id);
235 id = mutationNode(id).parent;
236 }
237 }
238
239 // Moves a node into a slot nothing refers to, repointing its parent and
240 // children. The tree keeps its shape and its augmented values; only the
241 // node's identity changes. That is safe precisely because the ID is a mere
242 // tie-break between equal intervals: every search orders by interval alone,
243 // so equal intervals stay a contiguous run whatever their IDs are, and a
244 // relabeled node stays inside its own run.
245 void relocateNode(NodeId from, NodeId to) {
246 nodes_[static_cast<std::size_t>(to)] = std::move(nodes_[static_cast<std::size_t>(from)]);
247 mutationNodes_[static_cast<std::size_t>(to)] =
248 mutationNodes_[static_cast<std::size_t>(from)];
249
250 const QueryNode& moved = node(to);
251 for (const NodeId child : {moved.left, moved.right}) {
252 if (child != invalidNode) {
253 mutationNode(child).parent = to;
254 }
255 }
256 const NodeId parent = mutationNode(to).parent;
257 if (parent == invalidNode) {
258 root_ = to;
259 } else if (node(parent).left == from) {
260 node(parent).left = to;
261 } else {
262 node(parent).right = to;
263 }
264 }
265
266 // Appends a slot and moves `from` into it. Nothing else changes if the
267 // growth throws.
268 NodeId relocateNodeToEnd(NodeId from) {
269 nodes_.push_back(QueryNode{});
270 try {
271 mutationNodes_.push_back(MutationNode{});
272 } catch (...) {
273 nodes_.pop_back();
274 throw;
275 }
276 const NodeId to = static_cast<NodeId>(nodes_.size() - 1);
277 relocateNode(from, to);
278 return to;
279 }
280
281 // Puts a fresh red node in `slot`, which is the element index of the shape
282 // it will own. That slot is either one past the last node or the first
283 // tombstone, which moves to the end to make room. One ID is left unused so
284 // that a removal always has a spare slot to swap two nodes through.
285 [[nodiscard]] NodeId allocateNode(const NumberType& low, const NumberType& high, NodeId slot) {
286 if (nodes_.size() + 1 >= static_cast<std::size_t>(invalidNode)) {
287 throw std::length_error("IntervalTree exceeds its 32-bit node capacity");
288 }
289
290 QueryNode fresh;
291 fresh.low = fresh.minLow = fresh.maxLow = low;
292 fresh.high = fresh.minHigh = fresh.maxHigh = high;
293 fresh.count = 1;
294 if (static_cast<std::size_t>(slot) == nodes_.size()) {
295 nodes_.push_back(std::move(fresh));
296 try {
297 mutationNodes_.push_back(MutationNode{invalidNode, Color::red});
298 } catch (...) {
299 nodes_.pop_back();
300 throw;
301 }
302 } else {
303 relocateNodeToEnd(slot);
304 node(slot) = std::move(fresh);
305 mutationNode(slot) = MutationNode{invalidNode, Color::red};
306 }
307 return slot;
308 }
309
310 void rotateLeft(NodeId x) {
311 const NodeId y = node(x).right;
312 node(x).right = node(y).left;
313 if (node(y).left != invalidNode) {
314 mutationNode(node(y).left).parent = x;
315 }
316 mutationNode(y).parent = mutationNode(x).parent;
317 if (mutationNode(x).parent == invalidNode) {
318 root_ = y;
319 } else if (x == node(mutationNode(x).parent).left) {
320 node(mutationNode(x).parent).left = y;
321 } else {
322 node(mutationNode(x).parent).right = y;
323 }
324 node(y).left = x;
325 mutationNode(x).parent = y;
326 update(x);
327 update(y);
328 }
329
330 void rotateRight(NodeId x) {
331 const NodeId y = node(x).left;
332 node(x).left = node(y).right;
333 if (node(y).right != invalidNode) {
334 mutationNode(node(y).right).parent = x;
335 }
336 mutationNode(y).parent = mutationNode(x).parent;
337 if (mutationNode(x).parent == invalidNode) {
338 root_ = y;
339 } else if (x == node(mutationNode(x).parent).right) {
340 node(mutationNode(x).parent).right = y;
341 } else {
342 node(mutationNode(x).parent).left = y;
343 }
344 node(y).right = x;
345 mutationNode(x).parent = y;
346 update(x);
347 update(y);
348 }
349
350 void insertFixup(NodeId z) {
351 while (z != root_ && colorOf(*this, mutationNode(z).parent) == Color::red) {
352 const NodeId parent = mutationNode(z).parent;
353 const NodeId grandparent = mutationNode(parent).parent;
354 if (parent == node(grandparent).left) {
355 NodeId uncle = node(grandparent).right;
356 if (colorOf(*this, uncle) == Color::red) {
357 mutationNode(parent).color = Color::black;
358 mutationNode(uncle).color = Color::black;
359 mutationNode(grandparent).color = Color::red;
360 z = grandparent;
361 } else {
362 if (z == node(parent).right) {
363 z = parent;
364 rotateLeft(z);
365 }
366 mutationNode(mutationNode(z).parent).color = Color::black;
367 mutationNode(mutationNode(mutationNode(z).parent).parent).color = Color::red;
368 rotateRight(mutationNode(mutationNode(z).parent).parent);
369 }
370 } else {
371 NodeId uncle = node(grandparent).left;
372 if (colorOf(*this, uncle) == Color::red) {
373 mutationNode(parent).color = Color::black;
374 mutationNode(uncle).color = Color::black;
375 mutationNode(grandparent).color = Color::red;
376 z = grandparent;
377 } else {
378 if (z == node(parent).left) {
379 z = parent;
380 rotateRight(z);
381 }
382 mutationNode(mutationNode(z).parent).color = Color::black;
383 mutationNode(mutationNode(mutationNode(z).parent).parent).color = Color::red;
384 rotateLeft(mutationNode(mutationNode(z).parent).parent);
385 }
386 }
387 }
388 mutationNode(root_).color = Color::black;
389 }
390
391 void insertExisting(const NumberType& low, const NumberType& high, NodeId slot) {
392 const NodeId z = allocateNode(low, high, slot);
393
394 NodeId parent = invalidNode;
395 NodeId current = root_;
396 while (current != invalidNode) {
397 parent = current;
398 if (keyLess(low, high, z, current)) {
399 current = node(current).left;
400 } else {
401 current = node(current).right;
402 }
403 }
404 mutationNode(z).parent = parent;
405 if (parent == invalidNode) {
406 root_ = z;
407 } else if (keyLess(low, high, z, parent)) {
408 node(parent).left = z;
409 } else {
410 node(parent).right = z;
411 }
412 updateUpward(z);
413 insertFixup(z);
414 updateUpward(z);
415 }
416
417 // Discards the node structure, tombstones included, and rebuilds it by
418 // reinserting the surviving shapes in storage order.
419 void rebuildFromElements() {
420 nodes_.clear();
421 mutationNodes_.clear();
422 nodes_.reserve(elements_.size());
423 mutationNodes_.reserve(elements_.size());
424 root_ = invalidNode;
425 for (std::size_t i = 0; i < elements_.size(); ++i) {
426 const auto [low, high] = project(elements_[i]);
427 insertExisting(low, high, static_cast<NodeId>(i));
428 }
429 }
430
431 [[nodiscard]] NodeId minimumNode(NodeId id) const {
432 while (node(id).left != invalidNode) {
433 id = node(id).left;
434 }
435 return id;
436 }
437
438 [[nodiscard]] NodeId successor(NodeId id) const {
439 if (node(id).right != invalidNode) {
440 return minimumNode(node(id).right);
441 }
442 NodeId parent = mutationNode(id).parent;
443 while (parent != invalidNode && id == node(parent).right) {
444 id = parent;
445 parent = mutationNode(parent).parent;
446 }
447 return parent;
448 }
449
450 [[nodiscard]] NodeId lowerBoundInterval(const NumberType& low,
451 const NumberType& high) const {
452 NodeId id = root_;
453 NodeId result = invalidNode;
454 while (id != invalidNode) {
455 const QueryNode& n = node(id);
456 if (intervalLess(n.low, n.high, low, high)) {
457 id = n.right;
458 } else {
459 result = id;
460 id = n.left;
461 }
462 }
463 return result;
464 }
465
466 [[nodiscard]] NodeId findEqualNode(const ShapeType& shape, const NumberType& low,
467 const NumberType& high) const {
468 for (NodeId id = lowerBoundInterval(low, high); id != invalidNode; id = successor(id)) {
469 const QueryNode& n = node(id);
470 if (!equivalent(n.low, low) || !equivalent(n.high, high)) {
471 break;
472 }
473 if (live(id) && shapeOf(id) == shape) {
474 return id;
475 }
476 }
477 return invalidNode;
478 }
479
480 template <class Low, class High>
481 [[nodiscard]] static bool intersects(const QueryNode& n, const Low& low, const High& high) {
482 return !(n.high < low) && !(high < n.low);
483 }
484
485 template <class Low, class High>
486 [[nodiscard]] static bool mayIntersect(const QueryNode& n, const Low& low, const High& high) {
487 return !(n.maxHigh < low) && !(high < n.minLow);
488 }
489
490 template <class Low, class High>
491 [[nodiscard]] static bool allIntersect(const QueryNode& n, const Low& low, const High& high) {
492 return !(high < n.maxLow) && !(n.minHigh < low);
493 }
494
495 template <class Low, class High>
496 [[nodiscard]] static bool containedIn(const QueryNode& n, const Low& low, const High& high) {
497 return !(n.low < low) && !(high < n.low) && !(high < n.high);
498 }
499
500 template <class Low, class High>
501 [[nodiscard]] static bool mayContain(const QueryNode& n, const Low& low, const High& high) {
502 return !(n.maxLow < low) && !(high < n.minLow) && !(high < n.minHigh);
503 }
504
505 template <class Low, class High>
506 [[nodiscard]] static bool allContainedIn(const QueryNode& n, const Low& low, const High& high) {
507 return !(n.minLow < low) && !(high < n.maxLow) && !(high < n.maxHigh);
508 }
509
510 template <class Fn>
511 [[nodiscard]] bool visitAll(NodeId id, Fn& fn) const {
512 if (id == invalidNode) {
513 return false;
514 }
515 const QueryNode& n = node(id);
516 if (n.count == 0) {
517 return false;
518 }
519 if (live(id) && detail::invokeIntervalTreeVisitor(fn, shapeOf(id))) {
520 return true;
521 }
522 return visitAll(n.left, fn) || visitAll(n.right, fn);
523 }
524
525 template <class Low, class High, class Fn>
526 [[nodiscard]] bool visitIntersecting(NodeId id, const Low& low, const High& high,
527 Fn& fn) const {
528 if (id == invalidNode) {
529 return false;
530 }
531 const QueryNode& n = node(id);
532 if (n.count == 0 || !mayIntersect(n, low, high)) {
533 return false;
534 }
535 if (allIntersect(n, low, high)) {
536 return visitAll(id, fn);
537 }
538 if (live(id) && intersects(n, low, high) &&
539 detail::invokeIntervalTreeVisitor(fn, shapeOf(id))) {
540 return true;
541 }
542 return visitIntersecting(n.left, low, high, fn) ||
543 visitIntersecting(n.right, low, high, fn);
544 }
545
546 template <class Low, class High, class Fn>
547 [[nodiscard]] bool visitContainedIn(NodeId id, const Low& low, const High& high,
548 Fn& fn) const {
549 if (id == invalidNode) {
550 return false;
551 }
552 const QueryNode& n = node(id);
553 if (n.count == 0 || !mayContain(n, low, high)) {
554 return false;
555 }
556 if (allContainedIn(n, low, high)) {
557 return visitAll(id, fn);
558 }
559 if (live(id) && containedIn(n, low, high) &&
560 detail::invokeIntervalTreeVisitor(fn, shapeOf(id))) {
561 return true;
562 }
563 return visitContainedIn(n.left, low, high, fn) ||
564 visitContainedIn(n.right, low, high, fn);
565 }
566
567 // The unqualified public query family uses the interval tree only as a
568 // necessary-condition filter, then applies the same exact shape predicate
569 // as ShapeTree. A subtree cannot be accepted wholesale here: matching
570 // projections do not imply that the original shapes match.
571 template <class Low, class High, class Q, class Fn>
572 [[nodiscard]] bool visitShapeIntersecting(NodeId id, const Low& low, const High& high,
573 const Q& q, Fn& fn) const {
574 if (id == invalidNode) {
575 return false;
576 }
577 const QueryNode& n = node(id);
578 if (n.count == 0 || !mayIntersect(n, low, high)) {
579 return false;
580 }
581 if (live(id)) {
582 const ShapeType& shape = shapeOf(id);
583 if (shape.intersects(q) && detail::invokeIntervalTreeVisitor(fn, shape)) {
584 return true;
585 }
586 }
587 return visitShapeIntersecting(n.left, low, high, q, fn) ||
588 visitShapeIntersecting(n.right, low, high, q, fn);
589 }
590
591 template <class Low, class High, class Q, class Fn>
592 [[nodiscard]] bool visitShapeContainedIn(NodeId id, const Low& low, const High& high,
593 const Q& q, Fn& fn) const {
594 if (id == invalidNode) {
595 return false;
596 }
597 const QueryNode& n = node(id);
598 if (n.count == 0 || !mayContain(n, low, high)) {
599 return false;
600 }
601 if (live(id)) {
602 const ShapeType& shape = shapeOf(id);
603 if (q.contains(shape) && detail::invokeIntervalTreeVisitor(fn, shape)) {
604 return true;
605 }
606 }
607 return visitShapeContainedIn(n.left, low, high, q, fn) ||
608 visitShapeContainedIn(n.right, low, high, q, fn);
609 }
610
611 template <class Low, class High>
612 [[nodiscard]] std::size_t countIntersecting(NodeId id, const Low& low,
613 const High& high) const {
614 if (id == invalidNode) {
615 return 0;
616 }
617 const QueryNode& n = node(id);
618 if (n.count == 0 || !mayIntersect(n, low, high)) {
619 return 0;
620 }
621 if (allIntersect(n, low, high)) {
622 return n.count;
623 }
624 return (live(id) && intersects(n, low, high) ? 1 : 0) +
625 countIntersecting(n.left, low, high) + countIntersecting(n.right, low, high);
626 }
627
628 template <class Low, class High>
629 [[nodiscard]] std::size_t countContainedIn(NodeId id, const Low& low,
630 const High& high) const {
631 if (id == invalidNode) {
632 return 0;
633 }
634 const QueryNode& n = node(id);
635 if (n.count == 0 || !mayContain(n, low, high)) {
636 return 0;
637 }
638 if (allContainedIn(n, low, high)) {
639 return n.count;
640 }
641 return (live(id) && containedIn(n, low, high) ? 1 : 0) +
642 countContainedIn(n.left, low, high) + countContainedIn(n.right, low, high);
643 }
644
645 public:
646 IntervalTree() = default;
647
649 template <class Container>
650 explicit IntervalTree(const Container& shapes) {
651 if constexpr (requires { shapes.size(); }) {
652 const std::size_t count = static_cast<std::size_t>(shapes.size());
653 if (count > static_cast<std::size_t>(invalidNode)) {
654 throw std::length_error("IntervalTree exceeds its 32-bit node capacity");
655 }
656 elements_.reserve(count);
657 nodes_.reserve(count);
658 mutationNodes_.reserve(count);
659 }
660 for (const auto& shape : shapes) {
661 insert(shape);
662 }
663 }
664
666 [[nodiscard]] std::size_t size() const {
667 return elements_.size();
668 }
669
671 [[nodiscard]] bool empty() const {
672 return elements_.empty();
673 }
674
676 [[nodiscard]] const std::vector<ShapeType>& shapes() const {
677 return elements_;
678 }
679
681 [[nodiscard]] const_iterator begin() const { return elements_.begin(); }
683 [[nodiscard]] const_iterator end() const { return elements_.end(); }
685 [[nodiscard]] const_iterator cbegin() const { return elements_.cbegin(); }
687 [[nodiscard]] const_iterator cend() const { return elements_.cend(); }
688
690 void insert(const ShapeType& shape) {
691 const auto [low, high] = project(shape);
692 elements_.push_back(shape);
693 try {
694 insertExisting(low, high, static_cast<NodeId>(elements_.size() - 1));
695 } catch (...) {
696 elements_.pop_back();
697 throw;
698 }
699 }
700
721 bool erase(const ShapeType& shape) {
722 if (root_ == invalidNode) {
723 return false;
724 }
725 const auto [low, high] = project(shape);
726 const NodeId id = findEqualNode(shape, low, high);
727 if (id == invalidNode) {
728 return false;
729 }
730
731 // The node ID is the element index, so the slot that has to become a
732 // tombstone is the last live one. When the removed shape is not already
733 // there, the last live node and the removed one exchange slots through
734 // a temporary at the end, each move keeping the tree structure intact.
735 const NodeId last = static_cast<NodeId>(elements_.size() - 1);
736 if (id != last) {
737 const NodeId temporary = relocateNodeToEnd(id);
738 relocateNode(last, id);
739 relocateNode(temporary, last);
740 nodes_.pop_back();
741 mutationNodes_.pop_back();
742 elements_[static_cast<std::size_t>(id)] =
743 std::move(elements_[static_cast<std::size_t>(last)]);
744 }
745 elements_.pop_back();
746
747 // The node now in slot `last` is the tombstone: it and its ancestors
748 // lose it from their live counts.
749 updateUpward(last);
750
751 if (tombstones() > elements_.size()) {
752 rebuildFromElements();
753 }
754 return true;
755 }
756
758 [[nodiscard]] bool has(const ShapeType& shape) const {
759 if (root_ == invalidNode) {
760 return false;
761 }
762 const auto [low, high] = project(shape);
763 return findEqualNode(shape, low, high) != invalidNode;
764 }
765
767 template <class Q>
768 [[nodiscard]] std::size_t countProjectionsIntersecting(const Q& q) const {
769 if (root_ == invalidNode) {
770 return 0;
771 }
772 const auto [low, high] = project(q);
773 return countIntersecting(root_, low, high);
774 }
775
777 template <class Q>
778 [[nodiscard]] std::vector<ShapeType> reportProjectionsIntersecting(const Q& q) const {
779 std::vector<ShapeType> out;
780 if (root_ != invalidNode) {
781 const auto [low, high] = project(q);
782 auto append = [&out](const ShapeType& shape) { out.push_back(shape); };
783 (void)visitIntersecting(root_, low, high, append);
784 }
785 return out;
786 }
787
789 template <class Q, class Fn>
790 bool visitProjectionsIntersecting(const Q& q, Fn fn) const {
791 if (root_ == invalidNode) {
792 return false;
793 }
794 const auto [low, high] = project(q);
795 return visitIntersecting(root_, low, high, fn);
796 }
797
799 template <class Q>
800 [[nodiscard]] bool emptyProjectionsIntersecting(const Q& q) const {
801 return visitProjectionsIntersecting(q, [](const ShapeType&) { return true; }) == false;
802 }
803
805 template <class Q>
806 [[nodiscard]] std::size_t countProjectionsContainedIn(const Q& q) const {
807 if (root_ == invalidNode) {
808 return 0;
809 }
810 const auto [low, high] = project(q);
811 return countContainedIn(root_, low, high);
812 }
813
815 template <class Q>
816 [[nodiscard]] std::vector<ShapeType> reportProjectionsContainedIn(const Q& q) const {
817 std::vector<ShapeType> out;
818 if (root_ != invalidNode) {
819 const auto [low, high] = project(q);
820 auto append = [&out](const ShapeType& shape) { out.push_back(shape); };
821 (void)visitContainedIn(root_, low, high, append);
822 }
823 return out;
824 }
825
827 template <class Q, class Fn>
828 bool visitProjectionsContainedIn(const Q& q, Fn fn) const {
829 if (root_ == invalidNode) {
830 return false;
831 }
832 const auto [low, high] = project(q);
833 return visitContainedIn(root_, low, high, fn);
834 }
835
837 template <class Q>
838 [[nodiscard]] bool emptyProjectionsContainedIn(const Q& q) const {
839 return visitProjectionsContainedIn(q, [](const ShapeType&) { return true; }) == false;
840 }
841
848 template <class Q>
849 [[nodiscard]] std::size_t countIntersecting(const Q& q) const {
850 std::size_t count = 0;
851 (void)visitIntersecting(q, [&](const ShapeType&) { ++count; });
852 return count;
853 }
854
856 template <class Q>
857 [[nodiscard]] std::vector<ShapeType> reportIntersecting(const Q& q) const {
858 std::vector<ShapeType> out;
859 (void)visitIntersecting(q, [&](const ShapeType& shape) { out.push_back(shape); });
860 return out;
861 }
862
869 template <class Q, class Fn>
870 bool visitIntersecting(const Q& q, Fn fn) const {
871 if (root_ == invalidNode) {
872 return false;
873 }
874 const auto [low, high] = project(q);
875 return visitShapeIntersecting(root_, low, high, q, fn);
876 }
877
879 template <class Q>
880 [[nodiscard]] bool emptyIntersecting(const Q& q) const {
881 return !visitIntersecting(q, [](const ShapeType&) { return true; });
882 }
883
890 template <class Q>
891 [[nodiscard]] std::size_t countContainedIn(const Q& q) const {
892 std::size_t count = 0;
893 (void)visitContainedIn(q, [&](const ShapeType&) { ++count; });
894 return count;
895 }
896
898 template <class Q>
899 [[nodiscard]] std::vector<ShapeType> reportContainedIn(const Q& q) const {
900 std::vector<ShapeType> out;
901 (void)visitContainedIn(q, [&](const ShapeType& shape) { out.push_back(shape); });
902 return out;
903 }
904
906 template <class Q, class Fn>
907 bool visitContainedIn(const Q& q, Fn fn) const {
908 if (root_ == invalidNode) {
909 return false;
910 }
911 const auto [low, high] = project(q);
912 return visitShapeContainedIn(root_, low, high, q, fn);
913 }
914
916 template <class Q>
917 [[nodiscard]] bool emptyContainedIn(const Q& q) const {
918 return !visitContainedIn(q, [](const ShapeType&) { return true; });
919 }
920};
921
922template <class Container>
924
925// -----------------------------------------------------------------------------
926// Polygon::untangle runtime implementation
927
928template <class PointType, class LabelType>
929void Polygon<PointType, LabelType>::untangleRuntime() {
930 using Edge = Segment<PointType>;
931
932 // IntervalTree needs the boundary occurrence as well as its geometry: equal
933 // segments can occur more than once in a non-simple ring and remain distinct
934 // candidates for a batch.
935 struct IndexedEdge {
936 std::size_t index;
937 Edge segment;
938
939 [[nodiscard]] auto bbox() const {
940 return segment.bbox();
941 }
942 };
943
944 struct EdgeId {
945 std::size_t first;
946 std::size_t second;
947 };
948
949 struct Flip {
950 EdgeId first;
951 EdgeId second;
952 };
953
954 // Vertex occurrences, unlike coordinates, are unique. They let a selected
955 // edge be found after an earlier flip in the same batch has moved it to a
956 // different array position or reversed its direction.
957 std::vector<std::size_t> vertexIds(points_.size());
958 for (std::size_t i = 0; i < vertexIds.size(); ++i) {
959 vertexIds[i] = i;
960 }
961
962 const auto edge = [this](std::ptrdiff_t a) {
963 const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(points_.size());
964 return Edge(points_[static_cast<std::size_t>(a)],
965 points_[static_cast<std::size_t>((a + 1) % n)]);
966 };
967
968 while (points_.size() >= 3) {
969 const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(points_.size());
970
971 std::vector<IndexedEdge> edges;
972 edges.reserve(points_.size());
973 for (std::ptrdiff_t i = 0; i < n; ++i) {
974 edges.push_back({static_cast<std::size_t>(i), edge(i)});
975 }
976 const IntervalTree<IndexedEdge> tree(edges);
977
978 // Greedily choose a matching in the crossing graph. No boundary edge is
979 // selected twice, so each unprocessed selected edge survives earlier
980 // 2-opt reversals in this batch as the same undirected segment.
981 std::unordered_set<std::size_t> flipped;
982 flipped.reserve(points_.size());
983 std::vector<Flip> flips;
984 flips.reserve(points_.size() / 2);
985
986 for (const IndexedEdge& current : edges) {
987 if (flipped.contains(current.index)) {
988 continue;
989 }
990 (void)tree.visitProjectionsIntersecting(current, [&](const IndexedEdge& candidate) {
991 if (candidate.index == current.index || flipped.contains(candidate.index) ||
992 !current.segment.crosses(candidate.segment)) {
993 return false;
994 }
995
996 flipped.insert(current.index);
997 flipped.insert(candidate.index);
998 const auto next = [size = points_.size()](std::size_t i) {
999 return (i + 1) % size;
1000 };
1001 flips.push_back({
1002 {vertexIds[current.index], vertexIds[next(current.index)]},
1003 {vertexIds[candidate.index], vertexIds[next(candidate.index)]}
1004 });
1005 return true;
1006 });
1007 }
1008
1009 if (!flips.empty()) {
1010 const auto findEdge = [&vertexIds](const EdgeId& wanted) {
1011 const std::size_t size = vertexIds.size();
1012 for (std::size_t i = 0; i < size; ++i) {
1013 const std::size_t a = vertexIds[i];
1014 const std::size_t b = vertexIds[(i + 1) % size];
1015 if ((a == wanted.first && b == wanted.second) ||
1016 (a == wanted.second && b == wanted.first)) {
1017 return static_cast<std::ptrdiff_t>(i);
1018 }
1019 }
1020 return std::ptrdiff_t{-1};
1021 };
1022
1023 for (const Flip& flip : flips) {
1024 std::ptrdiff_t i = findEdge(flip.first);
1025 std::ptrdiff_t j = findEdge(flip.second);
1026 if (i < 0 || j < 0 || i == j) {
1027 assert(false && "a selected edge must survive earlier disjoint flips");
1028 continue;
1029 }
1030 if (j < i) {
1031 std::swap(i, j);
1032 }
1033 if (!edge(i).crosses(edge(j))) {
1034 assert(false && "a selected crossing must survive earlier disjoint flips");
1035 continue;
1036 }
1037 std::reverse(points_.begin() + (i + 1), points_.begin() + (j + 1));
1038 std::reverse(vertexIds.begin() + (i + 1), vertexIds.begin() + (j + 1));
1039 }
1040 continue; // rebuild the interval tree for the new boundary
1041 }
1042
1043 // A projection query found no transversal crossings. Residual
1044 // self-contact must therefore be removed as in the constexpr path.
1045 bool removed = false;
1046 for (std::ptrdiff_t k = 0; k < n && !removed; ++k) {
1047 if (points_[static_cast<std::size_t>(k)] ==
1048 points_[static_cast<std::size_t>((k + 1) % n)]) {
1049 points_.erase(points_.begin() + k);
1050 vertexIds.erase(vertexIds.begin() + k);
1051 removed = true;
1052 break;
1053 }
1054 for (std::ptrdiff_t e = 0; e < n; ++e) {
1055 if (e == k || e == (k - 1 + n) % n) {
1056 continue;
1057 }
1058 if (edge(e).contains(points_[static_cast<std::size_t>(k)])) {
1059 points_.erase(points_.begin() + k);
1060 vertexIds.erase(vertexIds.begin() + k);
1061 removed = true;
1062 break;
1063 }
1064 }
1065 }
1066 if (!removed) {
1067 break;
1068 }
1069 }
1070
1071 normalize();
1072 resetCache();
1073}
1074
1075} // namespace pgl
Mutable interval tree over the projection of bounded shapes.
Definition intervaltree.hpp:73
std::size_t size_type
Definition intervaltree.hpp:80
std::vector< ShapeType > reportContainedIn(const Q &q) const
Returns copies of stored shapes geometrically contained in q.
Definition intervaltree.hpp:899
bool visitContainedIn(const Q &q, Fn fn) const
Visits stored shapes geometrically contained in q.
Definition intervaltree.hpp:907
std::size_t countIntersecting(const Q &q) const
Counts stored shapes that geometrically intersect q.
Definition intervaltree.hpp:849
void insert(const ShapeType &shape)
Inserts shape and its selected closed bounding-box interval.
Definition intervaltree.hpp:690
ShapeType value_type
Definition intervaltree.hpp:79
std::size_t countProjectionsIntersecting(const Q &q) const
Counts shapes whose projected interval intersects the projection of q.
Definition intervaltree.hpp:768
const std::vector< ShapeType > & shapes() const
Returns the stored shapes in internal storage order.
Definition intervaltree.hpp:676
bool visitProjectionsIntersecting(const Q &q, Fn fn) const
Visits projected-interval intersections, stopping early if fn returns true.
Definition intervaltree.hpp:790
bool emptyProjectionsIntersecting(const Q &q) const
Returns whether no stored projected interval intersects the projection of q.
Definition intervaltree.hpp:800
bool erase(const ShapeType &shape)
Removes one stored shape equal to shape.
Definition intervaltree.hpp:721
typename std::vector< ShapeType >::const_iterator const_iterator
Definition intervaltree.hpp:81
bool visitProjectionsContainedIn(const Q &q, Fn fn) const
Visits projected intervals contained in q, stopping early if fn returns true.
Definition intervaltree.hpp:828
bool emptyContainedIn(const Q &q) const
Returns whether no stored shape is geometrically contained in q.
Definition intervaltree.hpp:917
bool visitIntersecting(const Q &q, Fn fn) const
Visits stored shapes that geometrically intersect q.
Definition intervaltree.hpp:870
IntervalTree()=default
S ShapeType
Definition intervaltree.hpp:75
const ShapeType & const_reference
Definition intervaltree.hpp:82
bool emptyIntersecting(const Q &q) const
Returns whether no stored shape geometrically intersects q.
Definition intervaltree.hpp:880
const_iterator end() const
Returns a constant iterator past the last stored shape.
Definition intervaltree.hpp:683
std::remove_cvref_t< decltype(std::declval< const S & >().bbox())> BoxType
Definition intervaltree.hpp:76
std::vector< ShapeType > reportIntersecting(const Q &q) const
Returns copies of stored shapes that geometrically intersect q.
Definition intervaltree.hpp:857
const_iterator cbegin() const
Returns a constant iterator to the first stored shape.
Definition intervaltree.hpp:685
bool has(const ShapeType &shape) const
Returns whether a shape equal to shape is stored.
Definition intervaltree.hpp:758
std::size_t size() const
Returns the number of stored shapes.
Definition intervaltree.hpp:666
IntervalTree(const Container &shapes)
Builds a tree by inserting every shape in shapes.
Definition intervaltree.hpp:650
std::remove_cvref_t< decltype(std::declval< const BoxType & >().min().x())> NumberType
Definition intervaltree.hpp:77
bool empty() const
Returns whether no shape is stored.
Definition intervaltree.hpp:671
bool emptyProjectionsContainedIn(const Q &q) const
Returns whether no stored projected interval is contained in that of q.
Definition intervaltree.hpp:838
std::vector< ShapeType > reportProjectionsContainedIn(const Q &q) const
Returns copies of shapes whose projected interval is contained in that of q.
Definition intervaltree.hpp:816
std::vector< ShapeType > reportProjectionsIntersecting(const Q &q) const
Returns copies of shapes whose projected interval intersects that of q.
Definition intervaltree.hpp:778
const_iterator begin() const
Returns a constant iterator to the first stored shape.
Definition intervaltree.hpp:681
std::size_t countProjectionsContainedIn(const Q &q) const
Counts shapes whose projected interval is contained in the projection of q.
Definition intervaltree.hpp:806
std::size_t countContainedIn(const Q &q) const
Counts stored shapes geometrically contained in q.
Definition intervaltree.hpp:891
const_iterator cend() const
Returns a constant iterator past the last stored shape.
Definition intervaltree.hpp:687
Definition arrangement.hpp:67
ProjectionAxis
Axis used to project a shape's bounding box into an interval.
Definition intervaltree.hpp:24
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
@ edge
Definition bitmatrix.hpp:37
IntervalTree(const Container &) -> IntervalTree< typename Container::value_type >
Shape(const std::variant< T, Ts... > &) -> Shape< detail::shape_point_type_t< T > >
Static 2D shape tree over any bounded shape (one exposing bbox()).
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
constexpr Rectangle< PointType > bbox() const
Returns the bounding box of the segment.
Definition bounding.hpp:72