Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
separates.hpp
Go to the documentation of this file.
1#pragma once
2
4
9
10#include <algorithm>
11#include <array>
12#include <compare>
13#include <cstddef>
14#include <map>
15#include <optional>
16#include <stdexcept>
17#include <limits>
18#include <type_traits>
19#include <utility>
20#include <vector>
25
26
27namespace pgl {
28
29namespace detail {
30
43
49template <PointConcept HitPoint>
50struct LineHit {
51 HitPoint u{};
52 HitPoint v{};
53 bool crossing = false;
54};
55
64template <PointConcept LinePoint, PointConcept APoint, PointConcept BPoint>
65constexpr std::partial_ordering lineHitOrder(
66 const LinePoint& from, const LinePoint& to,
67 const LineHit<APoint>& a, const LineHit<BPoint>& b) {
68 if (!b.crossing) {
69 if (!a.crossing) {
70 // Two exact points: order by projection on the line direction.
71 return dotSign(from, to, b.u, a.u);
72 }
73 const auto reversed = lineHitOrder(from, to, b, a);
74 return reversed < 0 ? std::partial_ordering::greater
75 : reversed > 0 ? std::partial_ordering::less
76 : std::partial_ordering::equivalent;
77 }
78 // The side of edge b from which the directed line arrives at b's
79 // traversal point.
80 const auto arrival = crossSign(b.u, b.v, from, to);
81 if (!a.crossing) {
82 const auto side = orientationSign(b.u, b.v, a.u);
83 if (side == 0) {
84 return std::partial_ordering::equivalent;
85 }
86 return ((side > 0) != (arrival > 0)) ? std::partial_ordering::less
87 : std::partial_ordering::greater;
88 }
89 // Substitute a's traversal point p = a.u + t (a.v - a.u), with
90 // t = du / (du - dv), into the determinant against edge b and multiply
91 // through by (du - dv): sign(det(b, p)) = sign(ov du - ou dv) sign(du - dv).
92 const auto du = orientationDeterminant(from, to, a.u);
93 const auto dv = orientationDeterminant(from, to, a.v);
94 const auto ou = orientationDeterminant(b.u, b.v, a.u);
95 const auto ov = orientationDeterminant(b.u, b.v, a.v);
96 using Wide = promoted_number_t<std::common_type_t<decltype(du), decltype(ou)>>;
97 const auto scaled = threeWay(detail::asNumber<Wide>(ov) * detail::asNumber<Wide>(du),
98 detail::asNumber<Wide>(ou) * detail::asNumber<Wide>(dv));
99 if (scaled == 0) {
100 return std::partial_ordering::equivalent;
101 }
102 const bool side_positive = (scaled > 0) == (du > dv);
103 return (side_positive != (arrival > 0)) ? std::partial_ordering::less
104 : std::partial_ordering::greater;
105}
106
116template <PointConcept VertexPoint, PointConcept LinePoint>
117constexpr bool interiorWedgeContainsDirection(
118 const VertexPoint& vertex, const VertexPoint& previous, const VertexPoint& next,
119 const LinePoint& from, const LinePoint& to) {
120 const auto outgoing_side = crossSign(vertex, next, from, to);
121 if (outgoing_side == 0 && dotSign(vertex, next, from, to) > 0) {
122 return false; // the direction continues along the outgoing edge
123 }
124 const auto incoming_side = crossSign(from, to, vertex, previous);
125 if (incoming_side == 0 && dotSign(vertex, previous, from, to) > 0) {
126 return false; // the direction doubles back along the incoming edge
127 }
128 const auto wedge = orientationSign(vertex, next, previous);
129 if (wedge > 0) { // convex corner: wedge narrower than a halfplane
130 return outgoing_side > 0 && incoming_side > 0;
131 }
132 if (wedge < 0) { // reflex corner: wedge wider than a halfplane
133 return outgoing_side > 0 || incoming_side > 0;
134 }
135 return outgoing_side > 0; // straight vertex: open left halfplane
136}
137
160template <PointConcept LinePoint, PolygonConcept Polygon>
161constexpr bool lineSectionSeparatesPolygon(
162 const LinePoint& from, const LinePoint& to,
163 const Polygon& other, bool bounded_above) {
164 using PolygonPoint = typename Polygon::PointType;
165 using Hit = LineHit<PolygonPoint>;
166 const LineHit<LinePoint> low{from, from, false};
167 const LineHit<LinePoint> high{to, to, false};
168
169 const std::ptrdiff_t n = other.size();
170 // Visits every point where the boundary meets the line; stops early when
171 // the visitor returns true.
172 const auto scan_line_hits = [&](auto&& visit) {
173 auto side = orientationSign(from, to, other[0]);
174 for (std::ptrdiff_t i = 0; i < n; ++i) {
175 const auto next_side = orientationSign(from, to, other.get(i + 1));
176 if (side == 0) {
177 if (visit(Hit{other[i], other[i], false}, i)) {
178 return true;
179 }
180 } else if (next_side != 0 && side != next_side) {
181 if (visit(Hit{other[i], other.get(i + 1), true}, i)) {
182 return true;
183 }
184 }
185 side = next_side;
186 }
187 return false;
188 };
189
190 Hit opening{};
191 bool found = false;
192 scan_line_hits([&](const Hit& hit, std::ptrdiff_t i) {
193 const bool opens_interior = hit.crossing
194 ? crossSign(hit.u, hit.v, from, to) > 0
195 : interiorWedgeContainsDirection(
196 hit.u, other.get(i - 1), other.get(i + 1), from, to);
197 if (opens_interior &&
198 lineHitOrder(from, to, hit, low) >= 0 &&
199 (!bounded_above || lineHitOrder(from, to, hit, high) <= 0) &&
200 (!found || lineHitOrder(from, to, hit, opening) < 0)) {
201 opening = hit;
202 found = true;
203 }
204 return false;
205 });
206 if (!found) {
207 return false;
208 }
209
210 return scan_line_hits([&](const Hit& hit, std::ptrdiff_t) {
211 return lineHitOrder(from, to, hit, opening) > 0 &&
212 (!bounded_above || lineHitOrder(from, to, hit, high) <= 0);
213 });
214}
215
217
218} // namespace detail
219
225
226template <class Number, class Label>
227template<SegmentConcept OtherSegment>
228constexpr bool Point<Number, Label>::separates(const OtherSegment& other) const {
229 return other.interiorContains(*this);
230}
231
232template <class Number, class Label>
233template<OrientedSegmentConcept OtherOrientedSegment>
234constexpr bool Point<Number, Label>::separates(const OtherOrientedSegment& other) const {
236}
237
238template <class Number, class Label>
239template<LineConcept OtherLine>
240constexpr bool Point<Number, Label>::separates(const OtherLine& other) const {
241 return other.contains(*this);
242}
243
244template <class Number, class Label>
245template<OrientedLineConcept OtherOrientedLine>
246constexpr bool Point<Number, Label>::separates(const OtherOrientedLine& other) const {
247 return other.asLine().contains(*this);
248}
249
250template <class Number, class Label>
251template<RayConcept OtherRay>
252constexpr bool Point<Number, Label>::separates(const OtherRay& other) const {
253 return other.interiorContains(*this);
254}
255
256template <class Number, class Label>
257template<PointConcept OtherPoint>
258constexpr bool Point<Number, Label>::separates(const OtherPoint&) const {
259 return false;
260}
261
262template <class Number, class Label>
263template<HalfplaneConcept OtherHalfplane>
264constexpr bool Point<Number, Label>::separates(const OtherHalfplane&) const {
265 return false;
266}
267
268template <class Number, class Label>
269template<DiskConcept OtherDisk>
270constexpr bool Point<Number, Label>::separates(const OtherDisk&) const {
271 return false;
272}
273
274// A point can only separate a 2D region when that region is degenerate to a
275// segment; its convex hull then has size 2, whose whole segment is the
276// region's boundary, so the point separates it iff that segment contains it.
277template <class Number, class Label>
278template<ConvexConcept OtherConvex>
279constexpr bool Point<Number, Label>::separates(const OtherConvex& other) const {
280 return other.size() == 2 &&
281 Segment<typename OtherConvex::PointType>(other[0], other[1]).contains(*this);
282}
283
284template <class Number, class Label>
285template<RectangleConcept OtherRectangle>
286constexpr bool Point<Number, Label>::separates(const OtherRectangle& other) const {
287 if (other.empty()) {
288 // The empty set meets nothing and disconnects nothing.
289 return false;
290 }
291 return separates(static_cast<Convex<typename OtherRectangle::PointType>>(other));
292}
293
294template <class Number, class Label>
295template<TriangleConcept OtherTriangle>
296constexpr bool Point<Number, Label>::separates(const OtherTriangle& other) const {
297 return separates(static_cast<Convex<typename OtherTriangle::PointType>>(other));
298}
299
300template <class Number, class Label>
301template<PolygonConcept OtherPolygon>
302constexpr bool Point<Number, Label>::separates(const OtherPolygon& other) const {
303 return separates(Convex<typename OtherPolygon::PointType>(other.vertices()));
304}
305
312
313template <class PointType, class LabelType>
314template<PointConcept OtherPoint>
315constexpr bool Segment<PointType, LabelType>::separates(const OtherPoint&) const {
316 return false;
317}
318
319template <class PointType, class LabelType>
320template<SegmentConcept OtherSegment>
321constexpr bool Segment<PointType, LabelType>::separates(const OtherSegment& other) const {
322 using Coordinate = detail::sign_coordinate_t<NumberType, typename OtherSegment::NumberType>;
323 const auto& a = min();
324 const auto& b = max();
325 const auto& c = other.min();
326 const auto& d = other.max();
327
328 // Four orientation signs over four endpoints, each endpoint in three of
329 // them; see Segment::intersects for what the filtered wrappers buy.
330 const auto fa = detail::filtered<Coordinate>(a);
331 const auto fb = detail::filtered<Coordinate>(b);
332 const auto fc = detail::filtered<Coordinate>(c);
333 const auto fd = detail::filtered<Coordinate>(d);
334 const auto s1 = detail::orientationSignOf(fa, fb, fc);
335 const auto s2 = detail::orientationSignOf(fa, fb, fd);
336 const auto s3 = detail::orientationSignOf(fc, fd, fa);
337 const auto s4 = detail::orientationSignOf(fc, fd, fb);
338
339 if (detail::allDecided(s1, s2, s3, s4)) {
340 return s1.value() != s2.value() && s3.value() != s4.value();
341 }
342
343 const int cross = boundingBoxesCross(other);
344 if (cross == 0) {
345 return false;
346 }
347 if (cross == 2) {
348 return true;
349 }
350 const auto d1 = s1.value();
351 const auto d2 = s2.value();
352 if (d1 == 0 && d2 == 0) {
353 return other.interiorContains(a) && other.interiorContains(b);
354 }
355 const bool other_endpoints_are_on_strictly_opposite_sides =
356 d1 != 0 && d2 != 0 && d1 != d2;
357 if (!other_endpoints_are_on_strictly_opposite_sides) {
358 return false;
359 }
360 const auto d3 = s3.value();
361 if (d3 == 0 && other.containsCollinear(a)) {
362 return true;
363 }
364 const auto d4 = s4.value();
365 if (d4 == 0 && other.containsCollinear(b)) {
366 return true;
367 }
368 return d3 != 0 && d4 != 0 && d3 != d4;
369}
370
371template <class PointType, class LabelType>
372template<OrientedSegmentConcept OtherOrientedSegment>
373constexpr bool Segment<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
374 return separates(other.asSegment());
375}
376
377template <class PointType, class LabelType>
378template<LineConcept OtherLine>
379constexpr bool Segment<PointType, LabelType>::separates(const OtherLine& other) const {
380 return intersects(other);
381}
382
383template <class PointType, class LabelType>
384template<OrientedLineConcept OtherOrientedLine>
385constexpr bool Segment<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
386 return intersects(other);
387}
388
389template <class PointType, class LabelType>
390template<RayConcept OtherRay>
391constexpr bool Segment<PointType, LabelType>::separates(const OtherRay& other) const {
392 // The segment splits the ray only when it meets the ray ahead of the
393 // source: a piece then survives between the source and the segment, and
394 // another runs to infinity. If the source lies on the segment, the near
395 // piece is empty and the ray stays connected.
396 return intersects(other) && !contains(other.source());
397}
398
399template <class PointType, class LabelType>
400template<RectangleConcept OtherRectangle>
401constexpr bool Segment<PointType, LabelType>::separates(const OtherRectangle& other) const {
402 if (other.empty()) {
403 // The empty set meets nothing and disconnects nothing.
404 return false;
405 }
406 return other.interiorsIntersect(*this) && !other.interiorContains(min()) && !other.interiorContains(max());
407}
408
409template <class PointType, class LabelType>
410template<HalfplaneConcept OtherHalfplane>
411constexpr bool Segment<PointType, LabelType>::separates(const OtherHalfplane& other) const {
412 return other.separates(*this);
413}
414
415template <class PointType, class LabelType>
416template<TriangleConcept OtherTriangle>
417constexpr bool Segment<PointType, LabelType>::separates(const OtherTriangle& other) const {
418 if (other.isDegenerate()) {
419 // A collinear triangle is the segment or point it spans: the segment
420 // overload answers for the former, and a point is never disconnected.
421 if (const auto spanned = other.getIfSegment()) {
422 return separates(*spanned);
423 }
424 return false;
425 }
426 if (isDegenerate()) {
427 return false;
428 }
429 if (other.interiorContains(min()) || other.interiorContains(max())) {
430 return false;
431 }
432
433 const auto triangle_edges = other.edges();
434 for (const auto& edge : triangle_edges) {
435 if (parallel(edge) &&
436 (edge.contains(min()) ||
437 edge.contains(max()) ||
438 contains(edge.min()) ||
439 contains(edge.max()))) {
440 return false;
441 }
442 }
443
444 int boundary_contact_count = 0;
445 if (other.boundaryContains(min())) {
446 ++boundary_contact_count;
447 }
448 if (other.boundaryContains(max()) && max() != min()) {
449 ++boundary_contact_count;
450 }
451
452 const auto triangle_vertices = other.vertices();
453 for (const auto& vertex : triangle_vertices) {
454 if (contains(vertex) && vertex != min() && vertex != max()) {
455 ++boundary_contact_count;
456 }
457 }
458
459 for (const auto& edge : triangle_edges) {
461 ++boundary_contact_count;
462 }
463 }
464
465 return boundary_contact_count >= 2;
466}
467
468template <class PointType, class LabelType>
469template<ConvexConcept OtherConvex>
470constexpr bool Segment<PointType, LabelType>::separates(const OtherConvex& other) const {
471 // The segment separates the polygon iff its intersection with the polygon
472 // is a true chord through the interior — neither endpoint lies strictly
473 // inside (otherwise the segment ends midway and leaves a slit, not a
474 // split) and the segment actually crosses the interior (otherwise it
475 // lies along a single boundary edge). Both checks are O(log n).
476 if (other.isDegenerate()) {
477 // A convex with fewer than three vertices is the segment or point it
478 // spans: the segment overload answers for the former, and a point is
479 // never disconnected.
480 if (const auto spanned = other.getIfSegment()) {
481 return separates(*spanned);
482 }
483 return false;
484 }
485 return !isDegenerate()
486 && !other.interiorContains(min())
487 && !other.interiorContains(max())
488 && other.interiorsIntersect(*this);
489}
490
491template <class PointType, class LabelType>
492template<PolygonConcept OtherPolygon>
493constexpr bool Segment<PointType, LabelType>::separates(const OtherPolygon& other) const {
494 if (other.isDegenerate()) {
495 // A polygon that collapses to a segment is cut as that segment is; a
496 // point is never disconnected.
497 if (const auto spanned = other.getIfSegment()) {
498 return separates(*spanned);
499 }
500 return false;
501 }
502 if (isDegenerate()) {
503 return false;
504 }
505 // The segment separates the polygon iff it covers whole some connected
506 // component of supporting-line ∩ interior; see the engine for details.
507 return detail::lineSectionSeparatesPolygon(min(), max(), other, true);
508}
509
510template <class PointType, class LabelType>
511template<DiskConcept OtherDisk>
512constexpr bool Segment<PointType, LabelType>::separates(const OtherDisk& other) const {
513 return !other.interiorContains(min())
514 && !other.interiorContains(max())
515 && other.interiorsIntersect(*this);
516}
517
518template <class PointType, class LabelType>
520 return std::visit(
521 [this](const auto& value) {
522 return this->separates(value);
523 },
524 other.variant());
525}
526
532
533template <class PointType, class LabelType>
534template<SegmentConcept OtherSegment>
535constexpr bool Triangle<PointType, LabelType>::separates(const OtherSegment& other) const {
536 return !other.isDegenerate() && !contains(other.min()) && !contains(other.max()) && intersects(other);
537}
538
539template <class PointType, class LabelType>
540template<PointConcept OtherPoint>
541constexpr bool Triangle<PointType, LabelType>::separates(const OtherPoint&) const {
542 return false;
543}
544
545template <class PointType, class LabelType>
546template<OrientedSegmentConcept OtherOrientedSegment>
547constexpr bool Triangle<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
549}
550
551template <class PointType, class LabelType>
552template<LineConcept OtherLine>
553constexpr bool Triangle<PointType, LabelType>::separates(const OtherLine& other) const {
554 return !other.isDegenerate() && intersects(other);
555}
556
557template <class PointType, class LabelType>
558template<OrientedLineConcept OtherOrientedLine>
559constexpr bool Triangle<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
560 return separates(other.asLine());
561}
562
563template <class PointType, class LabelType>
564template<RayConcept OtherRay>
565constexpr bool Triangle<PointType, LabelType>::separates(const OtherRay& other) const {
566 // Removing the triangle splits the ray iff the ray reaches it with its
567 // source outside: the far end runs to infinity (always outside), so any
568 // contact -- including a tangential touch of the boundary -- leaves a piece
569 // on each side.
570 return !other.isDegenerate() && !isDegenerate()
571 && !contains(other.source()) && intersects(other);
572}
573
574template <class PointType, class LabelType>
575template<HalfplaneConcept OtherHalfplane>
576constexpr bool Triangle<PointType, LabelType>::separates(const OtherHalfplane&) const {
577 return false;
578}
579
580template <class PointType, class LabelType>
581template<RectangleConcept OtherRectangle>
582constexpr bool Triangle<PointType, LabelType>::separates(const OtherRectangle& other) const {
583 if (other.empty()) {
584 // The empty set meets nothing and disconnects nothing.
585 return false;
586 }
587 // A non-degenerate rectangle is its convex view; use the general convex-body
588 // algorithm so the result matches separates(Convex) exactly. The previous
589 // bespoke edge/vertex formula diverged from it.
590 return separates(other.asConvex());
591}
592
593template <class PointType, class LabelType>
594template<TriangleConcept OtherTriangle>
595constexpr bool Triangle<PointType, LabelType>::separates(const OtherTriangle& other) const {
596 // See separates(Rectangle): defer to the general convex-body algorithm.
597 return separates(other.asConvex());
598}
599
600template <class PointType, class LabelType>
601template<ConvexConcept OtherConvex>
602constexpr bool Triangle<PointType, LabelType>::separates(const OtherConvex& other) const {
603 if (other.isDegenerate()) {
604 // A convex with fewer than three vertices is the segment or point it
605 // spans: the segment overload answers for the former, and a point is
606 // never disconnected.
607 if (const auto spanned = other.getIfSegment()) {
608 return separates(*spanned);
609 }
610 return false;
611 }
612 if (isDegenerate()) {
613 // What this degenerate shape spans does the cutting: a segment answers
614 // as one, and a point disconnects nothing.
615 if (const auto spanned = getIfSegment()) {
616 return spanned->separates(other);
617 }
618 return false;
619 }
620
621 int interior_vertex_count = 0;
622 for (const auto& vertex : vertices()) {
623 if (other.interiorContains(vertex)) {
624 interior_vertex_count++;
625 if (interior_vertex_count >= 2) {
626 return false;
627 }
628 }
629 }
630 if (interior_vertex_count == 1) {
631 for (const auto& edge : edges()) {
632 if (edge.separates(other)) {
633 return true;
634 }
635 }
636 return false;
637 }
638
639 int separating_edge_count = 0;
640 for (const auto& edge : edges()) {
641 if (edge.separates(other)) {
642 separating_edge_count++;
643 }
644 }
645 return separating_edge_count >= 2;
646}
647
648// Disk: specialized rather than delegated to Convex. For a triangle, removing it
649// disconnects the disk iff the boundary crosses the circle >= 4 times. With k =
650// vertices strictly inside the disk and c = edges carrying a full chord, the
651// crossing count is fully determined by (k, c): a triangle can never weave in and
652// out four times via single crossings, so the answer is a closed-form table:
653// k == 0 -> separates iff c >= 2
654// k == 1 -> separates iff c >= 1
655// k >= 2 -> never (the disk bulges out on a single arc)
656// This avoids building a Convex and short-circuits on k before any chord test.
657// A degenerate disk is a point and disconnects nothing; a collinear triangle is
658// the segment it spans, which answers for it.
659template <class PointType, class LabelType>
660template<DiskConcept OtherDisk>
661constexpr bool Triangle<PointType, LabelType>::separates(const OtherDisk& other) const {
662 if (other.isDegenerate()) {
663 return false;
664 }
665 if (isDegenerate()) {
666 // What this degenerate shape spans does the cutting: a segment answers
667 // as one, and a point disconnects nothing.
668 if (const auto spanned = getIfSegment()) {
669 return spanned->separates(other);
670 }
671 return false;
672 }
673
674 int interior_vertex_count = 0;
675 for (const auto& vertex : vertices()) {
676 interior_vertex_count += other.interiorContains(vertex) ? 1 : 0;
677 }
678 if (interior_vertex_count >= 2) {
679 return false;
680 }
681
682 const int needed = interior_vertex_count == 0 ? 2 : 1;
683 int separating_edge_count = 0;
684 for (const auto& edge : edges()) {
685 if (edge.separates(other) && ++separating_edge_count >= needed) {
686 return true;
687 }
688 }
689 return false;
690}
691
692template <class PointType, class LabelType>
694 return std::visit(
695 [this](const auto& value) {
696 return this->separates(value);
697 },
698 other.variant());
699}
700
706
707template <class PointType, class LabelType>
708template<PointConcept OtherPoint>
709constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherPoint& other) const {
710 return this->asSegment().separates(other);
711}
712
713template <class PointType, class LabelType>
714template<SegmentConcept OtherSegment>
715constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherSegment& other) const {
716 return this->asSegment().separates(other);
717}
718
719template <class PointType, class LabelType>
720template<OrientedSegmentConcept OtherOrientedSegment>
721constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
722 return this->asSegment().separates(other.asSegment());
723}
724
725template <class PointType, class LabelType>
726template<LineConcept OtherLine>
727constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherLine& other) const {
728 return this->asSegment().separates(other);
729}
730
731template <class PointType, class LabelType>
732template<OrientedLineConcept OtherOrientedLine>
733constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
734 return this->asSegment().separates(other);
735}
736
737template <class PointType, class LabelType>
738template<RayConcept OtherRay>
739constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherRay& other) const {
740 return this->asSegment().separates(other);
741}
742
743template <class PointType, class LabelType>
744template<RectangleConcept OtherRectangle>
745constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherRectangle& other) const {
746 if (other.empty()) {
747 // The empty set meets nothing and disconnects nothing.
748 return false;
749 }
750 return this->asSegment().separates(other);
751}
752
753template <class PointType, class LabelType>
754template<TriangleConcept OtherTriangle>
755constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherTriangle& other) const {
756 return this->asSegment().separates(other);
757}
758
759template <class PointType, class LabelType>
760template<HalfplaneConcept OtherHalfplane>
761constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherHalfplane& other) const {
762 return this->asSegment().separates(other);
763}
764
765template <class PointType, class LabelType>
766template<ConvexConcept OtherConvex>
767constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherConvex& other) const {
768 return this->asSegment().separates(other);
769}
770
771template <class PointType, class LabelType>
772template<DiskConcept OtherDisk>
773constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherDisk& other) const {
774 return this->asSegment().separates(other);
775}
776
777template <class PointType, class LabelType>
779 return std::visit(
780 [this](const auto& value) {
781 return this->separates(value);
782 },
783 other.variant());
784}
785
791
792template <class PointType, class LabelType>
793template<PointConcept OtherPoint>
794constexpr bool Line<PointType, LabelType>::separates(const OtherPoint&) const {
795 return false;
796}
797
798template <class PointType, class LabelType>
799template<SegmentConcept OtherSegment>
800constexpr bool Line<PointType, LabelType>::separates(const OtherSegment& other) const {
801 if (isDegenerate() || other.isDegenerate()) {
802 return false;
803 }
804 const auto first_side = orientationSign(min(), max(), other.min());
805 const auto second_side = orientationSign(min(), max(), other.max());
806 return first_side != 0 && second_side != 0 && first_side != second_side;
807}
808
809template <class PointType, class LabelType>
810template<OrientedSegmentConcept OtherOrientedSegment>
811constexpr bool Line<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
812 return separates(other.asSegment());
813}
814
815template <class PointType, class LabelType>
816template<LineConcept OtherLine>
817constexpr bool Line<PointType, LabelType>::separates(const OtherLine& other) const {
818 return !isDegenerate() && !other.isDegenerate() &&
819 intersects(other) && !collinear(other);
820}
821
822template <class PointType, class LabelType>
823template<OrientedLineConcept OtherOrientedLine>
824constexpr bool Line<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
825 return separates(other.asLine());
826}
827
828template <class PointType, class LabelType>
829template<RayConcept OtherRay>
830constexpr bool Line<PointType, LabelType>::separates(const OtherRay& other) const {
831 if (isDegenerate() || other.isDegenerate()) {
832 return false;
833 }
834 const auto source_side = orientationSign(min(), max(), other.source());
835 const auto target_side = orientationSign(min(), max(), other.target());
836 if (source_side == 0) {
837 return false;
838 }
839 return target_side == 0 || target_side != source_side;
840}
841
842template <class PointType, class LabelType>
843template<RectangleConcept OtherRectangle>
844constexpr bool Line<PointType, LabelType>::separates(const OtherRectangle& other) const {
845 if (other.empty()) {
846 // The empty set meets nothing and disconnects nothing.
847 return false;
848 }
849 return other.interiorsIntersect(*this);
850}
851
852template <class PointType, class LabelType>
853template<TriangleConcept OtherTriangle>
854constexpr bool Line<PointType, LabelType>::separates(const OtherTriangle& other) const {
855 if (isDegenerate()) {
856 return false;
857 }
858 return other.interiorsIntersect(*this);
859}
860
861template <class PointType, class LabelType>
862template<HalfplaneConcept OtherHalfplane>
863constexpr bool Line<PointType, LabelType>::separates(const OtherHalfplane& other) const {
864 return other.separates(*this);
865}
866
867template <class PointType, class LabelType>
868template<ConvexConcept OtherConvex>
869constexpr bool Line<PointType, LabelType>::separates(const OtherConvex& other) const {
870 if (isDegenerate()) {
871 return false;
872 }
873 return other.interiorsIntersect(*this);
874}
875
876template <class PointType, class LabelType>
877template<DiskConcept OtherDisk>
878constexpr bool Line<PointType, LabelType>::separates(const OtherDisk& other) const {
879 if (isDegenerate()) {
880 return false;
881 }
882 return other.interiorsIntersect(*this);
883}
884
885template <class PointType, class LabelType>
886constexpr bool Line<PointType, LabelType>::separates(const Shape<PointType>& other) const {
887 return std::visit(
888 [this](const auto& value) {
889 return this->separates(value);
890 },
891 other.variant());
892}
893
899
900template <class PointType, class LabelType>
901template<PointConcept OtherPoint>
902constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherPoint& other) const {
903 return this->asLine().separates(other);
904}
905
906template <class PointType, class LabelType>
907template<SegmentConcept OtherSegment>
908constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherSegment& other) const {
909 return this->asLine().separates(other);
910}
911
912template <class PointType, class LabelType>
913template<OrientedSegmentConcept OtherOrientedSegment>
914constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
915 return this->asLine().separates(other);
916}
917
918template <class PointType, class LabelType>
919template<LineConcept OtherLine>
920constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherLine& other) const {
921 return this->asLine().separates(other);
922}
923
924template <class PointType, class LabelType>
925template<OrientedLineConcept OtherOrientedLine>
926constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
927 return this->asLine().separates(other);
928}
929
930template <class PointType, class LabelType>
931template<RayConcept OtherRay>
932constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherRay& other) const {
933 return this->asLine().separates(other);
934}
935
936template <class PointType, class LabelType>
937template<RectangleConcept OtherRectangle>
938constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherRectangle& other) const {
939 if (other.empty()) {
940 // The empty set meets nothing and disconnects nothing.
941 return false;
942 }
943 return this->asLine().separates(other);
944}
945
946template <class PointType, class LabelType>
947template<TriangleConcept OtherTriangle>
948constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherTriangle& other) const {
949 return this->asLine().separates(other);
950}
951
952template <class PointType, class LabelType>
953template<HalfplaneConcept OtherHalfplane>
954constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherHalfplane& other) const {
955 return this->asLine().separates(other);
956}
957
958template <class PointType, class LabelType>
959template<ConvexConcept OtherConvex>
960constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherConvex& other) const {
961 return this->asLine().separates(other);
962}
963
964template <class PointType, class LabelType>
965template<DiskConcept OtherDisk>
966constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherDisk& other) const {
967 return this->asLine().separates(other);
968}
969
970template <class PointType, class LabelType>
972 return std::visit(
973 [this](const auto& value) {
974 return this->separates(value);
975 },
976 other.variant());
977}
978
984
985template <class PointType, class LabelType>
986template<PointConcept OtherPoint>
987constexpr bool Ray<PointType, LabelType>::separates(const OtherPoint&) const {
988 return false;
989}
990
991template <class PointType, class LabelType>
992template<SegmentConcept OtherSegment>
993constexpr bool Ray<PointType, LabelType>::separates(const OtherSegment& other) const {
994 if (isDegenerate() || other.isDegenerate()) {
995 return false;
996 }
997
998 const auto first_side = orientationSign(source(), target(), other.min());
999 const auto second_side = orientationSign(source(), target(), other.max());
1000 if (first_side == 0 && second_side == 0) {
1001 return false;
1002 }
1003 if (other.interiorContains(source())) {
1004 return true;
1005 }
1006 if (first_side == 0 || second_side == 0 || first_side == second_side) {
1007 return false;
1008 }
1009
1010 // The segment crosses the ray's supporting line at a point interior to the
1011 // segment; it cuts the segment in two exactly when that crossing lies on the
1012 // ray itself (anywhere from the source onward, not just up to the target).
1013 return intersects(other);
1014}
1015
1016template <class PointType, class LabelType>
1017template<OrientedSegmentConcept OtherOrientedSegment>
1018constexpr bool Ray<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
1019 return separates(other.asSegment());
1020}
1021
1022template <class PointType, class LabelType>
1023template<LineConcept OtherLine>
1024constexpr bool Ray<PointType, LabelType>::separates(const OtherLine& other) const {
1025 return !isDegenerate() && !other.isDegenerate() &&
1026 intersects(other) && !collinear(other);
1027}
1028
1029template <class PointType, class LabelType>
1030template<OrientedLineConcept OtherOrientedLine>
1031constexpr bool Ray<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
1032 return separates(other.asLine());
1033}
1034
1035template <class PointType, class LabelType>
1036template<RayConcept OtherRay>
1037constexpr bool Ray<PointType, LabelType>::separates(const OtherRay& other) const {
1038 if (isDegenerate() || other.isDegenerate()) {
1039 return false;
1040 }
1041 if (collinear(other)) {
1042 return false;
1043 }
1044 if (other.interiorContains(source())) {
1045 return true;
1046 }
1047
1048 const auto other_source_side = orientationSign(source(), target(), other.source());
1049 const auto other_target_side = orientationSign(source(), target(), other.target());
1050 if (other_source_side == 0) {
1051 return false;
1052 }
1053 if (other_target_side != 0 && other_target_side == other_source_side) {
1054 return false;
1055 }
1056
1057 const auto source_side = orientationSign(other.source(), other.target(), source());
1058 const auto target_side = orientationSign(other.source(), other.target(), target());
1059 return source_side != 0 && (target_side == 0 || target_side != source_side);
1060}
1061
1062template <class PointType, class LabelType>
1063template<HalfplaneConcept OtherHalfplane>
1064constexpr bool Ray<PointType, LabelType>::separates(const OtherHalfplane& other) const {
1065 return other.separates(*this);
1066}
1067
1068template <class PointType, class LabelType>
1069template<RectangleConcept OtherRectangle>
1070constexpr bool Ray<PointType, LabelType>::separates(const OtherRectangle& other) const {
1071 if (other.empty()) {
1072 // The empty set meets nothing and disconnects nothing.
1073 return false;
1074 }
1075 return !other.interiorContains(source()) && other.interiorsIntersect(*this);
1076}
1077
1078template <class PointType, class LabelType>
1079template<TriangleConcept OtherTriangle>
1080constexpr bool Ray<PointType, LabelType>::separates(const OtherTriangle& other) const {
1081 if (isDegenerate() || other.isDegenerate() || other.interiorContains(source())) {
1082 return false;
1083 }
1084
1085 const auto triangle_edges = other.edges();
1086 for (const auto& edge : triangle_edges) {
1087 if (parallel(edge) && (contains(edge.min()) || contains(edge.max()))) {
1088 return false;
1089 }
1090 }
1091
1092 int boundary_contact_count = 0;
1093 if (other.boundaryContains(source())) {
1094 ++boundary_contact_count;
1095 }
1096 const auto triangle_vertices = other.vertices();
1097 for (const auto& vertex : triangle_vertices) {
1098 if (contains(vertex) && vertex != source()) {
1099 ++boundary_contact_count;
1100 }
1101 }
1102
1103 for (const auto& edge : triangle_edges) {
1104 if (interiorsIntersect(edge)) {
1105 ++boundary_contact_count;
1106 }
1107 }
1108
1109 return boundary_contact_count >= 2;
1110}
1111
1112template <class PointType, class LabelType>
1113template<ConvexConcept OtherConvex>
1114constexpr bool Ray<PointType, LabelType>::separates(const OtherConvex& other) const {
1115 if (other.isDegenerate()) {
1116 // A convex with fewer than three vertices is the segment or point it
1117 // spans: the segment overload answers for the former, and a point is
1118 // never disconnected.
1119 if (const auto spanned = other.getIfSegment()) {
1120 return separates(*spanned);
1121 }
1122 return false;
1123 }
1124
1125 if (other.interiorContains(source())) {
1126 return false;
1127 }
1128 return other.interiorsIntersect(*this);
1129}
1130
1131template <class PointType, class LabelType>
1132template<DiskConcept OtherDisk>
1133constexpr bool Ray<PointType, LabelType>::separates(const OtherDisk& other) const {
1134 // The disk is convex, so removing the ray disconnects it exactly when the ray
1135 // runs clear through as a full chord: the source must not be strictly inside
1136 // (an interior source leaves only a slit, which stays connected) and the ray's
1137 // interior must reach the disk's interior, after which the half-infinite far
1138 // end guarantees a second boundary crossing.
1139 if (other.isDegenerate()) {
1140 return false;
1141 }
1142 if (other.interiorContains(source())) {
1143 return false;
1144 }
1145 return other.interiorsIntersect(*this);
1146}
1147
1148template <class PointType, class LabelType>
1149template<PolygonConcept OtherPolygon>
1150constexpr bool Ray<PointType, LabelType>::separates(const OtherPolygon& other) const {
1151 if (isDegenerate() || other.isDegenerate()) {
1152 return false;
1153 }
1154 // Same chord criterion as Segment::separates(Polygon) with the far end at
1155 // infinity: only the source can leave a component end uncovered, so the
1156 // covered-component search is bounded below by the source alone.
1157 return detail::lineSectionSeparatesPolygon(source(), target(), other, false);
1158}
1159
1160template <class PointType, class LabelType>
1161constexpr bool Ray<PointType, LabelType>::separates(const Shape<PointType>& other) const {
1162 return std::visit(
1163 [this](const auto& value) {
1164 return this->separates(value);
1165 },
1166 other.variant());
1167}
1168
1174
1175template <class PointType, class LabelType>
1176template<RectangleConcept OtherRectangle>
1177constexpr bool Rectangle<PointType, LabelType>::separates(const OtherRectangle& other) const {
1178 // The empty set disconnects nothing and cannot be disconnected, and the
1179 // inverted corners of an empty rectangle can pass the tests below, so both
1180 // branches that reach true rule emptiness out. The check trails the
1181 // geometry rather than guarding the function because false is the common
1182 // answer, and that path then never pays for it.
1183 const bool splits_horizontally =
1184 other.min().x() < min().x() &&
1185 max().x() < other.max().x() &&
1186 !(other.min().y() < min().y()) &&
1187 !(max().y() < other.max().y());
1188 if (splits_horizontally) {
1189 return !empty() && !other.empty();
1190 }
1191 const bool splits_vertically =
1192 other.min().y() < min().y() &&
1193 max().y() < other.max().y() &&
1194 !(other.min().x() < min().x()) &&
1195 !(max().x() < other.max().x());
1196 return splits_vertically && !empty() && !other.empty();
1197}
1198
1199template <class PointType, class LabelType>
1200template<PointConcept OtherPoint>
1201constexpr bool Rectangle<PointType, LabelType>::separates(const OtherPoint&) const {
1202 return false;
1203}
1204
1205template <class PointType, class LabelType>
1206template<LineConcept OtherLine>
1207constexpr bool Rectangle<PointType, LabelType>::separates(const OtherLine& other) const {
1208 if (empty()) {
1209 // The empty set meets nothing and disconnects nothing.
1210 return false;
1211 }
1212 return intersects(other) && !contains(other);
1213}
1214
1215template <class PointType, class LabelType>
1216template<OrientedLineConcept OtherOrientedLine>
1217constexpr bool Rectangle<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
1218 if (empty()) {
1219 // The empty set meets nothing and disconnects nothing.
1220 return false;
1221 }
1222 return separates(other.asLine());
1223}
1224
1225template <class PointType, class LabelType>
1226template<SegmentConcept OtherSegment>
1227constexpr bool Rectangle<PointType, LabelType>::separates(const OtherSegment& other) const {
1228 if (empty()) {
1229 // The empty set meets nothing and disconnects nothing.
1230 return false;
1231 }
1232 // Both endpoints outside the closed rectangle and the segment touching it
1233 // anywhere (boundary contact included) leave a piece on each side.
1234 return !other.isDegenerate() && !contains(other.min()) && !contains(other.max()) && intersects(other);
1235}
1236
1237template <class PointType, class LabelType>
1238template<OrientedSegmentConcept OtherOrientedSegment>
1239constexpr bool Rectangle<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
1240 if (empty()) {
1241 // The empty set meets nothing and disconnects nothing.
1242 return false;
1243 }
1245}
1246
1247template <class PointType, class LabelType>
1248template<RayConcept OtherRay>
1249constexpr bool Rectangle<PointType, LabelType>::separates(const OtherRay& other) const {
1250 if (empty()) {
1251 // The empty set meets nothing and disconnects nothing.
1252 return false;
1253 }
1254 return !other.isDegenerate() &&
1255 !contains(other.source()) &&
1256 intersects(other);
1257}
1258
1259template <class PointType, class LabelType>
1260template<HalfplaneConcept OtherHalfplane>
1261constexpr bool Rectangle<PointType, LabelType>::separates(const OtherHalfplane& other) const {
1262 if (empty()) {
1263 // The empty set meets nothing and disconnects nothing.
1264 return false;
1265 }
1266 (void)other;
1267 return false;
1268}
1269
1270template <class PointType, class LabelType>
1271template<TriangleConcept OtherTriangle>
1272constexpr bool Rectangle<PointType, LabelType>::separates(const OtherTriangle& other) const {
1273 if (empty()) {
1274 // The empty set meets nothing and disconnects nothing.
1275 return false;
1276 }
1277 if (other.isDegenerate()) {
1278 // A collinear triangle is the segment or point it spans: the segment
1279 // overload answers for the former, and a point is never disconnected.
1280 if (const auto spanned = other.getIfSegment()) {
1281 return separates(*spanned);
1282 }
1283 return false;
1284 }
1285
1286 const auto target_edges = other.edges();
1287 if (contains(other.a()) && separates(target_edges[1])) {
1288 return true;
1289 }
1290 if (contains(other.b()) && separates(target_edges[2])) {
1291 return true;
1292 }
1293 if (contains(other.c()) && separates(target_edges[0])) {
1294 return true;
1295 }
1296
1297 int separated_edges = 0;
1298 for (const auto& edge : target_edges) {
1299 separated_edges += separates(edge) ? 1 : 0;
1300 }
1301 return separated_edges >= 2;
1302}
1303
1304template <class PointType, class LabelType>
1305template<ConvexConcept OtherConvex>
1306constexpr bool Rectangle<PointType, LabelType>::separates(const OtherConvex& other) const {
1307 if (empty()) {
1308 // The empty set meets nothing and disconnects nothing.
1309 return false;
1310 }
1311 return asConvex().separates(other);
1312}
1313
1314// Disk: specialized rather than delegated to Convex (see Triangle's overload for
1315// the reasoning). A rectangle's four corners are concyclic, so a disk can never
1316// contain exactly the two corners of a diagonal; hence the answer is again a
1317// closed-form table in k = corners strictly inside and c = full-chord edges:
1318// k == 0 -> separates iff c >= 2
1319// k in {1, 2} -> separates iff c >= 1 (k == 2 is always two *adjacent* corners)
1320// k >= 3 -> never
1321// Degenerate inputs are undefined here; we report false.
1322template <class PointType, class LabelType>
1323template<DiskConcept OtherDisk>
1324constexpr bool Rectangle<PointType, LabelType>::separates(const OtherDisk& other) const {
1325 if (empty()) {
1326 // The empty set meets nothing and disconnects nothing.
1327 return false;
1328 }
1329 if (other.isDegenerate()) {
1330 return false;
1331 }
1332 if (isDegenerate()) {
1333 // What this degenerate shape spans does the cutting: a segment answers
1334 // as one, and a point disconnects nothing.
1335 if (const auto spanned = getIfSegment()) {
1336 return spanned->separates(other);
1337 }
1338 return false;
1339 }
1340
1341 int interior_vertex_count = 0;
1342 for (const auto& vertex : vertices()) {
1343 interior_vertex_count += other.interiorContains(vertex) ? 1 : 0;
1344 }
1345 if (interior_vertex_count >= 3) {
1346 return false;
1347 }
1348
1349 const int needed = interior_vertex_count == 0 ? 2 : 1;
1350 int separating_edge_count = 0;
1351 for (const auto& edge : edges()) {
1352 if (edge.separates(other) && ++separating_edge_count >= needed) {
1353 return true;
1354 }
1355 }
1356 return false;
1357}
1358
1359template <class PointType, class LabelType>
1361 return std::visit(
1362 [this](const auto& value) {
1363 return this->separates(value);
1364 },
1365 other.variant());
1366}
1367
1373
1374template <class PointType, class LabelType>
1375template<PointConcept OtherPoint>
1376constexpr bool Halfplane<PointType, LabelType>::separates(const OtherPoint&) const {
1377 return false;
1378}
1379
1380template <class PointType, class LabelType>
1381template<LineConcept OtherLine>
1382constexpr bool Halfplane<PointType, LabelType>::separates(const OtherLine& other) const {
1383 (void)other;
1384 return false;
1385}
1386
1387template <class PointType, class LabelType>
1388template<OrientedLineConcept OtherOrientedLine>
1389constexpr bool Halfplane<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
1390 (void)other;
1391 return false;
1392}
1393
1394template <class PointType, class LabelType>
1395template<SegmentConcept OtherSegment>
1396constexpr bool Halfplane<PointType, LabelType>::separates(const OtherSegment& other) const {
1397 (void)other;
1398 return false;
1399}
1400
1401template <class PointType, class LabelType>
1402template<OrientedSegmentConcept OtherOrientedSegment>
1403constexpr bool Halfplane<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
1404 (void)other;
1405 return false;
1406}
1407
1408template <class PointType, class LabelType>
1409template<RayConcept OtherRay>
1410constexpr bool Halfplane<PointType, LabelType>::separates(const OtherRay& other) const {
1411 (void)other;
1412 return false;
1413}
1414
1415template <class PointType, class LabelType>
1416template<RectangleConcept OtherRectangle>
1417constexpr bool Halfplane<PointType, LabelType>::separates(const OtherRectangle& other) const {
1418 if (other.empty()) {
1419 // The empty set meets nothing and disconnects nothing.
1420 return false;
1421 }
1422 (void)other;
1423 return false;
1424}
1425
1426template <class PointType, class LabelType>
1427template<HalfplaneConcept OtherHalfplane>
1428constexpr bool Halfplane<PointType, LabelType>::separates(const OtherHalfplane& other) const {
1429 (void)other;
1430 return false;
1431}
1432
1433template <class PointType, class LabelType>
1434template<TriangleConcept OtherTriangle>
1435constexpr bool Halfplane<PointType, LabelType>::separates(const OtherTriangle& other) const {
1436 (void)other;
1437 return false;
1438}
1439
1440template <class PointType, class LabelType>
1441template<ConvexConcept OtherConvex>
1442constexpr bool Halfplane<PointType, LabelType>::separates(const OtherConvex&) const {
1443 return false;
1444}
1445
1446template <class PointType, class LabelType>
1447template<DiskConcept OtherDisk>
1448constexpr bool Halfplane<PointType, LabelType>::separates(const OtherDisk& other) const {
1449 // Removing a closed half-plane from a disk leaves the circular segment on the
1450 // far side of the boundary line, which is always a single connected piece, so
1451 // a half-plane never disconnects a (convex) disk.
1452 (void)other;
1453 return false;
1454}
1455
1456template <class PointType, class LabelType>
1457template<PolygonConcept OtherPolygon>
1458constexpr bool Halfplane<PointType, LabelType>::separates(const OtherPolygon& other) const {
1459 if (isDegenerate() || other.isDegenerate()) {
1460 return false;
1461 }
1462
1463 if (other.size() < 3) {
1464 return false;
1465 }
1466
1467 const ptrdiff_t m = other.size();
1468
1469 ptrdiff_t start = 0;
1470 for (; start < m && !contains(other[start]) ; ++start) {
1471 }
1472
1473 if (start == m) {
1474 return false; // No vertex in the halfplane
1475 }
1476 // Now we know that other[start] is in the halfplane
1477
1478 int arcs = 0;
1481 start++; // Now other[start-1] is in the halfplane
1482 bool prev_in = true;
1483
1484 for (ptrdiff_t i = start; i < start+m; ++i) {
1485 const bool cur_in = contains(other.get(i));
1486 if (prev_in && !cur_in) {
1487 // Just went outside
1488 leaving = pgl::Line<typename OtherPolygon::PointType>(other.get(i), other.get(i-1));
1489 }
1490 if (cur_in && !prev_in) {
1491 // Just came inside, must check order
1492 pgl::Line<typename OtherPolygon::PointType> entering(other.get(i), other.get(i-1));
1493 if (boundary.crossingOrder(entering, leaving) >= 0) {
1494 ++arcs;
1495 if (arcs >= 2) {
1496 return true;
1497 }
1498 }
1499 }
1500
1501 prev_in = cur_in;
1502 }
1503 return false;
1504
1505}
1506
1507template <class PointType, class LabelType>
1509 return std::visit(
1510 [this](const auto& value) {
1511 return this->separates(value);
1512 },
1513 other.variant());
1514}
1515
1516
1517// ---------------------------------------------------------------------------
1518// Convex
1519
1520template <class PointType, class LabelType>
1521template<PointConcept OtherPoint>
1522constexpr bool Convex<PointType, LabelType>::separates(const OtherPoint&) const {
1523 return false;
1524}
1525
1526template <class PointType, class LabelType>
1527template<SegmentConcept OtherSegment>
1528constexpr bool Convex<PointType, LabelType>::separates(const OtherSegment& other) const {
1529 // A contact anywhere on the convex polygon -- boundary touch included --
1530 // leaves a piece of the segment on each side when both endpoints are
1531 // outside, so this gates on closed intersection, not interior crossing.
1532 return !other.isDegenerate() && !contains(other.min()) && !contains(other.max()) && intersects(other);
1533}
1534
1535template <class PointType, class LabelType>
1536template<OrientedSegmentConcept OtherOrientedSegment>
1537constexpr bool Convex<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
1539}
1540
1541template <class PointType, class LabelType>
1542template<LineConcept OtherLine>
1543constexpr bool Convex<PointType, LabelType>::separates(const OtherLine& other) const {
1544 return !other.isDegenerate() && intersects(other);
1545}
1546
1547template <class PointType, class LabelType>
1548template<OrientedLineConcept OtherOrientedLine>
1549constexpr bool Convex<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
1550 return separates(static_cast<Line<typename OtherOrientedLine::PointType>>(other));
1551}
1552
1553template <class PointType, class LabelType>
1554template<RayConcept OtherRay>
1555constexpr bool Convex<PointType, LabelType>::separates(const OtherRay& other) const {
1556 return !other.isDegenerate() && !isDegenerate() &&
1557 !contains(other.source()) && intersects(other);
1558}
1559
1560template <class PointType, class LabelType>
1561template<HalfplaneConcept OtherHalfplane>
1562constexpr bool Convex<PointType, LabelType>::separates(const OtherHalfplane&) const {
1563 return false;
1564}
1565
1566template <class PointType, class LabelType>
1567template<RectangleConcept OtherRectangle>
1568constexpr bool Convex<PointType, LabelType>::separates(const OtherRectangle& other) const {
1569 if (other.empty()) {
1570 // The empty set meets nothing and disconnects nothing.
1571 return false;
1572 }
1573 return separates(other.asConvex());
1574}
1575
1576template <class PointType, class LabelType>
1577template<TriangleConcept OtherTriangle>
1578constexpr bool Convex<PointType, LabelType>::separates(const OtherTriangle& other) const {
1579 return separates(other.asConvex());
1580}
1581
1582template <class PointType, class LabelType>
1583template<ConvexConcept OtherConvex>
1584constexpr bool Convex<PointType, LabelType>::separates(const OtherConvex& other) const {
1585 if (other.isDegenerate()) {
1586 // A convex with fewer than three vertices is the segment or point it
1587 // spans: the segment overload answers for the former, and a point is
1588 // never disconnected.
1589 if (const auto spanned = other.getIfSegment()) {
1590 return separates(*spanned);
1591 }
1592 return false;
1593 }
1594 if (isDegenerate()) {
1595 // What this degenerate shape spans does the cutting: a segment answers
1596 // as one, and a point disconnects nothing.
1597 if (const auto spanned = getIfSegment()) {
1598 return spanned->separates(other);
1599 }
1600 return false;
1601 }
1602 if (!bbox().intersects(other.bbox())) {
1603 return false;
1604 }
1605
1606 if (other.size() <= 2*size()) {
1607 const ptrdiff_t m = other.size();
1608 int arcs = 0;
1609 bool prev_in = contains(other[m - 1]);
1610 for (ptrdiff_t i = 0; i < m; ++i) {
1611 const bool cur_in = contains(other[i]);
1612 if (!prev_in && !cur_in) {
1613 // Both endpoints are out, but maybe the edge went through this
1614 Segment<typename OtherConvex::PointType> edge(other.get(i-1),other[i]);
1615 if (intersects(edge)) {
1616 ++arcs;
1617 if (arcs >= 2) {
1618 return true;
1619 }
1620 }
1621 }
1622 else if (prev_in && !cur_in) {
1623 // Just went outside
1624 ++arcs;
1625 if (arcs >= 2) {
1626 return true;
1627 }
1628 }
1629
1630 prev_in = cur_in;
1631 }
1632 }
1633 else {
1634 const ptrdiff_t n = size();
1635 int arcs = 0;
1636 bool prev_in = other.interiorContains((*this)[n - 1]);
1637 for (ptrdiff_t i = 0; i < n; ++i) {
1638 const bool cur_in = other.interiorContains((*this)[i]);
1639 if (!prev_in && !cur_in) {
1640 // Both endpoints are out, but maybe the edge went through this
1642 if (edge.separates(other)) {
1643 ++arcs;
1644 if (arcs >= 2) {
1645 return true;
1646 }
1647 }
1648 }
1649 else if (prev_in && !cur_in) {
1650 // Just went outside
1651 ++arcs;
1652 if (arcs >= 2) {
1653 return true;
1654 }
1655 }
1656
1657 prev_in = cur_in;
1658 }
1659 }
1660
1661 return false;
1662}
1663
1664template <class PointType, class LabelType>
1665template<DiskConcept OtherDisk>
1666constexpr bool Convex<PointType, LabelType>::separates(const OtherDisk& other) const {
1667 if (other.isDegenerate()) {
1668 return false;
1669 }
1670 if (isDegenerate()) {
1671 // What this degenerate shape spans does the cutting: a segment answers
1672 // as one, and a point disconnects nothing.
1673 if (const auto spanned = getIfSegment()) {
1674 return spanned->separates(other);
1675 }
1676 return false;
1677 }
1678
1679 // Removing the polygon disconnects the disk iff its boundary crosses the
1680 // circle at least four times (>= 2 in/out arcs). Each edge contributes:
1681 // - one endpoint inside, one outside -> 1 crossing,
1682 // - both endpoints outside and the edge carries a full chord -> 2,
1683 // - otherwise 0.
1684 // (A segment with both endpoints outside meets the circle 0 or 2 times, so
1685 // an interior crossing is exactly a full chord.) Counting per edge avoids
1686 // the earlier scheme that ignored crossings split across single-crossing
1687 // edges -- which under-reported, e.g. a thin rhombus laid across the disk.
1688 int crossings = 0;
1689 for (const auto& edge : edgesView()) {
1690 const bool min_inside = other.interiorContains(edge.min());
1691 const bool max_inside = other.interiorContains(edge.max());
1692 if (min_inside != max_inside) {
1693 ++crossings;
1694 } else if (!min_inside && edge.separates(other)) {
1695 crossings += 2;
1696 }
1697 if (crossings >= 4) {
1698 return true;
1699 }
1700 }
1701
1702 return false;
1703}
1704
1709
1710template <class PointType, class LabelType>
1711template<PointConcept OtherPoint>
1712constexpr bool Disk<PointType, LabelType>::separates(const OtherPoint&) const {
1713 return false;
1714}
1715
1716template <class PointType, class LabelType>
1717template <SegmentConcept OtherSegment>
1718constexpr bool Disk<PointType, LabelType>::separates(const OtherSegment& other) const {
1719 if (isDegenerate() || other.isDegenerate()) {
1720 return false;
1721 }
1722 return !contains(other.min()) && !contains(other.max()) && intersects(other);
1723}
1724
1725template <class PointType, class LabelType>
1726template <OrientedSegmentConcept OtherOrientedSegment>
1727constexpr bool Disk<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
1728 return separates(other.asSegment());
1729}
1730
1731template <class PointType, class LabelType>
1732template <LineConcept OtherLine>
1733constexpr bool Disk<PointType, LabelType>::separates(const OtherLine& other) const {
1734 if (isDegenerate() || other.isDegenerate()) {
1735 return false;
1736 }
1737 return intersects(other);
1738}
1739
1740template <class PointType, class LabelType>
1741template <OrientedLineConcept OtherOrientedLine>
1742constexpr bool Disk<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
1743 return separates(other.asLine());
1744}
1745
1746template <class PointType, class LabelType>
1747template <ConvexConcept OtherConvex>
1748constexpr bool Disk<PointType, LabelType>::separates(const OtherConvex& other) const {
1749 if (other.isDegenerate()) {
1750 // A convex with fewer than three vertices is the segment or point it
1751 // spans: the segment overload answers for the former, and a point is
1752 // never disconnected.
1753 if (const auto spanned = other.getIfSegment()) {
1754 return separates(*spanned);
1755 }
1756 return false;
1757 }
1758 if (isDegenerate()) {
1759 return false;
1760 }
1761
1762 const ptrdiff_t m = other.size();
1763 int arcs = 0;
1764 bool prev_in = contains(other[m - 1]);
1765 for (ptrdiff_t i = 0; i < m; ++i) {
1766 const bool cur_in = contains(other[i]);
1767 if (!prev_in && !cur_in) {
1768 // Both endpoints are out, but maybe the edge went through this
1769 Segment<typename OtherConvex::PointType> edge(other.get(i-1),other[i]);
1770 if (intersects(edge)) {
1771 ++arcs;
1772 if (arcs >= 2) {
1773 return true;
1774 }
1775 }
1776 }
1777 else if (prev_in && !cur_in) {
1778 // Just went outside
1779 ++arcs;
1780 if (arcs >= 2) {
1781 return true;
1782 }
1783 }
1784
1785 prev_in = cur_in;
1786 }
1787 return false;
1788}
1789
1790template <class PointType, class LabelType>
1791template<RayConcept OtherRay>
1792constexpr bool Disk<PointType, LabelType>::separates(const OtherRay& other) const {
1793 return !contains(other.source()) && intersects(other);
1794}
1795
1796template <class PointType, class LabelType>
1797template<HalfplaneConcept OtherHalfplane>
1798constexpr bool Disk<PointType, LabelType>::separates(const OtherHalfplane&) const {
1799 return false; // A disk never separates a halfplane
1800}
1801
1802template <class PointType, class LabelType>
1803template<RectangleConcept OtherRectangle>
1804constexpr bool Disk<PointType, LabelType>::separates(const OtherRectangle& other) const {
1805 if (other.empty()) {
1806 // The empty set meets nothing and disconnects nothing.
1807 return false;
1808 }
1809 int count = 0;
1810 for (int i = 0; i < 4; i++) {
1811 pgl::Segment<typename OtherRectangle::PointType> edge(other.get(i),other.get(i+1));
1812 if (separates(edge)) {
1813 count++;
1814 typename OtherRectangle::PointType opposite1 = other.get(i+2);
1815 typename OtherRectangle::PointType opposite2 = other.get(i+3);
1816 if (contains(opposite1) || contains(opposite2)) {
1817 return true;
1818 }
1819 }
1820 }
1821 return count >= 2;
1822}
1823
1824template <class PointType, class LabelType>
1825template<TriangleConcept OtherTriangle>
1826constexpr bool Disk<PointType, LabelType>::separates(const OtherTriangle& other) const {
1827 int count = 0;
1828 for (int i = 0; i < 3; i++) {
1829 pgl::Segment<typename OtherTriangle::PointType> edge(other.get(i),other.get(i+1));
1830 if (separates(edge)) {
1831 count++;
1832 typename OtherTriangle::PointType opposite = other.get(i+2);
1833 if (contains(opposite)) {
1834 return true;
1835 }
1836 }
1837 }
1838 return count >= 2;
1839}
1840
1841template <class PointType, class LabelType>
1842template<DiskConcept OtherDisk>
1843constexpr bool Disk<PointType, LabelType>::separates(const OtherDisk&) const {
1844 return false; // a disk never separates another disk
1845}
1846
1847template <class PointType, class LabelType>
1848template <PointConcept OtherPoint>
1850 return std::visit(
1851 [this](const auto& value) {
1852 return this->separates(value);
1853 },
1854 other.variant());
1855}
1856
1861
1862template <class PointType, class LabelType>
1863template<PointConcept OtherPoint>
1864constexpr bool Polygon<PointType, LabelType>::separates(const OtherPoint&) const {
1865 return false;
1866}
1867
1868template <class PointType, class LabelType>
1869template<SegmentConcept OtherSegment>
1870constexpr bool Polygon<PointType, LabelType>::separates(const OtherSegment& other) const {
1871 if (isDegenerate() || other.isDegenerate()) {
1872 return false;
1873 }
1874
1875 std::optional<pgl::OrientedSegment<PointType>> minSeg, maxSeg;
1876
1878 bool gte = pgl::Triangle<PointType>(s[0],a[0],a[1]).interiorsIntersect(pgl::Triangle<PointType>(s[1],b[0],b[1]));
1879 bool lte = pgl::Triangle<PointType>(s[0],b[0],b[1]).interiorsIntersect(pgl::Triangle<PointType>(s[1],a[0],a[1]));
1880 if (gte && !lte) return std::partial_ordering::greater;
1881 if (lte && !gte) return std::partial_ordering::less;
1882 return std::partial_ordering::equivalent;
1883 };
1884
1885 for (ptrdiff_t i = 0; i < (ptrdiff_t) size(); ++i) {
1887 if (edge.separates(other) && !edge.collinear(other)) {
1888 auto h = edge.leftHalfplane();
1889 if (h.contains(other[0])) {
1890 if (!maxSeg || crossingOrder(other, *maxSeg, edge) < 0) {
1891 if (other.contains(edge[0])) {
1892 pgl::OrientedSegment<PointType> previous(get(i-1), get(i));
1893 if (previous.collinear(other) && !h.contains(previous)) continue;
1894 auto hprevious = previous.leftHalfplane();
1895 if (!previous.collinear(other) && hprevious.contains(other[1]) && !hprevious.contains(edge[1])) continue;
1896 }
1897 else if (other.contains(edge[1])) {
1898 pgl::OrientedSegment<PointType> next(get(i+1), get(i+2));
1899 if (next.collinear(other) && !h.contains(next)) continue;
1900 auto hnext = next.leftHalfplane();
1901 if (!next.collinear(other) && hnext.contains(other[1]) && !hnext.contains(edge[0])) continue;
1902 }
1903 maxSeg = edge;
1904 if (minSeg && maxSeg && crossingOrder(other, *minSeg, *maxSeg) <= 0) {
1905 return true;
1906 }
1907 }
1908 }
1909 else {
1910 if (!minSeg || crossingOrder(other, *minSeg, edge) > 0) {
1911 if (other.contains(edge[0])) {
1912 pgl::OrientedSegment<PointType> previous(get(i-1), get(i));
1913 if (previous.collinear(other) && !h.contains(previous)) continue;
1914 auto hprevious = previous.leftHalfplane();
1915 if (!previous.collinear(other) && hprevious.contains(other[0]) && !hprevious.contains(edge[1])) continue;
1916 }
1917 else if (other.contains(edge[1])) {
1918 pgl::OrientedSegment<PointType> next(get(i+1), get(i+2));
1919 if (next.collinear(other) && !h.contains(next)) continue;
1920 auto hnext = next.leftHalfplane();
1921 if (!next.collinear(other) && hnext.contains(other[0]) && !hnext.contains(edge[0])) continue;
1922 }
1923 minSeg = edge;
1924 if (minSeg && maxSeg && crossingOrder(other, *minSeg, *maxSeg) <= 0) {
1925 return true;
1926 }
1927 }
1928 }
1929 }
1930 }
1931 return false;
1932}
1933
1934template <class PointType, class LabelType>
1935template<RayConcept OtherRay>
1936constexpr bool Polygon<PointType, LabelType>::separates(const OtherRay& other) const {
1937 if (isDegenerate() || other.isDegenerate()) {
1938 return false;
1939 }
1940
1942
1943 for (ptrdiff_t i = 0; i < (ptrdiff_t) size(); ++i) {
1945 if (edge.separates(other) && !edge.collinear(other)) {
1946 auto h = edge.leftHalfplane();
1947 if (!h.contains(other.source())) {
1948 if (other.contains(edge[0])) {
1949 pgl::OrientedSegment<PointType> previous(get(i-1), get(i));
1950 if (previous.collinear(other) && !h.contains(previous)) continue;
1951 auto hprevious = previous.leftHalfplane();
1952 if (!previous.collinear(other) && hprevious.contains(other[0]) && !hprevious.contains(edge[1])) continue;
1953 }
1954 else if (other.contains(edge[1])) {
1955 pgl::OrientedSegment<PointType> next(get(i+1), get(i+2));
1956 if (next.collinear(other) && !h.contains(next)) continue;
1957 auto hnext = next.leftHalfplane();
1958 if (!next.collinear(other) && hnext.contains(other[0]) && !hnext.contains(edge[0])) continue;
1959 }
1960 return true;
1961 }
1962 }
1963 }
1964 return false;
1965}
1966
1967template <class PointType, class LabelType>
1968template<LineConcept OtherLine>
1969constexpr bool Polygon<PointType, LabelType>::separates(const OtherLine& other) const {
1970 if (isDegenerate() || other.isDegenerate()) {
1971 return false;
1972 }
1973 return intersects(other);
1974}
1975
1976template <class PointType, class LabelType>
1977template<OrientedSegmentConcept OtherOrientedSegment>
1978constexpr bool Polygon<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
1979 return separates(other.asSegment());
1980}
1981
1982template <class PointType, class LabelType>
1983template<OrientedLineConcept OtherOrientedLine>
1984constexpr bool Polygon<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
1985 return separates(other.asLine());
1986}
1987
1988template <class PointType, class LabelType>
1989template<HalfplaneConcept OtherHalfplane>
1990constexpr bool Polygon<PointType, LabelType>::separates(const OtherHalfplane&) const {
1991 return false; // A polygon never separates a halfplane
1992}
1993
1994
1995template <class PointType, class LabelType>
1996template<PolygonConcept OtherPolygon>
1997constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherPolygon& other) const {
1998 return this->asSegment().separates(other);
1999}
2000
2001template <class PointType, class LabelType>
2002template<PolygonConcept OtherPolygon>
2003constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherPolygon& other) const {
2004 return this->asLine().separates(other);
2005}
2006
2007template <class PointType, class LabelType>
2008template<PolygonConcept OtherPolygon>
2009constexpr bool Line<PointType, LabelType>::separates(const OtherPolygon& other) const {
2010 if (isDegenerate()) {
2011 return false;
2012 }
2013 return other.interiorsIntersect(*this);
2014}
2015
2016template <class PointType, class LabelType>
2017template<PolygonConcept OtherPolygon>
2018constexpr bool Rectangle<PointType, LabelType>::separates(const OtherPolygon& other) const {
2019 if (empty()) {
2020 // The empty set meets nothing and disconnects nothing.
2021 return false;
2022 }
2023 return asConvex().separates(other);
2024}
2025
2026template <class PointType, class LabelType>
2027template<PolygonConcept OtherPolygon>
2028constexpr bool Triangle<PointType, LabelType>::separates(const OtherPolygon& other) const {
2029 return asConvex().separates(other);
2030}
2031
2032template <class PointType, class LabelType>
2033template<PolygonConcept OtherPolygon>
2034constexpr bool Convex<PointType, LabelType>::separates(const OtherPolygon& other) const {
2035 if (other.isDegenerate()) {
2036 // A polygon that collapses to a segment is cut as that segment is; a
2037 // point is never disconnected.
2038 if (const auto spanned = other.getIfSegment()) {
2039 return separates(*spanned);
2040 }
2041 return false;
2042 }
2043 if (isDegenerate()) {
2044 // What this degenerate shape spans does the cutting: a segment answers
2045 // as one, and a point disconnects nothing.
2046 if (const auto spanned = getIfSegment()) {
2047 return spanned->separates(other);
2048 }
2049 return false;
2050 }
2051 if (!bbox().intersects(other.bbox())) {
2052 return false;
2053 }
2054
2055 // Removing the convex body C from the polygon P disconnects P iff some
2056 // connected component of C ∩ P touches ∂P in two or more pieces. Counting
2057 // boundary arcs (as the Convex overload does) is not enough here: a reflex
2058 // polygon can dip into C through several separate pockets while P \ C
2059 // stays connected. Instead, walk around ∂C: P is cut exactly when some
2060 // maximal arc of ∂C through the open interior of P joins two contacts
2061 // belonging to *different* components of ∂P ∩ C. This also catches a
2062 // convex body inside P that pinches ∂P at two isolated touch points.
2063 //
2064 // Events are the contacts of ∂P with ∂C, each labelled with the component
2065 // of ∂P ∩ C it belongs to and located on its convex edge by a line that
2066 // crosses the edge at the contact, so no intersection point is ever
2067 // constructed. Each event also records the local feature of ∂P at the
2068 // contact (the crossing edge, or the wedge at a polygon vertex), so that
2069 // whether the ∂C arc leaving a contact enters the interior of P is decided
2070 // by orientation signs at that contact alone. Stretches where the two
2071 // boundaries overlap are bounded by events of the same component and can
2072 // therefore never fire the test.
2073
2074 using CommonNumber = std::common_type_t<typename PointType::NumberType,
2075 typename OtherPolygon::NumberType>;
2076 using CommonPoint = Point<CommonNumber>;
2077 using PosLine = Line<CommonPoint>;
2078
2079 const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(other.size());
2080 const std::ptrdiff_t m = static_cast<std::ptrdiff_t>(size());
2081
2082 const auto common = [](const auto& p) {
2083 return CommonPoint(static_cast<CommonNumber>(p.x()),
2084 static_cast<CommonNumber>(p.y()));
2085 };
2086
2087 // A line through `at` perpendicular to convex edge k; it crosses that
2088 // edge exactly at `at`, placing a contact point that is known explicitly.
2089 const auto perpendicularAt = [&](const CommonPoint& at, std::ptrdiff_t k) {
2090 const CommonPoint a = common(get(k));
2091 const CommonPoint b = common(get(k + 1));
2092 return PosLine(at, CommonPoint(at.x() - (b.y() - a.y()),
2093 at.y() + (b.x() - a.x())));
2094 };
2095
2096 struct Event {
2097 std::ptrdiff_t cedge; // convex edge carrying the contact
2098 PosLine where; // crosses that edge at the contact point
2099 int component; // component of ∂P ∩ C the contact belongs to
2100 CommonPoint a, b, c; // ∂P at the contact: edge a->b, or wedge a->b->c
2101 bool atVertex; // contact is the polygon vertex b
2102 };
2103 std::vector<Event> events;
2104
2105 // The first (mode first) or last contact of polygon edge pa->pb with ∂C.
2106 // Contacts are ordered along pa->pb by where the convex edge's line
2107 // crosses it; that line is parallel to pa->pb only for a collinear
2108 // overlap, whose near and far ends are explicit endpoints. A contact
2109 // landing exactly on the run's vertex inside C is dropped when requested:
2110 // the vertex's own wedge events describe that contact better.
2111 const auto extremeContact = [&](const typename OtherPolygon::PointType& pa, const typename OtherPolygon::PointType& pb,
2112 bool first, bool skipAtRunVertex)
2113 -> std::optional<std::pair<std::ptrdiff_t, PosLine>> {
2114 const CommonPoint a = common(pa);
2115 const CommonPoint b = common(pb);
2116 const Segment<CommonPoint> s(a, b);
2117 const OrientedLine<CommonPoint> axis(a, b);
2118 std::optional<std::pair<std::ptrdiff_t, PosLine>> best;
2119 std::optional<PosLine> bestAlong;
2120 const auto consider = [&](std::ptrdiff_t k, const PosLine& where,
2121 const PosLine& along) {
2122 if (bestAlong) {
2123 const auto order = axis.crossingOrder(along, *bestAlong);
2124 const bool improves =
2125 first ? order == std::partial_ordering::less
2126 : order == std::partial_ordering::greater;
2127 if (!improves) {
2128 return;
2129 }
2130 }
2131 best.emplace(k, where);
2132 bestAlong = along;
2133 };
2134 for (std::ptrdiff_t k = 0; k < m; ++k) {
2135 const Segment<CommonPoint> edge(common(get(k)), common(get(k + 1)));
2136 if (!edge.intersects(s)) {
2137 continue;
2138 }
2139 if (s.parallel(edge)) {
2140 const CommonPoint lo = std::max(s.min(), edge.min());
2141 const CommonPoint hi = std::min(s.max(), edge.max());
2142 const CommonPoint at = (first == (a < b)) ? lo : hi;
2143 const PosLine across = perpendicularAt(at, k);
2144 consider(k, across, across);
2145 } else {
2146 consider(k, PosLine(a, b),
2147 PosLine(common(get(k)), common(get(k + 1))));
2148 }
2149 }
2150 if (best && skipAtRunVertex) {
2151 const CommonPoint inVertex = first ? b : a;
2152 const PosLine atInVertex(
2153 inVertex, CommonPoint(inVertex.x() - (b.y() - a.y()),
2154 inVertex.y() + (b.x() - a.x())));
2155 if (axis.crossingOrder(*bestAlong, atInVertex) ==
2156 std::partial_ordering::equivalent) {
2157 return std::nullopt;
2158 }
2159 }
2160 return best;
2161 };
2162
2163 std::vector<char> in(static_cast<std::size_t>(n));
2164 std::ptrdiff_t start = -1;
2165 for (std::ptrdiff_t i = 0; i < n; ++i) {
2166 in[static_cast<std::size_t>(i)] = contains(other.get(i));
2167 if (!in[static_cast<std::size_t>(i)]) {
2168 start = i;
2169 }
2170 }
2171 if (start < 0) {
2172 return false; // the whole polygon lies inside the convex body
2173 }
2174
2175 int component = 0;
2176
2177 // A polygon vertex inside C that lies on ∂C is a contact in its own
2178 // right; the pair of wedge events splits the ∂C arcs it sits between
2179 // (the two-touch pinch case) without affecting any other arc.
2180 const auto addVertexContact = [&](std::ptrdiff_t i) {
2181 const typename OtherPolygon::PointType vertex = other.get(i);
2182 if (!boundaryContains(vertex)) {
2183 return;
2184 }
2185 const CommonPoint v = common(vertex);
2186 for (std::ptrdiff_t k = 0; k < m; ++k) {
2187 const Segment<CommonPoint> edge(common(get(k)), common(get(k + 1)));
2188 if (edge.contains(v)) {
2189 const PosLine across = perpendicularAt(v, k);
2190 const Event event{k, across, component,
2191 common(other.get(i - 1)), v,
2192 common(other.get(i + 1)), true};
2193 events.push_back(event);
2194 events.push_back(event);
2195 return;
2196 }
2197 }
2198 };
2199
2200 bool previous = false; // in[start] is false
2201 for (std::ptrdiff_t i = start + 1; i <= start + n; ++i) {
2202 const bool current = in[static_cast<std::size_t>(i % n)];
2203 const auto u = other.get(i - 1);
2204 const auto v = other.get(i);
2205 if (current && !previous) {
2206 ++component;
2207 if (const auto contact = extremeContact(u, v, true, true)) {
2208 events.push_back({contact->first, contact->second, component,
2209 common(u), common(v), common(v), false});
2210 }
2211 } else if (!current && previous) {
2212 if (const auto contact = extremeContact(u, v, false, true)) {
2213 events.push_back({contact->first, contact->second, component,
2214 common(u), common(v), common(v), false});
2215 }
2216 } else if (!current && intersects(Segment<typename OtherPolygon::PointType>(u, v))) {
2217 // Both endpoints outside: the edge meets the convex body in a
2218 // single sub-segment or touch point, a component of its own.
2219 ++component;
2220 if (const auto contact = extremeContact(u, v, true, false)) {
2221 events.push_back({contact->first, contact->second, component,
2222 common(u), common(v), common(v), false});
2223 }
2224 if (const auto contact = extremeContact(u, v, false, false)) {
2225 events.push_back({contact->first, contact->second, component,
2226 common(u), common(v), common(v), false});
2227 }
2228 }
2229 if (current) {
2230 addVertexContact(i);
2231 }
2232 previous = current;
2233 }
2234
2235 if (events.empty()) {
2236 return false; // the boundaries never meet: nested or disjoint
2237 }
2238
2239 std::sort(events.begin(), events.end(),
2240 [&](const Event& x, const Event& y) {
2241 if (x.cedge != y.cedge) {
2242 return x.cedge < y.cedge;
2243 }
2244 const OrientedLine<CommonPoint> edge(common(get(x.cedge)),
2245 common(get(x.cedge + 1)));
2246 return edge.crossingOrder(x.where, y.where) ==
2247 std::partial_ordering::less;
2248 });
2249
2250 // Whether ∂C heads strictly into the interior of P as it leaves the
2251 // event's contact point, decided against the recorded ∂P feature there.
2252 const auto entersInterior = [&](const Event& event) {
2253 const std::ptrdiff_t k = event.cedge;
2254 const OrientedLine<CommonPoint> edgeLine(common(get(k)),
2255 common(get(k + 1)));
2256 const bool atEdgeEnd =
2257 edgeLine.crossingOrder(event.where,
2258 perpendicularAt(common(get(k + 1)), k)) ==
2259 std::partial_ordering::equivalent;
2260 const CommonPoint from = common(get(k + (atEdgeEnd ? 1 : 0)));
2261 const CommonPoint to = common(get(k + (atEdgeEnd ? 2 : 1)));
2262 const CommonNumber dx = to.x() - from.x();
2263 const CommonNumber dy = to.y() - from.y();
2264 if (!event.atVertex) {
2265 // Contact interior to polygon edge a->b: P's interior is strictly
2266 // to its left, so the arc dives inside iff it heads left.
2267 return orientationSign(event.a, event.b,
2268 CommonPoint(event.a.x() + dx,
2269 event.a.y() + dy)) > 0;
2270 }
2271 // Contact at polygon vertex b: the direction must point strictly into
2272 // the interior wedge between the edges a->b and b->c.
2273 const CommonPoint probe(event.b.x() + dx, event.b.y() + dy);
2274 const auto sideIn = orientationSign(event.a, event.b, probe);
2275 const auto sideOut = orientationSign(event.b, event.c, probe);
2276 return orientationSign(event.a, event.b, event.c) >= 0
2277 ? (sideIn > 0 && sideOut > 0)
2278 : (sideIn > 0 || sideOut > 0);
2279 };
2280
2281 const std::size_t count = events.size();
2282 for (std::size_t t = 0; t < count; ++t) {
2283 const Event& current = events[t];
2284 const Event& next = events[(t + 1) % count];
2285 if (current.component != next.component && entersInterior(current)) {
2286 return true;
2287 }
2288 }
2289 return false;
2290}
2291
2292template <class PointType, class LabelType>
2293template<PolygonConcept OtherPolygon>
2294constexpr bool Disk<PointType, LabelType>::separates(const OtherPolygon& other) const {
2295 if (isDegenerate() || other.isDegenerate()) {
2296 return false;
2297 }
2298
2299 // Convex fast path -- same arc count as Disk::separates(Convex): removing
2300 // the disk disconnects a convex polygon iff its boundary leaves the disk in
2301 // >= 2 separate arcs. Each vertex is inside or outside the disk (an exact
2302 // squared-distance test), and an edge with both endpoints outside still
2303 // contributes an arc if it touches the disk.
2304 if (other.isConvex()) {
2305 const std::ptrdiff_t m = static_cast<std::ptrdiff_t>(other.size());
2306 int arcs = 0;
2307 bool prev_in = contains(other[m - 1]);
2308 for (std::ptrdiff_t i = 0; i < m; ++i) {
2309 const bool cur_in = contains(other[i]);
2310 if (!prev_in && !cur_in) {
2311 const Segment<typename OtherPolygon::PointType> edge(other.get(i - 1), other[i]);
2312 if (intersects(edge)) {
2313 ++arcs;
2314 }
2315 } else if (prev_in && !cur_in) {
2316 ++arcs;
2317 }
2318 if (arcs >= 2) {
2319 return true;
2320 }
2321 prev_in = cur_in;
2322 }
2323 return false;
2324 }
2325
2326 // General simple polygon. The arc count above over-reports here: a reflex
2327 // polygon can wrap around the disk and reconnect two of its boundary arcs,
2328 // so connectivity of P \ D is decided directly -- still without ever
2329 // constructing the (irrational) circle crossings. Triangulate P and glue
2330 // the convex per-triangle answers:
2331 // - Within one triangle T, the components of T \ D correspond one-to-one
2332 // to the maximal runs of pieces of dT outside the disk that are not
2333 // interrupted by a contact of the (closed) disk with dT: the disk's
2334 // bite off the convex T disconnects it exactly at those contacts.
2335 // - A triangulation edge keeps at most two pieces outside the disk (the
2336 // whole edge, or the parts adjacent to each endpoint), and which of
2337 // them exist depends only on the edge, never on the incident triangle.
2338 // Both triangles sharing the edge therefore resolve a surviving piece
2339 // to the same slot, and any crossing between adjacent triangles goes
2340 // through such a piece (the complement of the closed disk is open
2341 // along the edge, so a crossing point sits in a piece of positive
2342 // length).
2343 // Union-find over the pieces then yields the components of P \ D: the disk
2344 // separates the polygon iff at least two classes remain. No pieces at all
2345 // means the polygon is swallowed by the disk, which does not separate it.
2346 //
2347 // Layering: Triangulation lives in algorithm/triangulation.hpp, which
2348 // pgl.hpp includes after this header. Reach it only through the dependent
2349 // call other.triangulation(), never by naming the class here.
2350 const auto mesh = other.triangulation();
2351 using Mesh = std::decay_t<decltype(mesh)>;
2352 using TriId = typename Mesh::TriId;
2353 using VertexId = typename Mesh::VertexId;
2355
2356 std::vector<std::size_t> parent;
2357 const auto findRoot = [&parent](std::size_t x) {
2358 while (parent[x] != x) {
2359 parent[x] = parent[parent[x]];
2360 x = parent[x];
2361 }
2362 return x;
2363 };
2364 const auto unite = [&](std::size_t a, std::size_t b) {
2365 parent[findRoot(a)] = findRoot(b);
2366 };
2367
2368 // Whether the disk swallows a vertex depends on the vertex alone, so the
2369 // test is paid once per vertex instead of once per incident triangle.
2370 std::vector<char> swallowed(mesh.vertexIndexBound(), 0);
2371 for (const VertexId v : mesh.vertexIds()) {
2372 swallowed[v.index()] = contains(mesh.getShape(v)) ? 1 : 0;
2373 }
2374
2375 // A piece is named by the edge that carries it and which end it hangs from.
2376 // Handle order stands in for the lexicographic order of the endpoints: all
2377 // the shared slot needs is that both incident triangles name the ends the
2378 // same way, and either order does that.
2379 enum : int { WHOLE = 0, NEAR_LOW = 1, NEAR_HIGH = 2 };
2380 using EdgeKey = std::pair<VertexId, VertexId>;
2381 std::map<EdgeKey, std::array<std::ptrdiff_t, 3>> pieceSlots;
2382 const auto slotOf = [&](VertexId a, VertexId b, int piece) {
2383 const EdgeKey key = a < b ? EdgeKey{a, b} : EdgeKey{b, a};
2384 auto [it, inserted] =
2385 pieceSlots.try_emplace(key, std::array<std::ptrdiff_t, 3>{-1, -1, -1});
2386 std::ptrdiff_t& slot = it->second[static_cast<std::size_t>(piece)];
2387 if (slot < 0) {
2388 slot = static_cast<std::ptrdiff_t>(parent.size());
2389 parent.push_back(parent.size());
2390 }
2391 return static_cast<std::size_t>(slot);
2392 };
2393
2394 constexpr std::size_t NO_PIECE = std::numeric_limits<std::size_t>::max();
2395 mesh.visitTriangles([&](TriId t) {
2396 // Walk dT once, emitting its outside pieces in boundary order and
2397 // recording every disk contact between them; gap-free consecutive
2398 // pieces (wrap-around included) bound the same component of T \ D.
2399 std::size_t firstPiece = NO_PIECE;
2400 std::size_t lastPiece = NO_PIECE;
2401 bool gapBeforeFirst = false;
2402 bool gap = false; // contact seen since the last emitted piece
2403 const auto emit = [&](std::size_t slot) {
2404 if (lastPiece == NO_PIECE) {
2405 firstPiece = slot;
2406 gapBeforeFirst = gap;
2407 } else if (!gap) {
2408 unite(lastPiece, slot);
2409 }
2410 lastPiece = slot;
2411 gap = false;
2412 };
2413
2414 // vertices(t)[i] is the vertex at getShape(t)[i], so side i still runs
2415 // from vertex i to vertex i + 1 as the value walk had it.
2416 const auto v = mesh.vertices(t);
2417 const bool in[3] = {swallowed[v[0].index()] != 0, swallowed[v[1].index()] != 0,
2418 swallowed[v[2].index()] != 0};
2419 for (int i = 0; i < 3; ++i) {
2420 const VertexId a = v[i];
2421 const VertexId b = v[(i + 1) % 3];
2422 const int nearA = a < b ? NEAR_LOW : NEAR_HIGH;
2423 const int nearB = nearA == NEAR_LOW ? NEAR_HIGH : NEAR_LOW;
2424 if (in[i] && in[(i + 1) % 3]) { // edge swallowed by the disk
2425 gap = true;
2426 } else if (in[i]) { // walk leaves the disk mid-edge
2427 gap = true;
2428 emit(slotOf(a, b, nearB));
2429 } else if (in[(i + 1) % 3]) { // walk enters the disk mid-edge
2430 emit(slotOf(a, b, nearA));
2431 gap = true;
2432 } else if (intersects(Seg(mesh.getShape(a), mesh.getShape(b)))) {
2433 emit(slotOf(a, b, nearA)); // contact strictly inside the edge
2434 gap = true;
2435 emit(slotOf(a, b, nearB));
2436 } else { // edge clear of the disk
2437 emit(slotOf(a, b, WHOLE));
2438 }
2439 }
2440 if (firstPiece != NO_PIECE && !gap && !gapBeforeFirst) {
2441 unite(lastPiece, firstPiece);
2442 }
2443 });
2444
2445 std::size_t classes = 0;
2446 for (std::size_t i = 0; i < parent.size(); ++i) {
2447 if (parent[i] == i && ++classes >= 2) {
2448 return true;
2449 }
2450 }
2451 return false;
2452}
2453
2454template <class PointType, class LabelType>
2455template<RectangleConcept OtherRectangle>
2456constexpr bool Polygon<PointType, LabelType>::separates(const OtherRectangle& other) const {
2457 if (other.empty()) {
2458 // The empty set meets nothing and disconnects nothing.
2459 return false;
2460 }
2461 return separates(other.asPolygon());
2462}
2463
2464template <class PointType, class LabelType>
2465template<TriangleConcept OtherTriangle>
2466constexpr bool Polygon<PointType, LabelType>::separates(const OtherTriangle& other) const {
2467 return separates(other.asPolygon());
2468}
2469
2470template <class PointType, class LabelType>
2471template<DiskConcept OtherDisk>
2472constexpr bool Polygon<PointType, LabelType>::separates(const OtherDisk& other) const {
2473 if (other.isDegenerate()) {
2474 return false;
2475 }
2476 if (isDegenerate()) {
2477 // What this degenerate shape spans does the cutting: a segment answers
2478 // as one, and a point disconnects nothing.
2479 if (const auto spanned = getIfSegment()) {
2480 return spanned->separates(other);
2481 }
2482 return false;
2483 }
2484
2485 // Convex fast path -- same crossing count as Convex::separates(Disk):
2486 // removing a convex polygon disconnects the disk when the polygon boundary
2487 // crosses the circle >= 4 times (>= 2 in/out arcs). Per edge:
2488 // - one endpoint inside, one outside -> 1 crossing,
2489 // - both endpoints outside and the edge carries a full chord -> 2,
2490 // - otherwise 0.
2491 if (isConvex()) {
2492 int crossings = 0;
2493 for (const auto& edge : edgesView()) {
2494 const bool min_inside = other.interiorContains(edge.min());
2495 const bool max_inside = other.interiorContains(edge.max());
2496 if (min_inside != max_inside) {
2497 ++crossings;
2498 } else if (!min_inside && edge.separates(other)) {
2499 crossings += 2;
2500 }
2501 if (crossings >= 4) {
2502 return true;
2503 }
2504 }
2505 return false;
2506 }
2507
2508 // General simple polygon P. The crossing count above over-counts here: a
2509 // non-convex P can take several separate bites out of the disk D, and the
2510 // bites' crossings do not disconnect D \ P. Connectivity is decided
2511 // directly instead -- still without constructing the (irrational) circle
2512 // crossings:
2513 // - Enclose both shapes in a box polygon and build its constrained
2514 // Delaunay triangulation with P's edges as constraints. Every triangle
2515 // then lies entirely inside or entirely outside P; the sides are told
2516 // apart by a flood fill from a box corner that flips whenever it steps
2517 // over a constrained edge.
2518 // - For an outside triangle T, the piece D ∩ T \ P is convex minus a
2519 // part of its own boundary, hence connected. Two pieces adjacent
2520 // across a triangulation edge e belong to the same component of D \ P
2521 // iff (D ∩ e) \ P is nonempty. Only e's endpoints can lie in P (a P
2522 // vertex; box corners are outside D by construction and e's relative
2523 // interior never meets P), so that reduces to: the open disk meets e,
2524 // or the closed disk touches e in a single interior point.
2525 // Union-find over the outside triangles then yields the components of
2526 // D \ P; the polygon separates the disk iff pieces of D \ P fall into at
2527 // least two classes. Pieces pinched at a P vertex on the circle stay
2528 // apart, because the pinch point itself belongs to P.
2529 using Common = std::common_type_t<typename PointType::NumberType,
2530 typename OtherDisk::NumberType>;
2531 using CPoint = Point<Common>;
2532 using CSeg = Segment<CPoint>;
2533
2534 // Bounding box of both shapes, inflated so the box strictly contains them.
2535 const auto dbox = other.bbox();
2536 Common xlo = static_cast<Common>(dbox.min().x());
2537 Common ylo = static_cast<Common>(dbox.min().y());
2538 Common xhi = static_cast<Common>(dbox.max().x());
2539 Common yhi = static_cast<Common>(dbox.max().y());
2540 for (std::size_t i = 0; i < size(); ++i) {
2541 const auto v = (*this)[i];
2542 const Common vx = static_cast<Common>(v.x());
2543 const Common vy = static_cast<Common>(v.y());
2544 xlo = vx < xlo ? vx : xlo;
2545 ylo = vy < ylo ? vy : ylo;
2546 xhi = vx > xhi ? vx : xhi;
2547 yhi = vy > yhi ? vy : yhi;
2548 }
2549 xlo = xlo - Common{1};
2550 ylo = ylo - Common{1};
2551 xhi = xhi + Common{1};
2552 yhi = yhi + Common{1};
2553 const Polygon<CPoint> box(std::vector<CPoint>{
2554 CPoint(xlo, ylo), CPoint(xhi, ylo), CPoint(xhi, yhi), CPoint(xlo, yhi)});
2555
2556 // P's edges become the constraints. The mesh keeps them flagged as such, and
2557 // that flag is what tells the flood fill below where the inside/outside
2558 // classification flips -- no separate set of P's edges to consult.
2559 std::vector<CSeg> constraints;
2560 constraints.reserve(size());
2561 for (std::size_t i = 0; i < size(); ++i) {
2562 const auto u = (*this)[i];
2563 const auto w = get(static_cast<std::ptrdiff_t>(i) + 1);
2564 constraints.emplace_back(
2565 CPoint(detail::asNumber<Common>(u.x()), detail::asNumber<Common>(u.y())),
2566 CPoint(detail::asNumber<Common>(w.x()), detail::asNumber<Common>(w.y())));
2567 }
2568
2569 // Layering: Triangulation lives in algorithm/triangulation.hpp, which
2570 // pgl.hpp includes after this header. Reach it only through the dependent
2571 // call box.triangulation(constraints), never by naming the class here.
2572 const auto mesh = box.triangulation(constraints);
2573 using TriId = typename std::decay_t<decltype(mesh)>::TriId;
2574 // A handle's index is a slot of a table this size, so the per-triangle
2575 // bookkeeping below is plain vectors and crossing an edge is an array read.
2576 // The bound counts the storage rather than the triangles: the slots it holds
2577 // beyond them stay untouched, their state left at "unvisited".
2578 const std::size_t n = mesh.triangleIndexBound();
2579
2580 // Flood fill from a box corner: -1 unvisited, 0 inside P, 1 outside P.
2581 std::vector<signed char> state(n, -1);
2582 std::vector<TriId> pending;
2583 for (const TriId t : mesh.incidentTriangles(mesh.getId(CPoint(xlo, ylo)))) {
2584 if (state[t.index()] < 0) {
2585 state[t.index()] = 1;
2586 pending.push_back(t);
2587 }
2588 }
2589 while (!pending.empty()) {
2590 const TriId t = pending.back();
2591 pending.pop_back();
2592 const std::size_t i = t.index();
2593 for (int k = 0; k < 3; ++k) {
2594 const auto nb = mesh.otherTriangle(t, k);
2595 if (!nb) {
2596 continue;
2597 }
2598 const std::size_t j = nb->index();
2599 if (state[j] < 0) {
2600 // Side k is constrained exactly when it is an edge of P, the
2601 // only place the classification flips. The box boundary is
2602 // constrained too, but it has no neighbor to cross to.
2603 state[j] = mesh.isConstrained(t, k)
2604 ? static_cast<signed char>(1 - state[i])
2605 : state[i];
2606 pending.push_back(*nb);
2607 }
2608 }
2609 }
2610
2611 std::vector<std::size_t> parent(n);
2612 for (std::size_t i = 0; i < n; ++i) {
2613 parent[i] = i;
2614 }
2615 const auto findRoot = [&parent](std::size_t x) {
2616 while (parent[x] != x) {
2617 parent[x] = parent[parent[x]];
2618 x = parent[x];
2619 }
2620 return x;
2621 };
2622
2623 mesh.visitTriangles([&](TriId t) {
2624 const std::size_t i = t.index();
2625 if (state[i] != 1) {
2626 return;
2627 }
2628 const auto tri = mesh.getShape(t);
2629 for (int k = 0; k < 3; ++k) {
2630 const auto nb = mesh.otherTriangle(t, k);
2631 if (!nb) {
2632 continue;
2633 }
2634 const std::size_t j = nb->index();
2635 if (j < i || state[j] != 1) {
2636 continue; // shared edge handled from the smaller index only
2637 }
2638 const CSeg e(tri.get(k), tri.get(k + 1));
2639 if (other.intersects(e) &&
2640 (other.interiorsIntersect(e) ||
2641 (!other.contains(e.min()) && !other.contains(e.max())))) {
2642 parent[findRoot(i)] = findRoot(j);
2643 }
2644 }
2645 });
2646
2647 // D \ P has no isolated points and no pinches, so every component holds a
2648 // triangle whose open interior meets the open disk; count those classes.
2649 std::size_t classes = 0;
2650 std::vector<char> counted(n, 0);
2651 return mesh.visitTriangles([&](TriId t) {
2652 const std::size_t i = t.index();
2653 if (state[i] != 1 || !other.interiorsIntersect(mesh.getShape(t))) {
2654 return false;
2655 }
2656 const std::size_t root = findRoot(i);
2657 if (counted[root]) {
2658 return false;
2659 }
2660 counted[root] = 1;
2661 return ++classes >= 2;
2662 });
2663}
2664
2665template <class PointType, class LabelType>
2666template<ConvexConcept OtherConvex>
2667constexpr bool Polygon<PointType, LabelType>::separates(const OtherConvex& other) const {
2668 return separates(other.asPolygon());
2669}
2670
2671template <class PointType, class LabelType>
2672template<PolygonConcept OtherPolygon>
2673constexpr bool Polygon<PointType, LabelType>::separates(const OtherPolygon& other) const {
2674 // Let A = *this and B = other. A.separates(B) asks whether B \ A is
2675 // disconnected. For a (topological) disk B this happens exactly when some
2676 // connected component of A ∩ B meets ∂B in two or more pieces -- a
2677 // "crosscut". A single bite (one contact arc) or an interior island (a hole,
2678 // zero contact arcs) leaves B connected, and two disjoint bites belong to
2679 // different components, so neither cuts. This is the same criterion the
2680 // Convex overload uses, generalized to a non-convex A: A may dip into B
2681 // through several separate pockets, so the count is per A ∩ B component, not
2682 // a raw boundary-arc tally.
2683 if (other.isDegenerate()) {
2684 // A polygon that collapses to a segment is cut as that segment is; a
2685 // point is never disconnected.
2686 if (const auto spanned = other.getIfSegment()) {
2687 return separates(*spanned);
2688 }
2689 return false;
2690 }
2691 if (isDegenerate()) {
2692 // What this degenerate shape spans does the cutting: a segment answers
2693 // as one, and a point disconnects nothing.
2694 if (const auto spanned = getIfSegment()) {
2695 return spanned->separates(other);
2696 }
2697 return false;
2698 }
2699 if (!bbox().intersects(other.bbox())) {
2700 return false;
2701 }
2702
2703 // A convex remover admits the event walk of Convex::separates(Polygon),
2704 // which stays in the shapes' native arithmetic; only a genuinely reflex A
2705 // needs the exact intersection machinery below.
2706 if (isConvex()) {
2707 return Convex<PointType>(vertices()).separates(other);
2708 }
2709
2710 // Exact rationals: the components of A ∩ B are polygons whose vertices are
2711 // the intersections of the two boundaries, which are rational in general.
2712 using Number = Rational<BigInt>; // exact, overflow-free (ERational's backing)
2714 using RPolygon = Polygon<RPoint>;
2715 using RSegment = Segment<RPoint>;
2716
2717 const auto toR = [](const auto& p) {
2718 return RPoint(Number(p.x()), Number(p.y()));
2719 };
2720
2721 // The 2-D components of A ∩ B. Lower-dimensional pieces (shared boundary
2722 // segments or touch points) cannot disconnect the 2-D body of B, so only the
2723 // polygon pieces matter.
2724 std::vector<RPolygon> pieces;
2725 for (const auto& piece : other.template intersection<Number>(*this)) {
2726 if (std::holds_alternative<RPolygon>(piece)) {
2727 pieces.push_back(std::get<RPolygon>(piece));
2728 }
2729 }
2730 if (pieces.empty()) {
2731 return false;
2732 }
2733
2734 // The intersection returns pinched faces (two faces meeting at a single
2735 // vertex) as separate polygons, but such faces form one connected component
2736 // of the closed set A ∩ B. Union those that share a vertex so a bowtie
2737 // crosscut is counted as one component touching ∂B twice.
2738 const int k = static_cast<int>(pieces.size());
2739 std::vector<int> parent(k);
2740 for (int i = 0; i < k; ++i) {
2741 parent[i] = i;
2742 }
2743 const auto find = [&parent](int x) {
2744 while (parent[x] != x) {
2745 parent[x] = parent[parent[x]];
2746 x = parent[x];
2747 }
2748 return x;
2749 };
2750 std::vector<std::pair<RPoint, int>> vertexOwner;
2751 for (int i = 0; i < k; ++i) {
2752 for (const auto& v : pieces[i].vertices()) {
2753 vertexOwner.emplace_back(v, i);
2754 }
2755 }
2756 std::sort(vertexOwner.begin(), vertexOwner.end(),
2757 [](const auto& a, const auto& b) { return a.first < b.first; });
2758 for (std::size_t i = 1; i < vertexOwner.size(); ++i) {
2759 if (vertexOwner[i].first == vertexOwner[i - 1].first) {
2760 parent[find(vertexOwner[i].second)] = find(vertexOwner[i - 1].second);
2761 }
2762 }
2763
2764 // Component roots present at a point q of ∂B: every A ∩ B component whose
2765 // (closed) region contains q.
2766 const auto rootsAt = [&](const RPoint& q) {
2767 std::vector<int> roots;
2768 for (int i = 0; i < k; ++i) {
2769 if (pieces[i].contains(q)) {
2770 const int r = find(i);
2771 if (std::find(roots.begin(), roots.end(), r) == roots.end()) {
2772 roots.push_back(r);
2773 }
2774 }
2775 }
2776 return roots;
2777 };
2778
2779 // All piece vertices: these are exactly the points on ∂B where component
2780 // membership can change (crossings of ∂A with ∂B and shared-boundary ends),
2781 // so subdividing each edge of B at them makes every sub-edge uniformly
2782 // inside or outside a given component.
2783 std::vector<RPoint> breakpoints;
2784 for (const auto& piece : pieces) {
2785 for (const auto& v : piece.vertices()) {
2786 breakpoints.push_back(v);
2787 }
2788 }
2789
2790 // Walk ∂B once, cyclically, sampling: each vertex, each interior crossing,
2791 // and the midpoint of each resulting sub-edge. Membership is constant between
2792 // consecutive breakpoints, so these samples label every arc (including
2793 // isolated point contacts, caught at the vertex/crossing samples).
2794 std::vector<std::vector<int>> elements;
2795 const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(other.size());
2796 for (std::ptrdiff_t i = 0; i < n; ++i) {
2797 const RPoint a = toR(other.get(i));
2798 const RPoint b = toR(other.get(i + 1));
2799
2800 std::vector<RPoint> cuts;
2801 const RSegment edge(a, b);
2802 for (const auto& bp : breakpoints) {
2803 if (edge.interiorContains(bp)) {
2804 cuts.push_back(bp);
2805 }
2806 }
2807 std::sort(cuts.begin(), cuts.end(), [&](const RPoint& u, const RPoint& v) {
2808 return dotSign(u, v, a, b) > 0;
2809 });
2810 cuts.erase(std::unique(cuts.begin(), cuts.end()), cuts.end());
2811
2812 std::vector<RPoint> chain;
2813 chain.push_back(a);
2814 chain.insert(chain.end(), cuts.begin(), cuts.end());
2815 chain.push_back(b);
2816
2817 elements.push_back(rootsAt(a)); // the vertex of B
2818 for (std::size_t j = 0; j + 1 < chain.size(); ++j) {
2819 const RPoint mid((chain[j].x() + chain[j + 1].x()) / Number(2),
2820 (chain[j].y() + chain[j + 1].y()) / Number(2));
2821 elements.push_back(rootsAt(mid)); // the sub-edge
2822 if (j + 1 + 1 < chain.size()) {
2823 elements.push_back(rootsAt(chain[j + 1])); // an interior crossing
2824 }
2825 }
2826 }
2827
2828 // A component cuts B iff its contacts with ∂B fall into >= 2 maximal cyclic
2829 // runs. Count, per root, the runs as rising edges around the cycle.
2830 const std::size_t total = elements.size();
2831 const auto has = [](const std::vector<int>& s, int r) {
2832 return std::find(s.begin(), s.end(), r) != s.end();
2833 };
2834 std::vector<int> roots;
2835 for (const auto& s : elements) {
2836 for (int r : s) {
2837 if (std::find(roots.begin(), roots.end(), r) == roots.end()) {
2838 roots.push_back(r);
2839 }
2840 }
2841 }
2842 for (int r : roots) {
2843 int runs = 0;
2844 for (std::size_t i = 0; i < total; ++i) {
2845 const std::size_t prev = (i + total - 1) % total;
2846 if (has(elements[i], r) && !has(elements[prev], r)) {
2847 if (++runs >= 2) {
2848 return true;
2849 }
2850 }
2851 }
2852 }
2853 return false;
2854}
2855
2868
2869template <class PointType, class LabelType, class Storage>
2870template <class OtherShape, class TouchesBoundary>
2871constexpr bool MonotoneChain<PointType, LabelType, Storage>::separatesOneDimensional(
2872 const OtherShape& other, TouchesBoundary touchesBoundary) const {
2873 if (points_.empty()) {
2874 return false;
2875 }
2876 if (points_.size() == 1) {
2877 // A single vertex disconnects `other` iff it is an interior point of it.
2878 return other.interiorContains((*this)[0]);
2879 }
2880 // Walk the connected components of (this ∩ other): consecutive edge pieces
2881 // belong to one component iff the shared chain vertex lies on `other`
2882 // (`other` is convex, so each edge meets it in one connected piece). A
2883 // component that avoids every boundary point of `other` disconnects it.
2884 bool active = false;
2885 bool touched = false;
2886 for (std::size_t i = 0; i + 1 < points_.size(); ++i) {
2887 const auto edge = this->template boundaryAt<false>(i);
2888 if (edge.intersects(other)) {
2889 const bool connected = active && other.contains((*this)[i]);
2890 if (!connected) {
2891 if (active && !touched) {
2892 return true;
2893 }
2894 touched = false;
2895 }
2896 active = true;
2897 if (touchesBoundary(edge)) {
2898 touched = true;
2899 }
2900 } else {
2901 if (active && !touched) {
2902 return true;
2903 }
2904 active = false;
2905 touched = false;
2906 }
2907 }
2908 return active && !touched;
2909}
2910
2911template <class PointType, class LabelType, class Storage>
2912template <bool OtherIsConvex, class OtherShape>
2913constexpr bool MonotoneChain<PointType, LabelType, Storage>::separatesTwoDimensional(const OtherShape& other) const {
2914 if (points_.size() <= 1) {
2915 return false;
2916 }
2917
2918 // Removing the chain disconnects the region iff it removes a crosscut:
2919 // either a single edge already disconnects the region, or the chain has
2920 // points p < x < q in chain order with x in the region's interior and
2921 // p, q both non-interior (boundary or beyond) -- the piece of the chain
2922 // around x then runs boundary-to-boundary through the interior.
2923 //
2924 // The scan looks for the p/x/q pattern at the vertices. That is complete
2925 // for a convex region: an edge between two interior vertices stays
2926 // interior, so a mid-edge p, x, or q always has a vertex witness on the
2927 // correct side (an edge whose non-interior endpoints sandwich an interior
2928 // point is caught as a separating edge). A non-convex region breaks the
2929 // first rule -- an edge can leave the interior between two interior
2930 // vertices -- so OtherIsConvex == false additionally tests such edges,
2931 // each excursion acting as one more non-interior stop between its
2932 // endpoints. The last vertex pre-seeds the final non-interior stop, so
2933 // the scan can stop as soon as the interior stop is confirmed.
2934 bool a_check = false; // saw a non-interior stop ...
2935 bool b_check = false; // ... then an interior stop ...
2936 bool c_check = !other.interiorContains((*this)[size() - 1]); // ... then a non-interior stop
2937 const auto pattern_found = [&](bool stop_is_interior) {
2938 if (!a_check) {
2939 a_check = !stop_is_interior;
2940 }
2941 else if (!b_check) {
2942 b_check = stop_is_interior;
2943 }
2944 else if (!c_check) {
2945 c_check = !stop_is_interior;
2946 }
2947 return a_check && b_check && c_check;
2948 };
2949
2950 bool prev_interior = other.interiorContains((*this)[0]);
2951 pattern_found(prev_interior);
2952 for (std::size_t i = 1; i < size(); ++i) {
2953 const Segment<PointType> edge((*this)[i - 1], (*this)[i]);
2954 if (edge.separates(other)) {
2955 return true;
2956 }
2957 const bool cur_interior = other.interiorContains((*this)[i]);
2958 if constexpr (!OtherIsConvex) {
2959 if (prev_interior && cur_interior && !other.interiorContains(edge) &&
2960 pattern_found(false)) {
2961 return true;
2962 }
2963 }
2964 if (pattern_found(cur_interior)) {
2965 return true;
2966 }
2967 prev_interior = cur_interior;
2968 }
2969 return false;
2970}
2971
2972template <class PointType, class LabelType, class Storage>
2973template<SegmentConcept OtherSegment>
2974constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherSegment& other) const {
2975 return separatesOneDimensional(other, [&other](const auto& edge) {
2976 return edge.contains(other.min()) || edge.contains(other.max());
2977 });
2978}
2979
2980template <class PointType, class LabelType, class Storage>
2981template<OrientedSegmentConcept OtherOrientedSegment>
2982constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherOrientedSegment& other) const {
2983 return separates(other.asSegment());
2984}
2985
2986template <class PointType, class LabelType, class Storage>
2987template<LineConcept OtherLine>
2988constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherLine& other) const {
2989 // A line has no boundary, so any nonempty intersection with the bounded
2990 // chain disconnects it.
2991 return intersects(other);
2992}
2993
2994template <class PointType, class LabelType, class Storage>
2995template<OrientedLineConcept OtherOrientedLine>
2996constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherOrientedLine& other) const {
2997 return intersects(other);
2998}
2999
3000template <class PointType, class LabelType, class Storage>
3001template<RayConcept OtherRay>
3002constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherRay& other) const {
3003 return separatesOneDimensional(other, [&other](const auto& edge) {
3004 return edge.contains(other.source());
3005 });
3006}
3007
3008template <class PointType, class LabelType, class Storage>
3009template<HalfplaneConcept OtherHalfplane>
3010constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherHalfplane& other) const {
3011 // No straight edge can disconnect a halfplane (Segment::separates is
3012 // always false there), but the chain can: bending through the interior
3013 // between two non-interior stops seals off a pocket against the boundary
3014 // line, which is exactly the crosscut pattern of the vertex scan.
3015 return separatesTwoDimensional(other);
3016}
3017
3018template <class PointType, class LabelType, class Storage>
3019template<RectangleConcept OtherRectangle>
3020constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherRectangle& other) const {
3021 if (other.empty()) {
3022 // The empty set meets nothing and disconnects nothing.
3023 return false;
3024 }
3025 if (!bbox().intersects(other)) {
3026 return false;
3027 }
3028 if (bbox().separates(other)) {
3029 return true;
3030 }
3031 return separatesTwoDimensional(other);
3032}
3033
3034template <class PointType, class LabelType, class Storage>
3035template<TriangleConcept OtherTriangle>
3036constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherTriangle& other) const {
3037 if (!bbox().intersects(other.bbox())) {
3038 return false;
3039 }
3040 if (bbox().separates(other.bbox())) {
3041 return true;
3042 }
3043 return separatesTwoDimensional(other);
3044}
3045
3046template <class PointType, class LabelType, class Storage>
3047template<DiskConcept OtherDisk>
3048constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherDisk& other) const {
3049 if (!bbox().intersects(other.bbox())) {
3050 return false;
3051 }
3052 return separatesTwoDimensional(other);
3053}
3054
3055template <class PointType, class LabelType, class Storage>
3056template<ConvexConcept OtherConvex>
3057constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherConvex& other) const {
3058 if (!bbox().intersects(other.bbox())) {
3059 return false;
3060 }
3061 if (bbox().separates(other.bbox())) {
3062 return true;
3063 }
3064 return separatesTwoDimensional(other);
3065}
3066
3067template <class PointType, class LabelType, class Storage>
3068template<PolygonConcept OtherPolygon>
3069constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherPolygon& other) const {
3070 if (!bbox().intersects(other.bbox())) {
3071 return false;
3072 }
3073 if (bbox().separates(other.bbox())) {
3074 return true;
3075 }
3076 // The polygon may be non-convex, so the scan must also spot excursion
3077 // edges (see separatesTwoDimensional).
3078 return separatesTwoDimensional<false>(other);
3079}
3080
3081template <class PointType, class LabelType, class Storage>
3082template<MonotoneChainConcept OtherChain>
3083constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherChain& other) const {
3084 if (!bbox().intersects(other.bbox())) {
3085 return false;
3086 }
3087 if (bbox().separates(other.bbox())) {
3088 return true;
3089 }
3090
3091 // Polyline this separates polyline other if there exist
3092 // three ordered points a,b,c in other, such that
3093 // !this->contains(a), this->contains(b), !this->contains(c)
3094 // These points a,b,c may not be vertices, but if they are on
3095 // the same edge, than the edge is separated. If they are not
3096 // on the same edge, then there are two edges that are not contained
3097 // in this with an intermediate point b that is contained
3098
3099 bool a_check = false;
3100 bool b_check = false;
3101 bool c_check = !contains(other.get(-1));
3102
3103 for (std::size_t i = 1; i < other.size(); ++i) {
3104 Segment<typename OtherChain::PointType> edge(other[i-1], other[i]);
3105 if (separates(edge)) {
3106 return true; // If edge is separated, then a,b,c are in edge
3107 }
3108 if (!a_check) { // We did not find point a st !this->contains(a) yet
3109 if (!contains(edge)) {
3110 a_check = true; // point of edge not contained in this is a
3111 b_check = contains(other[i]); // maybe b st this->contains(b) is in edge
3112 }
3113 }
3114 else if (!b_check) { // We did not find point b > a st this->contains(b) yet
3115 b_check = intersects(edge); // If the edge intersects this, we found b
3116 }
3117 else if (!c_check) { // We did not find point c > b st !this->contains(c) yet
3118 c_check = !contains(edge); // If edge is not contained, we found c
3119 }
3120
3121 if (a_check && b_check && c_check) { // All points found
3122 return true;
3123 }
3124 }
3125
3126 // There are no such a,b,c
3127 return false;
3128}
3129
3130template <class PointType, class LabelType, class Storage>
3131template<PointConcept OtherPoint>
3133 return std::visit(
3134 [this](const auto& value) {
3135 return this->separates(value);
3136 },
3137 other.variant());
3138}
3139
3140template <class Number, class Label>
3141template<MonotoneChainConcept OtherChain>
3142constexpr bool Point<Number, Label>::separates(const OtherChain& other) const {
3143 // Removing a point disconnects the arc iff it is a relative-interior point.
3144 return other.interiorContains(*this);
3145}
3146
3147template <class PointType, class LabelType>
3148template<MonotoneChainConcept OtherChain>
3149constexpr bool Segment<PointType, LabelType>::separates(const OtherChain& other) const {
3150 return detail::separatesChain(*this, other);
3151}
3152
3153template <class PointType, class LabelType>
3154template<MonotoneChainConcept OtherChain>
3155constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherChain& other) const {
3156 return detail::separatesChain(asSegment(), other);
3157}
3158
3159template <class PointType, class LabelType>
3160template<MonotoneChainConcept OtherChain>
3161constexpr bool Line<PointType, LabelType>::separates(const OtherChain& other) const {
3162 return detail::separatesChain(*this, other);
3163}
3164
3165template <class PointType, class LabelType>
3166template<MonotoneChainConcept OtherChain>
3167constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherChain& other) const {
3168 return detail::separatesChain(*this, other);
3169}
3170
3171template <class PointType, class LabelType>
3172template<MonotoneChainConcept OtherChain>
3173constexpr bool Ray<PointType, LabelType>::separates(const OtherChain& other) const {
3174 return detail::separatesChain(*this, other);
3175}
3176
3177template <class PointType, class LabelType>
3178template<MonotoneChainConcept OtherChain>
3179constexpr bool Halfplane<PointType, LabelType>::separates(const OtherChain& other) const {
3180 return detail::separatesChain(*this, other);
3181}
3182
3183template <class PointType, class LabelType>
3184template<MonotoneChainConcept OtherChain>
3185constexpr bool Rectangle<PointType, LabelType>::separates(const OtherChain& other) const {
3186 if (empty()) {
3187 // The empty set meets nothing and disconnects nothing.
3188 return false;
3189 }
3190 return detail::separatesChain(*this, other);
3191}
3192
3193template <class PointType, class LabelType>
3194template<MonotoneChainConcept OtherChain>
3195constexpr bool Triangle<PointType, LabelType>::separates(const OtherChain& other) const {
3196 return detail::separatesChain(*this, other);
3197}
3198
3199template <class PointType, class LabelType>
3200template<MonotoneChainConcept OtherChain>
3201constexpr bool Disk<PointType, LabelType>::separates(const OtherChain& other) const {
3202 return detail::separatesChain(*this, other);
3203}
3204
3205template <class PointType, class LabelType>
3206template<MonotoneChainConcept OtherChain>
3207constexpr bool Convex<PointType, LabelType>::separates(const OtherChain& other) const {
3208 return detail::separatesChain(*this, other);
3209}
3210
3211template <class PointType, class LabelType>
3212template<MonotoneChainConcept OtherChain>
3213constexpr bool Polygon<PointType, LabelType>::separates(const OtherChain& other) const {
3214 if (!bbox().intersects(other.bbox())) {
3215 return false;
3216 }
3217 if (bbox().separates(other.bbox())) {
3218 return true;
3219 }
3220
3221 // Polygon this separates polyline other if there exist
3222 // three ordered points a,b,c in other, such that
3223 // !this->contains(a), this->contains(b), !this->contains(c)
3224 // These points a,b,c may not be vertices, but if they are on
3225 // the same edge, than the edge is separated. If they are not
3226 // on the same edge, then there are two edges that are not contained
3227 // in this with an intermediate point b that is contained
3228
3229 bool a_check = false;
3230 bool b_check = false;
3231 bool c_check = !contains(other.get(-1));
3232
3233 for (std::size_t i = 1; i < other.size(); ++i) {
3234 Segment<typename OtherChain::PointType> edge(other[i-1], other[i]);
3235 if (separates(edge)) {
3236 return true; // If edge is separated, then a,b,c are in edge
3237 }
3238 if (!a_check) { // We did not find point a st !this->contains(a) yet
3239 if (!contains(edge)) {
3240 a_check = true; // point of edge not contained in this is a
3241 b_check = contains(other[i]); // maybe b st this->contains(b) is in edge
3242 }
3243 }
3244 else if (!b_check) { // We did not find point b > a st this->contains(b) yet
3245 b_check = intersects(edge); // If the edge intersects this, we found b
3246 }
3247 else if (!c_check) { // We did not find point c > b st !this->contains(c) yet
3248 c_check = !contains(edge); // If edge is not contained, we found c
3249 }
3250
3251 if (a_check && b_check && c_check) { // All points found
3252 return true;
3253 }
3254 }
3255
3256 // There are no such a,b,c
3257 return false;
3258}
3259
3269
3270namespace detail {
3271
3291template <class Remover, class TargetEdgeRange>
3292bool separates1DSet(const Remover& remover, const TargetEdgeRange& targetEdges) {
3293 using TargetEdge = std::remove_cvref_t<std::ranges::range_value_t<TargetEdgeRange>>;
3294 using TargetNumber = typename TargetEdge::PointType::NumberType;
3295 using RemoverNumber = typename Remover::NumberType;
3296 constexpr bool approximate = std::is_floating_point_v<TargetNumber> ||
3297 std::is_floating_point_v<RemoverNumber>;
3298 using ExactNumber = std::conditional_t<approximate, double, Rational<BigInt>>;
3299 using ExactPoint = Point<ExactNumber>;
3300 using ExactSegment = Segment<ExactPoint>;
3301
3302 // A free piece: a maximal subsegment of one target edge that survives the
3303 // removal. An endpoint marked open lies on the remover and is excluded
3304 // from the piece.
3305 struct Piece {
3306 ExactPoint lo;
3307 ExactPoint hi;
3308 bool loOpen;
3309 bool hiOpen;
3310 };
3311 std::vector<Piece> pieces;
3312
3313 for (const auto& edge : targetEdges) {
3314 if (edge.min() == edge.max()) {
3315 continue; // zero-length edge: adds no connectivity of its own
3316 }
3317 // Removed intervals along the edge, as closed intervals in the
3318 // lexicographic point order (the linear order along the edge).
3319 std::vector<std::pair<ExactPoint, ExactPoint>> removed;
3320 const auto addRemoved = [&removed](const auto& optionalPiece) {
3321 if (!optionalPiece) {
3322 return;
3323 }
3324 if (const auto* point = std::get_if<0>(&*optionalPiece)) {
3325 removed.emplace_back(ExactPoint(*point), ExactPoint(*point));
3326 } else {
3327 const auto& overlap = std::get<1>(*optionalPiece);
3328 removed.emplace_back(ExactPoint(overlap.min()), ExactPoint(overlap.max()));
3329 }
3330 };
3331 if constexpr (is_point_v<Remover>) {
3332 if (edge.contains(remover)) {
3333 removed.emplace_back(ExactPoint(remover), ExactPoint(remover));
3334 }
3335 } else if constexpr (is_segment_v<Remover>) {
3336 addRemoved(edge.template intersection<ExactNumber>(remover));
3337 } else if constexpr (is_halfplane_intersection_v<Remover>) {
3338 // A convex region meets each edge in one connected piece; its
3339 // segment intersection reports the point or subsegment directly.
3340 addRemoved(remover.template intersection<ExactNumber>(edge));
3341 } else if constexpr (is_polygon_v<Remover>) {
3342 // A (possibly reflex) polygon can bite several intervals out of
3343 // one edge; its segment intersection lists them all as pieces.
3344 for (const auto& piece : remover.template intersection<ExactNumber>(edge)) {
3345 if (const auto* point = std::get_if<0>(&piece)) {
3346 removed.emplace_back(ExactPoint(*point), ExactPoint(*point));
3347 } else {
3348 const auto& overlap = std::get<1>(piece);
3349 removed.emplace_back(ExactPoint(overlap.min()), ExactPoint(overlap.max()));
3350 }
3351 }
3352 } else {
3353 static_assert(is_polyline_v<Remover> || is_monotone_chain_v<Remover>,
3354 "unsupported remover kind");
3355 if (remover.size() == 1) {
3356 if (edge.contains(remover[0])) {
3357 removed.emplace_back(ExactPoint(remover[0]), ExactPoint(remover[0]));
3358 }
3359 } else {
3360 for (const auto& removerEdge : remover.edgesView()) {
3361 addRemoved(edge.template intersection<ExactNumber>(removerEdge));
3362 }
3363 }
3364 }
3365 std::sort(removed.begin(), removed.end());
3366 // Sweep the merged removed blocks and emit the complementary free
3367 // pieces. A free endpoint abutting a removed block is open; the edge
3368 // extremes are closed unless removed.
3369 ExactPoint cursor(edge.min());
3370 bool cursorOpen = false;
3371 std::size_t i = 0;
3372 while (i < removed.size()) {
3373 const ExactPoint blockLo = removed[i].first;
3374 ExactPoint blockHi = removed[i].second;
3375 for (++i; i < removed.size() && !(blockHi < removed[i].first); ++i) {
3376 if (blockHi < removed[i].second) {
3377 blockHi = removed[i].second;
3378 }
3379 }
3380 if (cursor < blockLo) {
3381 pieces.push_back({cursor, blockLo, cursorOpen, true});
3382 }
3383 cursor = blockHi;
3384 cursorOpen = true;
3385 }
3386 const ExactPoint edgeHi(edge.max());
3387 if (cursor < edgeHi) {
3388 pieces.push_back({cursor, edgeHi, cursorOpen, false});
3389 }
3390 }
3391
3392 if (pieces.size() < 2) {
3393 return false; // zero or one piece is never disconnected
3394 }
3395
3396 // Union-find over the pieces; two pieces join iff they share a point that
3397 // survives the removal.
3398 std::vector<std::size_t> parent(pieces.size());
3399 for (std::size_t i = 0; i < parent.size(); ++i) {
3400 parent[i] = i;
3401 }
3402 const auto findRoot = [&parent](std::size_t x) {
3403 while (parent[x] != x) {
3404 parent[x] = parent[parent[x]];
3405 x = parent[x];
3406 }
3407 return x;
3408 };
3409 const auto connected = [](const Piece& a, const Piece& b) {
3410 const ExactSegment sa(a.lo, a.hi);
3411 const ExactSegment sb(b.lo, b.hi);
3412 if (!sa.intersects(sb)) {
3413 return false;
3414 }
3415 if (sa.collinear(sb) && sa.min() < sb.max() && sb.min() < sa.max()) {
3416 // A positive-length overlap survives losing at most four
3417 // excluded endpoints.
3418 return true;
3419 }
3420 // Single common point: it joins the pieces unless it coincides with
3421 // an excluded (open) endpoint of either piece (the unique common
3422 // point equals x iff x lies on the other piece's segment).
3423 if (a.loOpen && sb.contains(a.lo)) {
3424 return false;
3425 }
3426 if (a.hiOpen && sb.contains(a.hi)) {
3427 return false;
3428 }
3429 if (b.loOpen && sa.contains(b.lo)) {
3430 return false;
3431 }
3432 if (b.hiOpen && sa.contains(b.hi)) {
3433 return false;
3434 }
3435 return true;
3436 };
3437 for (std::size_t i = 0; i < pieces.size(); ++i) {
3438 for (std::size_t j = i + 1; j < pieces.size(); ++j) {
3439 const std::size_t ri = findRoot(i);
3440 const std::size_t rj = findRoot(j);
3441 if (ri != rj && connected(pieces[i], pieces[j])) {
3442 parent[ri] = rj;
3443 }
3444 }
3445 }
3446 std::size_t components = 0;
3447 for (std::size_t i = 0; i < pieces.size(); ++i) {
3448 if (findRoot(i) == i) {
3449 ++components;
3450 }
3451 }
3452 return components >= 2;
3453}
3454
3455} // namespace detail
3456
3457template <class PointType, class LabelType>
3458template<SegmentConcept OtherSegment>
3459constexpr bool Polyline<PointType, LabelType>::separates(const OtherSegment& other) const {
3460 if (empty()) {
3461 return false;
3462 }
3463 if (size() == 1) {
3464 // A single vertex disconnects the segment iff it is an interior point of it.
3465 return other.interiorContains((*this)[0]);
3466 }
3467 return detail::separates1DSet(*this, std::array<OtherSegment, 1>{other});
3468}
3469
3470template <class PointType, class LabelType>
3471template<PolylineConcept OtherPolyline>
3472constexpr bool Polyline<PointType, LabelType>::separates(const OtherPolyline& other) const {
3473 if (empty() || other.size() < 2) {
3474 return false;
3475 }
3476 if (!bbox().intersects(other.bbox())) {
3477 return false;
3478 }
3479 if (size() == 1) {
3480 return (*this)[0].separates(other);
3481 }
3482 return detail::separates1DSet(*this, other.edgesView());
3483}
3484
3485template <class Number, class Label>
3486template<PolylineConcept OtherPolyline>
3487constexpr bool Point<Number, Label>::separates(const OtherPolyline& other) const {
3488 // Unlike a monotone chain, the polyline may reconnect around the removed
3489 // point through a self-crossing or a revisited vertex, so an interior
3490 // point is not necessarily a cut point.
3491 if (other.size() < 2) {
3492 return false;
3493 }
3494 return detail::separates1DSet(*this, other.edgesView());
3495}
3496
3497template <class PointType, class LabelType>
3498template<PolylineConcept OtherPolyline>
3499constexpr bool Segment<PointType, LabelType>::separates(const OtherPolyline& other) const {
3500 if (other.size() < 2) {
3501 return false;
3502 }
3503 return detail::separates1DSet(*this, other.edgesView());
3504}
3505
3506namespace detail {
3507
3524template <class ExactPoint, class PolylineType, class CutterRange>
3525std::vector<Segment<ExactPoint>> arrangedPolylineEdges(const PolylineType& polyline,
3526 const CutterRange& cutters) {
3527 using ExactSegment = Segment<ExactPoint>;
3528 using ExactNumber = typename ExactPoint::NumberType;
3529
3530 std::vector<ExactSegment> edges;
3531 for (const auto& edge : polyline.edgesView()) {
3532 if (edge.min() != edge.max()) {
3533 edges.emplace_back(ExactPoint(edge.min()), ExactPoint(edge.max()));
3534 }
3535 }
3536
3537 std::vector<ExactSegment> result;
3538 std::vector<ExactPoint> cuts;
3539 for (std::size_t i = 0; i < edges.size(); ++i) {
3540 cuts.clear();
3541 cuts.push_back(edges[i].min());
3542 cuts.push_back(edges[i].max());
3543 const auto addCuts = [&](const auto& s) {
3544 const auto piece = edges[i].template intersection<ExactNumber>(s);
3545 if (!piece) {
3546 return;
3547 }
3548 if (const auto* point = std::get_if<0>(&*piece)) {
3549 cuts.push_back(ExactPoint(*point));
3550 } else {
3551 const auto& overlap = std::get<1>(*piece);
3552 cuts.push_back(ExactPoint(overlap.min()));
3553 cuts.push_back(ExactPoint(overlap.max()));
3554 }
3555 };
3556 for (std::size_t j = 0; j < edges.size(); ++j) {
3557 if (j != i) {
3558 addCuts(edges[j]);
3559 }
3560 }
3561 for (const auto& cutter : cutters) {
3562 addCuts(cutter);
3563 }
3564 // All cut points lie on edge i, so the lexicographic point order is
3565 // the linear order along the edge.
3566 std::sort(cuts.begin(), cuts.end());
3567 cuts.erase(std::unique(cuts.begin(), cuts.end()), cuts.end());
3568 for (std::size_t k = 0; k + 1 < cuts.size(); ++k) {
3569 result.emplace_back(cuts[k], cuts[k + 1]);
3570 }
3571 }
3572 std::sort(result.begin(), result.end());
3573 result.erase(std::unique(result.begin(), result.end()), result.end());
3574 return result;
3575}
3576
3577// Exact1DNumber, the exact coordinate type for a mixed pair, lives in
3578// predicates_helpers.hpp: the containment predicates need it too, and that
3579// header is parsed well before this one.
3580
3595template <class Remover, class PolylineType>
3596bool separatesPolylineSet(const Remover& remover, const PolylineType& target) {
3597 if (target.size() < 2) {
3598 // Removing anything from at most one point cannot disconnect it.
3599 return false;
3600 }
3601 // A polyline is connected, so a remover that misses it removes nothing that
3602 // matters — the same shortcut @ref cellSeparates takes, and worth more here,
3603 // because the arrangement below is of the *target* alone and so is rebuilt
3604 // in full for every remover it is asked about. Only bounded removers can be
3605 // ruled out this way: a line, a ray and a half-plane have no bounding box,
3606 // which is exactly why they have no `bbox()` to call.
3607 if constexpr (requires { remover.bbox(); }) {
3608 if (!remover.bbox().intersects(target.bbox())) {
3609 return false;
3610 }
3611 }
3612 using ExactNumber = Exact1DNumber<typename PolylineType::NumberType,
3613 typename Remover::NumberType>;
3614 using ExactPoint = Point<ExactNumber>;
3615 const auto subEdges = arrangedPolylineEdges<ExactPoint>(
3616 target, std::array<Segment<ExactPoint>, 0>{});
3617
3618 std::map<ExactPoint, std::size_t> nodes;
3619 std::vector<std::size_t> parent;
3620 const auto findRoot = [&parent](std::size_t x) {
3621 while (parent[x] != x) {
3622 parent[x] = parent[parent[x]];
3623 x = parent[x];
3624 }
3625 return x;
3626 };
3627 const auto nodeOf = [&](const ExactPoint& p) {
3628 const auto [it, inserted] = nodes.try_emplace(p, parent.size());
3629 if (inserted) {
3630 parent.push_back(parent.size());
3631 }
3632 return it->second;
3633 };
3634
3635 for (const auto& s : subEdges) {
3636 // contains(endpoint) implies intersects(s), so a sub-edge missed by
3637 // the remover always has both endpoints surviving.
3638 const bool minSurvives = !remover.contains(s.min());
3639 const bool maxSurvives = !remover.contains(s.max());
3640 std::optional<std::size_t> a;
3641 std::optional<std::size_t> b;
3642 if (minSurvives) {
3643 a = nodeOf(s.min());
3644 }
3645 if (maxSurvives) {
3646 b = nodeOf(s.max());
3647 }
3648 if (a && b && !remover.intersects(s)) {
3649 parent[findRoot(*a)] = findRoot(*b);
3650 }
3651 }
3652
3653 std::size_t components = 0;
3654 for (std::size_t i = 0; i < parent.size(); ++i) {
3655 if (findRoot(i) == i && ++components >= 2) {
3656 return true;
3657 }
3658 }
3659 return false;
3660}
3661
3682template <class PolylineType, class Region>
3683bool polylineSeparatesConvexRegion(const PolylineType& polyline, const Region& other) {
3684 if (polyline.size() < 2) {
3685 return false;
3686 }
3687 // Both operands are connected, so shapes that miss each other neither cut
3688 // nor are cut — as in @ref cellSeparates. A half-plane operand has no
3689 // bounding box to test, and no `bbox()` to call.
3690 if constexpr (requires { other.bbox(); }) {
3691 if (!polyline.bbox().intersects(other.bbox())) {
3692 return false;
3693 }
3694 }
3695 using ExactNumber = Exact1DNumber<typename PolylineType::NumberType,
3696 typename Region::NumberType>;
3697 using ExactPoint = Point<ExactNumber>;
3698 const auto subEdges = arrangedPolylineEdges<ExactPoint>(
3699 polyline, std::array<Segment<ExactPoint>, 0>{});
3700
3701 std::map<ExactPoint, std::size_t> nodes;
3702 std::vector<std::size_t> parent(1, 0); // slot 0: the contracted boundary
3703 const auto findRoot = [&parent](std::size_t x) {
3704 while (parent[x] != x) {
3705 parent[x] = parent[parent[x]];
3706 x = parent[x];
3707 }
3708 return x;
3709 };
3710 const auto nodeOf = [&](const ExactPoint& p) {
3711 const auto [it, inserted] = nodes.try_emplace(p, parent.size());
3712 if (inserted) {
3713 parent.push_back(parent.size());
3714 }
3715 return it->second;
3716 };
3717
3718 for (const auto& s : subEdges) {
3719 if (!other.interiorsIntersect(s)) {
3720 continue; // no piece through the interior (skips boundary runs)
3721 }
3722 const std::size_t a = other.interiorContains(s.min()) ? nodeOf(s.min()) : 0;
3723 const std::size_t b = other.interiorContains(s.max()) ? nodeOf(s.max()) : 0;
3724 const std::size_t ra = findRoot(a);
3725 const std::size_t rb = findRoot(b);
3726 if (ra == rb) {
3727 return true; // the sub-edge closes a cycle through the interior
3728 }
3729 parent[ra] = rb;
3730 }
3731 return false;
3732}
3733
3743template <class PolylineType, class PolygonType>
3744bool polylineSeparatesPolygon(const PolylineType& polyline, const PolygonType& other) {
3745 if (polyline.size() < 2) {
3746 return false;
3747 }
3748 using ExactNumber = Exact1DNumber<typename PolylineType::NumberType,
3749 typename PolygonType::NumberType>;
3750 using ExactPoint = Point<ExactNumber>;
3751 using ExactSegment = Segment<ExactPoint>;
3752
3753 std::vector<ExactSegment> cutters;
3754 for (const auto& edge : other.edgesView()) {
3755 cutters.emplace_back(ExactPoint(edge.min()), ExactPoint(edge.max()));
3756 }
3757 const auto subEdges = arrangedPolylineEdges<ExactPoint>(polyline, cutters);
3758
3759 std::map<ExactPoint, std::size_t> nodes;
3760 std::vector<std::size_t> parent(1, 0); // slot 0: the contracted boundary
3761 const auto findRoot = [&parent](std::size_t x) {
3762 while (parent[x] != x) {
3763 parent[x] = parent[parent[x]];
3764 x = parent[x];
3765 }
3766 return x;
3767 };
3768 const auto nodeOf = [&](const ExactPoint& p) {
3769 const auto [it, inserted] = nodes.try_emplace(p, parent.size());
3770 if (inserted) {
3771 parent.push_back(parent.size());
3772 }
3773 return it->second;
3774 };
3775
3776 for (const auto& s : subEdges) {
3777 const ExactPoint mid((s.min().x() + s.max().x()) / ExactNumber(2),
3778 (s.min().y() + s.max().y()) / ExactNumber(2));
3779 if (!other.interiorContains(mid)) {
3780 continue; // outside or along the boundary
3781 }
3782 // The sub-edge never crosses the boundary, so its endpoints are in the
3783 // closed polygon: on the boundary (contracted) or strictly inside.
3784 const std::size_t a = other.boundaryContains(s.min()) ? 0 : nodeOf(s.min());
3785 const std::size_t b = other.boundaryContains(s.max()) ? 0 : nodeOf(s.max());
3786 const std::size_t ra = findRoot(a);
3787 const std::size_t rb = findRoot(b);
3788 if (ra == rb) {
3789 return true; // the sub-edge closes a cycle through the interior
3790 }
3791 parent[ra] = rb;
3792 }
3793 return false;
3794}
3795
3796} // namespace detail
3797
3798template <class PointType, class LabelType>
3799template<OrientedSegmentConcept OtherOrientedSegment>
3800constexpr bool Polyline<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
3801 return separates(other.asSegment());
3802}
3803
3804template <class PointType, class LabelType>
3805template<LineConcept OtherLine>
3806constexpr bool Polyline<PointType, LabelType>::separates(const OtherLine& other) const {
3807 // A line has no boundary, so any nonempty intersection with the bounded
3808 // polyline disconnects it.
3809 return intersects(other);
3810}
3811
3812template <class PointType, class LabelType>
3813template<OrientedLineConcept OtherOrientedLine>
3814constexpr bool Polyline<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
3815 return intersects(other);
3816}
3817
3818template <class PointType, class LabelType>
3819template<RayConcept OtherRay>
3820constexpr bool Polyline<PointType, LabelType>::separates(const OtherRay& other) const {
3821 if (empty()) {
3822 return false;
3823 }
3824 if (size() == 1) {
3825 // A single vertex disconnects the ray iff it is an interior point of it.
3826 return other.interiorContains((*this)[0]);
3827 }
3828 using ExactNumber = detail::Exact1DNumber<NumberType, typename OtherRay::PointType::NumberType>;
3829 using ExactPoint = Point<ExactNumber>;
3830 // Clip the ray to a segment leaving the polyline's bounding box: every
3831 // removed point lies inside the box, so the clipped segment has the same
3832 // free pieces as the ray, its far end standing in for the infinite tail.
3833 const ExactPoint source(other.source());
3834 const ExactPoint target(other.target());
3835 const ExactNumber dx = target.x() - source.x();
3836 const ExactNumber dy = target.y() - source.y();
3837 if (dx == ExactNumber{} && dy == ExactNumber{}) {
3838 return false; // degenerate ray
3839 }
3840 const auto& box = bbox();
3841 ExactNumber scale(1);
3842 const auto extend = [&scale](const ExactNumber& s, const ExactNumber& d,
3843 const ExactNumber& lo, const ExactNumber& hi) {
3844 if (d > ExactNumber{}) {
3845 const ExactNumber needed = (hi - s) / d + ExactNumber(1);
3846 if (scale < needed) {
3847 scale = needed;
3848 }
3849 } else if (d < ExactNumber{}) {
3850 const ExactNumber needed = (s - lo) / -d + ExactNumber(1);
3851 if (scale < needed) {
3852 scale = needed;
3853 }
3854 }
3855 };
3856 extend(source.x(), dx, ExactNumber(box.min().x()), ExactNumber(box.max().x()));
3857 extend(source.y(), dy, ExactNumber(box.min().y()), ExactNumber(box.max().y()));
3858 const ExactPoint beyond(source.x() + scale * dx, source.y() + scale * dy);
3859 return detail::separates1DSet(
3860 *this, std::array<Segment<ExactPoint>, 1>{Segment<ExactPoint>(source, beyond)});
3861}
3862
3863template <class PointType, class LabelType>
3864template<HalfplaneConcept OtherHalfplane>
3865constexpr bool Polyline<PointType, LabelType>::separates(const OtherHalfplane& other) const {
3866 return detail::polylineSeparatesConvexRegion(*this, other);
3867}
3868
3869template <class PointType, class LabelType>
3870template<RectangleConcept OtherRectangle>
3871constexpr bool Polyline<PointType, LabelType>::separates(const OtherRectangle& other) const {
3872 if (other.empty()) {
3873 // The empty set meets nothing and disconnects nothing.
3874 return false;
3875 }
3876 if (size() < 2) {
3877 return false;
3878 }
3879 if (!bbox().intersects(other)) {
3880 return false;
3881 }
3882 return detail::polylineSeparatesConvexRegion(*this, other);
3883}
3884
3885template <class PointType, class LabelType>
3886template<TriangleConcept OtherTriangle>
3887constexpr bool Polyline<PointType, LabelType>::separates(const OtherTriangle& other) const {
3888 if (size() < 2) {
3889 return false;
3890 }
3891 if (!bbox().intersects(other.bbox())) {
3892 return false;
3893 }
3894 return detail::polylineSeparatesConvexRegion(*this, other);
3895}
3896
3897template <class PointType, class LabelType>
3898template<DiskConcept OtherDisk>
3899constexpr bool Polyline<PointType, LabelType>::separates(const OtherDisk& other) const {
3900 if (size() < 2) {
3901 return false;
3902 }
3903 if (!bbox().intersects(other.bbox())) {
3904 return false;
3905 }
3906 return detail::polylineSeparatesConvexRegion(*this, other);
3907}
3908
3909template <class PointType, class LabelType>
3910template<ConvexConcept OtherConvex>
3911constexpr bool Polyline<PointType, LabelType>::separates(const OtherConvex& other) const {
3912 if (size() < 2) {
3913 return false;
3914 }
3915 if (!bbox().intersects(other.bbox())) {
3916 return false;
3917 }
3918 return detail::polylineSeparatesConvexRegion(*this, other);
3919}
3920
3921template <class PointType, class LabelType>
3922template<PolygonConcept OtherPolygon>
3923constexpr bool Polyline<PointType, LabelType>::separates(const OtherPolygon& other) const {
3924 if (size() < 2) {
3925 return false;
3926 }
3927 if (!bbox().intersects(other.bbox())) {
3928 return false;
3929 }
3930 return detail::polylineSeparatesPolygon(*this, other);
3931}
3932
3933template <class PointType, class LabelType>
3934template<MonotoneChainConcept OtherChain>
3935constexpr bool Polyline<PointType, LabelType>::separates(const OtherChain& other) const {
3936 if (empty() || other.size() < 2) {
3937 return false;
3938 }
3939 if (!bbox().intersects(other.bbox())) {
3940 return false;
3941 }
3942 if (size() == 1) {
3943 return (*this)[0].separates(other);
3944 }
3945 return detail::separates1DSet(*this, other.edgesView());
3946}
3947
3948template <class PointType, class LabelType>
3949template<PointConcept OtherPoint>
3951 return std::visit(
3952 [this](const auto& value) {
3953 return this->separates(value);
3954 },
3955 other.variant());
3956}
3957
3958template <class PointType, class LabelType>
3959template<PolylineConcept OtherPolyline>
3960constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherPolyline& other) const {
3961 return asSegment().separates(other);
3962}
3963
3964template <class PointType, class LabelType>
3965template<PolylineConcept OtherPolyline>
3966constexpr bool Line<PointType, LabelType>::separates(const OtherPolyline& other) const {
3967 return detail::separatesPolylineSet(*this, other);
3968}
3969
3970template <class PointType, class LabelType>
3971template<PolylineConcept OtherPolyline>
3972constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherPolyline& other) const {
3973 return asLine().separates(other);
3974}
3975
3976template <class PointType, class LabelType>
3977template<PolylineConcept OtherPolyline>
3978constexpr bool Ray<PointType, LabelType>::separates(const OtherPolyline& other) const {
3979 return detail::separatesPolylineSet(*this, other);
3980}
3981
3982template <class PointType, class LabelType>
3983template<PolylineConcept OtherPolyline>
3984constexpr bool Halfplane<PointType, LabelType>::separates(const OtherPolyline& other) const {
3985 return detail::separatesPolylineSet(*this, other);
3986}
3987
3988template <class PointType, class LabelType>
3989template<PolylineConcept OtherPolyline>
3990constexpr bool Rectangle<PointType, LabelType>::separates(const OtherPolyline& other) const {
3991 if (empty()) {
3992 // The empty set meets nothing and disconnects nothing.
3993 return false;
3994 }
3995 return detail::separatesPolylineSet(*this, other);
3996}
3997
3998template <class PointType, class LabelType>
3999template<PolylineConcept OtherPolyline>
4000constexpr bool Triangle<PointType, LabelType>::separates(const OtherPolyline& other) const {
4001 return detail::separatesPolylineSet(*this, other);
4002}
4003
4004template <class PointType, class LabelType>
4005template<PolylineConcept OtherPolyline>
4006constexpr bool Disk<PointType, LabelType>::separates(const OtherPolyline& other) const {
4007 return detail::separatesPolylineSet(*this, other);
4008}
4009
4010template <class PointType, class LabelType>
4011template<PolylineConcept OtherPolyline>
4012constexpr bool Convex<PointType, LabelType>::separates(const OtherPolyline& other) const {
4013 return detail::separatesPolylineSet(*this, other);
4014}
4015
4016template <class PointType, class LabelType, class Storage>
4017template<PolylineConcept OtherPolyline>
4018constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherPolyline& other) const {
4019 if (empty() || other.size() < 2) {
4020 return false;
4021 }
4022 if (size() == 1) {
4023 return (*this)[0].separates(other);
4024 }
4025 // The chain is neither convex nor a single interval on the polyline's
4026 // edges, so its removed pieces are computed exactly.
4027 return detail::separates1DSet(*this, other.edgesView());
4028}
4029
4030template <class PointType, class LabelType>
4031template<PolylineConcept OtherPolyline>
4032constexpr bool Polygon<PointType, LabelType>::separates(const OtherPolyline& other) const {
4033 if (other.size() < 2) {
4034 return false;
4035 }
4036 if (!bbox().intersects(other.bbox())) {
4037 return false;
4038 }
4039 // The polygon may be reflex, so it can bite several intervals out of one
4040 // polyline edge; the free pieces are joined geometrically.
4041 return detail::separates1DSet(*this, other.edgesView());
4042}
4043
4044
4045// ---------------------------------------------------------------------------
4046// HalfplaneIntersection
4047
4048template <class PointType, class LabelType>
4049template <PointConcept OtherPoint>
4050constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherPoint&) const {
4051 // Removing anything from a single point leaves the point or nothing,
4052 // never two components.
4053 return false;
4054}
4055
4056template <class PointType, class LabelType>
4057template <SegmentConcept OtherSegment>
4058constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherSegment& other) const {
4059 // The region meets the segment in one closed sub-interval; the remainder
4060 // is disconnected exactly when that interval is nonempty and touches
4061 // neither endpoint.
4062 return intersects(other) && !contains(other[0]) && !contains(other[1]);
4063}
4064
4065template <class PointType, class LabelType>
4066template <OrientedSegmentConcept OtherOrientedSegment>
4067constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
4069}
4070
4071template <class PointType, class LabelType>
4072template <LineConcept OtherLine>
4073constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherLine& other) const {
4074 // The region meets the line in one closed interval; the remainder is
4075 // disconnected exactly when the interval is nonempty and bounded on both
4076 // sides (an unbounded interval swallows an end of the line, leaving a
4077 // single ray, and a full-line interval leaves nothing).
4078 if (empty() || halfplanes_.empty()) {
4079 return false;
4080 }
4081 const Halfplane<typename OtherLine::PointType> along(other[0], other[1]);
4082 const auto clip = clipLine(along);
4083 return !clip.empty && clip.entry >= 0 && clip.exit >= 0;
4084}
4085
4086template <class PointType, class LabelType>
4087template <OrientedLineConcept OtherOrientedLine>
4088constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
4089 return separates(other.asLine());
4090}
4091
4092template <class PointType, class LabelType>
4093template <RayConcept OtherRay>
4094constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherRay& other) const {
4095 // As for a line, but the interval additionally must not contain the
4096 // source (which would leave a single piece ahead of it), and only the
4097 // forward end has to be bounded.
4098 if (empty() || halfplanes_.empty() || contains(other.source())) {
4099 return false;
4100 }
4101 const Halfplane<typename OtherRay::PointType> along(other.source(), other.target());
4102 const auto clip = clipLine(along);
4103 if (clip.empty || clip.exit < 0) {
4104 return false;
4105 }
4106 // The interval must reach into the ray: the line may not leave the region
4107 // before the source.
4108 return constraintSide(static_cast<std::size_t>(clip.exit), other.source()) >= 0;
4109}
4110
4111template <class PointType, class LabelType>
4112template <HalfplaneConcept OtherHalfplane>
4113constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherHalfplane& other) const {
4114 // Removing the region disconnects the half-plane exactly when the
4115 // region's closed contact set with the half-plane's extended boundary —
4116 // the boundary line plus the half-plane's arc of ideal directions — has at
4117 // least two connected components while the region also meets the
4118 // half-plane's interior. A bounded region only ever contributes the
4119 // boundary-line contact (one component), so only unbounded regions can
4120 // separate a half-plane. Complexity: O(n).
4121 if (empty() || halfplanes_.empty() || halfplanes_.size() == 1) {
4122 // The whole plane leaves nothing behind, and removing a single
4123 // half-plane leaves an open half-plane, which is connected.
4124 return false;
4125 }
4126 // The region must reach the half-plane's interior: with contact only on
4127 // the boundary line, the remainder stays connected through the interior.
4128 const SupStatus infimum = supStatus(other.opposite());
4129 if (infimum == SupStatus::below || infimum == SupStatus::on) {
4130 return false;
4131 }
4132 // Contact on the boundary line itself (one component when present,
4133 // possibly extended to the ideal endpoints of the line).
4134 const bool lineTouch = intersects(other.asLine());
4135 const auto reversed = other.opposite();
4136 int pieces = lineTouch ? 1 : 0;
4137 // Ideal contact: recession directions of the region that stay weakly
4138 // inside the half-plane. Each recession arc meets the half-plane's closed
4139 // ideal half-circle in at most one sub-arc.
4140 for (const auto& arc : recessionArcs()) {
4141 // Does the arc reach the line's ideal endpoints or the open ideal arc
4142 // strictly inside the half-plane?
4143 const bool atLeft = detail::arcContainsDirection(arc.first, arc.second, reversed);
4144 const bool atRight = detail::arcContainsDirection(arc.first, arc.second, other);
4145 const bool strictlyInside = detail::directionCross(other, arc.first) > 0 ||
4146 detail::directionCross(other, arc.second) > 0;
4147 if (!(atLeft || atRight || strictlyInside)) {
4148 continue; // this recession arc never touches the half-plane's ideal boundary
4149 }
4150 if (lineTouch && (atLeft || atRight)) {
4151 continue; // joined to the boundary-line contact at an ideal line endpoint
4152 }
4153 ++pieces;
4154 }
4155 return pieces >= 2;
4156}
4157
4158// Forward area targets: removing the region from a bounded 2D shape B is
4159// decided on the part of the region near B (B is bounded), so clip the region
4160// to a box around B and defer to Convex::separates. A degenerate region
4161// reduces to its carrier (a line through B still cuts it).
4162
4163template <class PointType, class LabelType>
4164template <RectangleConcept OtherRectangle>
4165constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherRectangle& other) const {
4166 if (other.empty()) {
4167 // The empty set meets nothing and disconnects nothing.
4168 return false;
4169 }
4170 if (empty()) {
4171 return false;
4172 }
4173 using E = detail::region_exact_number_t<NumberType>;
4174 if (isDegenerate()) {
4175 return std::visit([&other](const auto& carrier) { return carrier.separates(other); },
4176 detail::degenerateRegionCarrier(*this));
4177 }
4178 const auto clipped = detail::regionClippedToBox(*this, other.bbox());
4179 if (!clipped.isBounded()) {
4180 return false;
4181 }
4182 return clipped.template asConvex<E>().separates(other);
4183}
4184
4185template <class PointType, class LabelType>
4186template <TriangleConcept OtherTriangle>
4187constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherTriangle& other) const {
4188 if (empty()) {
4189 return false;
4190 }
4191 using E = detail::region_exact_number_t<NumberType>;
4192 if (isDegenerate()) {
4193 return std::visit([&other](const auto& carrier) { return carrier.separates(other); },
4194 detail::degenerateRegionCarrier(*this));
4195 }
4196 const auto clipped = detail::regionClippedToBox(*this, other.bbox());
4197 if (!clipped.isBounded()) {
4198 return false;
4199 }
4200 return clipped.template asConvex<E>().separates(other);
4201}
4202
4203template <class PointType, class LabelType>
4204template <ConvexConcept OtherConvex>
4205constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherConvex& other) const {
4206 if (empty()) {
4207 return false;
4208 }
4209 if (other.isDegenerate()) {
4210 // A convex with fewer than three vertices is the segment or point it
4211 // spans: the segment overload answers for the former, and a point is
4212 // never disconnected.
4213 if (const auto spanned = other.getIfSegment()) {
4214 return separates(*spanned);
4215 }
4216 return false;
4217 }
4218 using E = detail::region_exact_number_t<NumberType>;
4219 if (isDegenerate()) {
4220 return std::visit([&other](const auto& carrier) { return carrier.separates(other); },
4221 detail::degenerateRegionCarrier(*this));
4222 }
4223 const auto clipped = detail::regionClippedToBox(*this, other.bbox());
4224 if (!clipped.isBounded()) {
4225 return false;
4226 }
4227 return clipped.template asConvex<E>().separates(other);
4228}
4229
4230template <class PointType, class LabelType>
4231template <DiskConcept OtherDisk>
4232constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherDisk& other) const {
4233 if (empty() || other.isDegenerate()) {
4234 return false;
4235 }
4236 using E = detail::region_exact_number_t<NumberType>;
4237 if (isDegenerate()) {
4238 return std::visit([&other](const auto& carrier) { return carrier.separates(other); },
4239 detail::degenerateRegionCarrier(*this));
4240 }
4241 const auto clipped = detail::regionClippedToBox(*this, other.bbox());
4242 if (!clipped.isBounded()) {
4243 return false;
4244 }
4245 return clipped.template asConvex<E>().separates(other);
4246}
4247
4248template <class PointType, class LabelType>
4249template <PolygonConcept OtherPolygon>
4250constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherPolygon& other) const {
4251 if (empty() || other.size() < 3) {
4252 return false;
4253 }
4254 using E = detail::region_exact_number_t<NumberType>;
4255 if (isDegenerate()) {
4256 return std::visit([&other](const auto& carrier) { return carrier.separates(other); },
4257 detail::degenerateRegionCarrier(*this));
4258 }
4259 const auto clipped = detail::regionClippedToBox(*this, other.bbox());
4260 if (!clipped.isBounded()) {
4261 return false;
4262 }
4263 return clipped.template asConvex<E>().separates(other);
4264}
4265
4266template <class PointType, class LabelType>
4267template <MonotoneChainConcept OtherChain>
4268constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherChain& other) const {
4269 // The chain is a one-dimensional arc; the convex region removes one
4270 // connected piece from each edge, so the arc-component test applies with
4271 // the region as the (convex) remover.
4272 if (empty()) {
4273 return false;
4274 }
4275 return detail::separatesChain(*this, other);
4276}
4277
4278template <class PointType, class LabelType>
4279template <PolylineConcept OtherPolyline>
4280constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherPolyline& other) const {
4281 if (empty() || other.size() < 2) {
4282 return false;
4283 }
4284 return detail::separates1DSet(*this, other.edgesView());
4285}
4286
4287
4288// ---------------------------------------------------------------------------
4289// Reverse direction: removing a lower-ranked shape from the region.
4290
4291namespace detail {
4292
4311template <class Region, class Remover>
4312bool boundedRemoverSeparatesRegion(const Region& region, const Remover& remover) {
4313 using N = typename Region::NumberType;
4314 using E = region_exact_number_t<N>;
4316 const auto bounds = remover.bbox();
4317 const N margin(2);
4318 const E boxLoX(N(bounds.min().x()) - margin);
4319 const E boxLoY(N(bounds.min().y()) - margin);
4320 const E boxHiX(N(bounds.max().x()) + margin);
4321 const E boxHiY(N(bounds.max().y()) + margin);
4322 const auto clipped = regionClippedToBox(region, bounds);
4323 if (!clipped.isBounded()) {
4324 return false; // the remover's neighborhood misses the region
4325 }
4326 const auto poly = clipped.template asConvex<E>();
4327 const std::size_t n = poly.size();
4328 if (n < 3) {
4329 return false;
4330 }
4331 // The boundary is walked as a cyclic sequence of pieces, each free
4332 // (surviving the removal) or removed. Box (ideal) edges are always free.
4333 std::vector<bool> free;
4334 for (std::size_t i = 0; i < n; ++i) {
4335 const EPoint u(poly[i]);
4336 const EPoint v(poly[(i + 1) % n]);
4337 const bool ideal =
4338 (u.x() == boxLoX && v.x() == boxLoX) || (u.x() == boxHiX && v.x() == boxHiX) ||
4339 (u.y() == boxLoY && v.y() == boxLoY) || (u.y() == boxHiY && v.y() == boxHiY);
4340 if (ideal) {
4341 free.push_back(true);
4342 continue;
4343 }
4344 const Segment<EPoint> edge(u, v);
4345 if constexpr (is_polygon_v<Remover>) {
4346 // A reflex remover may bite several blocks out of one edge; list
4347 // them by their parameter along u -> v and alternate free/removed.
4348 const EPoint dir = v - u;
4349 const E edgeLen = dir.x() * dir.x() + dir.y() * dir.y();
4350 std::vector<std::pair<E, E>> blocks;
4351 for (const auto& piece : remover.template intersection<E>(edge)) {
4352 if (const auto* overlap = std::get_if<1>(&piece)) {
4353 const EPoint lo(overlap->min());
4354 const EPoint hi(overlap->max());
4355 E tLo = (lo - u).x() * dir.x() + (lo - u).y() * dir.y();
4356 E tHi = (hi - u).x() * dir.x() + (hi - u).y() * dir.y();
4357 if (tHi < tLo) {
4358 std::swap(tLo, tHi);
4359 }
4360 if (tLo < tHi) {
4361 blocks.emplace_back(tLo, tHi);
4362 }
4363 }
4364 }
4365 std::sort(blocks.begin(), blocks.end());
4366 E cursor{};
4367 bool wroteAny = false;
4368 for (const auto& block : blocks) {
4369 if (cursor < block.first) {
4370 free.push_back(true);
4371 }
4372 free.push_back(false);
4373 cursor = block.second;
4374 wroteAny = true;
4375 }
4376 if (!wroteAny || cursor < edgeLen) {
4377 free.push_back(true);
4378 }
4379 } else {
4380 // A convex remover meets the edge in one connected block.
4381 const bool uIn = remover.contains(u);
4382 const bool vIn = remover.contains(v);
4383 if (uIn && vIn) {
4384 free.push_back(false);
4385 } else if (uIn) {
4386 free.push_back(false);
4387 free.push_back(true);
4388 } else if (vIn) {
4389 free.push_back(true);
4390 free.push_back(false);
4391 } else if (remover.interiorsIntersect(edge)) {
4392 free.push_back(true);
4393 free.push_back(false);
4394 free.push_back(true);
4395 } else {
4396 free.push_back(true);
4397 }
4398 }
4399 }
4400 const std::size_t m = free.size();
4401 bool anyRemoved = false;
4402 for (const bool f : free) {
4403 anyRemoved = anyRemoved || !f;
4404 }
4405 if (!anyRemoved) {
4406 return false; // the whole boundary survives: one component
4407 }
4408 // Count maximal cyclic runs of free pieces: starting the scan at a removed
4409 // piece keeps the runs from wrapping. Two runs means two components.
4410 std::size_t start = 0;
4411 while (free[start]) {
4412 ++start;
4413 }
4414 int components = 0;
4415 std::size_t i = 0;
4416 while (i < m) {
4417 if (!free[(start + i) % m]) {
4418 ++i;
4419 continue;
4420 }
4421 while (i < m && free[(start + i) % m]) {
4422 ++i;
4423 }
4424 ++components;
4425 }
4426 return components >= 2;
4427}
4428
4429// Removing a bounded shape from the region: empty region -> false; degenerate
4430// region -> the remover cuts its carrier; full-dimensional region -> the
4431// boundary-component test (bounded regions delegate to Convex::separates for
4432// speed and reuse, which is the same predicate).
4433template <class Shape2, class Region>
4434bool boundedShapeSeparatesRegion(const Shape2& remover, const Region& region) {
4435 if (region.empty()) {
4436 return false;
4437 }
4438 if (region.isDegenerate()) {
4439 return std::visit([&remover](const auto& carrier) { return remover.separates(carrier); },
4440 degenerateRegionCarrier(region));
4441 }
4442 using E = region_exact_number_t<typename Region::NumberType>;
4443 if (region.isBounded()) {
4444 return remover.separates(region.template asConvex<E>());
4445 }
4446 return boundedRemoverSeparatesRegion(region, remover);
4447}
4448
4464template <class RemoverRegion, class Region>
4465bool regionSeparatesRegion(const RemoverRegion& remover, const Region& region) {
4466 using E = region_exact_number_t<typename RemoverRegion::NumberType>;
4467 using EPoint = Point<E, typename RemoverRegion::PointType::LabelType>;
4468 const HalfplaneIntersection<EPoint> removerExact(remover);
4469 const HalfplaneIntersection<EPoint> regionExact(region);
4470
4471 bool started = false;
4472 E loX{}, loY{}, hiX{}, hiY{};
4473 const auto include = [&](const EPoint& point) {
4474 if (!started) {
4475 loX = hiX = point.x();
4476 loY = hiY = point.y();
4477 started = true;
4478 return;
4479 }
4480 loX = std::min(loX, point.x());
4481 loY = std::min(loY, point.y());
4482 hiX = std::max(hiX, point.x());
4483 hiY = std::max(hiY, point.y());
4484 };
4485
4486 std::vector<Line<EPoint>> lines;
4487 for (const auto& regionSide : {&removerExact, &regionExact}) {
4488 for (const auto& halfplane : *regionSide) {
4489 include(halfplane.source());
4490 include(halfplane.target());
4491 lines.push_back(halfplane.asLine());
4492 }
4493 }
4494 for (std::size_t i = 0; i < lines.size(); ++i) {
4495 for (std::size_t j = i + 1; j < lines.size(); ++j) {
4496 const auto crossing = lines[i].template intersection<E>(lines[j]);
4497 if (crossing && crossing->index() == 0) {
4498 include(std::get<0>(*crossing));
4499 }
4500 }
4501 }
4502
4503 // Both regions are full-dimensional and nonempty, so each stores at least
4504 // one half-plane and the coordinate pool is nonempty.
4505 const Rectangle<EPoint> bounds(EPoint(loX, loY), EPoint(hiX, hiY));
4506 const auto removerPoly = regionClippedToBox(removerExact, bounds).template asConvex<E>();
4507 const auto regionPoly = regionClippedToBox(regionExact, bounds).template asConvex<E>();
4508 return removerPoly.separates(regionPoly);
4509}
4510
4511} // namespace detail
4512
4513template <class PointType, class LabelType>
4514template <HalfplaneIntersectionConcept OtherRegion>
4515constexpr bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherRegion& other) const {
4516 // A degenerate side reduces to its carrier shape; the full-dimensional
4517 // pair takes the shared-box boundary-component count. Both are computed
4518 // over the exact coordinate type, since exact carriers and boxes are
4519 // generally not representable in the inputs' own coordinate type.
4520 if (empty() || other.empty()) {
4521 return false;
4522 }
4523 using E = detail::region_exact_number_t<NumberType>;
4525 if (isDegenerate()) {
4526 const ERegion otherExact(other);
4527 return std::visit(
4528 [&otherExact](const auto& carrier) { return carrier.separates(otherExact); },
4529 detail::degenerateRegionCarrier(*this));
4530 }
4531 if (other.isDegenerate()) {
4532 const ERegion selfExact(*this);
4533 return std::visit(
4534 [&selfExact](const auto& carrier) { return selfExact.separates(carrier); },
4535 detail::degenerateRegionCarrier(other));
4536 }
4537 return detail::regionSeparatesRegion(*this, other);
4538}
4539
4540template <class PointType, class LabelType>
4541template <PointConcept OtherPoint>
4543 return std::visit(
4544 [this](const auto& value) {
4545 return this->separates(value);
4546 },
4547 other.variant());
4548}
4549
4550template <class Number, class Label>
4551template <HalfplaneIntersectionConcept OtherRegion>
4552constexpr bool Point<Number, Label>::separates(const OtherRegion& other) const {
4553 // A point has empty interior; it can only sever a one-dimensional carrier.
4554 if (other.empty() || !other.isDegenerate()) {
4555 return false;
4556 }
4557 return std::visit([this](const auto& carrier) { return this->separates(carrier); },
4558 detail::degenerateRegionCarrier(other));
4559}
4560
4561template <class PointType, class LabelType>
4562template <HalfplaneIntersectionConcept OtherRegion>
4563constexpr bool Segment<PointType, LabelType>::separates(const OtherRegion& other) const {
4564 if (other.empty()) {
4565 return false;
4566 }
4567 if (other.isDegenerate()) {
4568 // A one-dimensional region is cut along its carrier.
4569 return std::visit([this](const auto& carrier) { return this->separates(carrier); },
4570 detail::degenerateRegionCarrier(other));
4571 }
4572 // The same chord criterion as Segment::separates(Convex), which holds for
4573 // every convex region, bounded or not. Meeting the interior forces the
4574 // region to have interior strictly on both sides of the supporting line,
4575 // so the region's chord on that line is the line's interior trace; both
4576 // endpoints staying out of the interior then forces the segment to cover
4577 // that whole chord (a segment ending midway leaves a slit, not a split),
4578 // and a covered chord is necessarily bounded.
4579 return !isDegenerate() && !other.interiorContains(min()) &&
4580 !other.interiorContains(max()) && other.interiorsIntersect(*this);
4581}
4582
4583template <class PointType, class LabelType>
4584template <HalfplaneIntersectionConcept OtherRegion>
4585constexpr bool OrientedSegment<PointType, LabelType>::separates(const OtherRegion& other) const {
4586 return asSegment().separates(other);
4587}
4588
4589template <class PointType, class LabelType>
4590template <HalfplaneIntersectionConcept OtherRegion>
4591constexpr bool Line<PointType, LabelType>::separates(const OtherRegion& other) const {
4592 if (other.empty()) {
4593 return false;
4594 }
4595 if (other.isDegenerate()) {
4596 return std::visit([this](const auto& carrier) { return this->separates(carrier); },
4597 detail::degenerateRegionCarrier(other));
4598 }
4599 // A full line severs a full-dimensional convex region exactly when the
4600 // region has interior on both sides of it.
4601 return detail::regionStrictlyOnBothSides(other, (*this)[0], (*this)[1]);
4602}
4603
4604template <class PointType, class LabelType>
4605template <HalfplaneIntersectionConcept OtherRegion>
4606constexpr bool OrientedLine<PointType, LabelType>::separates(const OtherRegion& other) const {
4607 return asLine().separates(other);
4608}
4609
4610template <class PointType, class LabelType>
4611template <HalfplaneIntersectionConcept OtherRegion>
4612constexpr bool Ray<PointType, LabelType>::separates(const OtherRegion& other) const {
4613 if (other.empty()) {
4614 return false;
4615 }
4616 if (other.isDegenerate()) {
4617 return std::visit([this](const auto& carrier) { return this->separates(carrier); },
4618 detail::degenerateRegionCarrier(other));
4619 }
4620 // The same chord criterion as Ray::separates(Convex) with the far end at
4621 // infinity: only the source can leave the chord uncovered, since a source
4622 // outside the region sits before the whole chord and a source on the
4623 // boundary sits at its near end. An interior source leaves a slit.
4624 return !isDegenerate() && !other.interiorContains(source()) &&
4625 other.interiorsIntersect(*this);
4626}
4627
4628template <class PointType, class LabelType>
4629template <HalfplaneIntersectionConcept OtherRegion>
4630constexpr bool Halfplane<PointType, LabelType>::separates(const OtherRegion& other) const {
4631 // Removing a closed half-plane from any convex set leaves a convex set,
4632 // which is connected; so a half-plane never disconnects the region.
4633 (void)other;
4634 return false;
4635}
4636
4637template <class PointType, class LabelType>
4638template <HalfplaneIntersectionConcept OtherRegion>
4639constexpr bool Rectangle<PointType, LabelType>::separates(const OtherRegion& other) const {
4640 if (empty()) {
4641 // The empty set meets nothing and disconnects nothing.
4642 return false;
4643 }
4644 if (isDegenerate()) {
4645 return false; // a degenerate rectangle is one-dimensional
4646 }
4647 return detail::boundedShapeSeparatesRegion(*this, other);
4648}
4649
4650template <class PointType, class LabelType>
4651template <HalfplaneIntersectionConcept OtherRegion>
4652constexpr bool Triangle<PointType, LabelType>::separates(const OtherRegion& other) const {
4653 return detail::boundedShapeSeparatesRegion(*this, other);
4654}
4655
4656template <class PointType, class LabelType>
4657template <HalfplaneIntersectionConcept OtherRegion>
4658constexpr bool Disk<PointType, LabelType>::separates(const OtherRegion& other) const {
4659 if (isDegenerate()) {
4660 return false;
4661 }
4662 return detail::boundedShapeSeparatesRegion(*this, other);
4663}
4664
4665template <class PointType, class LabelType>
4666template <HalfplaneIntersectionConcept OtherRegion>
4667constexpr bool Convex<PointType, LabelType>::separates(const OtherRegion& other) const {
4668 if (isDegenerate()) {
4669 return false;
4670 }
4671 return detail::boundedShapeSeparatesRegion(*this, other);
4672}
4673
4674template <class PointType, class LabelType, class Storage>
4675template <HalfplaneIntersectionConcept OtherRegion>
4676constexpr bool MonotoneChain<PointType, LabelType, Storage>::separates(const OtherRegion& other) const {
4677 if (other.empty()) {
4678 return false;
4679 }
4680 if (other.isDegenerate()) {
4681 return std::visit([this](const auto& carrier) { return this->separates(carrier); },
4682 detail::degenerateRegionCarrier(other));
4683 }
4684 // The region is convex, so the crosscut scan applies as it does for a
4685 // Convex target; the bbox guards used there are skipped because an
4686 // unbounded region has no bounding box. An x-monotone chain never closes
4687 // a loop, so no sealed pocket escapes the scan.
4688 return separatesTwoDimensional(other);
4689}
4690
4691template <class PointType, class LabelType>
4692template <HalfplaneIntersectionConcept OtherRegion>
4693constexpr bool Polyline<PointType, LabelType>::separates(const OtherRegion& other) const {
4694 if (other.empty() || size() < 2) {
4695 return false;
4696 }
4697 if (other.isDegenerate()) {
4698 return std::visit([this](const auto& carrier) { return this->separates(carrier); },
4699 detail::degenerateRegionCarrier(other));
4700 }
4701 // The region is convex, so the same cycle criterion as for a Convex
4702 // target applies; it is predicate-based and never asks for a bounding box.
4703 return detail::polylineSeparatesConvexRegion(*this, other);
4704}
4705
4706template <class PointType, class LabelType>
4707template <HalfplaneIntersectionConcept OtherRegion>
4708constexpr bool Polygon<PointType, LabelType>::separates(const OtherRegion& other) const {
4709 if (isDegenerate()) {
4710 return false;
4711 }
4712 return detail::boundedShapeSeparatesRegion(*this, other);
4713}
4714
4715// ---------------------------------------------------------------------------
4716// PolygonWithHoles
4717//
4718// Every cut predicate a region takes part in — in either direction — is the
4719// same question: does `target ∖ remover` have at least two connected
4720// components? For a region neither operand's shape can be leaned on the way
4721// the polygon overloads above lean on theirs. A region's interior is not
4722// simply connected, so a crosscut of its boundary is not a cut; that interior
4723// need not even be connected — a band hole leaves two slabs — so a
4724// boundary-arc tally does not see all of its pieces; and the region carries
4725// pinch points and slits (decision (b) of the design), so it is not a surface
4726// and no Euler-characteristic count applies to it either.
4727//
4728// What survives all of that is the plainest reading of the question. Both
4729// operands are polygonal, so the arrangement of their boundaries cuts the
4730// plane into cells — open faces, open edges and vertices — on each of which
4731// membership in *both* operands is constant, and `target ∖ remover` is exactly
4732// the union of the kept cells. Two kept cells are adjacent in the remainder
4733// exactly when one is a face of the other (a cell's closure meets another kept
4734// cell only along shared faces, and `c ∪ f` is connected for a face `f` of
4735// `c`), so a union-find over the incidences counts the components with nothing
4736// assumed about either shape. Vertices and edges are cells in their own right,
4737// which is what keeps slits — region material with no area beside it — and
4738// pinch points from being lost.
4739//
4740// The construction is the @ref pgl::Arrangement of every boundary edge of both
4741// operands, which is that decomposition and carries the incidences directly.
4742// Coordinates are exact rationals, since the crossings are. A region without
4743// holes *is* its outer polygon, and forwards to it: that keeps the tested
4744// polygon implementations in charge of everything they already settle, and
4745// leaves the engine for the cases only a region reaches.
4746
4747namespace detail {
4748
4757template <class ExactPoint, class Shape>
4758void appendCutSegments(const Shape& shape, std::vector<Segment<ExactPoint>>& out) {
4759 const auto add = [&out](const auto& edge) {
4760 const ExactPoint lo(edge.min());
4761 const ExactPoint hi(edge.max());
4762 if (lo != hi) {
4763 out.emplace_back(lo, hi);
4764 }
4765 };
4766 if constexpr (is_point_v<Shape>) {
4767 (void)shape;
4768 (void)add;
4769 } else if constexpr (is_segment_v<Shape>) {
4770 add(shape);
4771 } else if constexpr (requires { shape.edgesView(); }) {
4772 for (const auto& edge : shape.edgesView()) {
4773 add(edge);
4774 }
4775 } else {
4776 // Everything else materializes its edges: a region and a set assemble
4777 // theirs from their rings, and a triangle and a rectangle return a
4778 // fixed-size array, which is why neither carries a lazy view.
4779 for (const auto& edge : shape.edges()) {
4780 add(edge);
4781 }
4782 }
4783}
4784
4786template <class ExactPoint, class Shape>
4787void appendCutPoints(const Shape& shape, std::vector<ExactPoint>& out) {
4788 if constexpr (is_point_v<Shape>) {
4789 out.emplace_back(shape);
4790 } else if constexpr (is_segment_v<Shape>) {
4791 if (shape.min() == shape.max()) {
4792 out.emplace_back(shape.min());
4793 }
4794 } else if constexpr (is_monotone_chain_v<Shape> || is_polyline_v<Shape>) {
4795 if (shape.size() == 1) {
4796 out.emplace_back(shape[0]);
4797 }
4798 } else {
4799 (void)shape;
4800 (void)out;
4801 }
4802}
4803
4813template <class Target>
4814bool disconnectedOnItsOwn(const Target& target) {
4815 if constexpr (is_polygon_set_v<Target>) {
4816 return !target.isConnected();
4817 } else {
4818 (void)target;
4819 return false;
4820 }
4821}
4822
4851template <class Target, class Remover>
4852bool cellSeparates(const Target& target, const Remover& remover) {
4853 using ExactNumber = Exact1DNumber<typename Target::NumberType, typename Remover::NumberType>;
4854 using ExactPoint = Point<ExactNumber>;
4855 using ExactSegment = Segment<ExactPoint>;
4856
4857 // Every operand the engine is called with is connected — a region included,
4858 // see @ref regionsAreConnected — so a remover that misses the target
4859 // removes nothing that matters. A set of regions is the exception: it may
4860 // come apart on its own, and then no remover has to touch it at all.
4861 if (!target.bbox().intersects(remover.bbox())) {
4862 return disconnectedOnItsOwn(target);
4863 }
4864
4865 std::vector<ExactSegment> cuts;
4866 std::vector<ExactPoint> cutPoints;
4867 appendCutSegments<ExactPoint>(target, cuts);
4868 appendCutSegments<ExactPoint>(remover, cuts);
4869 appendCutPoints<ExactPoint>(target, cutPoints);
4870 appendCutPoints<ExactPoint>(remover, cutPoints);
4871 if (cuts.empty()) {
4872 return false; // a target with no extent holds at most one component
4873 }
4874
4875 // Layering: Arrangement lives in algorithm/arrangement.hpp, which pgl.hpp
4876 // includes after this header. Only the name is available here — see the
4877 // declaration in core/forward.hpp — and that is enough, since the type
4878 // depends on the operands and so is instantiated at the call site.
4879 const Arrangement<ExactPoint> arrangement(cuts, cutPoints);
4880 using FaceHandle = typename Arrangement<ExactPoint>::FaceId;
4881 using HalfedgeHandle = typename Arrangement<ExactPoint>::HalfedgeId;
4882
4883 // One union-find node per kept cell. A cell is kept when its relative
4884 // interior lies in target ∖ remover, which one witness point decides.
4885 std::vector<std::size_t> parent;
4886 const auto findRoot = [&parent](std::size_t x) {
4887 while (parent[x] != x) {
4888 parent[x] = parent[parent[x]];
4889 x = parent[x];
4890 }
4891 return x;
4892 };
4893 constexpr std::size_t dropped = std::numeric_limits<std::size_t>::max();
4894 const auto unite = [&](std::size_t a, std::size_t b) {
4895 if (a != dropped && b != dropped) {
4896 parent[findRoot(a)] = findRoot(b);
4897 }
4898 };
4899 const auto cellOf = [&](const ExactPoint& witness) {
4900 if (!target.contains(witness) || remover.contains(witness)) {
4901 return dropped;
4902 }
4903 parent.push_back(parent.size());
4904 return parent.size() - 1;
4905 };
4906
4907 std::vector<std::size_t> faceCell(arrangement.faceCount(), dropped);
4908 for (std::uint32_t i = 0; i < arrangement.faceCount(); ++i) {
4909 const FaceHandle f(i);
4910 if (!arrangement.isUnbounded(f)) {
4911 faceCell[f.index()] = cellOf(arrangement.template witness<ExactNumber>(f));
4912 }
4913 }
4914 // A vertex the input left isolated — a cut point lying on no cut segment —
4915 // gets a cell of its own that the loop below never joins to anything, which
4916 // is right because it only arises when an operand is a single point: as the
4917 // target it is then the one cell that can survive at all, and as the remover
4918 // it is dropped along with every other point of the remover.
4919 std::vector<std::size_t> vertexCell(arrangement.vertexCount(), dropped);
4920 for (std::size_t i = 0; i < arrangement.vertexCount(); ++i) {
4921 vertexCell[i] = cellOf(arrangement.vertices()[i]); // a vertex is its own witness
4922 }
4923
4924 std::vector<std::size_t> edgeCell(arrangement.edgeCount(), dropped);
4925 for (std::uint32_t i = 0; i < arrangement.halfedgeCount(); ++i) {
4926 const HalfedgeHandle h(i);
4927 const std::size_t edge = h.index() / 2;
4928 if (h.index() % 2 == 0) {
4929 edgeCell[edge] = cellOf(arrangement.template witness<ExactNumber>(h));
4930 }
4931 const std::size_t side = edgeCell[edge];
4932 const std::size_t corner = vertexCell[arrangement.source(h).index()];
4933 const std::size_t face = faceCell[arrangement.face(h).index()];
4934 unite(side, corner);
4935 unite(side, face);
4936 unite(face, corner);
4937 }
4938
4939 std::size_t components = 0;
4940 for (std::size_t i = 0; i < parent.size(); ++i) {
4941 if (findRoot(i) == i && ++components >= 2) {
4942 return true;
4943 }
4944 }
4945 return false;
4946}
4947
4963
4976
4978template <class ExactPoint, class Linear, class Region>
4979std::optional<Segment<ExactPoint>> clipLinearToRegion(const Linear& linear, const Region& region) {
4980 using ExactNumber = typename ExactPoint::NumberType;
4981 const auto bounds = region.bbox();
4982 const ExactNumber margin(1);
4983 const Rectangle<ExactPoint> box(
4984 ExactPoint(ExactNumber(bounds.min().x()) - margin, ExactNumber(bounds.min().y()) - margin),
4985 ExactPoint(ExactNumber(bounds.max().x()) + margin, ExactNumber(bounds.max().y()) + margin));
4986 const auto piece = box.template intersection<ExactNumber>(linear);
4987 if (!piece) {
4988 return std::nullopt;
4989 }
4990 const auto* overlap = std::get_if<1>(&*piece);
4991 if (!overlap) {
4992 return std::nullopt; // a single corner touch stays clear of the region
4993 }
4994 return *overlap;
4995}
4996
4998
5003template <bool LinearIsTarget, class Linear, class Region>
5004bool linearAndRegionSeparate(const Linear& linear, const Region& region) {
5005 using ExactNumber = Exact1DNumber<typename Linear::NumberType, typename Region::NumberType>;
5006 using ExactPoint = Point<ExactNumber>;
5007 const auto clip = clipLinearToRegion<ExactPoint>(linear, region);
5008 if (!clip) {
5009 // The operand stays clear of the region; only a region that was already
5010 // in several pieces answers anything but false.
5011 return LinearIsTarget ? false : disconnectedOnItsOwn(region);
5012 }
5013 if constexpr (LinearIsTarget) {
5014 return cellSeparates(*clip, region);
5015 } else {
5016 return cellSeparates(region, *clip);
5017 }
5018}
5019
5025template <bool ConvexIsTarget, class ConvexOperand, class Region>
5026bool convexAndRegionSeparate(const ConvexOperand& convex, const Region& region) {
5027 using ExactNumber = Exact1DNumber<typename ConvexOperand::NumberType, typename Region::NumberType>;
5028 using ExactPoint = Point<ExactNumber>;
5029 HalfplaneIntersection<ExactPoint> clipped(convex);
5030 if (!clipped.isBounded()) {
5031 clipped = regionClippedToBox(clipped, region.bbox());
5032 }
5033 const auto run = [&region](const auto& operand) {
5034 if constexpr (ConvexIsTarget) {
5035 return cellSeparates(operand, region);
5036 } else {
5037 return cellSeparates(region, operand);
5038 }
5039 };
5040 if (clipped.empty()) {
5041 // As in @ref linearAndRegionSeparate: the operand cannot reach the
5042 // region, so only a region already in several pieces answers true.
5043 return ConvexIsTarget ? false : disconnectedOnItsOwn(region);
5044 }
5045 if (clipped.isDegenerate()) {
5046 return std::visit(
5047 [&run](const auto& carrier) {
5048 using Carrier = std::remove_cvref_t<decltype(carrier)>;
5049 if constexpr (is_point_v<Carrier> || is_segment_v<Carrier>) {
5050 return run(carrier);
5051 } else {
5052 return false; // a bounded region carries no ray or line
5053 }
5054 },
5055 degenerateRegionCarrier(clipped));
5056 }
5057 return run(clipped.template asConvex<ExactNumber>().asPolygon());
5058}
5059
5060// ---------------------------------------------------------------------------
5061// The disk pair.
5062//
5063// A circle is not polygonal, so the cell engine does not take it: it has no
5064// boundary to add to the arrangement, being neither a segment nor a source of
5065// rational crossings. What replaces it in each direction is the one property a
5066// disk has that a polygon does not — convexity — and each direction uses it
5067// differently. Neither ever constructs a circle crossing: every test below is a
5068// predicate on the disk, exact in the operands' own arithmetic.
5069//
5070// Convexity is also why the direction below keeps a triangulation where
5071// @ref cellSeparates dropped one. Its cells must each meet the disk in a
5072// connected set, and a general arrangement face need not: an L-shaped face and
5073// a disk covering both of its ends but not its corner meet in two pieces. A
5074// triangle cannot do that, so the arrangement's faces are cut into triangles
5075// before the union-find walks them, and only the split of the constraints —
5076// which the arrangement does by sweep — is shared with the polygonal engine.
5077
5087template <class ExactPoint>
5088std::vector<Segment<ExactPoint>> splitCutSegments(const std::vector<Segment<ExactPoint>>& cuts) {
5089 // Layering: see the note in @ref cellSeparates.
5090 const Arrangement<ExactPoint> arrangement(cuts);
5091 return arrangement.boundedEdges();
5092}
5093
5109template <class Region, class OtherDisk>
5110bool regionSeparatesDisk(const Region& region, const OtherDisk& disk) {
5111 using ExactNumber = Exact1DNumber<typename Region::NumberType, typename OtherDisk::NumberType>;
5112 using ExactPoint = Point<ExactNumber>;
5113 using ExactSegment = Segment<ExactPoint>;
5114
5115 // A disk is connected, so a region that misses it removes nothing that
5116 // matters — the same shortcut @ref cellSeparates takes, and worth as much
5117 // here, since everything below is one arrangement and one triangulation.
5118 if (!region.bbox().intersects(disk.bbox())) {
5119 return false;
5120 }
5121
5122 std::vector<ExactSegment> cuts;
5123 appendCutSegments<ExactPoint>(region, cuts);
5124 if (cuts.empty()) {
5125 return false; // an empty region removes nothing
5126 }
5127
5128 // A box strictly containing both shapes. Holding the whole disk is what
5129 // keeps every piece of `D ∖ A` inside the arrangement, and keeping the disk
5130 // off the box boundary is what leaves every vertex it reaches with a
5131 // complete star of triangles for the union-find to walk.
5132 const auto regionBox = region.bbox();
5133 const auto diskBox = disk.bbox();
5134 const ExactNumber margin(1);
5135 const ExactNumber loX =
5136 std::min(ExactNumber(regionBox.min().x()), ExactNumber(diskBox.min().x())) - margin;
5137 const ExactNumber loY =
5138 std::min(ExactNumber(regionBox.min().y()), ExactNumber(diskBox.min().y())) - margin;
5139 const ExactNumber hiX =
5140 std::max(ExactNumber(regionBox.max().x()), ExactNumber(diskBox.max().x())) + margin;
5141 const ExactNumber hiY =
5142 std::max(ExactNumber(regionBox.max().y()), ExactNumber(diskBox.max().y())) + margin;
5143 const Polygon<ExactPoint> box(std::vector<ExactPoint>{
5144 ExactPoint(loX, loY), ExactPoint(hiX, loY), ExactPoint(hiX, hiY), ExactPoint(loX, hiY)});
5145 const auto mesh = box.triangulation(splitCutSegments(cuts));
5146
5147 // The disk in the arrangement's own arithmetic, so every test below is a
5148 // like-typed exact predicate.
5149 const Disk<ExactPoint> exactDisk(disk);
5150
5151 std::vector<std::size_t> parent;
5152 const auto findRoot = [&parent](std::size_t x) {
5153 while (parent[x] != x) {
5154 parent[x] = parent[parent[x]];
5155 x = parent[x];
5156 }
5157 return x;
5158 };
5159 const auto unite = [&](std::size_t a, std::size_t b) {
5160 parent[findRoot(a)] = findRoot(b);
5161 };
5162 // `dropped` marks a cell the disk does not reach and `unset` one not yet
5163 // classified. Real cell numbers are positions in `parent`, so neither
5164 // collides with one.
5165 constexpr std::size_t dropped = std::numeric_limits<std::size_t>::max();
5166 constexpr std::size_t unset = dropped - 1;
5167 const auto cellOf = [&](bool kept) {
5168 if (!kept) {
5169 return dropped;
5170 }
5171 parent.push_back(parent.size());
5172 return parent.size() - 1;
5173 };
5174
5175 // Cells are keyed by handle rather than by position: a vertex indexes a
5176 // plain table, and an edge is named by its two vertex handles. Keyed by
5177 // coordinates instead, as the arrangement's own arithmetic makes them, every
5178 // step of every lookup would compare a pair of exact rationals.
5179 using TriId = typename std::decay_t<decltype(mesh)>::TriId;
5180 using VertexId = typename std::decay_t<decltype(mesh)>::VertexId;
5181 using EdgeKey = std::pair<VertexId, VertexId>;
5182 std::map<EdgeKey, std::size_t> edgeCells;
5183 std::vector<std::size_t> vertexCells(mesh.vertexIndexBound(), unset);
5184 const auto vertexCell = [&](VertexId v) {
5185 std::size_t& cell = vertexCells[v.index()];
5186 if (cell == unset) {
5187 const auto& vertex = mesh.getShape(v);
5188 cell = cellOf(exactDisk.contains(vertex) && !region.contains(vertex));
5189 }
5190 return cell;
5191 };
5192 const auto edgeCell = [&](VertexId a, VertexId b) {
5193 const EdgeKey key = a < b ? EdgeKey{a, b} : EdgeKey{b, a};
5194 const auto [it, inserted] = edgeCells.try_emplace(key, dropped);
5195 if (inserted) {
5196 // The disk reaches the open edge either through its own interior or
5197 // by touching it at a single point, and that point is interior to
5198 // the edge exactly when neither endpoint carries it. Membership in
5199 // the region is constant along the open edge, so its midpoint
5200 // decides it — an edge along a slit is region material, not
5201 // complement.
5202 const ExactSegment edge(mesh.getShape(a), mesh.getShape(b));
5203 const ExactPoint mid((edge.min().x() + edge.max().x()) / ExactNumber(2),
5204 (edge.min().y() + edge.max().y()) / ExactNumber(2));
5205 const bool met = exactDisk.interiorsIntersect(edge) ||
5206 (exactDisk.intersects(edge) && !exactDisk.contains(edge.min()) &&
5207 !exactDisk.contains(edge.max()));
5208 it->second = cellOf(met && !region.contains(mid));
5209 }
5210 return it->second;
5211 };
5212
5213 mesh.visitTriangles([&](TriId t) {
5214 const auto triangle = mesh.getShape(t);
5215 const auto v = mesh.vertices(t);
5216 const std::size_t face =
5217 cellOf(exactDisk.interiorsIntersect(triangle) &&
5218 !region.contains(triangle.template pointInside<ExactNumber>()));
5219 for (int k = 0; k < 3; ++k) {
5220 // vertices(t)[k] is the vertex at getShape(t)[k], so side k joins
5221 // the same two corners the value walk paired.
5222 const std::size_t side = edgeCell(v[k], v[(k + 1) % 3]);
5223 const std::size_t corner = vertexCell(v[k]);
5224 if (face != dropped && side != dropped) {
5225 unite(face, side);
5226 }
5227 if (face != dropped && corner != dropped) {
5228 unite(face, corner);
5229 }
5230 if (side != dropped && corner != dropped) {
5231 unite(side, corner);
5232 }
5233 const std::size_t next = vertexCell(v[(k + 1) % 3]);
5234 if (side != dropped && next != dropped) {
5235 unite(side, next);
5236 }
5237 }
5238 });
5239
5240 std::size_t components = 0;
5241 for (std::size_t i = 0; i < parent.size(); ++i) {
5242 if (findRoot(i) == i && ++components >= 2) {
5243 return true;
5244 }
5245 }
5246 return false;
5247}
5248
5259template <class Region>
5260std::vector<Segment<typename Region::PointType>> regionSlits(const Region& region) {
5261 using RegionPoint = typename Region::PointType;
5262 using RegionSegment = Segment<RegionPoint>;
5263
5264 const auto edges = region.edges();
5265 std::vector<RegionPoint> vertices = region.vertices();
5266 std::sort(vertices.begin(), vertices.end());
5267 vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end());
5268
5269 std::vector<RegionSegment> slits;
5270 std::vector<RegionPoint> cuts;
5271 for (const auto& edge : edges) {
5272 cuts.clear();
5273 cuts.push_back(edge.min());
5274 cuts.push_back(edge.max());
5275 for (const auto& vertex : vertices) {
5276 if (edge.contains(vertex)) {
5277 cuts.push_back(vertex);
5278 }
5279 }
5280 // Every cut lies on the edge, so the lexicographic point order is the
5281 // linear order along it.
5282 std::sort(cuts.begin(), cuts.end());
5283 cuts.erase(std::unique(cuts.begin(), cuts.end()), cuts.end());
5284 for (std::size_t k = 0; k + 1 < cuts.size(); ++k) {
5285 const RegionSegment piece(cuts[k], cuts[k + 1]);
5286 int covers = 0;
5287 for (const auto& cover : edges) {
5288 if (cover.contains(piece.min()) && cover.contains(piece.max()) && ++covers == 2) {
5289 slits.push_back(piece);
5290 break;
5291 }
5292 }
5293 }
5294 }
5295 std::sort(slits.begin(), slits.end());
5296 slits.erase(std::unique(slits.begin(), slits.end()), slits.end());
5297 return slits;
5298}
5299
5321template <class OtherDisk, class Region>
5322bool diskSeparatesRegion(const OtherDisk& disk, const Region& region) {
5323 using RegionPoint = typename Region::PointType;
5324 using RegionSegment = Segment<RegionPoint>;
5325
5326 // A region is connected (@ref regionsAreConnected), so a disk that misses
5327 // it removes nothing that matters, and the triangulation below is not worth
5328 // building. The same shortcut @ref cellSeparates takes.
5329 if (!disk.bbox().intersects(region.bbox())) {
5330 return disconnectedOnItsOwn(region);
5331 }
5332
5333 std::vector<std::size_t> parent;
5334 const auto findRoot = [&parent](std::size_t x) {
5335 while (parent[x] != x) {
5336 parent[x] = parent[parent[x]];
5337 x = parent[x];
5338 }
5339 return x;
5340 };
5341 const auto unite = [&](std::size_t a, std::size_t b) {
5342 parent[findRoot(a)] = findRoot(b);
5343 };
5344
5345 // Layering: Triangulation lives in algorithm/triangulation.hpp, which
5346 // pgl.hpp includes after this header. Reach it only through the dependent
5347 // call region.triangulation(), never by naming the class here.
5348 const auto mesh = region.triangulation();
5349 using TriId = typename std::decay_t<decltype(mesh)>::TriId;
5350 using VertexId = typename std::decay_t<decltype(mesh)>::VertexId;
5351
5352 // One node per vertex the disk leaves behind; a vertex it swallows carries
5353 // nothing to connect, and is never handed to @c nodeOf. Nodes are kept by
5354 // vertex handle, so the walk indexes a table instead of searching a map
5355 // keyed by coordinates.
5356 constexpr std::size_t none = std::numeric_limits<std::size_t>::max();
5357 std::vector<std::size_t> nodes(mesh.vertexIndexBound(), none);
5358 const auto nodeOf = [&](VertexId v) {
5359 std::size_t& node = nodes[v.index()];
5360 if (node == none) {
5361 node = parent.size();
5362 parent.push_back(parent.size());
5363 }
5364 return node;
5365 };
5366
5367 // Whether the disk swallows a vertex is a property of the vertex, so it is
5368 // decided once rather than once per incident triangle.
5369 std::vector<char> eatenAt(mesh.vertexIndexBound(), 0);
5370 for (const VertexId v : mesh.vertexIds()) {
5371 eatenAt[v.index()] = disk.contains(mesh.getShape(v)) ? 1 : 0;
5372 }
5373
5374 mesh.visitTriangles([&](TriId t) {
5375 // vertices(t)[i] is the vertex at getShape(t)[i], so side i runs from
5376 // vertex i to vertex i + 1 as the value walk had it.
5377 const auto v = mesh.vertices(t);
5378 const bool eaten[3] = {eatenAt[v[0].index()] != 0, eatenAt[v[1].index()] != 0,
5379 eatenAt[v[2].index()] != 0};
5380 std::size_t first = none;
5381 std::size_t last = none;
5382 bool gapBeforeFirst = false;
5383 bool gap = false; // a contact with the disk since the last vertex kept
5384 const auto keep = [&](VertexId vertex) {
5385 const std::size_t node = nodeOf(vertex);
5386 if (last == none) {
5387 first = node;
5388 gapBeforeFirst = gap;
5389 } else if (!gap) {
5390 unite(last, node);
5391 }
5392 last = node;
5393 gap = false;
5394 };
5395 for (int i = 0; i < 3; ++i) {
5396 const VertexId from = v[i];
5397 const VertexId to = v[(i + 1) % 3];
5398 if (eaten[i] && eaten[(i + 1) % 3]) { // the disk is convex: the edge is gone
5399 gap = true;
5400 } else if (eaten[i]) { // the walk leaves the disk mid-edge
5401 gap = true;
5402 keep(to);
5403 } else if (eaten[(i + 1) % 3]) { // the walk enters the disk mid-edge
5404 keep(from);
5405 gap = true;
5406 } else if (disk.intersects(
5407 RegionSegment(mesh.getShape(from), mesh.getShape(to)))) {
5408 keep(from); // a contact inside the edge
5409 gap = true;
5410 keep(to);
5411 } else { // the edge is clear of the disk
5412 keep(from);
5413 keep(to);
5414 }
5415 }
5416 if (first != none && !gap && !gapBeforeFirst) {
5417 unite(last, first);
5418 }
5419 });
5420
5421 const auto slits = regionSlits(region);
5422 if (!slits.empty()) {
5423 // A slit endpoint is a ring vertex, hence a vertex of the mesh, but the
5424 // tip of a spur carries no triangle at all -- locating it would not find
5425 // it. So the handles come from a lookup over the stored vertices, built
5426 // once and only for a region that has slits in the first place.
5427 std::map<RegionPoint, VertexId> vertexAt;
5428 for (const VertexId v : mesh.vertexIds()) {
5429 vertexAt.emplace(mesh.getShape(v), v);
5430 }
5431 const auto nodeAtPoint = [&](const RegionPoint& p) {
5432 const auto it = vertexAt.find(p);
5433 return it == vertexAt.end() ? none : nodeOf(it->second);
5434 };
5435 for (const auto& slit : slits) {
5436 // Whatever the disk takes out of a slit, what is left hangs from an
5437 // endpoint — it meets the segment in a single subsegment — so each end
5438 // it spares is a piece of its own, and the two stay joined only when it
5439 // misses the slit altogether.
5440 const std::size_t low = disk.contains(slit.min()) ? none : nodeAtPoint(slit.min());
5441 const std::size_t high = disk.contains(slit.max()) ? none : nodeAtPoint(slit.max());
5442 if (low != none && high != none && !disk.intersects(slit)) {
5443 unite(low, high);
5444 }
5445 }
5446 }
5447
5448 std::size_t components = 0;
5449 for (std::size_t i = 0; i < parent.size(); ++i) {
5450 if (findRoot(i) == i && ++components >= 2) {
5451 return true;
5452 }
5453 }
5454 return false;
5455}
5456
5457} // namespace detail
5458
5464
5465template <class PointType, class LabelType>
5466template <PointConcept OtherPoint>
5468 return false; // removing anything from a point leaves at most one piece
5469}
5470
5471template <class PointType, class LabelType>
5472template <SegmentConcept OtherSegment>
5473bool PolygonWithHoles<PointType, LabelType>::separates(const OtherSegment& other) const {
5474 if (!hasHoles()) {
5475 return outer_.separates(other);
5476 }
5477 if (other.isDegenerate()) {
5478 return false;
5479 }
5480 return detail::cellSeparates(other, *this);
5481}
5482
5483template <class PointType, class LabelType>
5484template <OrientedSegmentConcept OtherOrientedSegment>
5485bool PolygonWithHoles<PointType, LabelType>::separates(const OtherOrientedSegment& other) const {
5486 return separates(other.asSegment());
5487}
5488
5489template <class PointType, class LabelType>
5490template <LineConcept OtherLine>
5491bool PolygonWithHoles<PointType, LabelType>::separates(const OtherLine& other) const {
5492 if (!hasHoles()) {
5493 return outer_.separates(other);
5494 }
5495 if (other.isDegenerate()) {
5496 return false;
5497 }
5498 return detail::linearAndRegionSeparate<true>(other, *this);
5499}
5500
5501template <class PointType, class LabelType>
5502template <OrientedLineConcept OtherOrientedLine>
5503bool PolygonWithHoles<PointType, LabelType>::separates(const OtherOrientedLine& other) const {
5504 return separates(other.asLine());
5505}
5506
5507template <class PointType, class LabelType>
5508template <RayConcept OtherRay>
5509bool PolygonWithHoles<PointType, LabelType>::separates(const OtherRay& other) const {
5510 if (!hasHoles()) {
5511 return outer_.separates(other);
5512 }
5513 if (other.isDegenerate()) {
5514 return false;
5515 }
5516 return detail::linearAndRegionSeparate<true>(other, *this);
5517}
5518
5519template <class PointType, class LabelType>
5520template <HalfplaneConcept OtherHalfplane>
5521bool PolygonWithHoles<PointType, LabelType>::separates(const OtherHalfplane& other) const {
5522 if (!hasHoles()) {
5523 return outer_.separates(other);
5524 }
5525 if (other.isUndefined()) {
5526 return false;
5527 }
5528 return detail::convexAndRegionSeparate<true>(other, *this);
5529}
5530
5531template <class PointType, class LabelType>
5532template <RectangleConcept OtherRectangle>
5533bool PolygonWithHoles<PointType, LabelType>::separates(const OtherRectangle& other) const {
5534 if (other.empty()) {
5535 // The empty set meets nothing and disconnects nothing.
5536 return false;
5537 }
5538 return separates(other.asPolygon());
5539}
5540
5541template <class PointType, class LabelType>
5542template <TriangleConcept OtherTriangle>
5543bool PolygonWithHoles<PointType, LabelType>::separates(const OtherTriangle& other) const {
5544 return separates(other.asPolygon());
5545}
5546
5547template <class PointType, class LabelType>
5548template <ConvexConcept OtherConvex>
5549bool PolygonWithHoles<PointType, LabelType>::separates(const OtherConvex& other) const {
5550 return separates(other.asPolygon());
5551}
5552
5553template <class PointType, class LabelType>
5554template <PolygonConcept OtherPolygon>
5555bool PolygonWithHoles<PointType, LabelType>::separates(const OtherPolygon& other) const {
5556 if (!hasHoles()) {
5557 return outer_.separates(other);
5558 }
5559 if (other.isDegenerate()) {
5560 return false;
5561 }
5562 return detail::cellSeparates(other, *this);
5563}
5564
5565template <class PointType, class LabelType>
5566template <PolygonWithHolesConcept OtherRegion>
5567bool PolygonWithHoles<PointType, LabelType>::separates(const OtherRegion& other) const {
5568 if (!hasHoles()) {
5569 return outer_.separates(other);
5570 }
5571 if (!other.hasHoles()) {
5572 return separates(other.outer());
5573 }
5574 if (other.isDegenerate()) {
5575 return false;
5576 }
5577 return detail::cellSeparates(other, *this);
5578}
5579
5580template <class PointType, class LabelType>
5581template <MonotoneChainConcept OtherChain>
5582bool PolygonWithHoles<PointType, LabelType>::separates(const OtherChain& other) const {
5583 if (!hasHoles()) {
5584 return outer_.separates(other);
5585 }
5586 return detail::cellSeparates(other, *this);
5587}
5588
5589template <class PointType, class LabelType>
5590template <PolylineConcept OtherPolyline>
5591bool PolygonWithHoles<PointType, LabelType>::separates(const OtherPolyline& other) const {
5592 if (!hasHoles()) {
5593 return outer_.separates(other);
5594 }
5595 return detail::cellSeparates(other, *this);
5596}
5597
5598template <class PointType, class LabelType>
5599template <DiskConcept OtherDisk>
5600bool PolygonWithHoles<PointType, LabelType>::separates(const OtherDisk& other) const {
5601 if (!hasHoles()) {
5602 return outer_.separates(other);
5603 }
5604 if (other.isDegenerate()) {
5605 return false; // a disk of radius zero is a point, and an undefined one has no circle
5606 }
5607 return detail::regionSeparatesDisk(*this, other);
5608}
5609
5610template <class PointType, class LabelType>
5611template <HalfplaneIntersectionConcept OtherIntersection>
5612bool PolygonWithHoles<PointType, LabelType>::separates(const OtherIntersection& other) const {
5613 if (!hasHoles()) {
5614 return outer_.separates(other);
5615 }
5616 return detail::convexAndRegionSeparate<true>(other, *this);
5617}
5618
5619// ---------------------------------------------------------------------------
5620// Reverse direction: removing a lower-ranked shape from the region.
5621//
5622// Every one of these is a real computation: a region is cut by shapes that
5623// cannot cut a simply connected target — a single point at a pinch, or a
5624// segment run from one hole to another — and no operand can be dismissed for
5625// its shape alone the way a half-plane can be dismissed against a convex
5626// region.
5627
5628template <class Number, class Label>
5629template <PolygonWithHolesConcept OtherRegion>
5630bool Point<Number, Label>::separates(const OtherRegion& other) const {
5631 if (!other.hasHoles()) {
5632 return separates(other.outer());
5633 }
5634 // No single point cuts a region, however pinched. By the same duality as
5635 // @ref regionsAreConnected, the components of `A ∖ {p}` are counted by the
5636 // first cohomology of `(S² ∖ A) ∪ {p}`; each complement component is a
5637 // Jordan domain, so adding one of its boundary points keeps it simply
5638 // connected, and the union is then a wedge of simply connected spaces at
5639 // p. It is trivial, so what is left of the region is one piece — a pinch
5640 // point always has some other way around it, along the ring boundaries
5641 // that meet there.
5642 return false;
5643}
5644
5645template <class PointType, class LabelType>
5646template <PolygonWithHolesConcept OtherRegion>
5647bool Segment<PointType, LabelType>::separates(const OtherRegion& other) const {
5648 if (!other.hasHoles()) {
5649 return separates(other.outer());
5650 }
5651 if (isDegenerate()) {
5652 return min().separates(other);
5653 }
5654 return detail::cellSeparates(other, *this);
5655}
5656
5657template <class PointType, class LabelType>
5658template <PolygonWithHolesConcept OtherRegion>
5659bool OrientedSegment<PointType, LabelType>::separates(const OtherRegion& other) const {
5660 return asSegment().separates(other);
5661}
5662
5663template <class PointType, class LabelType>
5664template <PolygonWithHolesConcept OtherRegion>
5665bool Line<PointType, LabelType>::separates(const OtherRegion& other) const {
5666 if (!other.hasHoles()) {
5667 return separates(other.outer());
5668 }
5669 if (isDegenerate()) {
5670 return false;
5671 }
5672 return detail::linearAndRegionSeparate<false>(*this, other);
5673}
5674
5675template <class PointType, class LabelType>
5676template <PolygonWithHolesConcept OtherRegion>
5677bool OrientedLine<PointType, LabelType>::separates(const OtherRegion& other) const {
5678 return asLine().separates(other);
5679}
5680
5681template <class PointType, class LabelType>
5682template <PolygonWithHolesConcept OtherRegion>
5683bool Ray<PointType, LabelType>::separates(const OtherRegion& other) const {
5684 if (!other.hasHoles()) {
5685 return separates(other.outer());
5686 }
5687 if (isDegenerate()) {
5688 return false;
5689 }
5690 return detail::linearAndRegionSeparate<false>(*this, other);
5691}
5692
5693template <class PointType, class LabelType>
5694template <PolygonWithHolesConcept OtherRegion>
5695bool Halfplane<PointType, LabelType>::separates(const OtherRegion& other) const {
5696 if (!other.hasHoles()) {
5697 return separates(other.outer());
5698 }
5699 if (isUndefined()) {
5700 return false;
5701 }
5702 return detail::convexAndRegionSeparate<false>(*this, other);
5703}
5704
5705template <class PointType, class LabelType>
5706template <PolygonWithHolesConcept OtherRegion>
5707bool Rectangle<PointType, LabelType>::separates(const OtherRegion& other) const {
5708 if (empty()) {
5709 // The empty set meets nothing and disconnects nothing.
5710 return false;
5711 }
5712 return asPolygon().separates(other);
5713}
5714
5715template <class PointType, class LabelType>
5716template <PolygonWithHolesConcept OtherRegion>
5717bool Triangle<PointType, LabelType>::separates(const OtherRegion& other) const {
5718 return asPolygon().separates(other);
5719}
5720
5721template <class PointType, class LabelType>
5722template <PolygonWithHolesConcept OtherRegion>
5723bool Convex<PointType, LabelType>::separates(const OtherRegion& other) const {
5724 return asPolygon().separates(other);
5725}
5726
5727template <class PointType, class LabelType>
5728template <PolygonWithHolesConcept OtherRegion>
5729bool Polygon<PointType, LabelType>::separates(const OtherRegion& other) const {
5730 if (!other.hasHoles()) {
5731 return separates(other.outer());
5732 }
5733 if (isDegenerate()) {
5734 return false;
5735 }
5736 return detail::cellSeparates(other, *this);
5737}
5738
5739template <class PointType, class LabelType, class Storage>
5740template <PolygonWithHolesConcept OtherRegion>
5742 if (!other.hasHoles()) {
5743 return separates(other.outer());
5744 }
5745 return detail::cellSeparates(other, *this);
5746}
5747
5748template <class PointType, class LabelType>
5749template <PolygonWithHolesConcept OtherRegion>
5750bool Polyline<PointType, LabelType>::separates(const OtherRegion& other) const {
5751 if (!other.hasHoles()) {
5752 return separates(other.outer());
5753 }
5754 return detail::cellSeparates(other, *this);
5755}
5756
5757template <class PointType, class LabelType>
5758template <PolygonWithHolesConcept OtherRegion>
5759bool Disk<PointType, LabelType>::separates(const OtherRegion& other) const {
5760 if (!other.hasHoles()) {
5761 return separates(other.outer());
5762 }
5763 if (isDegenerate()) {
5764 // A disk of radius zero is a point, which never cuts a region (see
5765 // Point::separates above); an undefined disk determines no circle.
5766 return false;
5767 }
5768 return detail::diskSeparatesRegion(*this, other);
5769}
5770
5771template <class PointType, class LabelType>
5772template <PolygonWithHolesConcept OtherHoledRegion>
5773bool HalfplaneIntersection<PointType, LabelType>::separates(const OtherHoledRegion& other) const {
5774 if (!other.hasHoles()) {
5775 return separates(other.outer());
5776 }
5777 if (empty()) {
5778 return false; // nothing is removed from a connected region
5779 }
5780 return detail::convexAndRegionSeparate<false>(*this, other);
5781}
5782
5783// ---------------------------------------------------------------------------
5784// Runtime Shape argument: unwrap the stored alternative and re-dispatch. Every
5785// alternative has a per-shape overload above, so no fallback is needed.
5786
5787template <class PointType, class LabelType>
5788template <PointConcept OtherPoint>
5790 return std::visit(
5791 [this](const auto& value) {
5792 return this->separates(value);
5793 },
5794 other.variant());
5795}
5796
5797
5798template <class PointType, class LabelType>
5800 // Each component is connected on its own, so the set is connected exactly
5801 // when the graph joining components that meet is — a union-find over the
5802 // pairs whose boxes overlap.
5803 if (components_.size() < 2) {
5804 return true;
5805 }
5806 std::vector<std::size_t> parent(components_.size());
5807 for (std::size_t i = 0; i < parent.size(); ++i) {
5808 parent[i] = i;
5809 }
5810 const auto findRoot = [&parent](std::size_t x) {
5811 while (parent[x] != x) {
5812 parent[x] = parent[parent[x]];
5813 x = parent[x];
5814 }
5815 return x;
5816 };
5817 std::size_t pieces = components_.size();
5818 for (std::size_t i = 0; i < components_.size(); ++i) {
5819 for (std::size_t j = i + 1; j < components_.size(); ++j) {
5820 if (findRoot(i) == findRoot(j) ||
5821 !components_[i].bbox().intersects(components_[j].bbox())) {
5822 continue;
5823 }
5824 if (components_[i].intersects(components_[j])) {
5825 parent[findRoot(i)] = findRoot(j);
5826 --pieces;
5827 }
5828 }
5829 }
5830 return pieces == 1;
5831}
5832
5833// ---------------------------------------------------------------------------
5834// The set of regions, in both directions.
5835//
5836// A set is the first shape in the library that may be disconnected, and that is
5837// what shapes these. As a **target** it can already be in several pieces, so
5838// none of the shortcuts that lean on a connected target apply and the engine has
5839// to count for real — including for a remover that misses it entirely, which
5840// still leaves it disconnected. As a **remover** it behaves like any other
5841// polygonal shape: its rings are cut segments like a region's.
5842//
5843// Neither direction folds over the components. Removing one component may leave
5844// the target whole while removing all of them cuts it, and a target's pieces are
5845// counted across the whole set at once.
5846
5847template <class PointType, class LabelType>
5848template <detail::SetOperandConcept OtherShape>
5849bool PolygonSet<PointType, LabelType>::separates(const OtherShape& other) const {
5850 if (empty()) {
5851 return false; // nothing removed, and every operand here is connected
5852 }
5853
5854 if constexpr (PointConcept<OtherShape>) {
5855 return false; // removing anything from a point leaves at most one piece
5856 } else if constexpr (OrientedSegmentConcept<OtherShape>) {
5857 return separates(other.asSegment());
5858 } else if constexpr (OrientedLineConcept<OtherShape>) {
5859 return separates(other.asLine());
5860 } else if constexpr (LineConcept<OtherShape> || RayConcept<OtherShape>) {
5861 if (other.isDegenerate()) {
5862 return false;
5863 }
5864 return detail::linearAndRegionSeparate<true>(other, *this);
5865 } else if constexpr (HalfplaneConcept<OtherShape>) {
5866 if (other.isUndefined()) {
5867 return false;
5868 }
5869 return detail::convexAndRegionSeparate<true>(other, *this);
5870 } else if constexpr (HalfplaneIntersectionConcept<OtherShape>) {
5871 return detail::convexAndRegionSeparate<true>(other, *this);
5872 } else if constexpr (DiskConcept<OtherShape>) {
5873 if (other.isDegenerate()) {
5874 return false; // a disk of radius zero is a point, an undefined one has no circle
5875 }
5876 return detail::regionSeparatesDisk(*this, other);
5879 return separates(other.asPolygon());
5881 return detail::cellSeparates(other, *this);
5882 } else {
5883 if (other.isDegenerate()) {
5884 return false;
5885 }
5886 return detail::cellSeparates(other, *this);
5887 }
5888}
5889
5890template <class PointType, class LabelType>
5891template <PolygonSetConcept OtherSet>
5892bool PolygonSet<PointType, LabelType>::separates(const OtherSet& other) const {
5893 return detail::cellSeparates(other, *this);
5894}
5895
5896template <class PointType, class LabelType>
5897template <PointConcept OtherPoint>
5899 return std::visit([this](const auto& value) { return this->separates(value); },
5900 other.variant());
5901}
5902
5903// Reverse direction: removing a lower-ranked shape from a set. Every one of
5904// these is a real computation — even a point's, since a set that is already in
5905// two pieces stays in two however little is taken out of it.
5906
5907template <class Number, class Label>
5908template <PolygonSetConcept OtherSet>
5909bool Point<Number, Label>::separates(const OtherSet& other) const {
5910 return detail::cellSeparates(other, *this);
5911}
5912
5913template <class PointType, class LabelType>
5914template <PolygonSetConcept OtherSet>
5915bool Segment<PointType, LabelType>::separates(const OtherSet& other) const {
5916 return detail::cellSeparates(other, *this);
5917}
5918
5919template <class PointType, class LabelType>
5920template <PolygonSetConcept OtherSet>
5921bool OrientedSegment<PointType, LabelType>::separates(const OtherSet& other) const {
5922 return asSegment().separates(other);
5923}
5924
5925template <class PointType, class LabelType>
5926template <PolygonSetConcept OtherSet>
5927bool Line<PointType, LabelType>::separates(const OtherSet& other) const {
5928 if (isDegenerate()) {
5929 return min().separates(other);
5930 }
5931 return detail::linearAndRegionSeparate<false>(*this, other);
5932}
5933
5934template <class PointType, class LabelType>
5935template <PolygonSetConcept OtherSet>
5936bool OrientedLine<PointType, LabelType>::separates(const OtherSet& other) const {
5937 return asLine().separates(other);
5938}
5939
5940template <class PointType, class LabelType>
5941template <PolygonSetConcept OtherSet>
5942bool Ray<PointType, LabelType>::separates(const OtherSet& other) const {
5943 if (isDegenerate()) {
5944 return source().separates(other);
5945 }
5946 return detail::linearAndRegionSeparate<false>(*this, other);
5947}
5948
5949template <class PointType, class LabelType>
5950template <PolygonSetConcept OtherSet>
5951bool Halfplane<PointType, LabelType>::separates(const OtherSet& other) const {
5952 if (isUndefined()) {
5953 // A collapsed half-plane is the whole plane or none of it, so it removes
5954 // everything or nothing; either way only a set already in several pieces
5955 // comes apart.
5956 return detail::disconnectedOnItsOwn(other);
5957 }
5958 return detail::convexAndRegionSeparate<false>(*this, other);
5959}
5960
5961template <class PointType, class LabelType>
5962template <PolygonSetConcept OtherSet>
5963bool Rectangle<PointType, LabelType>::separates(const OtherSet& other) const {
5964 // No empty short-circuit here: a set of regions may already be in several
5965 // pieces, and removing the empty set from one of those still leaves it
5966 // disconnected. The cell engine decides, reached through the empty polygon.
5967 return asPolygon().separates(other);
5968}
5969
5970template <class PointType, class LabelType>
5971template <PolygonSetConcept OtherSet>
5972bool Triangle<PointType, LabelType>::separates(const OtherSet& other) const {
5973 return asPolygon().separates(other);
5974}
5975
5976template <class PointType, class LabelType>
5977template <PolygonSetConcept OtherSet>
5978bool Convex<PointType, LabelType>::separates(const OtherSet& other) const {
5979 return asPolygon().separates(other);
5980}
5981
5982template <class PointType, class LabelType>
5983template <PolygonSetConcept OtherSet>
5984bool Polygon<PointType, LabelType>::separates(const OtherSet& other) const {
5985 return detail::cellSeparates(other, *this);
5986}
5987
5988template <class PointType, class LabelType>
5989template <PolygonSetConcept OtherSet>
5990bool PolygonWithHoles<PointType, LabelType>::separates(const OtherSet& other) const {
5991 return detail::cellSeparates(other, *this);
5992}
5993
5994template <class PointType, class LabelType, class Storage>
5995template <PolygonSetConcept OtherSet>
5997 return detail::cellSeparates(other, *this);
5998}
5999
6000template <class PointType, class LabelType>
6001template <PolygonSetConcept OtherSet>
6002bool Polyline<PointType, LabelType>::separates(const OtherSet& other) const {
6003 return detail::cellSeparates(other, *this);
6004}
6005
6006template <class PointType, class LabelType>
6007template <PolygonSetConcept OtherSet>
6008bool Disk<PointType, LabelType>::separates(const OtherSet& other) const {
6009 if (isDegenerate()) {
6010 return detail::cellSeparates(other, a());
6011 }
6012 return detail::diskSeparatesRegion(*this, other);
6013}
6014
6015template <class PointType, class LabelType>
6016template <PolygonSetConcept OtherSet>
6018 if (empty()) {
6019 return detail::disconnectedOnItsOwn(other); // nothing is removed
6020 }
6021 return detail::convexAndRegionSeparate<false>(*this, other);
6022}
6023
6024} // namespace pgl
Exact rational number class template.
Definition rational.hpp:106
Definition forward.hpp:315
Definition forward.hpp:322
Definition forward.hpp:312
Definition forward.hpp:319
Definition forward.hpp:309
Definition forward.hpp:320
Definition forward.hpp:310
Definition forward.hpp:308
Definition forward.hpp:306
Definition forward.hpp:321
Definition forward.hpp:311
Definition forward.hpp:313
Definition forward.hpp:314
Implementations of the 'intersects' predicate.
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
Line() -> Line< Point<>, NoLabel >
Point< ERational > EPoint
Definition pgl.hpp:98
constexpr std::partial_ordering dotSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b)
Tells if the angle between two vectors is acute, right, or obtuse.
Definition orientation.hpp:688
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
constexpr std::partial_ordering crossSign(const Point< UNumber, ULabel > &u, const Point< VNumber, VLabel > &v)
Classifies the turn from one vector to another.
Definition orientation.hpp:583
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
Segment() -> Segment< Point<>, NoLabel >
constexpr auto orientationDeterminant(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Returns the signed orientation determinant of three points.
Definition orientation.hpp:518
Polygon() -> Polygon< Point<>, NoLabel >
Definition polygon.hpp:3200
Triangle() -> Triangle< Point<>, NoLabel >
Definition triangle.hpp:2029
Exact low-level orientation and incircle predicates.
Public declaration of pgl::OrientedLine.
Public declaration of pgl::OrientedSegment.
Small dispatch traits and geometry helpers reused by the implementations.
Closed convex polygon stored by its vertices.
Definition convex.hpp:170
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the convex polygon.
Definition bounding.hpp:374
constexpr bool intersects(const OtherSegment &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:716
constexpr PointType get(std::ptrdiff_t index) const
Cyclic access: same as operator[] but index is taken modulo size(); negative indices wrap from the en...
Definition convex.hpp:289
constexpr auto edgesView() const
Returns a lazy view over the edges, materializing each Segment on the fly instead of allocating a vec...
Definition convex.hpp:575
constexpr bool isDegenerate() const
Checks if the convex polygon is degenerate (has zero area).
Definition predicates.hpp:982
constexpr bool separates(const EmptyShape< EmptyPoint > &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition convex.hpp:1345
constexpr std::optional< BoundaryType< false > > getIfSegment() const
Returns the segment the convex polygon collapses to, if it does.
Definition predicates.hpp:1008
constexpr Polygon< PointType > asPolygon() const
Returns the convex polygon as a simple polygon.
Definition convex.hpp:636
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1135
size_t size() const
Returns the number of vertices in the convex polygon.
Definition convex.hpp:840
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:652
constexpr bool intersects(const OtherSegment &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:904
constexpr bool isDegenerate() const
Returns whether the three boundary points are collinear.
Definition disk.hpp:348
constexpr bool contains(const OtherPoint &other) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1015
constexpr const PointType & a() const
Returns the first boundary point (lexicographically smallest).
Definition disk.hpp:228
constexpr bool separates(const OtherPolygon &other) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition separates.hpp:2294
constexpr const PointType & b() const
Returns the second boundary point in canonical order.
Definition disk.hpp:235
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:2511
constexpr bool empty() const
Returns whether the region is the empty set.
Definition halfplaneintersection.hpp:649
constexpr bool isDegenerate() const
Returns whether the region has empty interior (it is empty or lower-dimensional: a line,...
Definition halfplaneintersection.hpp:664
constexpr Convex< Point< ResultNumber, typename PointType::LabelType > > asConvex() const
Returns the region as a convex polygon.
Definition halfplaneintersection.hpp:955
friend struct HalfplaneIntersection
Definition halfplaneintersection.hpp:2308
constexpr bool separates(const OtherPoint &other) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition separates.hpp:4050
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:1808
Closed half-plane defined by an oriented boundary line.
Definition halfplane.hpp:51
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:872
constexpr bool isUndefined() const
Returns whether the half-plane is degenerate without collapsing to a point or to a segment.
Definition predicates.hpp:957
constexpr OrientedLine< PointType > asOrientedLine() const
Returns the oriented boundary line.
Definition halfplane.hpp:334
constexpr bool isDegenerate() const
Returns whether the defining points coincide.
Definition predicates.hpp:952
constexpr bool separates(const EmptyShape< EmptyPoint > &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition halfplane.hpp:567
Unoriented infinite line.
Definition line.hpp:52
constexpr bool separates(const EmptyShape< EmptyPoint > &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition line.hpp:579
constexpr const PointType & max() const
Returns the largest stored defining point.
Definition line.hpp:189
constexpr const PointType & min() const
Returns the smallest stored defining point.
Definition line.hpp:180
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:312
constexpr bool isDegenerate() const
Returns whether the defining points coincide.
Definition predicates.hpp:451
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:1353
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1822
constexpr std::size_t size() const
Returns the number of vertices in the chain.
Definition monotonechain.hpp:393
constexpr bool separates(const OtherPoint &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition monotonechain.hpp:1374
constexpr bool empty() const
Checks whether the chain has no vertex.
Definition monotonechain.hpp:400
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the chain.
Definition bounding.hpp:495
Directed infinite line with left/right side semantics plus optional line label.
Definition orientedline.hpp:53
constexpr bool separates(const EmptyShape< EmptyPoint > &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition orientedline.hpp:688
constexpr std::partial_ordering crossingOrder(const OtherLine &first, const OtherLine &second) const
Orders two lines by where they cross this oriented line.
Definition crosses.hpp:293
constexpr Line< PointType > asLine() const
Returns the line without orientation.
Definition orientedline.hpp:321
Directed segment preserving source-to-target order plus optional segment label.
Definition orientedsegment.hpp:44
constexpr bool separates(const EmptyShape< EmptyPoint > &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition orientedsegment.hpp:725
constexpr Segment< PointType > asSegment() const
Returns the segment without orientation.
Definition orientedsegment.hpp:322
constexpr Halfplane< PointType > leftHalfplane() const
Returns the half-plane on the left of the segment direction.
Definition predicates.hpp:412
constexpr bool collinear(const OtherPoint &point) const
Returns whether the given point is collinear with the oriented segment.
Definition predicates.hpp:333
Two-dimensional point with optional label payload.
Definition point.hpp:129
constexpr Rectangle< Point > bbox() const
Returns the bounding box of the point.
Definition bounding.hpp:18
constexpr bool separates(const OtherPoint &other) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition separates.hpp:258
constexpr const NumberType & x() const
Returns the x coordinate.
Definition point.hpp:193
constexpr bool interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition point.hpp:471
bool isConnected() const
Tests whether the set is connected as a point set.
Definition separates.hpp:5799
constexpr bool empty() const
Tests whether the set has no components at all.
Definition polygonset.hpp:485
bool intersects(const OtherShape &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:2234
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the set.
Definition bounding.hpp:469
bool separates(const OtherShape &other) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition separates.hpp:5849
constexpr bool hasHoles() const
Tests whether the region has at least one hole.
Definition polygonwithholes.hpp:188
bool separates(const OtherPoint &other) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition separates.hpp:5467
constexpr bool intersects(const OtherChain &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:1596
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the polygon.
Definition bounding.hpp:449
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1296
constexpr auto edgesView() const
Returns a lazy view over the edges, materializing each Segment on the fly instead of allocating a vec...
Definition polygon.hpp:782
constexpr std::optional< BoundaryType< false > > getIfSegment() const
Returns the segment the polygon collapses to, if it does.
Definition polygon.hpp:369
constexpr bool isConvex() const
Tests whether the polygon is convex.
Definition polygon.hpp:423
auto triangulation() const
Builds the constrained Delaunay triangulation of this polygon.
Definition triangulation.hpp:6860
constexpr Polygon()=default
Creates a polygon with no vertex.
constexpr auto intersection(const Shape< OtherPoint > &other) const
Returns the intersection of the two shapes (A ∩ B), re-dispatching through the wrapper's own intersec...
Definition polygon.hpp:2510
constexpr std::size_t size() const
Returns the number of vertices in the polygon.
Definition polygon.hpp:259
PointType_ PointType
Definition polygon.hpp:60
constexpr std::vector< PointType > vertices() const
Returns the vertices of the polygon (translation applied).
Definition polygon.hpp:587
constexpr PointType get(std::ptrdiff_t index) const
Cyclic access: same as operator[] but index is taken modulo size(); negative indices wrap from the en...
Definition polygon.hpp:169
constexpr bool isDegenerate() const
Checks if the polygon is degenerate (has zero area).
Definition polygon.hpp:319
constexpr bool separates(const OtherPoint &other) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition separates.hpp:1864
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the polyline.
Definition bounding.hpp:515
constexpr bool separates(const OtherPoint &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition polyline.hpp:1176
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:1620
constexpr bool empty() const
Checks whether the polyline has no vertex.
Definition polyline.hpp:395
constexpr std::size_t size() const
Returns the number of vertices in the polyline.
Definition polyline.hpp:388
constexpr bool parallel(const OtherLine &other) const
Returns whether the given line is parallel to the ray.
Definition predicates.hpp:808
constexpr bool isDegenerate() const
Returns whether the defining points coincide.
Definition predicates.hpp:727
constexpr const PointType & target() const
Returns the second stored point defining the direction.
Definition ray.hpp:193
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:426
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:409
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:625
constexpr const PointType & source() const
Returns the source point of the ray.
Definition ray.hpp:181
constexpr bool separates(const EmptyShape< EmptyPoint > &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition ray.hpp:594
constexpr bool isDegenerate() const
Returns whether the rectangle has empty interior.
Definition predicates.hpp:869
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:502
constexpr const PointType & min() const
Returns the minimum corner (min x, min y).
Definition rectangle.hpp:347
constexpr bool empty() const
Returns whether the rectangle is the empty set of points.
Definition rectangle.hpp:290
constexpr std::array< Segment< PointType >, 4 > edges() const
Returns the four edges as unordered segments.
Definition bounding.hpp:199
constexpr std::array< PointType, 4 > vertices() const
Returns the four vertices in counterclockwise order.
Definition bounding.hpp:188
constexpr Convex< PointType > asConvex() const
Returns the rectangle as a convex polygon.
Definition rectangle.hpp:690
constexpr const PointType & max() const
Returns the maximum corner (max x, max y).
Definition rectangle.hpp:359
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:728
constexpr std::optional< BoundaryType< false > > getIfSegment() const
Returns the segment the rectangle collapses to, if it does.
Definition predicates.hpp:895
constexpr Polygon< PointType > asPolygon() const
Returns the rectangle as a simple polygon.
Definition rectangle.hpp:737
constexpr bool separates(const EmptyShape< EmptyPoint > &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition rectangle.hpp:867
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:119
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:48
constexpr bool isDegenerate() const
Returns whether both endpoints coincide.
Definition predicates.hpp:54
constexpr Rectangle< PointType > bbox() const
Returns the bounding box of the segment.
Definition bounding.hpp:72
constexpr const PointType & max() const
Returns the largest stored endpoint.
Definition segment.hpp:199
constexpr bool separates(const EmptyShape< EmptyPoint > &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition segment.hpp:741
constexpr const PointType & min() const
Returns the smallest stored endpoint.
Definition segment.hpp:190
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:39
constexpr bool parallel(const OtherSegment &other) const
Returns whether another segment is parallel to this one.
Definition predicates.hpp:182
Runtime variant wrapper over the supported primitive shapes.
Definition shape.hpp:160
constexpr const Variant & variant() const
Returns the underlying variant.
Definition shape.hpp:264
Closed triangle stored by three vertices.
Definition triangle.hpp:53
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:223
constexpr std::array< PointType, 3 > vertices() const
Returns the vertices in canonical order.
Definition bounding.hpp:235
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:134
constexpr std::optional< BoundaryType< false > > getIfSegment() const
Returns the segment the triangle collapses to, if it does.
Definition predicates.hpp:255
constexpr Polygon< PointType > asPolygon() const
Returns the triangle as a simple polygon.
Definition triangle.hpp:533
constexpr Convex< PointType > asConvex() const
Returns the triangle as a convex polygon.
Definition triangle.hpp:490
constexpr bool isDegenerate() const
Tests whether the three vertices are collinear.
Definition predicates.hpp:223
constexpr bool separates(const EmptyShape< EmptyPoint > &) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition triangle.hpp:811
constexpr std::array< Segment< PointType >, 3 > edges() const
Returns the three unoriented boundary edges.
Definition bounding.hpp:240