Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
intersections.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "algorithm/graph.hpp"
4
12
13#include <array>
14#include <cassert>
15#include <functional>
16#include <map>
17#include <queue>
18#include <set>
19#include <type_traits>
20#include <unordered_set>
21#include <utility>
22
23
24namespace pgl::detail{
25
41template <class Coefficient, class Part, class Fraction>
42struct sweepHeightNumber {
43 using type = promoted_number_t<std::common_type_t<Coefficient, Part>>;
44};
45
46template <class Int, class Part, class Fraction>
47struct sweepHeightNumber<pgl::Rational<Int>, Part, Fraction> {
48 using type = Fraction;
49};
50
51template <class Coefficient, class Part, class Fraction>
52using sweepHeightNumber_t = typename sweepHeightNumber<Coefficient, Part, Fraction>::type;
53
54template <class Rational, SegmentConcept Segment>
55class BentleyOttmann {
56 using Point = Segment::PointType;
57 using Number = Point::NumberType;
58 static_assert(!std::is_floating_point_v<Number>,
59 "Bentley-Ottmann requires exact (non-floating-point) input "
60 "coordinates; the sweep line's predicates are not robust under "
61 "rounding. Use integer or rational coordinates.");
62 using Rectangle = pgl::Rectangle<Point>;
63 // The point label rides along with the point type through
64 // Segment::intersection, so these must carry the input's label to name the
65 // alternatives that variant actually holds. Spelling them without it made
66 // the sweep instantiable only for unlabelled points.
67 using RPoint = pgl::Point<Rational, typename Point::LabelType>;
68 using RSegment = pgl::Segment<RPoint>;
69 using CrossingPair = std::array<Segment,2>;
70
71 // Integer types the status order's arithmetic runs in. `Integer` is what
72 // the sweep abscissa's numerator and denominator are; `Wide` holds a
73 // degree-three product of coordinate differences, which is as far as the
74 // height comparison's coefficients go; `Exact` multiplies one of those by
75 // one of the abscissa's parts, and is the widest quantity the sweep forms.
76 using Integer = pgl::rational_int_t<Rational>;
77 using Coordinate = pgl::detail::promoted_number_t<Number>;
78 using Wide = pgl::detail::promoted_number_t<Coordinate>;
79 using Exact = pgl::detail::sweepHeightNumber_t<Wide, Integer, Rational>;
80
81 enum class EventEnum {
82 RIGHT, CROSS, VERTICAL, LEFT
83 };
84
85 struct Event {
86 Rational x;
87 // The abscissa in double, with the bound that makes it a proof. Two
88 // events are ordered by their abscissas, and the queue compares
89 // O(log n) pairs per push and per pop; comparing fractions over
90 // arbitrary-precision parts exactly, every time, costs far more than
91 // the events whose abscissas are nowhere near each other are worth.
92 pgl::detail::Approximate approx;
93 EventEnum type;
94 Segment s1;
95
96 Event(Rational x_, EventEnum type_, Segment s1_)
97 : x(std::move(x_)), approx(pgl::detail::approximate(x)),
98 type(type_), s1(std::move(s1_)) {}
99
100 auto operator<(const Event &other) const { // Order is backwards by x
101 const std::partial_ordering filtered =
102 pgl::detail::approximateSign(other.approx - approx);
103 if (filtered != std::partial_ordering::unordered) {
104 return filtered < 0;
105 }
106 return other.x < x;
107 }
108
109 friend std::ostream &operator<<(std::ostream &out, const Event &e) {
110 return out << e.type << "@" << e.x << ": " << e.s1;
111 }
112 };
113
114 std::priority_queue<Event> queue;
115 Rectangle bbox;
116
126 struct Abscissa {
127 Rational x;
128 Integer num{};
129 Integer den{1};
130 // The same value in double, with the bound that makes it a proof. A
131 // fraction over arbitrary-precision parts reaches double the slow way,
132 // through a long double per part, so converting it once per step rather
133 // than once per comparison is the difference between the filter paying
134 // for itself and not.
135 pgl::detail::Approximate approx{};
136 };
137
138 // Where the sweep stands. The status tree's order is the order *at this
139 // abscissa*, so it is only ever moved between emptying the tree of the
140 // segments the move reorders and putting them back.
141 Abscissa line;
142
143 // A plain function object rather than a std::function: the tree calls this
144 // O(log n) times per operation and millions of times per sweep, and the
145 // type-erased call cannot be inlined.
146 struct AlongLine {
147 const BentleyOttmann *sweep;
148 bool operator()(const Segment &a, const Segment &b) const {
149 return sweep->CompareAlongLine(a, b);
150 }
151 };
152 using Tree = std::set<Segment, AlongLine>;
153 Tree tree;
154
155 // This step's events, split by kind; see @ref getEvents.
156 std::array<std::vector<Event>, 4> events;
157
168 struct Run {
169 typename Tree::iterator node;
170 std::vector<Segment> segments;
171 };
172
173 // Hashed, not ordered: these accumulate one entry per reported pair, and
174 // the sweep only ever asks whether a pair is already in them. Ordering them
175 // as they are built charges a log-sized run of segment comparisons, and a
176 // red-black node, for every crossing found; the results are put in order
177 // once, at the end, where it costs a single sort.
178 struct CrossingPairHash {
179 std::size_t operator()(const CrossingPair &pair) const {
180 std::size_t seed = 0;
181 pgl::detail::hashCombine(seed, pair[0]);
182 pgl::detail::hashCombine(seed, pair[1]);
183 return seed;
184 }
185 };
186 using CrossingPairSet = std::unordered_set<CrossingPair, CrossingPairHash>;
187 CrossingPairSet crossingsSet, intersectionSet;
188
189 // The set's contents in the order the public entry points hand them back.
190 static std::vector<CrossingPair> sorted(const CrossingPairSet &pairs) {
191 std::vector<CrossingPair> ordered(pairs.begin(), pairs.end());
192 std::sort(ordered.begin(), ordered.end());
193 return ordered;
194 }
195
196 std::function<bool(const CrossingPair&)> onCrossing = [](const CrossingPair&){return false;},
197 onIntersection = [](const CrossingPair&){return false;};
198 bool onlyCrossings = true;
199 bool stopNow = false;
200
201 bool addCrossing(const CrossingPair &p) {
202 crossingsSet.insert(p);
203 if (onCrossing(p)) {
204 stopNow = true;
205 return true;
206 }
207
208 return false;
209 }
210
211 bool addIntersection(const CrossingPair &p) {
212 intersectionSet.insert(p);
213 if (onIntersection(p)) {
214 stopNow = true;
215 return true;
216 }
217
218 return false;
219 }
220
221 void initQueue(const std::vector<Segment> &segments) {
222 for (const Segment &s :segments) {
223 if(s.isVertical()) {
224 queue.emplace(static_cast<Rational>(s.min().x()), EventEnum::VERTICAL, s);
225 }
226 else {
227 queue.emplace(static_cast<Rational>(s.min().x()), EventEnum::LEFT, s);
228 }
229 }
230 }
231
232 void initBbox(const std::vector<Segment> &segments) {
233 // Min and Max y-coordinate for sentinels
234 bbox = Rectangle(segments[0]);
235 for (const Segment &s :segments) {
236 bbox.insert(s);
237 }
238 // Grow bbox by 1
239 bbox = Rectangle(bbox.min().x()-1, bbox.min().y()-1, bbox.max().x()+1, bbox.max().y()+1);
240 }
241
242 // ── The status order ────────────────────────────────────────────────────
243 //
244 // The tree holds the segments straddling the sweep line in the order their
245 // heights on it run, bottom to top, with segments meeting the line at one
246 // point ordered by which of them leaves that point above. What that order
247 // *is* is fixed by the geometry; everything below is about computing it
248 // without ever evaluating those heights.
249 //
250 // Evaluating them is what the direct implementation does — one `yAtX` per
251 // segment in the sweep's rational type, then a comparison — and it is what
252 // the sweep used to spend itself on. The sweep abscissa is a fraction, so
253 // each height comes out over a wider denominator still, and every one of
254 // the O(log n) comparisons a single tree operation makes builds two of them
255 // through a chain of rational operations that each reduce to lowest terms.
256 // Over 3,000 small segments that comparison alone was 62% of the whole run.
257 //
258 // The difference of the two heights is affine in x, so its sign across the
259 // x-range the two segments share is pinned by its sign at that range's two
260 // ends — and each of those is one endpoint of one segment tested against
261 // the other, an orientation over the *input* coordinates. Only when the
262 // sign differs between the two ends do the segments meet inside the shared
263 // range, and only then does where the sweep sits relative to that meeting
264 // decide anything. That case, and nothing else, touches the abscissa.
265
273 using FilteredEnd = decltype(pgl::detail::filtered<Coordinate>(std::declval<const Point&>()));
274 struct Endpoints {
275 FilteredEnd aLo, aHi, bLo, bHi;
276 };
277
284 int heightSign(const Segment &a, const Segment &b, const Abscissa &at) const {
285 // Four endpoints, each approximated once. Every sign below reads the
286 // same four, and an approximation of an exact coordinate is not cheap:
287 // letting each predicate convert its own operands would do the same
288 // conversion up to five times over one call.
289 const Endpoints ends{pgl::detail::filtered<Coordinate>(a.min()),
290 pgl::detail::filtered<Coordinate>(a.max()),
291 pgl::detail::filtered<Coordinate>(b.min()),
292 pgl::detail::filtered<Coordinate>(b.max())};
293
294 // The height difference at each end of the shared x-range. Whichever
295 // segment contributes the end, its endpoint is tested against the other
296 // segment, and the sign flips when the endpoint is a's, since the
297 // difference is measured b minus a.
298 const int atLeft = a.min().x() < b.min().x()
299 ? pgl::detail::signOf(
300 pgl::detail::orientationSignOf(ends.aLo, ends.aHi, ends.bLo).value())
301 : -pgl::detail::signOf(
302 pgl::detail::orientationSignOf(ends.bLo, ends.bHi, ends.aLo).value());
303 const int atRight = b.max().x() < a.max().x()
304 ? pgl::detail::signOf(
305 pgl::detail::orientationSignOf(ends.aLo, ends.aHi, ends.bHi).value())
306 : -pgl::detail::signOf(
307 pgl::detail::orientationSignOf(ends.bLo, ends.bHi, ends.aHi).value());
308
309 if (atLeft == atRight) {
310 // Same sign at both ends: one segment runs clear of the other
311 // across the whole range, the sweep included. Both ends zero: an
312 // affine function with two roots is the zero function, so the two
313 // segments are collinear and the difference vanishes everywhere.
314 return atLeft;
315 }
316 if (atLeft == 0) {
317 // The segments touch at the range's left end and separate to the
318 // right of it, so all that matters is whether the sweep has left
319 // that end behind — and the end is an input coordinate.
320 return at.x > std::max(a.min().x(), b.min().x()) ? atRight : 0;
321 }
322 if (atRight == 0) {
323 return at.x < std::min(a.max().x(), b.max().x()) ? atLeft : 0;
324 }
325 return crossedHeightSign(a, b, at, ends);
326 }
327
332 bool sameHeight(const Segment &a, const Segment &b, const Abscissa &at) const {
333 return a == b || heightSign(a, b, at) == 0;
334 }
335
344 static std::pair<Wide, Wide> heightCoefficients(const Segment &a, const Segment &b) {
345 const Wide dax = static_cast<Wide>(a.max().x()) - static_cast<Wide>(a.min().x());
346 const Wide day = static_cast<Wide>(a.max().y()) - static_cast<Wide>(a.min().y());
347 const Wide dbx = static_cast<Wide>(b.max().x()) - static_cast<Wide>(b.min().x());
348 const Wide dby = static_cast<Wide>(b.max().y()) - static_cast<Wide>(b.min().y());
349
350 return {dax * dby - dbx * day,
351 dax * dbx * (static_cast<Wide>(b.min().y()) - static_cast<Wide>(a.min().y())) -
352 dax * dby * static_cast<Wide>(b.min().x()) +
353 dbx * day * static_cast<Wide>(a.min().x())};
354 }
355
373 static std::partial_ordering approximateHeightSign(const Endpoints &ends,
374 const Abscissa &at) {
375 const pgl::detail::ApproximatePoint aMin = pgl::detail::approximationOf(ends.aLo);
376 const pgl::detail::ApproximatePoint aMax = pgl::detail::approximationOf(ends.aHi);
377 const pgl::detail::ApproximatePoint bMin = pgl::detail::approximationOf(ends.bLo);
378 const pgl::detail::ApproximatePoint bMax = pgl::detail::approximationOf(ends.bHi);
379 const auto axMin = aMin.x;
380 const auto ayMin = aMin.y;
381 const auto bxMin = bMin.x;
382 const auto byMin = bMin.y;
383 const auto dax = aMax.x - axMin;
384 const auto day = aMax.y - ayMin;
385 const auto dbx = bMax.x - bxMin;
386 const auto dby = bMax.y - byMin;
387
388 const auto slope = dax * dby - dbx * day;
389 const auto offset = dax * dbx * (byMin - ayMin) - dax * dby * bxMin + dbx * day * axMin;
390 return pgl::detail::approximateSign(slope * at.approx + offset);
391 }
392
393 int crossedHeightSign(const Segment &a, const Segment &b, const Abscissa &at,
394 const Endpoints &ends) const {
395 if constexpr (pgl::detail::filtersSign<Wide>) {
396 // Where a coefficient is an arbitrary-precision fraction, forming
397 // the two of them exactly is itself most of what this costs, and
398 // the filter has to come before them rather than after: the whole
399 // expression is evaluated in bounded double arithmetic, and only a
400 // sign those bounds cannot settle pays for the exact coefficients
401 // below. Over machine-integer coefficients the trade goes the other
402 // way — forming them is a few multiplications — and the tighter
403 // filter over the exact pair is the one that runs.
404 const std::partial_ordering approximated = approximateHeightSign(ends, at);
405 if (approximated != std::partial_ordering::unordered) {
406 return pgl::detail::signOf(approximated);
407 }
408 }
409
410 const auto [slope, offset] = heightCoefficients(a, b);
411
412 if constexpr (pgl::detail::filtersSign<Exact>) {
413 // Same bargain the orientation predicates strike: a double
414 // evaluation carrying its own error bound proves the sign outright
415 // for all but the near-degenerate pairs, and only those pay below.
416 //
417 // Filtering the coefficients rather than rebuilding the whole
418 // expression in bounded double arithmetic: the expression is degree
419 // three, and propagating a bound through that many operations
420 // measured slower than forming the coefficients exactly and
421 // converting the two of them.
422 const std::partial_ordering filtered = pgl::detail::approximateSign(
423 pgl::detail::approximate(slope) * at.approx +
424 pgl::detail::approximate(offset));
425 if (filtered != std::partial_ordering::unordered) {
426 return pgl::detail::signOf(filtered);
427 }
428 }
429
430 // Scaled once more by the abscissa's denominator, which is positive,
431 // and there is nothing left to divide.
432 const Exact scaled = static_cast<Exact>(slope) * static_cast<Exact>(at.num) +
433 static_cast<Exact>(offset) * static_cast<Exact>(at.den);
434 return scaled > Exact(0) ? 1 : (scaled < Exact(0) ? -1 : 0);
435 }
436
437 // Compare segments by intersection points vertically along line
438 bool CompareAlongLine (const Segment& a, const Segment& b) const {
439 // Vertical segments are never stored in the set. They reach the
440 // comparator only as a probe, and only ever at their own abscissa —
441 // which is the sweep's, since a vertical segment is probed at the step
442 // its event belongs to. So the probe's own endpoint is the point whose
443 // side of the stored segment is wanted, and no height is needed at all.
444 if (a.isVertical()) {
445 if (b.isVertical()) {
446 return a.min().y() < b.min().y();
447 }
448 assert(line.x == a.min().x());
449 if (a.min().y() < std::min(b.min().y(),b.max().y()))
450 return true;
451
452 if (std::max(b.min().y(),b.max().y()) < a.min().y())
453 return false;
454
455 return pgl::orientationSign(b.min(), b.max(), a.min()) < 0;
456 }
457 if(b.isVertical()) {
458 assert(line.x == b.min().x());
459 if (std::max(a.min().y(),a.max().y()) < b.min().y())
460 return true;
461
462 if (b.min().y() < std::min(a.min().y(),a.max().y()))
463 return false;
464
465 return pgl::orientationSign(a.min(), a.max(), b.min()) > 0;
466 }
467
468 if (std::max(a.min().y(),a.max().y()) < std::min(b.min().y(),b.max().y()))
469 return true;
470
471 if (std::max(b.min().y(),b.max().y()) < std::min(a.min().y(),a.max().y()))
472 return false;
473
474 if (a == b) { // Same segment
475 return false;
476 }
477
478 const int height = heightSign(a, b, line);
479 if (height != 0) {
480 return height > 0;
481 }
482
483 // Segments intersecting line at same point
484 auto o = pgl::orientationSign(a.min(), a.max(), b.max());
485 if (o > 0) {
486 return true;
487 }
488 if (o < 0) {
489 return false;
490 }
491 // One segment is a subset of the other
492 return a < b;
493 }
494
495 // Splits `x` into the parts the height tests read.
496 //
497 // Reducing the fraction here, once per step, is the same work the status
498 // order would otherwise do over and over: every comparison that reaches the
499 // exact fallback reads both parts, and reading either off an unreduced
500 // fraction runs a gcd that is thrown away with the expression's locals.
501 //
502 // Unconditionally, and not @ref Rational::simplifyIfLarge: an abscissa
503 // narrow enough that no operation would reduce it still feeds every
504 // comparison at this step. Measured over a sweep of 3000 segments, gating
505 // on width left the gcd count untouched while reducing outright cut it by
506 // 41%.
507 Abscissa abscissa(Rational x) const {
508 Abscissa at;
509 at.x = std::move(x);
510 if constexpr (pgl::is_Rational_v<Rational>) {
511 at.x.simplify();
512 at.num = at.x.numerator();
513 at.den = at.x.denominator();
514 } else {
515 // pgl::arrangement runs this sweep over a plain integer when its
516 // coordinates are integral, and then the abscissa is whole.
517 at.num = at.x;
518 at.den = Integer(1);
519 }
520 at.approx = pgl::detail::approximate(at.x);
521 return at;
522 }
523
524 void initTree() {
525 tree = Tree(AlongLine{this});
526 tree.emplace(bbox.edges()[0]); // Bottom edge as sentinel
527 tree.emplace(bbox.edges()[2]); // Top edge as sentinel
528 }
529
530 void printTree() const {
531 std::cout << "Tree: ";
532 Segment previous = *tree.begin();
533 for(Segment s : tree) {
534 if (s != *tree.begin()) {
535 if (CompareAlongLine(previous,s)) {
536 std::cout << " < ";
537 } else if (CompareAlongLine(s,previous)) {
538 std::cout << " _>_ ";
539 }
540 else {
541 std::cout << " _=_ ";
542 }
543 }
544 std::cout << s;
545 previous = s;
546 }
547 std::cout << std::endl;
548 };
549
550 void printCrossings() const {
551 std::cout << "Crossings: ";
552 for(auto [sa,sb] : crossingsSet) {
553 auto p = std::get<0>(*sa.template intersection<Rational>(sb));
554 std::cout << sa << "crosses" << sb << " at " << p << "; ";
555 }
556 std::cout << std::endl;
557 }
558
559 void printQueue() const {
560 std::cout << "Queue: ";
561 auto l = queue;
562 while (!l.empty()) {
563 auto ev = l.top();
564 std::cout << ev;
565 l.pop();
566 }
567 std::cout << std::endl;
568 }
569
570 // Moves every event at `currentX` off the queue and into @ref events.
571 //
572 // The buckets are a member reused across steps rather than four fresh
573 // vectors per step: there is a step per event, and the sweep's heap traffic
574 // is worth more than the reallocation saves.
575 void getEvents(const Rational &currentX) {
576 for (std::vector<Event> &bucket : events) {
577 bucket.clear();
578 }
579 do {
580 events[static_cast<std::size_t>(queue.top().type)].push_back(queue.top());
581 queue.pop();
582 } while (!queue.empty() && queue.top().x == currentX);
583 }
584
585 void possibleCrossing(Tree::iterator ita, Tree::iterator itb) {
586 Segment sa = *ita, sb = *itb;
587
588 CrossingPair pair{sa,sb};
589 if (pair[1] < pair[0]) std::swap(pair[0],pair[1]);
590
591 if (sa.crosses(sb) && !crossingsSet.contains(pair)) {
592 RPoint cross = std::get<RPoint>(*sa.template intersection<Rational>(sb));
593 if (cross.x() > line.x) {
594 // assert(CompareAlongLine(sa,sb));
595 queue.emplace(cross.x(), EventEnum::CROSS, sa);
596 addCrossing(pair);
597 }
598 }
599 }
600
601 void processRIGHT(const std::vector<Event> &evts) {
602 for (Event ev : evts) {
603 auto it1 = tree.find(ev.s1);
604 // The segment is located by the status order, so a comparator that
605 // has gone inconsistent with the tree's shape surfaces here as a
606 // miss on a segment the tree still physically holds. Erasing end()
607 // then frees the set's own header node, which turns a wrong order
608 // into heap corruption several steps away from its cause. Dropping
609 // the event loses whatever crossings it would have reported —
610 // wrong, but bounded and diagnosable.
611 assert(it1 != tree.end() && "RIGHT event for a segment not in the status");
612 if (it1 == tree.end()) {
613 continue;
614 }
615 // Both bbox sentinels sit in the tree, so a real segment always has
616 // a neighbour on either side of it.
617 auto it0 = it1; --it0;
618 auto it2 = it1; ++it2;
619 tree.erase(it1);
620
621 possibleCrossing(it0, it2);
622 }
623
624 }
625
626 void getNewCrossEvents(std::vector<Event> &crossEvents, const Rational &currentX) {
627 while (!queue.empty() && queue.top().x == currentX) {
628 crossEvents.push_back(queue.top());
629 queue.pop();
630 }
631 }
632
633 // The lowest segment of the run that meets the sweep line where `s` does,
634 // `s` itself included.
635 //
636 // `s` is located by the tree's own order, so this must be called while the
637 // tree is still ordered where `s` sits in it — which is not necessarily
638 // `at`: the run about to collapse onto one point at `at` is found while the
639 // tree still holds the order that keeps it contiguous.
640 auto findFirst(const Segment &s, const Abscissa &at) {
641 auto it = tree.find(s);
642 while (it != tree.begin() && sameHeight(*it, s, at)) {
643 --it;
644 }
645 ++it;
646 return it;
647 }
648
649 // The segments of the status tree that meet at each of this step's crossing
650 // points, each run in tree order, the runs in no particular order.
651 //
652 // Grouping by identity of the crossing point, rather than by the point
653 // itself: what used to key this by an exact y-coordinate paid for that
654 // coordinate — one `yAtX` per event and a std::map over fractions — to
655 // express something the runs already say, since two segments meet the sweep
656 // line at the same point exactly when they are one run.
657 std::vector<Run> getCrossingSegments(const std::vector<Event> &evts, const Abscissa &at) {
658 std::vector<Run> ret;
659 std::set<Segment> done;
660
661 for (const Event &ev : evts) {
662 if (done.contains(ev.s1)) {
663 continue; // already collected as part of an earlier run
664 }
665 Run run;
666 run.node = findFirst(ev.s1, at);
667 for (auto it = run.node; it != tree.end() && sameHeight(*it, ev.s1, at); ++it) {
668 run.segments.push_back(*it);
669 done.insert(*it);
670 }
671 if (!run.segments.empty()) {
672 ret.push_back(std::move(run));
673 }
674 }
675
676 return ret;
677 }
678
679 void processCROSS(std::vector<Event> &evts, const Rational &currentX) {
680 // 3) Check possible new cross events
681 getNewCrossEvents(evts, currentX);
682
683 // Where the crossings are, split once for the whole step. Built before
684 // the tree is touched but installed as the sweep's own only at 5): the
685 // tree is still ordered at the previous abscissa, and erasing under a
686 // changed order would not find the nodes it is asked for.
687 const Abscissa crossing = abscissa(currentX);
688
689 std::vector<Run> crossingAt = getCrossingSegments(evts, crossing);
690
691 // 4) Do all CROSS removals from tree.
692 // By iterator, walking the run: erasing a node the tree has already
693 // handed us costs no comparison, where erasing by value searches for
694 // it first — and this is one of the two searches per crossing that
695 // used to dominate what a crossing costs.
696 for (const Run &run : crossingAt) {
697 auto it = run.node;
698 for (std::size_t i = 0; i < run.segments.size(); ++i) {
699 it = tree.erase(it);
700 }
701 }
702
703 // 5) Move the line to currentX
704 line = crossing;
705
706 // 6) Do all CROSS insertions to tree
707 for (Run &run : crossingAt) {
708 for (const Segment &s : run.segments) {
709 run.node = tree.insert(s).first;
710 }
711 }
712
713 // 7) Create all CROSS new events
714 for (const Run &run : crossingAt) {
715 // The run is back in the tree, reordered but occupying the same
716 // consecutive positions, and 6) kept one of them. Walking out from
717 // there costs the length of the run; finding it again would cost
718 // the other of the two searches.
719 auto it1 = run.node;
720 while (it1 != tree.begin() &&
721 sameHeight(*std::prev(it1), run.segments.front(), line)) {
722 --it1;
723 }
724 auto it2 = run.node;
725 while (std::next(it2) != tree.end() &&
726 sameHeight(*std::next(it2), run.segments.front(), line)) {
727 ++it2;
728 }
729
730 auto it0 = it1; --it0;
731 auto it3 = it2; ++it3;
732
733 // Possible new crossings
734 possibleCrossing(it0, it1);
735 possibleCrossing(it2, it3);
736 }
737
738 // 8) Add crossings to set
739 for (const Run &run : crossingAt) {
740 const std::vector<Segment> &segs = run.segments;
741 for (size_t i = 0; i+1 < segs.size(); i++) {
742 for (size_t j = i+1; j < segs.size(); j++) {
743 CrossingPair pair{segs[i], segs[j]};
744 if (pair[1] < pair[0]) std::swap(pair[0],pair[1]);
745
746 if (pair[0].crosses(pair[1])) {
747 if (addCrossing(pair)) {
748 return;
749 }
750 }
751 }
752 }
753 }
754 }
755
756 void processRIGHT_interior(const std::vector<Event> &evts) {
757 for (Event ev : evts) {
758 // Find top segment intersecting ev.s1 on line
759 // Use fake vertical segment
760 Segment sv(ev.s1.max().x(), ev.s1.max().y(), ev.s1.max().x(), ev.s1.max().y()+1);
761 auto it = tree.lower_bound(sv);
762
763 for (; it != tree.begin() && it->contains(ev.s1.max()); --it) {
764 }
765 if (it != tree.end()) {
766 ++it;
767 }
768
769 for (; it != tree.end() && it->contains(ev.s1.max()); ++it) {
770 CrossingPair pair{ev.s1,*it};
771 if (pair[1] < pair[0]) std::swap(pair[0],pair[1]);
772 if (addIntersection(pair)) {
773 return;
774 }
775 }
776 }
777 }
778
779 void processVERTICAL(const std::vector<Event> &evts) {
780 for (Event ev : evts) {
781 for (auto it = tree.lower_bound(ev.s1); it != tree.end(); ++it) {
782 if (!ev.s1.intersects(*it))
783 break;
784 if (onlyCrossings) {
785 if (ev.s1.crosses(*it)) {
786 CrossingPair pair{ev.s1, *it};
787 if (pair[1] < pair[0]) std::swap(pair[0],pair[1]);
788 addCrossing(pair);
789 }
790 }
791 else {
792 if (ev.s1.intersects(*it)) {
793 CrossingPair pair{ev.s1, *it};
794 if (pair[1] < pair[0]) std::swap(pair[0],pair[1]);
795 addCrossing(pair);
796 }
797 }
798 }
799 }
800 }
801
802 void processVERTICAL_interior(const std::vector<Event> &v_evts, const std::vector<Event> &r_evts, const std::vector<Event> &l_evts) {
803 std::vector<std::pair<Number, Segment>> order;
804 for (Event ev : l_evts) {
805 order.emplace_back(ev.s1.min().y(), ev.s1);
806 }
807 for (Event ev : r_evts) {
808 order.emplace_back(ev.s1.max().y(), ev.s1);
809 }
810 for (Event ev : v_evts) {
811 order.emplace_back(ev.s1.min().y(), ev.s1);
812 order.emplace_back(ev.s1.max().y(), ev.s1);
813 }
814 std::sort(order.begin(),order.end());
815
816 for (Event ev : v_evts) {
817 auto y1 = ev.s1.min().y();
818 auto y2 = ev.s1.max().y();
819 for (auto it = std::lower_bound(order.begin(), order.end(), std::make_pair(y1, Segment()));
820 it != order.end() && it->first < y2;
821 ++it) {
822 CrossingPair pair{ev.s1, it->second};
823 if (pair[1] < pair[0]) std::swap(pair[0],pair[1]);
824 if (pair[0] != pair[1]) {
825 addIntersection(pair);
826 }
827 }
828 }
829 }
830
831 void processLEFT(const std::vector<Event> &evts) {
832 for (Event ev : evts) {
833 auto [it1,_] = tree.insert(ev.s1);
834 auto it0 = it1; --it0;
835 auto it2 = it1; ++it2;
836
837 queue.emplace(static_cast<Rational>(ev.s1.max().x()), EventEnum::RIGHT, ev.s1);
838 possibleCrossing(it0, it1);
839 possibleCrossing(it1, it2);
840
841 if (!onlyCrossings) {
842 while (it0->contains(ev.s1.min())) {
843 CrossingPair pair{*it0, ev.s1};
844 if (pair[1] < pair[0]) std::swap(pair[0],pair[1]);
845 addIntersection(pair);
846 --it0;
847 }
848 while (it2->contains(ev.s1.min())) {
849 CrossingPair pair{*it2, ev.s1};
850 if (pair[1] < pair[0]) std::swap(pair[0],pair[1]);
851 addIntersection(pair);
852 ++it2;
853 }
854 if (stopNow) {
855 break;
856 }
857 }
858 }
859 }
860
861
862 void run(const std::vector<Segment> &segments) {
863 // Return directly for 0 or 1 segment
864 if (segments.size() <= (size_t) 1)
865 return;
866
867 initQueue(segments);
868 initBbox(segments);
869 line = abscissa(static_cast<Rational>(bbox.min().x()));
870 initTree();
871
872 while (!queue.empty()) {
873 // printTree();
874 // 1) Get all events with same x into events
875 const Rational currentX = queue.top().x;
876 getEvents(currentX);
877
878 // 2) Do all RIGHT events
879 processRIGHT(events[(size_t)EventEnum::RIGHT]);
880
881 // 3) Check possible new cross events
882 // 4) Do all CROSS removals from tree
883 // 5) Move the line to currentX
884 // 6) Do all CROSS insertions to tree
885 // 7) Create all CROSS new events
886 // 8) Add new crossings to the output
887 processCROSS(events[1], currentX);
888
889 if (!onlyCrossings) {
890 processRIGHT_interior(events[(size_t)EventEnum::RIGHT]);
891 if (stopNow)
892 break;
893 }
894
895 // 10) Do all VERTICAL events
896 processVERTICAL(events[(size_t)EventEnum::VERTICAL]);
897 if (!onlyCrossings) {
898 processVERTICAL_interior(events[(size_t)EventEnum::VERTICAL],
899 events[(size_t)EventEnum::RIGHT],
900 events[(size_t)EventEnum::LEFT]);
901 if (stopNow)
902 break;
903 }
904
905 // 11) Do all LEFT events
906 processLEFT(events[(size_t)EventEnum::LEFT]);
907
908 if (stopNow)
909 break;
910 }
911 }
912
913public:
914 std::vector<CrossingPair> findCrossings(const std::vector<Segment> &segments) {
915 onlyCrossings = true;
916 run(segments);
917 return sorted(crossingsSet);
918 }
919
920 std::vector<CrossingPair> findIntersections(const std::vector<Segment> &segments) {
921 onlyCrossings = false;
922 run(segments);
923 intersectionSet.insert(crossingsSet.begin(), crossingsSet.end());
924
925 // Insert segments sharing an endpoint
926 std::map<Point,std::vector<Segment>> adjacent;
927 for (const Segment &s : segments) {
928 adjacent[s.min()].push_back(s);
929 adjacent[s.max()].push_back(s);
930 }
931 for (const auto &[_,segs] : adjacent) {
932 for (size_t i = 0; i+1 < segs.size(); i++) {
933 for (size_t j = i+1; j < segs.size(); j++) {
934 CrossingPair pair{segs[i], segs[j]};
935 if (pair[1] < pair[0]) std::swap(pair[0],pair[1]);
936 intersectionSet.insert(pair);
937 }
938 }
939 }
940
941 return sorted(intersectionSet);
942 }
943
944 bool detectCrossings(const std::vector<Segment> &segments) {
945 onlyCrossings = true;
946 onCrossing = [] (const CrossingPair &) {return true;};
947 run(segments);
948 return !crossingsSet.empty();
949 }
950
951 bool detectIntersections(const std::vector<Segment> &segments) {
952 onlyCrossings = false;
953 onCrossing = [] (const CrossingPair &) {return true;};
954 onIntersection = [] (const CrossingPair &) {return true;};
955 run(segments);
956
957 if (!crossingsSet.empty() || !intersectionSet.empty())
958 return true;
959
960 // Insert segments sharing an endpoint
961 std::set<Point> adjacent;
962 for (const Segment &s : segments) {
963 auto [_1,b1] = adjacent.insert(s.min());
964 if (!b1)
965 return true;
966 auto [_2,b2] = adjacent.insert(s.max());
967 if (!b2)
968 return true;
969 }
970
971 return false;
972 }
973
974 // Tests whether the segments form a simple polygon: every vertex appears in
975 // exactly 2 segments, and the only intersections are those shared vertices.
976 bool testPolygon(const std::vector<Segment> &segments) {
977 onlyCrossings = false;
978 bool notSimple = false;
979 onCrossing = [&notSimple] (const CrossingPair &) {notSimple = true; return true;};
980 size_t count = 0;
981 size_t n = segments.size();
982 onIntersection = [n,&count, &notSimple] (const CrossingPair &p) {
983 if (p[0].collinear(p[1]) && p[0].interiorsIntersect(p[1])) {
984 notSimple = true; // Collinear overlap
985 return true;
986 }
987 const int shared = (p[0].min() == p[1].min()) + (p[0].min() == p[1].max())
988 + (p[0].max() == p[1].min()) + (p[0].max() == p[1].max());
989 if (!shared) {
990 notSimple = true; // Vertex inside an edge
991 return true;
992 }
993 count++;
994 if (count > 2*n) { // A vertex appears twice
995 notSimple = true;
996 }
997 return notSimple;
998 };
999 run(segments);
1000
1001 return !notSimple;
1002 }
1003
1004 // Tests if every vertex appears in exactly 2 segments
1005 // except for two vertices appearing only once
1006 // and has no intersection elsewhere
1007 bool testPolyLine(const std::vector<Segment> &segments) {
1008 onlyCrossings = false;
1009 bool notSimple = false;
1010 onCrossing = [&notSimple] (const CrossingPair &) {notSimple = true; return true;};
1011 size_t count = 0;
1012 size_t n = segments.size();
1013 onIntersection = [n,&count, &notSimple] (const CrossingPair &p) {
1014 if (p[0].collinear(p[1]) && p[0].interiorsIntersect(p[1])) {
1015 notSimple = true; // Collinear overlap
1016 return true;
1017 }
1018 const int shared = (p[0].min() == p[1].min()) + (p[0].min() == p[1].max())
1019 + (p[0].max() == p[1].min()) + (p[0].max() == p[1].max());
1020 if (!shared) {
1021 notSimple = true; // Vertex inside an edge
1022 return true;
1023 }
1024 count++;
1025 if (count > 2*n - 2) { // A vertex appears twice
1026 notSimple = true;
1027 }
1028 return notSimple;
1029 };
1030 run(segments);
1031
1032 return !notSimple;
1033 }
1034}; // class BentleyOttmann
1035} // namespace pgl::detail
1036
1037namespace pgl {
1038
1051template<class Rational = pgl::Rational<pgl::BigInt>, class Container>
1052auto findIntersections(const Container &segments) {
1053 using Segment = Container::value_type;
1054 std::vector<Segment> v(segments.begin(),segments.end());
1055
1056 pgl::detail::BentleyOttmann<Rational, Segment> bo;
1057 return bo.findIntersections(v);
1058}
1059
1072template<class Rational = pgl::Rational<pgl::BigInt>, class Container>
1073auto findCrossings(const Container &segments) {
1074 using Segment = Container::value_type;
1075 std::vector<Segment> v(segments.begin(),segments.end());
1076
1077 pgl::detail::BentleyOttmann<Rational,Segment> bo;
1078 return bo.findCrossings(v);
1079}
1080
1081
1093template<class Rational = pgl::Rational<pgl::BigInt>, class Container>
1094bool detectIntersections(const Container &segments) {
1095 using Segment = Container::value_type;
1096 std::vector<Segment> v(segments.begin(),segments.end());
1097
1098 pgl::detail::BentleyOttmann<Rational,Segment> bo;
1099 return bo.detectIntersections(v);
1100}
1101
1113template<class Rational = pgl::Rational<pgl::BigInt>, class Container>
1114bool detectCrossings(const Container &segments) {
1115 using Segment = Container::value_type;
1116 std::vector<Segment> v(segments.begin(),segments.end());
1117
1118 pgl::detail::BentleyOttmann<Rational,Segment> bo;
1119 return bo.detectCrossings(v);
1120}
1121
1132template<class Rational = pgl::Rational<pgl::BigInt>, class Container>
1133auto bruteForceCrossings(const Container &segments) {
1134 using Point = Container::value_type::PointType;
1135 std::vector<std::array<pgl::Segment<Point>,2>> ret;
1136
1137 for (auto it_i = segments.begin(); it_i != segments.end(); ++it_i) {
1138 for (auto it_j = it_i; it_j != segments.end(); ++it_j) {
1139 if (it_i != it_j) {
1140 pgl::Segment<Point> s1 = *it_i;
1141 pgl::Segment<Point> s2 = *it_j;
1142 if (s1.crosses(s2)) {
1143 if (s2 < s1)
1144 std::swap(s1,s2);
1145 ret.push_back({s1,s2});
1146 }
1147 }
1148 }
1149 }
1150
1151 return ret;
1152}
1153
1154
1165template<class Rational = pgl::Rational<pgl::BigInt>, class Container>
1166auto bruteForceIntersections(const Container &segments) {
1167 using Point = Container::value_type::PointType;
1168 std::vector<std::array<pgl::Segment<Point>,2>> ret;
1169
1170 for (auto it_i = segments.begin(); it_i != segments.end(); ++it_i) {
1171 for (auto it_j = it_i; it_j != segments.end(); ++it_j) {
1172 if (it_i != it_j) {
1173 pgl::Segment<Point> s1 = *it_i;
1174 pgl::Segment<Point> s2 = *it_j;
1175 if (s1.intersects(s2)) {
1176 if (s2 < s1)
1177 std::swap(s1,s2);
1178 ret.push_back({s1,s2});
1179 }
1180 }
1181 }
1182 }
1183
1184 return ret;
1185}
1186
1187template <class PointType_, class LabelType>
1188template <class Rational>
1190 // The empty region carries no boundary, so it can carry no hole either.
1191 if (empty()) {
1192 return holes_.empty();
1193 }
1194 // Every ring simple on its own. This is also what rules out a zero-length
1195 // edge or a repeated vertex on any ring.
1196 if (!isSimple<Rational>()) {
1197 return false;
1198 }
1199 // Each hole inside the outer boundary. Polygon::contains is closed
1200 // containment, so a hole whose boundary touches or runs along the outer ring
1201 // passes, while one poking out — or one merely crossing it — fails. Closed
1202 // containment also gets the interior condition for free: a hole interior
1203 // reaching ∂outer would carry points beyond it, and the hole would not be
1204 // contained.
1205 for (const auto& hole : holes_) {
1206 if (!outer_.contains(hole)) {
1207 return false;
1208 }
1209 }
1210 // Hole interiors pairwise disjoint — the whole of the contract between two
1211 // holes. Boundaries meeting at points or along shared edges is allowed,
1212 // which is exactly what interiorsIntersect lets through; overlapping and
1213 // nested holes are not. The bounding boxes prefilter the quadratic scan.
1214 for (std::size_t i = 0; i < holes_.size(); ++i) {
1215 for (std::size_t j = i + 1; j < holes_.size(); ++j) {
1216 if (!holes_[i].bbox().intersects(holes_[j].bbox())) {
1217 continue;
1218 }
1219 if (holes_[i].interiorsIntersect(holes_[j])) {
1220 return false;
1221 }
1222 }
1223 }
1224 return true;
1225}
1226
1227// Next to isValid because that is where a reader looks for the structural
1228// queries, though what it needs is detail::regionSlits, from separates.hpp.
1229template <class PointType_, class LabelType>
1231 // The empty region is the closure of its own (empty) interior; anything else
1232 // without area is material that no interior comes near.
1233 if (empty()) {
1234 return true;
1235 }
1236 if (isDegenerate()) {
1237 return false;
1238 }
1239 // With area and a valid structure, the only points of A that closure(A°)
1240 // misses are the doubly covered stretches of the boundary.
1241 return detail::regionSlits(*this).empty();
1242}
1243
1244// The set's structural contract, beside the region's for the same reason: this
1245// is where a reader looks for it.
1246template <class PointType_, class LabelType>
1247template <class Rational>
1249 // Every component a valid region on its own.
1250 for (const auto& component : components_) {
1251 if (!component.template isValid<Rational>()) {
1252 return false;
1253 }
1254 }
1255 for (std::size_t i = 0; i < components_.size(); ++i) {
1256 for (std::size_t j = i + 1; j < components_.size(); ++j) {
1257 if (!components_[i].bbox().intersects(components_[j].bbox())) {
1258 continue; // the boxes prefilter the quadratic scan
1259 }
1260 // Interiors pairwise disjoint. Boundaries meeting at isolated points
1261 // is allowed, which is exactly what interiorsIntersect lets through.
1262 if (components_[i].interiorsIntersect(components_[j])) {
1263 return false;
1264 }
1265 // And no stretch of edge in common. Two components glued along one
1266 // would have interior points belonging to neither component's
1267 // interior, which is what the componentwise predicates would then
1268 // miss. Interiors being disjoint already, two of their edges can
1269 // only meet in a point or overlap along a stretch, so a
1270 // segment-valued edge intersection is exactly the case to reject.
1271 using ExactSegment = Segment<Point<NumberType>>;
1272 for (const auto& first : components_[i].edges()) {
1273 for (const auto& second : components_[j].edges()) {
1274 const auto shared = first.template intersection<NumberType>(second);
1275 if (shared && std::holds_alternative<ExactSegment>(*shared)) {
1276 return false;
1277 }
1278 }
1279 }
1280 }
1281 }
1282 return true;
1283}
1284
1285} // namespace pgl
Simple undirected graph with hashable vertices.
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
Rectangle() -> Rectangle< Point<>, NoLabel >
Definition rectangle.hpp:2384
constexpr bool is_Rational_v
Definition rational.hpp:37
auto bruteForceIntersections(const Container &segments)
Finds all intersecting segment pairs by brute force.
Definition intersections.hpp:1166
constexpr std::partial_ordering orientationSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Classifies the orientation of three points.
Definition orientation.hpp:544
Rational(T) -> Rational< T >
typename rational_int< T >::type rational_int_t
Definition rational.hpp:61
constexpr bool collinear(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Tests whether three points are collinear.
Definition orientation.hpp:651
auto bruteForceCrossings(const Container &segments)
Finds all crossing segment pairs by brute force.
Definition intersections.hpp:1133
Segment() -> Segment< Point<>, NoLabel >
auto findCrossings(const Container &segments)
Finds all proper crossing segment pairs with Bentley-Ottmann.
Definition intersections.hpp:1073
auto findIntersections(const Container &segments)
Finds all intersecting segment pairs with Bentley-Ottmann.
Definition intersections.hpp:1052
bool detectCrossings(const Container &segments)
Detects whether any two segments properly cross.
Definition intersections.hpp:1114
bool detectIntersections(const Container &segments)
Detects whether any two segments intersect.
Definition intersections.hpp:1094
Two-dimensional point with optional label payload.
Definition point.hpp:129
TNumber NumberType
Definition point.hpp:131
constexpr std::vector< EdgeType > edges() const
Returns the boundary edges of every ring of every component.
Definition polygonset.hpp:420
std::vector< std::variant< Point< ResultNumber, typename PointType::LabelType >, Polyline< Point< ResultNumber, typename PointType::LabelType > >, PolygonWithHoles< Point< ResultNumber, typename PointType::LabelType > > > > intersection(const OtherShape &other) const
Returns the intersection of the two shapes (A ∩ B), empty when they are disjoint.
bool interiorsIntersect(const OtherShape &other) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition interiorsintersect.hpp:2764
bool intersects(const OtherShape &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:2234
constexpr const ComponentType & component(std::size_t index) const
Accesses a component by index.
Definition polygonset.hpp:271
bool isValid() const
Tests the structural contract: every component valid, component interiors pairwise disjoint,...
Definition intersections.hpp:1248
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the set.
Definition bounding.hpp:469
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the region.
Definition polygonwithholes.hpp:1571
constexpr bool interiorsIntersect(const OtherPoint &) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition polygonwithholes.hpp:1660
constexpr bool isDegenerate() const
Tests whether the region has zero area.
Definition polygonwithholes.hpp:441
constexpr const PolygonType & hole(std::size_t index) const
Accesses a hole by index.
Definition polygonwithholes.hpp:196
bool isRegular() const
Tests whether the region is the closure of its own interior (A = closure(A°)).
Definition intersections.hpp:1230
constexpr bool intersects(const OtherPoint &point) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition polygonwithholes.hpp:1649
bool isValid() const
Tests the structural contract: every ring simple, every hole inside the outer boundary,...
Definition intersections.hpp:1189
constexpr bool empty() const
Tests whether the region has no outer boundary at all.
Definition polygonwithholes.hpp:430
bool isSimple() const
Tests whether every ring is simple.
Definition polygonwithholes.hpp:478
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
constexpr bool crosses(const OtherSegment &other) const
Tests whether the two shapes mutually separate each other (each disconnects the other).
Definition crosses.hpp:38
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:48
TPoint PointType
Definition segment.hpp:59