Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
interiorsintersect.hpp
Go to the documentation of this file.
1#pragma once
2
4
9
10#include <limits>
12
13
14namespace pgl {
15
21
22template <class Number, class Label>
23template<PointConcept OtherPoint>
24constexpr bool Point<Number, Label>::interiorsIntersect(const OtherPoint& other) const {
25 // A point's interior is the point itself, so interiors intersect exactly
26 // when its interior contains the other shape.
27 return interiorContains(other);
28}
29
36
37template <class PointType, class LabelType>
38template<PointConcept OtherPoint>
39constexpr bool Segment<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
40 // A point's interior is the point itself, so this matches interiorContains.
41 return interiorContains(other);
42}
43
44template <class PointType, class LabelType>
45template<SegmentConcept OtherSegment>
46constexpr bool Segment<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
47 using Coordinate = detail::sign_coordinate_t<NumberType, typename OtherSegment::NumberType>;
48
49 const auto& a = min();
50 const auto& b = max();
51 const auto& c = other.min();
52 const auto& d = other.max();
53
54 // Four orientation signs over four endpoints, each endpoint in three of
55 // them; see Segment::intersects for what the filtered wrappers buy.
56 const auto fa = detail::filtered<Coordinate>(a);
57 const auto fb = detail::filtered<Coordinate>(b);
58 const auto fc = detail::filtered<Coordinate>(c);
59 const auto fd = detail::filtered<Coordinate>(d);
60 const auto s1 = detail::orientationSignOf(fa, fb, fc);
61 const auto s2 = detail::orientationSignOf(fa, fb, fd);
62 const auto s3 = detail::orientationSignOf(fc, fd, fa);
63 const auto s4 = detail::orientationSignOf(fc, fd, fb);
64
65 // Four proved signs are all nonzero, so neither segment is degenerate and
66 // the collinear cases below cannot arise.
67 if (detail::allDecided(s1, s2, s3, s4)) {
68 return s1.value() != s2.value() && s3.value() != s4.value();
69 }
70
72 const int cross = boundingBoxesCross(other);
73 if (cross == 0) {
74 return false;
75 }
76 if (cross == 2) {
77 return true;
78 }
79 }
80 else if (!boundingBoxesOverlap(other)) {
81 return false;
82 }
83 if (a == b || c == d) {
84 return false;
85 }
86 const auto d1 = s1.value();
87 const auto d2 = s2.value();
88 const auto d3 = s3.value();
89 const auto d4 = s4.value();
90 const bool no_endpoint_is_collinear = d1 != 0 && d2 != 0 && d3 != 0 && d4 != 0;
91 const bool this_segment_straddles_other = (d1 > 0) != (d2 > 0);
92 const bool other_segment_straddles_this = (d3 > 0) != (d4 > 0);
93 const bool proper_cross =
94 no_endpoint_is_collinear &&
95 this_segment_straddles_other &&
96 other_segment_straddles_this;
97 if (proper_cross) {
98 return true;
99 }
100 if (d1 != 0 || d2 != 0) {
101 return false;
102 }
103 // Collinear: the relative interiors meet iff the two overlap along a
104 // sub-segment of positive length, which happens iff an endpoint of one lies
105 // strictly inside the other — or the two coincide, the single overlapping
106 // configuration in which neither holds an endpoint of the other strictly
107 // inside (any other positive-length overlap pushes one endpoint in).
108 return interiorContains(c) ||
109 interiorContains(d) ||
110 other.interiorContains(a) ||
111 other.interiorContains(b) ||
112 (a == c && b == d);
113}
114
115template <class PointType, class LabelType>
117 return std::visit(
118 [this](const auto& value) {
119 return this->interiorsIntersect(value);
120 },
121 other.variant());
122}
123
129
130template <class PointType, class LabelType>
131template<PointConcept OtherPoint>
132constexpr bool Triangle<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
133 // A point's interior is the point itself, so this matches interiorContains.
134 return interiorContains(other);
135}
136
137template <class PointType, class LabelType>
138template<LineConcept OtherLine>
139constexpr bool Triangle<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
140 if (isDegenerate()) {
141 return false;
142 }
143 if (other.isDegenerate()) {
144 return interiorContains(other.min());
145 }
146 bool has_positive = false;
147 bool has_negative = false;
148 const auto triangle_vertices = vertices();
149 for (const auto& vertex : triangle_vertices) {
150 const auto side = orientationSign(other.min(), other.max(), vertex);
151 has_positive = has_positive || side == std::partial_ordering::greater;
152 has_negative = has_negative || side == std::partial_ordering::less;
153 if (has_positive && has_negative) {
154 return true;
155 }
156 }
157 return false;
158}
159
160template <class PointType, class LabelType>
161template<OrientedLineConcept OtherOrientedLine>
162constexpr bool Triangle<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
163 if (isDegenerate()) {
164 return false;
165 }
166 if (other.isDegenerate()) {
167 return interiorContains(other.source());
168 }
169 bool has_positive = false;
170 bool has_negative = false;
171 const auto triangle_vertices = vertices();
172 for (const auto& vertex : triangle_vertices) {
173 const auto side = orientationSign(other.source(), other.target(), vertex);
174 has_positive = has_positive || side == std::partial_ordering::greater;
175 has_negative = has_negative || side == std::partial_ordering::less;
176 if (has_positive && has_negative) {
177 return true;
178 }
179 }
180 return false;
181}
182
183template <class PointType, class LabelType>
184template<SegmentConcept OtherSegment>
185constexpr bool Triangle<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
186 if (isDegenerate() || other.isDegenerate()) {
187 return false;
188 }
189 return interiorContains(other.min()) ||
190 interiorContains(other.max()) ||
191 other.separates(*this);
192}
193
194template <class PointType, class LabelType>
195template<OrientedSegmentConcept OtherOrientedSegment>
196constexpr bool Triangle<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
198}
199
200template <class PointType, class LabelType>
201template<RayConcept OtherRay>
202constexpr bool Triangle<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
203 if (isDegenerate() || other.isDegenerate()) {
204 return false;
205 }
206 return interiorContains(other.source()) || other.separates(*this);
207}
208
209template <class PointType, class LabelType>
210template<HalfplaneConcept OtherHalfplane>
211constexpr bool Triangle<PointType, LabelType>::interiorsIntersect(const OtherHalfplane& other) const {
212 if (isDegenerate() || other.isDegenerate()) {
213 return false;
214 }
215 return other.interiorContains(a()) || other.interiorContains(b()) || other.interiorContains(c());
216}
217
218template <class PointType, class LabelType>
219template<RectangleConcept OtherRectangle>
220constexpr bool Triangle<PointType, LabelType>::interiorsIntersect(const OtherRectangle& other) const {
221 if (other.empty()) {
222 // The empty set meets nothing and disconnects nothing.
223 return false;
224 }
225 if (isDegenerate() || other.isDegenerate()) {
226 return false;
227 }
228 if (other.interiorContains(a()) || other.interiorContains(b()) || other.interiorContains(c())) {
229 return true;
230 }
231 const auto rectangle_vertices = other.vertices();
232 for (const auto& vertex : rectangle_vertices) {
234 return true;
235 }
236 }
237 const auto rectangle_edges = other.edges();
238 for (const auto& edge : rectangle_edges) {
239 if (edge.separates(*this)) {
240 return true;
241 }
242 }
243 const auto triangle_edges = edges();
244 for (const auto& edge : triangle_edges) {
245 if (edge.separates(other)) {
246 return true;
247 }
248 }
249 return false;
250}
251
252template <class PointType, class LabelType>
253template<TriangleConcept OtherTriangle>
254constexpr bool Triangle<PointType, LabelType>::interiorsIntersect(const OtherTriangle& other) const {
255 if (isDegenerate() || other.isDegenerate()) {
256 return false;
257 }
258
259 if (!bbox().intersects(other.bbox())) {
260 return false;
261 }
262
263 for (const auto& thisEdge : edges()) {
264 if (thisEdge.interiorsIntersect(other)) {
265 return true;
266 }
267 }
268
269 for (const auto& otherEdge : other.edges()) {
270 if (otherEdge.interiorsIntersect(*this)) {
271 return true;
272 }
273 }
274
275 return other == *this;
276}
277
278template <class PointType, class LabelType>
280 return std::visit(
281 [this](const auto& value) {
282 return this->interiorsIntersect(value);
283 },
284 other.variant());
285}
286
292
293template <class PointType, class LabelType>
294template<PointConcept OtherPoint>
295constexpr bool OrientedSegment<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
296 return this->asSegment().interiorsIntersect(other);
297}
298
299template <class PointType, class LabelType>
300template<SegmentConcept OtherSegment>
301constexpr bool OrientedSegment<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
302 return this->asSegment().interiorsIntersect(other);
303}
304
305template <class PointType, class LabelType>
306template<OrientedSegmentConcept OtherOrientedSegment>
307constexpr bool OrientedSegment<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
308 return this->asSegment().interiorsIntersect(other.asSegment());
309}
310
311template <class PointType, class LabelType>
313 return std::visit(
314 [this](const auto& value) {
315 return this->interiorsIntersect(value);
316 },
317 other.variant());
318}
319
325
326template <class PointType, class LabelType>
327template<PointConcept OtherPoint>
328constexpr bool Line<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
329 // A point's interior is the point itself, so this matches interiorContains.
330 return interiorContains(other);
331}
332
333template <class PointType, class LabelType>
334template<LineConcept OtherLine>
335constexpr bool Line<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
336 return intersects(other);
337}
338
339template <class PointType, class LabelType>
340template<SegmentConcept OtherSegment>
341constexpr bool Line<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
342 if (isDegenerate() || other.isDegenerate()) {
343 return false;
344 }
345 const auto first_side = orientationSign(min(), max(), other.min());
346 const auto second_side = orientationSign(min(), max(), other.max());
347 if (first_side == std::partial_ordering::equivalent &&
348 second_side == std::partial_ordering::equivalent) {
349 return true;
350 }
351 if (first_side == std::partial_ordering::equivalent ||
352 second_side == std::partial_ordering::equivalent) {
353 return false;
354 }
355 return first_side != second_side;
356}
357
358template <class PointType, class LabelType>
359template<OrientedSegmentConcept OtherOrientedSegment>
360constexpr bool Line<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
362}
363
364template <class PointType, class LabelType>
366 return std::visit(
367 [this](const auto& value) {
368 return this->interiorsIntersect(value);
369 },
370 other.variant());
371}
372
378
379template <class PointType, class LabelType>
380template<PointConcept OtherPoint>
381constexpr bool OrientedLine<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
382 return this->asLine().interiorsIntersect(other);
383}
384
385template <class PointType, class LabelType>
386template<LineConcept OtherLine>
387constexpr bool OrientedLine<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
388 return intersects(other);
389}
390
391template <class PointType, class LabelType>
392template<OrientedLineConcept OtherOrientedLine>
393constexpr bool OrientedLine<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
394 return intersects(other);
395}
396
397template <class PointType, class LabelType>
398template<SegmentConcept OtherSegment>
399constexpr bool OrientedLine<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
400 return this->asLine().interiorsIntersect(other);
401}
402
403template <class PointType, class LabelType>
404template<OrientedSegmentConcept OtherOrientedSegment>
405constexpr bool OrientedLine<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
406 return this->asLine().interiorsIntersect(other);
407}
408
409template <class PointType, class LabelType>
411 return std::visit(
412 [this](const auto& value) {
413 return this->interiorsIntersect(value);
414 },
415 other.variant());
416}
417
423
424template <class PointType, class LabelType>
425template<PointConcept OtherPoint>
426constexpr bool Ray<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
427 // A point's interior is the point itself, so this matches interiorContains.
428 return interiorContains(other);
429}
430
431template <class PointType, class LabelType>
432template<LineConcept OtherLine>
433constexpr bool Ray<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
434 if (other.isDegenerate() || isDegenerate()) {
435 return false;
436 }
437
438 const auto source_side = orientationSign(other.min(), other.max(), source());
439 if (source_side == 0) {
440 return other.contains(target()); // source lies on the line
441 }
442 // As the ray runs to infinity it tends to the side given by its direction.
443 // It meets the line exactly when that side is opposite the source's (a
444 // forward crossing); parallel (equivalent) or same-side rays never reach it.
445 const auto direction_side =
446 orientationSign(other.min(), other.max(), other.min() + (target() - source()));
447 return direction_side != 0 && direction_side != source_side;
448}
449
450template <class PointType, class LabelType>
451template<OrientedLineConcept OtherOrientedLine>
452constexpr bool Ray<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
453 return interiorsIntersect(other.asLine());
454}
455
456template <class PointType, class LabelType>
457template<SegmentConcept OtherSegment>
458constexpr bool Ray<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
459 if (isDegenerate() || other.isDegenerate()) {
460 return false;
461 }
462
463 const auto ray_min_side = orientationSign(source(), target(), other.min());
464 const auto ray_max_side = orientationSign(source(), target(), other.max());
465
466 if (ray_min_side == std::partial_ordering::equivalent &&
467 ray_max_side == std::partial_ordering::equivalent) {
468 return interiorContains(other.min()) ||
469 interiorContains(other.max()) ||
470 other.interiorContains(source());
471 }
472
473 if (ray_min_side == std::partial_ordering::equivalent ||
474 ray_max_side == std::partial_ordering::equivalent ||
475 ray_min_side == ray_max_side) {
476 return false;
477 }
478
479 // The segment straddles the ray's line, so the lines cross at a point
480 // interior to the segment. The interiors meet iff that crossing is strictly
481 // ahead of the ray's source: the source is off the segment's line and the
482 // ray's direction tends to the opposite side of it.
483 const auto source_side = orientationSign(other.min(), other.max(), source());
484 if (source_side == std::partial_ordering::equivalent) {
485 return false;
486 }
487 const auto direction_side =
488 orientationSign(other.min(), other.max(), other.min() + (target() - source()));
489 return direction_side != std::partial_ordering::equivalent &&
490 direction_side != source_side;
491}
492
493template <class PointType, class LabelType>
494template<OrientedSegmentConcept OtherOrientedSegment>
495constexpr bool Ray<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
497}
498
499template <class PointType, class LabelType>
500template<RayConcept OtherRay>
501constexpr bool Ray<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
502 if (isDegenerate() || other.isDegenerate()) {
503 return false;
504 }
505
506 const auto other_source_side = orientationSign(source(), target(), other.source());
507 const auto other_target_side = orientationSign(source(), target(), other.target());
508
509 if (other_source_side == std::partial_ordering::equivalent &&
510 other_target_side == std::partial_ordering::equivalent) {
511 // Collinear, as in the segment case: one source lies strictly inside the
512 // other ray, or the two rays coincide — same source, same direction —
513 // which is the overlapping configuration neither test catches.
514 return interiorContains(other.source()) || other.interiorContains(source()) ||
515 (source() == other.source() && contains(other.target()));
516 }
517
518 if (other_source_side == other_target_side &&
519 other_source_side != std::partial_ordering::equivalent) {
520 return false;
521 }
522
523 const auto this_source_side = orientationSign(other.source(), other.target(), source());
524 const auto this_target_side = orientationSign(other.source(), other.target(), target());
525
526 if (this_source_side == this_target_side &&
527 this_source_side != std::partial_ordering::equivalent) {
528 return false;
529 }
530
531 return !boundaryContains(other.source()) && !other.boundaryContains(source());
532}
533
534template <class PointType, class LabelType>
536 return std::visit(
537 [this](const auto& value) {
538 return this->interiorsIntersect(value);
539 },
540 other.variant());
541}
542
548
549template <class PointType, class LabelType>
550template<PointConcept OtherPoint>
551constexpr bool Rectangle<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
552 // A point's interior is the point itself, so this matches interiorContains.
553 // The empty set needs no case of its own: it has no interior to meet, and
554 // interiorContains is already false for it.
555 return interiorContains(other);
556}
557
558template <class PointType, class LabelType>
559template<RectangleConcept OtherRectangle>
560constexpr bool Rectangle<PointType, LabelType>::interiorsIntersect(const OtherRectangle& other) const {
561 // An empty rectangle has inverted corners, so it is already degenerate and
562 // the test below answers false for it. The empty set has no interior to
563 // meet, which is the same answer, so it needs no case of its own.
564 if (isDegenerate() || other.isDegenerate()) {
565 return false;
566 }
567 return intervalsOverlapStrict(min().x(), max().x(), other.min().x(), other.max().x()) &&
568 intervalsOverlapStrict(min().y(), max().y(), other.min().y(), other.max().y());
569}
570
571template <class PointType, class LabelType>
572template<LineConcept OtherLine>
573constexpr bool Rectangle<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
574 if (empty()) {
575 // The empty set meets nothing and disconnects nothing.
576 return false;
577 }
578 if (isDegenerate()) {
579 return false;
580 }
581 if (other.isDegenerate()) {
582 return interiorContains(other.min());
583 }
584 return detail::lineIntersectsRectangleInterior(*this, other.min(), other.max());
585}
586
587template <class PointType, class LabelType>
588template<OrientedLineConcept OtherOrientedLine>
589constexpr bool Rectangle<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
590 if (empty()) {
591 // The empty set meets nothing and disconnects nothing.
592 return false;
593 }
594 if (isDegenerate()) {
595 return false;
596 }
597 if (other.isDegenerate()) {
598 return interiorContains(other.source());
599 }
600 return detail::lineIntersectsRectangleInterior(*this, other.source(), other.target());
601}
602
603template <class PointType, class LabelType>
604template<SegmentConcept OtherSegment>
605constexpr bool Rectangle<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
606 if (empty()) {
607 // The empty set meets nothing and disconnects nothing.
608 return false;
609 }
610 if (isDegenerate() || other.isDegenerate()) {
611 return false;
612 }
613 return detail::segmentIntersectsRectangleInteriorExact(*this, other.min(), other.max());
614}
615
616template <class PointType, class LabelType>
617template<OrientedSegmentConcept OtherOrientedSegment>
618constexpr bool Rectangle<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
619 if (empty()) {
620 // The empty set meets nothing and disconnects nothing.
621 return false;
622 }
623 if (isDegenerate() || other.isDegenerate()) {
624 return false;
625 }
626 return detail::segmentIntersectsRectangleInteriorExact(*this, other.source(), other.target());
627}
628
629template <class PointType, class LabelType>
630template<RayConcept OtherRay>
631constexpr bool Rectangle<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
632 if (empty()) {
633 // The empty set meets nothing and disconnects nothing.
634 return false;
635 }
636 if (isDegenerate() || other.isDegenerate()) {
637 return false;
638 }
639 if (interiorContains(other.source())) {
640 return true;
641 }
642 return Segment<PointType>((*this)[0], (*this)[2]).interiorsIntersect(other) ||
643 Segment<PointType>((*this)[1], (*this)[3]).interiorsIntersect(other);
644}
645
646template <class PointType, class LabelType>
647template<HalfplaneConcept OtherHalfplane>
648constexpr bool Rectangle<PointType, LabelType>::interiorsIntersect(const OtherHalfplane& other) const {
649 if (empty()) {
650 // The empty set meets nothing and disconnects nothing.
651 return false;
652 }
653 if (isDegenerate() || other.isDegenerate()) {
654 return false;
655 }
656 const auto rectangle_vertices = vertices();
657 for (const auto& vertex : rectangle_vertices) {
658 if (other.interiorContains(vertex)) {
659 return true;
660 }
661 }
662 return false;
663}
664
665template <class PointType, class LabelType>
667 return std::visit(
668 [this](const auto& value) {
669 return this->interiorsIntersect(value);
670 },
671 other.variant());
672}
673
679
680template <class PointType, class LabelType>
681template<PointConcept OtherPoint>
682constexpr bool Halfplane<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
683 // A point's interior is the point itself, so this matches interiorContains.
684 return interiorContains(other);
685}
686
687template <class PointType, class LabelType>
688template<LineConcept OtherLine>
689constexpr bool Halfplane<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
690 if (isDegenerate()) {
691 return false;
692 }
693 if (other.isDegenerate()) {
694 return interiorContains(other.min());
695 }
696 const auto direction_side =
697 orientationDeterminant(source(), target(), other.max()) -
698 orientationDeterminant(source(), target(), other.min());
699 return direction_side != decltype(direction_side){} || interiorContains(other.min());
700}
701
702template <class PointType, class LabelType>
703template<OrientedLineConcept OtherOrientedLine>
704constexpr bool Halfplane<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
705 if (isDegenerate()) {
706 return false;
707 }
708 if (other.isDegenerate()) {
709 return interiorContains(other.source());
710 }
711 const auto direction_side =
712 orientationDeterminant(source(), target(), other.target()) -
713 orientationDeterminant(source(), target(), other.source());
714 return direction_side != decltype(direction_side){} || interiorContains(other.source());
715}
716
717template <class PointType, class LabelType>
718template<SegmentConcept OtherSegment>
719constexpr bool Halfplane<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
720 if (isDegenerate() || other.isPoint()) {
721 // A segment collapsed to a point is all boundary and has no interior.
722 return false;
723 }
724 const auto first_side = orientationDeterminant(source(), target(), other.min());
725 const auto second_side = orientationDeterminant(source(), target(), other.max());
726 const auto zero = decltype(first_side){};
727 return zero < first_side || zero < second_side;
728}
729
730template <class PointType, class LabelType>
731template<OrientedSegmentConcept OtherOrientedSegment>
732constexpr bool Halfplane<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
733 if (isDegenerate() || other.isPoint()) {
734 // A segment collapsed to a point is all boundary and has no interior.
735 return false;
736 }
737 const auto first_side = orientationDeterminant(source(), target(), other.source());
738 const auto second_side = orientationDeterminant(source(), target(), other.target());
739 const auto zero = decltype(first_side){};
740 return zero < first_side || zero < second_side;
741}
742
743template <class PointType, class LabelType>
744template<RayConcept OtherRay>
745constexpr bool Halfplane<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
746 if (isDegenerate()) {
747 return false;
748 }
749 if (other.isDegenerate()) {
750 return interiorContains(other.source());
751 }
752 const auto source_side = orientationDeterminant(source(), target(), other.source());
753 const auto direction_side =
754 orientationDeterminant(source(), target(), other.target()) -
755 orientationDeterminant(source(), target(), other.source());
756 const auto zero = decltype(source_side){};
757 return zero < source_side || zero < direction_side;
758}
759
760template <class PointType, class LabelType>
761template<HalfplaneConcept OtherHalfplane>
762constexpr bool Halfplane<PointType, LabelType>::interiorsIntersect(const OtherHalfplane& other) const {
763 if (isDegenerate() || other.isDegenerate()) {
764 return false;
765 }
766
767 const auto this_boundary = this->asLine();
768 const auto other_boundary = other.asLine();
769 if (!this_boundary.parallel(other_boundary)) {
770 return true;
771 }
772
773 const auto side_of_other_source = orientationDeterminant(source(), target(), other.source());
774 const auto zero = decltype(side_of_other_source){};
775 if (zero < side_of_other_source) {
776 return true;
777 }
778
779 const auto side_of_this_source = orientationDeterminant(other.source(), other.target(), source());
780 if (zero < side_of_this_source) {
781 return true;
782 }
783
784 const auto this_oriented_boundary = static_cast<OrientedLine<PointType>>(*this);
785 const auto other_oriented_boundary = static_cast<OrientedLine<typename OtherHalfplane::PointType>>(other);
786 return this_oriented_boundary == other_oriented_boundary;
787}
788
789template <class PointType, class LabelType>
791 return std::visit(
792 [this](const auto& value) {
793 return this->interiorsIntersect(value);
794 },
795 other.variant());
796}
797
798
799// ---------------------------------------------------------------------------
800// Convex
801
802template <class PointType, class LabelType>
803template<PointConcept OtherPoint>
804constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
805 // A point's interior is the point itself, so this matches interiorContains.
806 return interiorContains(other);
807}
808
809template <class PointType, class LabelType>
810template<LineConcept OtherLine>
811constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
812 if (isDegenerate()) {
813 return false;
814 }
815 if (other.isDegenerate()) {
816 return interiorContains(other.min());
817 }
818 // The line cuts the interior iff the convex polygon has vertices strictly on
819 // both sides. Find the vertex with maximum signed distance and the
820 // one with minimum (= max of the negated key) in O(log n) each.
821 const auto max_it = detail::cyclicMax(points_.begin(), points_.end(),
822 [&other, this](const PointType& a) {
823 return orientationDeterminant(other.min(), other.max(), a + translation_);
824 });
825 const auto min_it = detail::cyclicMax(points_.begin(), points_.end(),
826 [&other, this](const PointType& a) {
827 return -orientationDeterminant(other.min(), other.max(), a + translation_);
828 });
829 const auto max_val = orientationDeterminant(other.min(), other.max(), *max_it + translation_);
830 const auto min_val = orientationDeterminant(other.min(), other.max(), *min_it + translation_);
831 return max_val > 0 && min_val < 0;
832}
833
834template <class PointType, class LabelType>
835template<OrientedLineConcept OtherOrientedLine>
836constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
838}
839
840template <class PointType, class LabelType>
841template<SegmentConcept OtherSegment>
842constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
843 if (isDegenerate() || other.isDegenerate() || !bbox().intersects(other.bbox())) {
844 return false;
845 }
846 if (interiorContains(other.min()) || interiorContains(other.max())) {
847 return true;
848 }
849 auto translatedOther = other - translation_;
850 auto it1 = detail::cyclicMaxOrPositive(points_.begin(), points_.end(), [&translatedOther](const PointType& a) {
851 return orientationDeterminant(translatedOther[0], translatedOther[1], a);
852 });
853 auto it2 = detail::cyclicMaxOrPositive(points_.begin(), points_.end(), [&translatedOther](const PointType& a) {
854 return orientationDeterminant(translatedOther[1], translatedOther[0], a);
855 });
856 auto i3 = it1 - points_.begin() - 1;
857 i3 = i3 < 0 ? points_.size()-1 : i3;
858 if (points_[i3] == *it2) {
859 i3 = (i3+2) % points_.size();
860 }
861
862 Triangle<PointType> tri(*it1, *it2, points_[i3]);
863 return tri.interiorsIntersect(translatedOther);
864}
865
866template <class PointType, class LabelType>
867template<OrientedSegmentConcept OtherOrientedSegment>
868constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
870}
871
872template <class PointType, class LabelType>
873template<RayConcept OtherRay>
874constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
875 if (isDegenerate() || other.isDegenerate()) {
876 return false;
877 }
878 if (interiorContains(other.source())) {
879 return true;
880 }
881
882 auto translatedOther = other - translation_;
883 auto it1 = detail::cyclicMaxOrPositive(points_.begin(), points_.end(), [&translatedOther](const PointType& a) {
884 return orientationDeterminant(translatedOther[0], translatedOther[1], a);
885 });
886 auto it2 = detail::cyclicMaxOrPositive(points_.begin(), points_.end(), [&translatedOther](const PointType& a) {
887 return orientationDeterminant(translatedOther[1], translatedOther[0], a);
888 });
889 auto i3 = it1 - points_.begin() - 1;
890 i3 = i3 < 0 ? points_.size()-1 : i3;
891 if (points_[i3] == *it2) {
892 i3 = (i3+2) % points_.size();
893 }
894
895 Triangle<PointType> tri(*it1, *it2, points_[i3]);
896 return tri.interiorsIntersect(translatedOther);
897}
898
899template <class PointType, class LabelType>
900template<HalfplaneConcept OtherHalfplane>
901constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherHalfplane& other) const {
902 if (isDegenerate() || other.isDegenerate()) {
903 return false;
904 }
905 // The vertex maximizing the inward orientation is the deepest into
906 // the half-plane. If it is not strictly inside, no vertex is.
907 const auto it = detail::cyclicMaxOrPositive(points_.begin(), points_.end(),
908 [&other, this](const PointType& a) {
909 return orientationDeterminant(other.source(), other.target(), a + translation_);
910 });
911 return other.interiorContains(*it + translation_);
912}
913
914template <class PointType, class LabelType>
915template<RectangleConcept OtherRectangle>
916constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherRectangle& other) const {
917 if (other.empty()) {
918 // The empty set meets nothing and disconnects nothing.
919 return false;
920 }
921 return interiorsIntersect(other.asConvex());
922}
923
924template <class PointType, class LabelType>
925template<TriangleConcept OtherTriangle>
926constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherTriangle& other) const {
927 return interiorsIntersect(other.asConvex());
928}
929
930template <class PointType, class LabelType>
931template<ConvexConcept OtherConvex>
932constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherConvex& other) const {
933 if (isDegenerate() || other.isDegenerate() || !bbox().interiorsIntersect(other.bbox())) {
934 return false;
935 }
936 if (bbox().crosses(other.bbox())) {
937 return true;
938 }
939 if (size() > other.size()) {
940 return other.interiorsIntersect(*this);
941 }
942
943 // If this contains 3 vertices of other, the interiors intersect,
944 // even if the vertices are on the boundary.
945 if (contains(other[0]) && contains(other[1]) && contains(other[2])) {
946 return true;
947 }
948
949 // Here we know that other is not inside this, so if the interiors intersect,
950 // then there is an edge of this that intersects the interior of other.
951
952
953 for (const auto& edge : edgesView()) {
954 if (other.interiorsIntersect(edge)) {
955 return true;
956 }
957 }
958
959 return false;
960}
961
962template <class PointType, class LabelType>
963template<DiskConcept OtherDisk>
964constexpr bool Convex<PointType, LabelType>::interiorsIntersect(const OtherDisk& other) const {
965 if (isDegenerate() || other.isDegenerate()) {
966 // Either operand collapsed to a lower dimension has empty interior.
967 return false;
968 }
969 // Interiors meet when a convex edge passes through the open disk, or the
970 // disk lies inside the convex (a point strictly inside the disk is in the
971 // convex's interior). The latter uses a disk-interior point as the witness:
972 // a disk tangent to an edge from inside still overlaps the interior.
973 for (const auto& edge : edgesView()) {
974 if (edge.interiorsIntersect(other)) {
975 return true;
976 }
977 }
978 return other.pointInsideInteriorContainedIn(*this);
979}
980
981template <class PointType, class LabelType>
982template <PointConcept OtherPoint>
984 return std::visit(
985 [this](const auto& value) {
986 return this->interiorsIntersect(value);
987 },
988 other.variant());
989}
990
991
992// ---------------------------------------------------------------------------
993// Polygon
994
995template <class PointType, class LabelType>
996template<PointConcept OtherPoint>
997constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
998 // A point's interior is the point itself, so this matches interiorContains.
999 return interiorContains(other);
1000}
1001
1002template <class PointType, class LabelType>
1003template<LineConcept OtherLine>
1004constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
1005 if (isDegenerate() || other.isDegenerate()) {
1006 return false;
1007 }
1008 // The line meets the open interior iff vertices lie strictly on both sides:
1009 // the boundary is connected, so a straddle forces a transversal edge
1010 // crossing, which puts an interior point on the line. A polygon entirely on
1011 // one closed side only touches the line on its boundary.
1012 bool positive = false, negative = false;
1013 for (const auto& vertex : vertices()) {
1014 const auto side = orientationSign(other.min(), other.max(), vertex);
1015 positive = positive || side > 0;
1016 negative = negative || side < 0;
1017 if (positive && negative) {
1018 return true;
1019 }
1020 }
1021 return false;
1022}
1023
1024template <class PointType, class LabelType>
1025template<OrientedLineConcept OtherOrientedLine>
1026constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
1028}
1029
1030template <class PointType, class LabelType>
1031template<SegmentConcept OtherSegment>
1032constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
1033 if (isDegenerate() || other.isDegenerate()) {
1034 return false;
1035 }
1036 // A transversal crossing of any boundary edge places an interior point on
1037 // the open segment.
1038 for (const auto& edge : edgesView()) {
1039 if (edge.crosses(other)) {
1040 return true;
1041 }
1042 }
1043 // No transversal crossings: the segment touches the boundary only at
1044 // vertices or along collinear overlaps, so every boundary contact is a
1045 // polygon vertex or a segment endpoint. Split the segment at those contacts
1046 // and classify each piece by its midpoint. Doubling the polygon keeps the
1047 // midpoint test exact and division-free: (p+q)/2 is interior iff (p+q) is
1048 // interior to 2*polygon.
1049 using C = std::common_type_t<NumberType, typename OtherSegment::NumberType>;
1050 using V = Point<C>;
1051 std::vector<V> contacts{static_cast<V>(other.min()), static_cast<V>(other.max())};
1052 for (const auto& vertex : vertices()) {
1053 if (other.contains(vertex)) {
1054 contacts.push_back(static_cast<V>(vertex));
1055 }
1056 }
1057 // Ordered along the operand: the projections of two contacts differ by the
1058 // projection of their separation, so one dot product decides each
1059 // comparison and neither projection is ever formed.
1060 std::sort(contacts.begin(), contacts.end(), [&](const V& p, const V& q) {
1061 return dotSign(p, q, other.min(), other.max()) > 0;
1062 });
1063 const auto doubled = (*this) * NumberType(2);
1064 for (std::size_t i = 1; i < contacts.size(); ++i) {
1065 if (contacts[i - 1] == contacts[i]) {
1066 continue;
1067 }
1068 if (doubled.interiorContains(contacts[i - 1] + contacts[i])) {
1069 return true;
1070 }
1071 }
1072 return false;
1073}
1074
1075template <class PointType, class LabelType>
1076template<OrientedSegmentConcept OtherOrientedSegment>
1077constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
1079}
1080
1081template <class PointType, class LabelType>
1082template<RayConcept OtherRay>
1083constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
1084 if (isDegenerate() || other.isDegenerate()) {
1085 return false;
1086 }
1087 for (const auto& edge : edgesView()) {
1088 if (edge.crosses(other)) {
1089 return true;
1090 }
1091 }
1092 // As with the segment, the remaining boundary contacts are the ray's source
1093 // and the polygon vertices on the ray; split there and test midpoints. The
1094 // ray exits the bounded polygon for good past its last contact.
1095 using C = std::common_type_t<NumberType, typename OtherRay::NumberType>;
1096 using V = Point<C>;
1097 std::vector<V> contacts{static_cast<V>(other.source())};
1098 for (const auto& vertex : vertices()) {
1099 if (other.contains(vertex)) {
1100 contacts.push_back(static_cast<V>(vertex));
1101 }
1102 }
1103 std::sort(contacts.begin(), contacts.end(), [&](const V& p, const V& q) {
1104 return dotSign(p, q, other.source(), other.target()) > 0;
1105 });
1106 const auto doubled = (*this) * NumberType(2);
1107 for (std::size_t i = 1; i < contacts.size(); ++i) {
1108 if (contacts[i - 1] == contacts[i]) {
1109 continue;
1110 }
1111 if (doubled.interiorContains(contacts[i - 1] + contacts[i])) {
1112 return true;
1113 }
1114 }
1115 return false;
1116}
1117
1118template <class PointType, class LabelType>
1119template<HalfplaneConcept OtherHalfplane>
1120constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherHalfplane& other) const {
1121 if (isDegenerate() || other.isDegenerate()) {
1122 return false;
1123 }
1124 // A vertex strictly inside the open half-plane has interior points of the
1125 // polygon near it that are also in the half-plane; if no vertex is strictly
1126 // inside, the whole polygon lies in the closed complement.
1127 for (const auto& vertex : vertices()) {
1128 if (other.interiorContains(vertex)) {
1129 return true;
1130 }
1131 }
1132 return false;
1133}
1134
1135namespace detail {
1136
1137// Interiors of a filled simple polygon and another filled convex region (a
1138// rectangle, triangle, or convex polygon).
1139//
1140// An edge of the area whose relative interior reaches into the polygon's interior
1141// settles it: a vertex of the area strictly inside, a properly crossing pair of
1142// edges, and an area sitting snugly in a corner of the polygon (every vertex on
1143// the boundary, no edge crossing) all put one there. If no edge of the area does,
1144// then the area's whole boundary misses the polygon's interior, which — the
1145// interior being connected — leaves it wholly inside the area, coincident regions
1146// included; an interior witness point of either then decides.
1147template <class Poly, class Area>
1148constexpr bool polygonAreaInteriorsIntersect(const Poly& poly, const Area& area) {
1149 if (area.isDegenerate() || poly.isDegenerate()) {
1150 return false;
1151 }
1152 auto abbox = area.bbox();
1153 if (!poly.bbox().interiorsIntersect(abbox)) {
1154 return false;
1155 }
1156 if (poly.bbox().separates(abbox) || abbox.separates(poly.bbox())) {
1157 return true;
1158 }
1159
1160 for (const auto& edge : area.edges()) {
1161 if (edge.interiorsIntersect(poly)) {
1162 return true;
1163 }
1164 }
1165
1166 // Nested (or coincident) with the boundaries in contact: one region's own
1167 // interior witness point decides, exactly and without scanning a boundary.
1168 return area.pointInsideInteriorContainedIn(poly) || poly.pointInsideInteriorContainedIn(area);
1169}
1170
1171} // namespace detail
1172
1173template <class PointType, class LabelType>
1174template<RectangleConcept OtherRectangle>
1175constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherRectangle& other) const {
1176 if (other.empty()) {
1177 // The empty set meets nothing and disconnects nothing.
1178 return false;
1179 }
1180 return detail::polygonAreaInteriorsIntersect(*this, other);
1181}
1182
1183template <class PointType, class LabelType>
1184template<TriangleConcept OtherTriangle>
1185constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherTriangle& other) const {
1186 return detail::polygonAreaInteriorsIntersect(*this, other);
1187}
1188
1189template <class PointType, class LabelType>
1190template<ConvexConcept OtherConvex>
1191constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherConvex& other) const {
1192 return detail::polygonAreaInteriorsIntersect(*this, other);
1193}
1194
1195template <class PointType, class LabelType>
1196template<PolygonConcept OtherPolygon>
1197constexpr bool Polygon<PointType, LabelType>::boundariesIntersect(const OtherPolygon& other) const {
1198 // A polygon collapsed to a single point is all boundary; it has no edge to
1199 // feed the sweep, and its boundary has no lexicographic break for
1200 // BoundaryChains to split on, so reduce it to that point first.
1201 if (const auto vertex = getIfPoint()) {
1202 return other.boundaryContains(*vertex);
1203 }
1204 if (const auto vertex = other.getIfPoint()) {
1205 return boundaryContains(*vertex);
1206 }
1207 // One combined sweep over both edge sets, in O((n + m) log(n + m)) whatever
1208 // the boundaries look like, when the chain-pair test below would cost more
1209 // (see preferSweep).
1210 if (preferSweep(*this, other)) {
1211 return boundariesMeet(edgesView(), other.edgesView());
1212 }
1213 // Produce both boundary decompositions in lockstep, testing each new chain
1214 // against every already-produced chain of the other polygon before building
1215 // the next, and stop at the first shared point. See BoundaryChains.
1216 BoundaryChains<Polygon> mine(*this);
1217 BoundaryChains<OtherPolygon> theirs(other);
1218 while (!mine.exhausted() || !theirs.exhausted()) {
1219 if (!mine.exhausted()) {
1220 const auto& chain = mine.produceNext();
1221 for (const auto& their : theirs.produced()) {
1222 if (chain.intersects(their)) {
1223 return true;
1224 }
1225 }
1226 }
1227 if (!theirs.exhausted()) {
1228 const auto& chain = theirs.produceNext();
1229 for (const auto& my : mine.produced()) {
1230 if (chain.intersects(my)) {
1231 return true;
1232 }
1233 }
1234 }
1235 }
1236 return false;
1237}
1238
1239template <class PointType, class LabelType>
1240template<PolygonConcept OtherPolygon>
1241constexpr bool Polygon<PointType, LabelType>::boundariesStrongCross(const OtherPolygon& other) const {
1242 // A polygon collapsed to a single point has no edges to cross with, and
1243 // its boundary offers BoundaryChains no lexicographic break to split on.
1244 if (isPoint() || other.isPoint()) {
1245 return false;
1246 }
1247 if (preferSweep(*this, other)) {
1248 return boundariesCross(edgesView(), other.edgesView());
1249 }
1250 // Produce both boundary decompositions in lockstep, testing each new chain
1251 // against every already-produced chain of the other polygon before building
1252 // the next, and stop at the first crossing. See BoundaryChains.
1253 BoundaryChains<Polygon> mine(*this);
1254 BoundaryChains<OtherPolygon> theirs(other);
1255 while (!mine.exhausted() || !theirs.exhausted()) {
1256 if (!mine.exhausted()) {
1257 const auto& chain = mine.produceNext();
1258 for (const auto& their : theirs.produced()) {
1259 if (chain.edgesCross(their)) {
1260 return true;
1261 }
1262 }
1263 }
1264 if (!theirs.exhausted()) {
1265 const auto& chain = theirs.produceNext();
1266 for (const auto& my : mine.produced()) {
1267 if (chain.edgesCross(my)) {
1268 return true;
1269 }
1270 }
1271 }
1272 }
1273 return false;
1274}
1275
1276template <class PointType, class LabelType>
1277template<PolygonConcept OtherPolygon>
1278constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherPolygon& other) const {
1279 // Cheap bounding-box reject: if the closed boxes are disjoint the interiors
1280 // cannot meet, so the machinery below is skipped for distant pairs.
1281 if (size() == 0 || other.size() == 0 || !bbox().intersects(other.bbox())) {
1282 return false;
1283 }
1284 if (bbox().crosses(other.bbox())) {
1285 return true;
1286 }
1287 if (isDegenerate() || other.isDegenerate()) {
1288 return false;
1289 }
1290 if (*this == other) {
1291 return true;
1292 }
1293
1294 // When the boundaries are disjoint the polygons are either separate or one
1295 // is nested in the other, so the interiors meet iff one polygon contains the
1296 // other — which, with no boundary contact, reduces to a single interior
1297 // point-in-polygon test each way (every vertex of the inner polygon lies in
1298 // the outer interior). A verified crossing settles it the other way at
1299 // once: it puts points of each polygon on both sides of the other's
1300 // boundary right there, so the interiors overlap regardless of anything
1301 // else. Only a mere touch (boundaries meet, no crossing found) reaches the
1302 // quadratic scan below.
1303 //
1304 // Both bits come either from one combined sweep over the two edge sets, in
1305 // O((n + m) log(n + m)) whatever the boundaries look like, or from testing
1306 // their lexicographically monotone chains against each other pairwise (see
1307 // BoundaryChains), whose product-of-chain-counts cost wins on near-convex
1308 // or small input and degrades to O(n * m) on a jagged, comb-like or
1309 // star-shaped boundary. preferSweep picks.
1310
1311 bool boundaries_intersect = false;
1312
1313 if (preferSweep(*this, other)) {
1314 const auto [crossed, met] = boundaryContactBits(edgesView(), other.edgesView());
1315 if (crossed) {
1316 return true;
1317 }
1318 boundaries_intersect = met;
1319 } else {
1320 BoundaryChains<Polygon> mine(*this);
1321 BoundaryChains<OtherPolygon> theirs(other);
1322 while (!mine.exhausted() || !theirs.exhausted()) {
1323 if (!mine.exhausted()) {
1324 const auto& chain = mine.produceNext();
1325 for (const auto& their : theirs.produced()) {
1326 if (chain.intersects(their)) {
1327 boundaries_intersect = true;
1328 if (chain.edgesCross(their)) {
1329 return true;
1330 }
1331 }
1332 }
1333 }
1334 if (!theirs.exhausted()) {
1335 const auto& chain = theirs.produceNext();
1336 for (const auto& my : mine.produced()) {
1337 if (chain.intersects(my)) {
1338 boundaries_intersect = true;
1339 if (chain.edgesCross(my)) {
1340 return true;
1341 }
1342 }
1343 }
1344 }
1345 }
1346 }
1347
1348 if (!boundaries_intersect) {
1349 return interiorContains(other.get(0)) || other.interiorContains(get(0));
1350 }
1351
1352 // Boundaries touch but no crossing was found: distinguish a mere boundary
1353 // contact from a real interior overlap that went through a vertex between
1354 // monotone chains, or slipped past the sweep's degenerate-corner shortcuts
1355 // (see BoundaryContact).
1356
1357 for (const auto& vertex : other.vertices()) {
1358 if (interiorContains(vertex)) {
1359 return true;
1360 }
1361 }
1362 for (const auto& vertex : vertices()) {
1363 if (other.interiorContains(vertex)) {
1364 return true;
1365 }
1366 }
1367 for (const auto& edge : edgesView()) {
1368 if (edge.separates(other)) {
1369 return true;
1370 }
1371 }
1372 for (const auto& edge : other.edgesView()) {
1373 if (edge.separates(*this)) {
1374 return true;
1375 }
1376 }
1377 return false;
1378}
1379
1380
1381template <class PointType, class LabelType>
1382template<PointConcept OtherPoint>
1384 return std::visit(
1385 [this](const auto& value) {
1386 return this->interiorsIntersect(value);
1387 },
1388 other.variant());
1389}
1390
1391template <class Number, class Label>
1392constexpr bool Point<Number, Label>::interiorsIntersect(const Shape<Point<Number, Label>>& other) const {
1393 return std::visit(
1394 [this](const auto& value) {
1395 return this->interiorsIntersect(value);
1396 },
1397 other.variant());
1398}
1399
1400template <class PointType, class LabelType>
1401template<PointConcept OtherPoint>
1402constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
1403 // A point's interior is the point itself, so this matches interiorContains.
1404 return interiorContains(other);
1405}
1406
1407template <class PointType, class LabelType>
1408template<SegmentConcept OtherSegment>
1409constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
1410 // A degenerate disk has empty interior and a degenerate segment has empty
1411 // relative interior, so neither can contribute an interior intersection.
1412 if (isDegenerate() || other.isDegenerate()) {
1413 return false;
1414 }
1415
1416 // An endpoint strictly inside the open disk drags the adjacent open segment
1417 // inside with it, so the relative interiors already meet.
1418 if (interiorContains(other.min()) || interiorContains(other.max())) {
1419 return true;
1420 }
1421
1422 // Neither endpoint is strictly inside, so the interiors meet exactly when
1423 // the segment pierces the open disk. This is the same exact, division-free
1424 // in-circle formulation as intersects(), only with strict inequalities so a
1425 // mere boundary tangency does not count: writing power(p) = |p-center|^2-r^2
1426 // and inCircleDeterminant(a,b,c,p) = -A*power(p) with A = 2*signedArea, the
1427 // value h(t) = A*inCircleDeterminant along the segment is a concave quadratic
1428 // (leading coefficient -L*A^2 < 0) that is positive exactly where the point
1429 // is strictly inside the disk, so the open disk is pierced iff h has an
1430 // interior maximum above zero.
1431 //
1432 // A = orientation determinant of the three boundary points
1433 // J0 = inCircleDeterminant(a,b,c, min), J1 = ... (a,b,c, max)
1434 // L = |max - min|^2, M = L * A
1435 // Foot on the segment (t* in [0,1]): |(J0 - J1)*A| <= M*A (M*A >= 0).
1436 // Line cuts the circle in two distinct points: (J0 + J1 + M)^2 > 4*J0*J1.
1437 using W = detail::promoted_number_t<
1438 decltype(inCircleDeterminant(a(), b(), c(), other.min()))>;
1439
1440 const W det = static_cast<W>(orientationDeterminant(a(), b(), c()));
1441 const W j0 = static_cast<W>(inCircleDeterminant(a(), b(), c(), other.min()));
1442 const W j1 = static_cast<W>(inCircleDeterminant(a(), b(), c(), other.max()));
1443 const W squared_length = other.min().template squaredDistance<W>(other.max());
1444 const W m = squared_length * det;
1445
1446 const W projection = (j0 - j1) * det; // (J0 - J1) * A
1447 const W half_span = m * det; // M * A = L * A^2 >= 0
1448 const W discriminant_base = j0 + j1 + m; // J0 + J1 + M
1449
1450 const bool foot_on_segment = projection >= -half_span && projection <= half_span;
1451 const bool pierces_disk = discriminant_base * discriminant_base > W{4} * j0 * j1;
1452
1453 return foot_on_segment && pierces_disk;
1454}
1455
1456template <class PointType, class LabelType>
1457template<OrientedSegmentConcept OtherOrientedSegment>
1458constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
1459 return interiorsIntersect(other.asSegment());
1460}
1461
1462template <class PointType, class LabelType>
1463template<LineConcept OtherLine>
1464constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
1465 if (isDegenerate() || other.isDegenerate()) {
1466 return false;
1467 }
1468 // A line meets the open disk exactly when it is a strict secant: it cuts the
1469 // boundary circle in two distinct points (discriminant > 0). A tangent line
1470 // touches only the boundary and does not count. Same division-free in-circle
1471 // formulation as interiorsIntersect(Segment), without a parameter range.
1472 //
1473 // A = orientation determinant of the three boundary points
1474 // J0 = inCircleDeterminant(a,b,c, other[0]), J1 = ... (a,b,c, other[1])
1475 // L = |other[1] - other[0]|^2, M = L * A
1476 // Line is a strict secant: (J0 + J1 + M)^2 > 4*J0*J1.
1477 using W = detail::promoted_number_t<
1478 decltype(inCircleDeterminant(a(), b(), c(), other[0]))>;
1479
1480 const W det = static_cast<W>(orientationDeterminant(a(), b(), c()));
1481 const W j0 = static_cast<W>(inCircleDeterminant(a(), b(), c(), other[0]));
1482 const W j1 = static_cast<W>(inCircleDeterminant(a(), b(), c(), other[1]));
1483 const W squared_length = other[0].template squaredDistance<W>(other[1]);
1484 const W discriminant_base = j0 + j1 + squared_length * det; // J0 + J1 + M
1485
1486 return discriminant_base * discriminant_base > W{4} * j0 * j1;
1487}
1488
1489template <class PointType, class LabelType>
1490template<OrientedLineConcept OtherOrientedLine>
1491constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
1492 return interiorsIntersect(other.asLine());
1493}
1494
1495template <class PointType, class LabelType>
1496template<RayConcept OtherRay>
1497constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
1498 if (isDegenerate() || other.isDegenerate()) {
1499 return false;
1500 }
1501 // A source strictly inside the open disk drags the adjacent ray inside.
1502 if (interiorContains(other.source())) {
1503 return true;
1504 }
1505 // Otherwise the interiors meet exactly when the supporting line is a strict
1506 // secant (discriminant > 0) AND the pierced span lies ahead of the source
1507 // (the perpendicular foot has positive parameter). Same division-free
1508 // in-circle formulation as interiorsIntersect(Segment); the source is
1509 // parameter 0 and the target parameter 1.
1510 //
1511 // A = orientation determinant of the three boundary points
1512 // J0 = inCircleDeterminant(a,b,c, source), J1 = ... (a,b,c, target)
1513 // L = |target - source|^2, M = L * A
1514 // Strict secant: (J0 + J1 + M)^2 > 4*J0*J1.
1515 // Pierced span ahead of the source (foot t* > 0): (J0 - J1)*A < M*A.
1516 using W = detail::promoted_number_t<
1517 decltype(inCircleDeterminant(a(), b(), c(), other.source()))>;
1518
1519 const W det = static_cast<W>(orientationDeterminant(a(), b(), c()));
1520 const W j0 = static_cast<W>(inCircleDeterminant(a(), b(), c(), other.source()));
1521 const W j1 = static_cast<W>(inCircleDeterminant(a(), b(), c(), other.target()));
1522 const W squared_length = other.source().template squaredDistance<W>(other.target());
1523 const W m = squared_length * det;
1524
1525 const W projection = (j0 - j1) * det; // (J0 - J1) * A
1526 const W half_span = m * det; // M * A = L * A^2 >= 0
1527 const W discriminant_base = j0 + j1 + m; // J0 + J1 + M
1528
1529 const bool strict_secant = discriminant_base * discriminant_base > W{4} * j0 * j1;
1530 const bool contact_ahead = projection < half_span; // foot parameter t* > 0
1531
1532 return strict_secant && contact_ahead;
1533}
1534
1535template <class PointType, class LabelType>
1536template<HalfplaneConcept OtherHalfplane>
1537constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherHalfplane& other) const {
1538 if (isDegenerate() || other.isDegenerate()) {
1539 // A radius-zero disk is a point and has empty interior.
1540 return false;
1541 }
1542 return interiorsIntersect(other.asLine()) || pointInsideInteriorContainedIn(other);
1543}
1544
1545template <class PointType, class LabelType>
1546template<RectangleConcept OtherRectangle>
1547constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherRectangle& other) const {
1548 if (other.empty()) {
1549 // The empty set meets nothing and disconnects nothing.
1550 return false;
1551 }
1552 if (isDegenerate() || other.isDegenerate()) {
1553 return false;
1554 }
1555 // Interiors meet when a rectangle edge passes through the open disk, or the
1556 // disk lies inside the rectangle (a point strictly inside the disk is in the
1557 // rectangle's interior). The latter uses a disk-interior point as the
1558 // witness: a disk tangent to an edge from inside still overlaps the interior.
1559 for (const auto& edge : other.edges()) {
1560 if (interiorsIntersect(edge)) {
1561 return true;
1562 }
1563 }
1564 return pointInsideInteriorContainedIn(other);
1565}
1566
1567template <class PointType, class LabelType>
1568template<TriangleConcept OtherTriangle>
1569constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherTriangle& other) const {
1570 if (isDegenerate() || other.isDegenerate()) {
1571 return false;
1572 }
1573 // Interiors meet when a triangle edge passes through the open disk, or the
1574 // disk lies inside the triangle (a point strictly inside the disk is in the
1575 // triangle's interior). The latter uses a disk-interior point as the
1576 // witness: a disk tangent to an edge from inside still overlaps the interior.
1577 for (const auto& edge : other.edges()) {
1578 if (interiorsIntersect(edge)) {
1579 return true;
1580 }
1581 }
1582 return pointInsideInteriorContainedIn(other);
1583}
1584
1585template <class PointType, class LabelType>
1586template<DiskConcept OtherDisk>
1587constexpr bool Disk<PointType, LabelType>::interiorsIntersect(const OtherDisk& other) const {
1588 if (isDegenerate() || other.isDegenerate()) {
1589 // A radius-zero disk is a point and has empty interior.
1590 return false;
1591 }
1592 // Open disks overlap iff the centre distance is strictly less than the sum
1593 // of the radii, so externally tangent disks do not count. With
1594 // A = d^2 - r1^2 - r2^2 that is A < 0 or A^2 < 4 r1^2 r2^2 (the strict form
1595 // of the closed-disk test in intersects).
1596 using R = std::conditional_t<
1597 std::is_floating_point_v<NumberType> ||
1598 std::is_floating_point_v<typename OtherDisk::NumberType>,
1599 long double,
1601 const R d2 = center<R>().template squaredDistance<R>(other.template center<R>());
1602 const R r1_sq = squaredRadius<R>();
1603 const R r2_sq = other.template squaredRadius<R>();
1604 const R A = d2 - r1_sq - r2_sq;
1605 return A < R{} || A * A < R{4} * r1_sq * r2_sq;
1606}
1607
1608template <class PointType, class LabelType>
1609template<PointConcept OtherPoint>
1611 return std::visit(
1612 [this](const auto& value) {
1613 return this->interiorsIntersect(value);
1614 },
1615 other.variant());
1616}
1617
1618
1619template <class PointType, class LabelType>
1620template<DiskConcept OtherDisk>
1621constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherDisk& other) const {
1622 if (isDegenerate() || other.isDegenerate()) {
1623 // Either operand collapsed to a lower dimension has empty interior.
1624 return false;
1625 }
1626 // The open interiors meet when a boundary edge passes through the open disk
1627 // (this also covers the polygon lying inside the disk, whose edges are then
1628 // inside it), or when the disk lies inside the polygon -- witnessed by a point
1629 // strictly inside the disk falling in the polygon's strict interior. The
1630 // interior witness matters: a disk tangent to an edge from inside still
1631 // overlaps the interior.
1632 for (const auto& edge : edgesView()) {
1633 if (edge.interiorsIntersect(other)) {
1634 return true;
1635 }
1636 }
1637 return other.pointInsideInteriorContainedIn(*this);
1638}
1639
1646
1647template <class PointType, class LabelType, class Storage>
1648template<PointConcept OtherPoint>
1649constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherPoint& other) const {
1650 // A point's interior is the point itself, so this matches interiorContains.
1651 return interiorContains(other);
1652}
1653
1654template <class PointType, class LabelType, class Storage>
1655template<SegmentConcept OtherSegment>
1656constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherSegment& other) const {
1657 if (points_.size() < 2) {
1658 // A chain without an edge has an empty relative interior.
1659 return false;
1660 }
1661 const auto window = edgeWindow(other.min().x(), other.max().x());
1662 if (!window) {
1663 return false;
1664 }
1665 for (std::size_t i = window->first; i <= window->second; ++i) {
1666 if (this->template boundaryAt<false>(i).interiorsIntersect(other)) {
1667 return true;
1668 }
1669 }
1670 // The open edges miss the chain's own vertices, but the non-extreme ones
1671 // are interior points of the chain: a segment whose open part passes
1672 // exactly through such a vertex still meets the chain's interior.
1673 const std::size_t firstVertex = std::max<std::size_t>(window->first, 1);
1674 const std::size_t lastVertex = std::min(window->second + 1, points_.size() - 2);
1675 for (std::size_t v = firstVertex; v <= lastVertex; ++v) {
1676 if (other.interiorContains((*this)[v])) {
1677 return true;
1678 }
1679 }
1680 return false;
1681}
1682
1683template <class PointType, class LabelType, class Storage>
1684template<OrientedSegmentConcept OtherOrientedSegment>
1685constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherOrientedSegment& other) const {
1687}
1688
1689namespace detail {
1690
1691// Shared body of MonotoneChain::interiorsIntersect against a shape whose
1692// interior test is `other.interiorContains(point)`: the chain's relative
1693// interior is its open edges plus its non-extreme vertices, so it meets the
1694// other interior iff an open edge does or such a vertex lies inside it.
1695template <class Chain, class OtherShape>
1696constexpr bool chainInteriorsIntersect(const Chain& chain, const OtherShape& other) {
1697 const std::size_t n = chain.size();
1698 if (n < 2) {
1699 // A chain without an edge has an empty relative interior.
1700 return false;
1701 }
1702 for (std::size_t i = 0; i + 1 < n; ++i) {
1703 if (Segment<typename Chain::PointType>(chain[i], chain[i + 1]).interiorsIntersect(other)) {
1704 return true;
1705 }
1706 }
1707 for (std::size_t v = 1; v + 1 < n; ++v) {
1708 if (other.interiorContains(chain[v])) {
1709 return true;
1710 }
1711 }
1712 return false;
1713}
1714
1715} // namespace detail
1716
1717template <class PointType, class LabelType, class Storage>
1718template<LineConcept OtherLine>
1719constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherLine& other) const {
1720 return detail::chainInteriorsIntersect(*this, other);
1721}
1722
1723template <class PointType, class LabelType, class Storage>
1724template<OrientedLineConcept OtherOrientedLine>
1725constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherOrientedLine& other) const {
1726 return detail::chainInteriorsIntersect(*this, other);
1727}
1728
1729template <class PointType, class LabelType, class Storage>
1730template<RayConcept OtherRay>
1731constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherRay& other) const {
1732 return detail::chainInteriorsIntersect(*this, other);
1733}
1734
1735template <class PointType, class LabelType, class Storage>
1736template<HalfplaneConcept OtherHalfplane>
1737constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherHalfplane& other) const {
1738 return detail::chainInteriorsIntersect(*this, other);
1739}
1740
1741template <class PointType, class LabelType, class Storage>
1742template<RectangleConcept OtherRectangle>
1743constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherRectangle& other) const {
1744 if (other.empty()) {
1745 // The empty set meets nothing and disconnects nothing.
1746 return false;
1747 }
1748 return detail::chainInteriorsIntersect(*this, other);
1749}
1750
1751template <class PointType, class LabelType, class Storage>
1752template<TriangleConcept OtherTriangle>
1753constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherTriangle& other) const {
1754 return detail::chainInteriorsIntersect(*this, other);
1755}
1756
1757template <class PointType, class LabelType, class Storage>
1758template<ConvexConcept OtherConvex>
1759constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherConvex& other) const {
1760 return detail::chainInteriorsIntersect(*this, other);
1761}
1762
1763template <class PointType, class LabelType, class Storage>
1764template<DiskConcept OtherDisk>
1765constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherDisk& other) const {
1766 return detail::chainInteriorsIntersect(*this, other);
1767}
1768
1769template <class PointType, class LabelType, class Storage>
1770template<MonotoneChainConcept OtherChain>
1771constexpr bool MonotoneChain<PointType, LabelType, Storage>::interiorsIntersect(const OtherChain& other) const {
1772 if (size() < 2 || other.size() < 2) {
1773 return false;
1774 }
1775 // Open-edge pairs via the same merge sweep as intersects().
1776 const std::size_t iEnd = size() - 1;
1777 const std::size_t jEnd = other.size() - 1;
1778 // Seed past the leading edges left of the shared x-window (see the sweep in
1779 // MonotoneChain::intersects): indexAtX locates the first candidate in
1780 // O(log n); a disengaged result means the x-ranges are disjoint, so the
1781 // merge has no work (the vertex loops below still run and correctly find
1782 // nothing, as no vertex of one chain can lie on the disjoint other).
1783 using XType = std::common_type_t<NumberType, typename OtherChain::PointType::NumberType>;
1784 const XType xlo = std::max<XType>((*this)[0].x(), other[0].x());
1785 const auto iSeed = indexAtX(xlo);
1786 const auto jSeed = other.indexAtX(xlo);
1787 // Back up one edge: the edge whose right endpoint sits exactly on xlo can
1788 // still meet the other chain there, yet indexAtX returns the next edge.
1789 std::size_t i = (iSeed && jSeed) ? (*iSeed > 0 ? *iSeed - 1 : 0) : iEnd;
1790 std::size_t j = (iSeed && jSeed) ? (*jSeed > 0 ? *jSeed - 1 : 0) : jEnd;
1791 while (i < iEnd && j < jEnd) {
1792 const Segment<PointType> mine((*this)[i], (*this)[i + 1]);
1793 const Segment<typename OtherChain::PointType> theirs(other[j], other[j + 1]);
1794 if (!(mine.max().x() < theirs.min().x() || theirs.max().x() < mine.min().x()) &&
1795 mine.interiorsIntersect(theirs)) {
1796 return true;
1797 }
1798 const auto order = mine.max() <=> theirs.max();
1799 if (order <= 0) {
1800 ++i;
1801 }
1802 if (order >= 0) {
1803 ++j;
1804 }
1805 }
1806 // The open edges miss the chains' own vertices, but the non-extreme ones
1807 // are interior, whichever chain they belong to.
1808 for (std::size_t v = 1; v + 1 < size(); ++v) {
1809 if (other.interiorContains((*this)[v])) {
1810 return true;
1811 }
1812 }
1813 for (std::size_t v = 1; v + 1 < other.size(); ++v) {
1814 if (interiorContains(other[v])) {
1815 return true;
1816 }
1817 }
1818 return false;
1819}
1820
1821template <class PointType, class LabelType, class Storage>
1822template<PointConcept OtherPoint>
1824 return std::visit(
1825 [this](const auto& value) {
1826 return this->interiorsIntersect(value);
1827 },
1828 other.variant());
1829}
1830
1831template <class PointType, class LabelType>
1832template<MonotoneChainConcept OtherChain>
1833constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherChain& other) const {
1834 return detail::chainInteriorsIntersect(other, *this);
1835}
1836
1845
1846template <class PointType, class LabelType>
1847template<PointConcept OtherPoint>
1848constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
1849 // A point's interior is the point itself, so this matches interiorContains.
1850 return interiorContains(other);
1851}
1852
1853template <class PointType, class LabelType>
1854template<SegmentConcept OtherSegment>
1855constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
1856 if (size() < 2) {
1857 // A polyline covering at most one point has an empty relative interior.
1858 return false;
1859 }
1860 // The wanted set is (A ∖ {front, back}) ∩ (S ∖ {S.min, S.max}): the closed
1861 // intersection minus at most four excluded points. A positive-length
1862 // collinear overlap survives the removal of finitely many points; a single
1863 // meeting point survives iff it is not one of the four. Working with
1864 // closed edges also covers a segment passing exactly through a non-extreme
1865 // vertex without crossing any open edge.
1866 const PointType front = (*this)[0];
1867 const PointType back = (*this)[size() - 1];
1868 for (const auto& edge : edgesView()) {
1869 if (!edge.intersects(other)) {
1870 continue;
1871 }
1872 if (edge.collinear(other) && edge.min() < other.max() && other.min() < edge.max()) {
1873 return true; // positive-length overlap
1874 }
1875 // The intersection is a single point; it counts unless it is one of
1876 // the excluded extremes/endpoints (tested division-free: the unique
1877 // common point equals x iff x lies on both segments).
1878 const bool excluded = (edge.contains(front) && other.contains(front)) ||
1879 (edge.contains(back) && other.contains(back)) ||
1880 edge.contains(other.min()) || edge.contains(other.max());
1881 if (!excluded) {
1882 return true;
1883 }
1884 }
1885 return false;
1886}
1887
1888template <class PointType, class LabelType>
1889template<OrientedSegmentConcept OtherOrientedSegment>
1890constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
1892}
1893
1894template <class PointType, class LabelType>
1895template<LineConcept OtherLine>
1896constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
1897 if (size() < 2) {
1898 return false;
1899 }
1900 // The wanted set is (A ∖ {front, back}) ∩ line: the closed intersection
1901 // minus at most two excluded points. Since the line is infinite, an edge
1902 // meets it either along the whole edge (edge on the line) or in a single
1903 // point.
1904 const PointType front = (*this)[0];
1905 const PointType back = (*this)[size() - 1];
1906 for (const auto& edge : edgesView()) {
1907 if (!edge.intersects(other)) {
1908 continue;
1909 }
1910 if (other.contains(edge.min()) && other.contains(edge.max()) &&
1911 edge.min() != edge.max()) {
1912 return true; // positive-length overlap survives losing two points
1913 }
1914 // Single meeting point; it counts unless it is an excluded extreme
1915 // (tested division-free: the unique common point equals x iff x lies
1916 // on both operands).
1917 const bool excluded = (edge.contains(front) && other.contains(front)) ||
1918 (edge.contains(back) && other.contains(back));
1919 if (!excluded) {
1920 return true;
1921 }
1922 }
1923 return false;
1924}
1925
1926template <class PointType, class LabelType>
1927template<OrientedLineConcept OtherOrientedLine>
1928constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
1929 return interiorsIntersect(other.asLine());
1930}
1931
1932template <class PointType, class LabelType>
1933template<RayConcept OtherRay>
1934constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
1935 if (size() < 2) {
1936 return false;
1937 }
1938 // The ray's relative interior is the ray minus its source, so the excluded
1939 // points are the polyline's extremes and the source.
1940 const PointType front = (*this)[0];
1941 const PointType back = (*this)[size() - 1];
1942 const auto supporting = other.asLine();
1943 for (const auto& edge : edgesView()) {
1944 if (!edge.intersects(other)) {
1945 continue;
1946 }
1947 if (supporting.contains(edge.min()) && supporting.contains(edge.max())) {
1948 // Collinear contact: the overlap with the ray is an interval whose
1949 // far end is an edge endpoint on the ray.
1950 const bool minOnRay = other.contains(edge.min());
1951 const bool maxOnRay = other.contains(edge.max());
1952 if (minOnRay && maxOnRay && edge.min() != edge.max()) {
1953 return true; // positive-length overlap
1954 }
1955 if (minOnRay != maxOnRay) {
1956 // Overlap [source, endpoint-on-ray]; positive length iff that
1957 // endpoint is not the source itself.
1958 const PointType onRay = minOnRay ? edge.min() : edge.max();
1959 if (onRay != other.source()) {
1960 return true;
1961 }
1962 continue; // the single meeting point is the excluded source
1963 }
1964 // Zero-length edge on the ray: fall through to the single-point test.
1965 }
1966 const bool excluded = (edge.contains(front) && other.contains(front)) ||
1967 (edge.contains(back) && other.contains(back)) ||
1968 edge.contains(other.source());
1969 if (!excluded) {
1970 return true;
1971 }
1972 }
1973 return false;
1974}
1975
1976// The shapes below have open two-dimensional interiors, so whenever such an
1977// interior meets the polyline at all it also meets a polyline point other
1978// than the two excluded extremes (an open neighborhood of the meeting point
1979// contains further polyline points); the chain helper is therefore exact here
1980// even though the polyline may revisit an extreme mid-sequence.
1981
1982template <class PointType, class LabelType>
1983template<HalfplaneConcept OtherHalfplane>
1984constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherHalfplane& other) const {
1985 return detail::chainInteriorsIntersect(*this, other);
1986}
1987
1988template <class PointType, class LabelType>
1989template<RectangleConcept OtherRectangle>
1990constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherRectangle& other) const {
1991 if (other.empty()) {
1992 // The empty set meets nothing and disconnects nothing.
1993 return false;
1994 }
1995 return detail::chainInteriorsIntersect(*this, other);
1996}
1997
1998template <class PointType, class LabelType>
1999template<TriangleConcept OtherTriangle>
2000constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherTriangle& other) const {
2001 return detail::chainInteriorsIntersect(*this, other);
2002}
2003
2004template <class PointType, class LabelType>
2005template<ConvexConcept OtherConvex>
2006constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherConvex& other) const {
2007 return detail::chainInteriorsIntersect(*this, other);
2008}
2009
2010template <class PointType, class LabelType>
2011template<DiskConcept OtherDisk>
2012constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherDisk& other) const {
2013 return detail::chainInteriorsIntersect(*this, other);
2014}
2015
2016template <class PointType, class LabelType>
2017template<MonotoneChainConcept OtherChain>
2018constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherChain& other) const {
2019 if (size() < 2 || other.size() < 2) {
2020 return false;
2021 }
2022 // Same closed-edge scheme as the polyline overload, with the four excluded
2023 // points being the extremes of the polyline and of the chain.
2024 const PointType front = (*this)[0];
2025 const PointType back = (*this)[size() - 1];
2026 const auto otherFront = other[0];
2027 const auto otherBack = other[other.size() - 1];
2028 for (const auto& mine : edgesView()) {
2029 for (const auto& theirs : other.edgesView()) {
2030 if (!mine.intersects(theirs)) {
2031 continue;
2032 }
2033 if (mine.collinear(theirs) && mine.min() < theirs.max() && theirs.min() < mine.max()) {
2034 return true; // positive-length overlap
2035 }
2036 const bool excluded =
2037 (mine.contains(front) && theirs.contains(front)) ||
2038 (mine.contains(back) && theirs.contains(back)) ||
2039 (mine.contains(otherFront) && theirs.contains(otherFront)) ||
2040 (mine.contains(otherBack) && theirs.contains(otherBack));
2041 if (!excluded) {
2042 return true;
2043 }
2044 }
2045 }
2046 return false;
2047}
2048
2049template <class PointType, class LabelType>
2050template<PolylineConcept OtherPolyline>
2051constexpr bool Polyline<PointType, LabelType>::interiorsIntersect(const OtherPolyline& other) const {
2052 if (size() < 2 || other.size() < 2) {
2053 return false;
2054 }
2055 // Same closed-edge scheme as the segment overload, with the four excluded
2056 // points being the extremes of the two polylines.
2057 const PointType front = (*this)[0];
2058 const PointType back = (*this)[size() - 1];
2059 const auto otherFront = other[0];
2060 const auto otherBack = other[other.size() - 1];
2061 for (const auto& mine : edgesView()) {
2062 for (const auto& theirs : other.edgesView()) {
2063 if (!mine.intersects(theirs)) {
2064 continue;
2065 }
2066 if (mine.collinear(theirs) && mine.min() < theirs.max() && theirs.min() < mine.max()) {
2067 return true; // positive-length overlap
2068 }
2069 const bool excluded =
2070 (mine.contains(front) && theirs.contains(front)) ||
2071 (mine.contains(back) && theirs.contains(back)) ||
2072 (mine.contains(otherFront) && theirs.contains(otherFront)) ||
2073 (mine.contains(otherBack) && theirs.contains(otherBack));
2074 if (!excluded) {
2075 return true;
2076 }
2077 }
2078 }
2079 return false;
2080}
2081
2082template <class PointType, class LabelType>
2083template<PointConcept OtherPoint>
2085 return std::visit(
2086 [this](const auto& value) {
2087 return this->interiorsIntersect(value);
2088 },
2089 other.variant());
2090}
2091
2092// The polygon's interior is open and two-dimensional, so the chain helper is
2093// exact for a polyline too (see the polyline section note above).
2094template <class PointType, class LabelType>
2095template<PolylineConcept OtherPolyline>
2096constexpr bool Polygon<PointType, LabelType>::interiorsIntersect(const OtherPolyline& other) const {
2097 return detail::chainInteriorsIntersect(other, *this);
2098}
2099
2100
2101// ---------------------------------------------------------------------------
2102// HalfplaneIntersection
2103//
2104// The other shape's interior is its relative interior (a point is itself, a
2105// segment is the open segment, ...), matching the conventions used by the
2106// other shapes. The region's interior is its topological interior, which is
2107// empty when the region is degenerate.
2108
2109template <class PointType, class LabelType>
2110template <PointConcept OtherPoint>
2111constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherPoint& other) const {
2112 // A point's interior is the point itself, so this matches interiorContains.
2113 return interiorContains(other);
2114}
2115
2116template <class PointType, class LabelType>
2117template <SegmentConcept OtherSegment>
2118constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
2119 if (isDegenerate()) {
2120 return false;
2121 }
2122 if (halfplanes_.empty()) {
2123 return true;
2124 }
2125 if (other.isDegenerate()) {
2126 return interiorContains(other.min());
2127 }
2128 // The open clip interval must overlap the open parameter window (0, 1).
2129 const Halfplane<typename OtherSegment::PointType> along(other.min(), other.max());
2130 const auto clip = clipLine(along);
2131 if (clip.empty || clip.onParallelBoundary || !clipHasLength(clip, along)) {
2132 return false;
2133 }
2134 if (clip.entry >= 0 && !(constraintSide(static_cast<std::size_t>(clip.entry), other.max()) > 0)) {
2135 return false;
2136 }
2137 if (clip.exit >= 0 && !(constraintSide(static_cast<std::size_t>(clip.exit), other.min()) > 0)) {
2138 return false;
2139 }
2140 return true;
2141}
2142
2143template <class PointType, class LabelType>
2144template <OrientedSegmentConcept OtherOrientedSegment>
2145constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
2147}
2148
2149template <class PointType, class LabelType>
2150template <LineConcept OtherLine>
2151constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
2152 if (isDegenerate()) {
2153 return false;
2154 }
2155 if (halfplanes_.empty()) {
2156 return true;
2157 }
2158 const Halfplane<typename OtherLine::PointType> along(other[0], other[1]);
2159 const auto clip = clipLine(along);
2160 return !clip.empty && !clip.onParallelBoundary && clipHasLength(clip, along);
2161}
2162
2163template <class PointType, class LabelType>
2164template <OrientedLineConcept OtherOrientedLine>
2165constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
2166 return interiorsIntersect(other.asLine());
2167}
2168
2169template <class PointType, class LabelType>
2170template <RayConcept OtherRay>
2171constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
2172 if (isDegenerate()) {
2173 return false;
2174 }
2175 if (halfplanes_.empty()) {
2176 return true;
2177 }
2178 // The open clip interval must overlap the open window (0, +inf).
2179 const Halfplane<typename OtherRay::PointType> along(other.source(), other.target());
2180 const auto clip = clipLine(along);
2181 if (clip.empty || clip.onParallelBoundary || !clipHasLength(clip, along)) {
2182 return false;
2183 }
2184 return clip.exit < 0 || constraintSide(static_cast<std::size_t>(clip.exit), other.source()) > 0;
2185}
2186
2187template <class PointType, class LabelType>
2188template <HalfplaneConcept OtherHalfplane>
2189constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherHalfplane& other) const {
2190 if (isDegenerate()) {
2191 return false;
2192 }
2193 if (halfplanes_.empty()) {
2194 return true;
2195 }
2196 // The infimum of the half-plane's normal functional must lie strictly
2197 // below its boundary value; a full-dimensional region then has interior
2198 // points arbitrarily close to any witness.
2199 const SupStatus infimum = supStatus(other.opposite());
2200 return infimum == SupStatus::unbounded || infimum == SupStatus::above;
2201}
2202
2203template <class PointType, class LabelType>
2204template <RectangleConcept OtherRectangle>
2205constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherRectangle& other) const {
2206 if (other.empty()) {
2207 // The empty set meets nothing and disconnects nothing.
2208 return false;
2209 }
2210 // The open interiors meet exactly when the region intersected with the
2211 // rectangle is full-dimensional.
2212 if (isDegenerate() || other.isDegenerate()) {
2213 return false;
2214 }
2215 return !this->template intersection<NumberType>(other).isDegenerate();
2216}
2217
2218template <class PointType, class LabelType>
2219template <TriangleConcept OtherTriangle>
2220constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherTriangle& other) const {
2221 if (isDegenerate()) {
2222 return false;
2223 }
2224 return !this->template intersection<NumberType>(other).isDegenerate();
2225}
2226
2227template <class PointType, class LabelType>
2228template <ConvexConcept OtherConvex>
2229constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherConvex& other) const {
2230 if (isDegenerate() || other.isDegenerate()) {
2231 return false;
2232 }
2233 return !this->template intersection<NumberType>(other).isDegenerate();
2234}
2235
2236template <class PointType, class LabelType>
2237template <DiskConcept OtherDisk>
2238constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherDisk& other) const {
2239 if (isDegenerate() || other.isDegenerate()) {
2240 return false;
2241 }
2242 if (halfplanes_.empty()) {
2243 return true;
2244 }
2245 using E = detail::region_exact_number_t<NumberType>;
2246 const auto clipped = detail::regionClippedToBox(*this, other.bbox());
2247 if (clipped.isDegenerate()) {
2248 return false;
2249 }
2250 return clipped.template asConvex<E>().interiorsIntersect(other);
2251}
2252
2253template <class PointType, class LabelType>
2254template <MonotoneChainConcept OtherChain>
2255constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherChain& other) const {
2256 // A chain is one-dimensional: its interior meets the region's interior
2257 // exactly when some edge's relative interior enters the open region.
2258 if (isDegenerate() || other.size() < 2) {
2259 return false;
2260 }
2261 for (const auto& edge : other.edgesView()) {
2262 if (interiorsIntersect(edge)) {
2263 return true;
2264 }
2265 }
2266 return false;
2267}
2268
2269template <class PointType, class LabelType>
2270template <PolylineConcept OtherPolyline>
2271constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherPolyline& other) const {
2272 if (isDegenerate() || other.size() < 2) {
2273 return false;
2274 }
2275 for (const auto& edge : other.edgesView()) {
2276 if (interiorsIntersect(edge)) {
2277 return true;
2278 }
2279 }
2280 return false;
2281}
2282
2283template <class PointType, class LabelType>
2284template <PolygonConcept OtherPolygon>
2285constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherPolygon& other) const {
2286 if (isDegenerate() || other.isDegenerate() || other.size() < 3) {
2287 return false;
2288 }
2289 if (halfplanes_.empty()) {
2290 return true;
2291 }
2292 // Clip to the polygon's box, then defer to the polygon's own interior-
2293 // intersection test against the resulting bounded convex region.
2294 using E = detail::region_exact_number_t<NumberType>;
2295 const auto clipped = detail::regionClippedToBox(*this, other.bbox());
2296 if (clipped.isDegenerate()) {
2297 return false;
2298 }
2299 return other.interiorsIntersect(clipped.template asConvex<E>());
2300}
2301
2302template <class PointType, class LabelType>
2303template <HalfplaneIntersectionConcept OtherRegion>
2304constexpr bool HalfplaneIntersection<PointType, LabelType>::interiorsIntersect(const OtherRegion& other) const {
2305 // The interiors are intersections of open half-planes, and a finite family
2306 // of open half-planes has a common point exactly when the corresponding
2307 // closed intersection is full-dimensional.
2308 if (isDegenerate() || other.isDegenerate()) {
2309 return false;
2310 }
2311 return !this->template intersection<NumberType>(other).isDegenerate();
2312}
2313
2314template <class PointType, class LabelType>
2315template <PointConcept OtherPoint>
2317 return std::visit(
2318 [this](const auto& value) {
2319 return this->interiorsIntersect(value);
2320 },
2321 other.variant());
2322}
2323
2324
2325// ---------------------------------------------------------------------------
2326// PolygonWithHoles
2327
2328template <class PointType, class LabelType>
2329template <class OtherLinear, class ContactNumber>
2330constexpr bool PolygonWithHoles<PointType, LabelType>::linearInteriorsIntersect(
2331 const OtherLinear& other,
2332 std::vector<Point<ContactNumber>> contacts,
2333 const Point<ContactNumber>& tail,
2334 const Point<ContactNumber>& head) const {
2335 // The split points: the operand's own ends, already in `contacts`, then
2336 // every ring vertex on the operand. Every other contact between the operand
2337 // and ∂A is a transversal crossing — a non-collinear pair meets in at most
2338 // one point, which is a crossing unless it is an endpoint of one of them,
2339 // and a collinear overlap begins and ends at such a point.
2340 using V = Point<ContactNumber>;
2341 const std::size_t firstRingVertex = contacts.size();
2342 for (const auto& vertex : outer_) {
2343 if (other.contains(vertex)) {
2344 contacts.push_back(static_cast<V>(vertex));
2345 }
2346 }
2347 for (const auto& hole : holes_) {
2348 for (const auto& vertex : hole) {
2349 if (other.contains(vertex)) {
2350 contacts.push_back(static_cast<V>(vertex));
2351 }
2352 }
2353 }
2354 // A transversal crossing of a ring edge normally settles it: the operand
2355 // passes from one open side of the edge to the other, and one of those sides
2356 // is region interior — inside the outer ring for an outer edge, outside the
2357 // hole for a hole edge.
2358 //
2359 // The exception is a crossing where the region pinches shut, i.e. where two
2360 // rings meet, because there both sides of the crossing are outside the
2361 // region interior. That happens in two ways, and neither needs the crossing
2362 // point itself:
2363 //
2364 // - the rings touch at an isolated point, which has to be a vertex of one
2365 // of them (meeting anywhere else would make their edges cross or
2366 // overlap), so it is already among the split points collected above;
2367 // - the rings run along one another, so the crossed edge is covered twice.
2368 // A second crossed edge collinear with the first meets the operand at the
2369 // same point — both crossing points are `line(edge) ∩ line(operand)` —
2370 // so collinearity among the crossed edges detects exactly this.
2371 //
2372 // Skipping a pinched crossing is safe and needs no split point of its own:
2373 // the region is locally one-dimensional there, so the piece carrying it
2374 // stays outside the region interior on both sides and its midpoint says so.
2375 std::vector<EdgeType> crossed;
2376 anyBoundaryEdge([&](const auto& edge) {
2377 if (edge.crosses(other)) {
2378 crossed.push_back(edge);
2379 }
2380 return false;
2381 });
2382 for (std::size_t i = 0; i < crossed.size(); ++i) {
2383 bool pinched = false;
2384 for (std::size_t v = firstRingVertex; v < contacts.size() && !pinched; ++v) {
2385 pinched = crossed[i].contains(contacts[v]);
2386 }
2387 for (std::size_t j = 0; j < crossed.size() && !pinched; ++j) {
2388 pinched = j != i && crossed[i].collinear(crossed[j]);
2389 }
2390 if (!pinched) {
2391 return true;
2392 }
2393 }
2394 // Every remaining contact is a split point, so the open pieces between
2395 // consecutive split points each lie wholly inside or wholly outside the
2396 // region and their midpoints classify them. Doubling the region keeps the
2397 // midpoint test exact and division-free: (p+q)/2 is interior to A iff (p+q)
2398 // is interior to 2A. The unbounded pieces past the extreme split points need
2399 // no test: the region is bounded, and a piece that escapes it for good is
2400 // one whose far end left through an unpinched crossing, already answered
2401 // above.
2402 std::sort(contacts.begin(), contacts.end(), [&](const V& p, const V& q) {
2403 return dotSign(p, q, tail, head) > 0;
2404 });
2405 const auto doubled = (*this) * NumberType(2);
2406 for (std::size_t i = 1; i < contacts.size(); ++i) {
2407 if (contacts[i - 1] == contacts[i]) {
2408 continue;
2409 }
2410 if (doubled.interiorContains(contacts[i - 1] + contacts[i])) {
2411 return true;
2412 }
2413 }
2414 return false;
2415}
2416
2417template <class PointType, class LabelType>
2418template <SegmentConcept OtherSegment>
2419constexpr bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherSegment& other) const {
2420 if (isDegenerate() || other.isDegenerate()) {
2421 return false;
2422 }
2423 // Both endpoints bound the segment, so both are split points.
2424 using C = std::common_type_t<NumberType, typename OtherSegment::NumberType>;
2425 using V = Point<C>;
2426 const V begin = static_cast<V>(other.min());
2427 const V end = static_cast<V>(other.max());
2428 return linearInteriorsIntersect(other, std::vector<V>{begin, end}, begin, end);
2429}
2430
2431template <class PointType, class LabelType>
2432template <OrientedSegmentConcept OtherOrientedSegment>
2433constexpr bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherOrientedSegment& other) const {
2434 return interiorsIntersect(other.asSegment());
2435}
2436
2437template <class PointType, class LabelType>
2438template <LineConcept OtherLine>
2439constexpr bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherLine& other) const {
2440 if (isDegenerate() || other.isDegenerate()) {
2441 return false;
2442 }
2443 // A line ends nowhere, so it contributes no split point of its own; the ring
2444 // vertices on it are the whole list. Unlike @ref Polygon, a straddle of the
2445 // line is not enough: a hole touching the outer ring at two points can hold
2446 // the entire chord between them, and then the line misses the region
2447 // interior even though vertices lie on both sides of it.
2448 using C = std::common_type_t<NumberType, typename OtherLine::NumberType>;
2449 using V = Point<C>;
2450 const V begin = static_cast<V>(other.min());
2451 const V end = static_cast<V>(other.max());
2452 return linearInteriorsIntersect(other, std::vector<V>{}, begin, end);
2453}
2454
2455template <class PointType, class LabelType>
2456template <OrientedLineConcept OtherOrientedLine>
2457constexpr bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherOrientedLine& other) const {
2458 return interiorsIntersect(other.asLine());
2459}
2460
2461template <class PointType, class LabelType>
2462template <RayConcept OtherRay>
2463constexpr bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherRay& other) const {
2464 if (isDegenerate() || other.isDegenerate()) {
2465 return false;
2466 }
2467 // The source is where the ray begins, hence a split point; the far end runs
2468 // out of the bounded region for good.
2469 using C = std::common_type_t<NumberType, typename OtherRay::NumberType>;
2470 using V = Point<C>;
2471 const V source = static_cast<V>(other.source());
2472 const V target = static_cast<V>(other.target());
2473 return linearInteriorsIntersect(other, std::vector<V>{source}, source, target);
2474}
2475
2476template <class PointType, class LabelType>
2477template <HalfplaneConcept OtherHalfplane>
2478constexpr bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherHalfplane& other) const {
2479 if (isDegenerate() || other.isDegenerate()) {
2480 return false;
2481 }
2482 // The open half-plane meets the open region exactly when some vertex of some
2483 // ring is both strictly inside the half-plane and two-dimensional for the
2484 // region (@ref isSolidVertex).
2485 //
2486 // (⇐) is immediate: the half-plane is open, so it holds a neighbourhood of
2487 // that vertex, and a two-dimensional vertex has region interior in every one
2488 // of its neighbourhoods.
2489 //
2490 // (⇒) Take a connected piece W of A° ∩ H°. Its boundary runs along ∂A and
2491 // along the half-plane's edge line, and the stretches along ∂A are covered
2492 // once — a doubly covered stretch has the region pinched shut against it and
2493 // no interior beside it. Those stretches cannot all lie on the edge line, or
2494 // W would be bounded by a line alone and hence unbounded, which it is not.
2495 // So one of them has a point strictly inside the half-plane, and since the
2496 // covering multiplicity along a ring edge only changes at ring vertices, the
2497 // singly covered stretch containing it extends to a ring vertex; the farther
2498 // of its two ends is strictly inside the half-plane too, and is a
2499 // two-dimensional vertex because a singly covered stretch reaches it.
2500 for (const auto& vertex : outer_) {
2501 if (other.interiorContains(vertex) && isSolidVertex(vertex)) {
2502 return true;
2503 }
2504 }
2505 for (const auto& hole : holes_) {
2506 for (const auto& vertex : hole) {
2507 if (other.interiorContains(vertex) && isSolidVertex(vertex)) {
2508 return true;
2509 }
2510 }
2511 }
2512 return false;
2513}
2514
2515template <class PointType, class LabelType>
2516constexpr bool PolygonWithHoles<PointType, LabelType>::isSolidVertex(const PointType& vertex) const {
2517 if (holes_.empty()) {
2518 return true; // a lone simple ring has interior along all of itself
2519 }
2520 bool solid = false;
2521 anyBoundaryEdge([&](const auto& edge) {
2522 if (!edge.contains(vertex)) {
2523 return false;
2524 }
2525 for (const auto& endpoint : {edge.min(), edge.max()}) {
2526 if (endpoint == vertex) {
2527 continue; // the stretch on this side of the vertex is empty
2528 }
2529 // Shrink the stretch to the nearest ring vertex beyond `vertex`: the
2530 // covering multiplicity is constant between consecutive ring
2531 // vertices, since ring edges begin and end at them and never cross.
2532 PointType beyond = endpoint;
2533 const auto shrink = [&](const PointType& candidate) {
2534 if (candidate != vertex && EdgeType(vertex, beyond).contains(candidate)) {
2535 beyond = candidate;
2536 }
2537 };
2538 for (const auto& candidate : outer_) {
2539 shrink(candidate);
2540 }
2541 for (const auto& hole : holes_) {
2542 for (const auto& candidate : hole) {
2543 shrink(candidate);
2544 }
2545 }
2546 // Count the ring edges covering the stretch, reading multiplicity at
2547 // its midpoint. Doubling both keeps that exact: m is on an edge iff
2548 // 2m is on the doubled edge.
2549 const PointType doubledMidpoint = vertex + beyond;
2550 std::size_t covers = 0;
2551 anyBoundaryEdge([&](const auto& candidate) {
2552 if (EdgeType(candidate.min() + candidate.min(), candidate.max() + candidate.max())
2553 .contains(doubledMidpoint)) {
2554 ++covers;
2555 }
2556 return false;
2557 });
2558 if (covers == 1) {
2559 solid = true;
2560 return true;
2561 }
2562 }
2563 return false;
2564 });
2565 return solid;
2566}
2567
2568// The area operands are where the region's shape finally costs something. For a
2569// simple polygon the textbook argument is "if neither boundary reaches the
2570// other's interior, one interior witness point decides", and it works because a
2571// simple polygon's interior is connected. A region's is not: a hole spanning it
2572// leaves two slabs, and a witness in one says nothing about the other. The
2573// counterexample is small — take `[0,4]² ∖ ((0,4)×(1,2))`, whose interior is two
2574// slabs, against the rectangle `[0,4]×[0,2]`. Every edge of the rectangle lies
2575// on ∂A, every ring vertex of the region lies on ∂(rectangle), and the
2576// rectangle's own centre falls on the hole's boundary — yet the lower slab is
2577// inside the rectangle and the interiors do meet.
2578//
2579// So the fallback is the triangulated domain, which is where the region already
2580// keeps its answer to "what part of me has area". Its triangles tile
2581// closure(A°), so A° ∩ B° ≠ ∅ exactly when some triangle's interior meets B°:
2582// a non-empty open intersection has positive area, the triangles cover it, and
2583// a triangle carrying positive area of it has interior in it.
2584//
2585// That test alone is complete. The edge scan before it is a fast path — an edge
2586// of B whose relative interior reaches A° settles the question without
2587// triangulating anything, and it is what answers a query that actually overlaps
2588// the region.
2589//
2590// The fast path costs one assumption, though: an edge reaching A° only implies
2591// that B° does when B has interior beside that edge, i.e. when B is the closure
2592// of its own interior. Every operand here is — except another region, which may
2593// carry a slit, a stretch of boundary with the region pinched shut against it.
2594// Two regions sharing a slit line would both report a hit with no interior
2595// anywhere near it, so region against region skips the scan and compares the
2596// two triangulated domains directly.
2597template <class PointType, class LabelType>
2598template <class OtherArea>
2599bool PolygonWithHoles<PointType, LabelType>::areaInteriorsIntersect(const OtherArea& other) const {
2600 if (isDegenerate() || other.isDegenerate()) {
2601 return false;
2602 }
2603 if (!bbox().interiorsIntersect(other.bbox())) {
2604 return false;
2605 }
2606 if constexpr (PolygonWithHolesConcept<OtherArea>) {
2607 // A region without holes is its outer polygon, and that is regular, so
2608 // reducing either side to it brings the cheaper path back — and with it
2609 // one triangulation instead of two.
2610 if (!other.hasHoles()) {
2611 return areaInteriorsIntersect(other.outer());
2612 }
2613 if (holes_.empty()) {
2614 return other.interiorsIntersect(outer_);
2615 }
2616 const auto mine = triangulation();
2617 const auto theirs = other.triangulation();
2618 return mine.visitTriangles([&theirs](const auto& t) {
2619 return theirs.visitTriangles([&t](const auto& u) { return t.interiorsIntersect(u); });
2620 });
2621 } else {
2622 // Without holes the region is its outer polygon and the simply connected
2623 // argument applies unchanged.
2624 if (holes_.empty()) {
2625 return other.interiorsIntersect(outer_);
2626 }
2627 for (const auto& edge : other.edges()) {
2628 if (interiorsIntersect(edge)) {
2629 return true;
2630 }
2631 }
2632 return triangulation().visitTriangles(
2633 [&other](const auto& triangle) { return other.interiorsIntersect(triangle); });
2634 }
2635}
2636
2637template <class PointType, class LabelType>
2638template <RectangleConcept OtherRectangle>
2639bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherRectangle& other) const {
2640 if (other.empty()) {
2641 // The empty set meets nothing and disconnects nothing.
2642 return false;
2643 }
2644 return areaInteriorsIntersect(other);
2645}
2646
2647template <class PointType, class LabelType>
2648template <TriangleConcept OtherTriangle>
2650 return areaInteriorsIntersect(other);
2651}
2652
2653template <class PointType, class LabelType>
2654template <ConvexConcept OtherConvex>
2656 return areaInteriorsIntersect(other);
2657}
2658
2659template <class PointType, class LabelType>
2660template <PolygonConcept OtherPolygon>
2662 return areaInteriorsIntersect(other);
2663}
2664
2665template <class PointType, class LabelType>
2666template <PolygonWithHolesConcept OtherRegion>
2668 return areaInteriorsIntersect(other);
2669}
2670
2671// The chain's relative interior is its open edges together with the vertices
2672// between them, and the region's interior is open and two-dimensional — which is
2673// all the shared chain helper needs. (An open-edge point that has to be
2674// discarded, because a self-intersecting polyline passes through one of its own
2675// extremes there, is surrounded by edge points that do not; an open subset of
2676// the edge cannot consist of those two points alone.)
2677template <class PointType, class LabelType>
2678template <MonotoneChainConcept OtherChain>
2679constexpr bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherChain& other) const {
2680 return detail::chainInteriorsIntersect(other, *this);
2681}
2682
2683template <class PointType, class LabelType>
2684template <PolylineConcept OtherPolyline>
2685constexpr bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherPolyline& other) const {
2686 return detail::chainInteriorsIntersect(other, *this);
2687}
2688
2689// The disk brings no edges to scan, so the fast path the area operands use —
2690// an edge of the operand whose relative interior reaches A° — has no analogue
2691// here, and the triangulated domain does the work. It answers completely: the
2692// domain triangles tile closure(A°) and their interiors lie in A°, so A° ∩ D°
2693// is nonempty exactly when some triangle interior meets the open disk.
2694//
2695// Two cheap tests come first. A disk missing the closed region misses its
2696// interior too, and that is an O(n) edge scan; and a disk whose own interior
2697// witness falls in A° settles it outright, which is what a query that actually
2698// overlaps the region usually does.
2699template <class PointType, class LabelType>
2700template <DiskConcept OtherDisk>
2702 if (isDegenerate() || other.isDegenerate()) {
2703 return false;
2704 }
2705 if (holes_.empty()) {
2706 return other.interiorsIntersect(outer_);
2707 }
2708 if (!intersects(other)) {
2709 return false;
2710 }
2711 if (other.pointInsideInteriorContainedIn(*this)) {
2712 return true;
2713 }
2714 return triangulation().visitTriangles(
2715 [&other](const auto& triangle) { return other.interiorsIntersect(triangle); });
2716}
2717
2718// Clipping the operand to the region's bounding box loses nothing: A° is an open
2719// subset of that box, so it stays clear of the box boundary, and the clip is
2720// inflated past the box anyway. What it gains is a bounded operand — a convex
2721// polygon — which the area path handles.
2722template <class PointType, class LabelType>
2723template <HalfplaneIntersectionConcept OtherIntersection>
2724bool PolygonWithHoles<PointType, LabelType>::interiorsIntersect(const OtherIntersection& other) const {
2725 if (isDegenerate() || other.isDegenerate()) {
2726 return false;
2727 }
2728 const auto clipped = detail::regionClippedToBox(other, bbox());
2729 if (clipped.isDegenerate()) {
2730 return false;
2731 }
2732 return areaInteriorsIntersect(asConvexOperand(clipped));
2733}
2734
2735
2736// ---------------------------------------------------------------------------
2737// Reverse direction: interiorsIntersect is symmetric, so the lower-ranked
2738// shapes' generic rank-guarded forwarders dispatch these here — no per-shape
2739// definitions are needed.
2740
2741// ---------------------------------------------------------------------------
2742// Runtime Shape argument: unwrap the stored alternative and re-dispatch. Every
2743// alternative has a per-shape overload above, so no fallback is needed.
2744
2745template <class PointType, class LabelType>
2746template <PointConcept OtherPoint>
2748 return std::visit(
2749 [this](const auto& value) {
2750 return this->interiorsIntersect(value);
2751 },
2752 other.variant());
2753}
2754
2755
2756// ---------------------------------------------------------------------------
2757// PolygonSet
2758//
2759// `A° = ⋃ Aᵢ°` for a valid set, so this is componentwise for the same reason
2760// PolygonSet::intersects is.
2761
2762template <class PointType, class LabelType>
2763template <detail::SetOperandConcept OtherShape>
2764bool PolygonSet<PointType, LabelType>::interiorsIntersect(const OtherShape& other) const {
2765 return anyComponent(
2766 [&](const ComponentType& component) { return component.interiorsIntersect(other); });
2767}
2768
2769template <class PointType, class LabelType>
2770template <PolygonSetConcept OtherSet>
2772 for (const auto& component : other) {
2774 return true;
2775 }
2776 }
2777 return false;
2778}
2779
2780template <class PointType, class LabelType>
2781template <PointConcept OtherPoint>
2783 return std::visit([this](const auto& value) { return this->interiorsIntersect(value); },
2784 other.variant());
2785}
2786
2787} // namespace pgl
Exact rational number class template.
Definition rational.hpp:106
Definition forward.hpp:317
constexpr bool preferSweep(const RedShape &red, const BlueShape &blue)
Whether red against blue is a job for redBlueSweep rather than for a pairwise test of their monotone ...
Definition redbluesweep.hpp:673
Implementations of the 'interiorContains' predicate.
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
constexpr auto inCircleDeterminant(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c, const Point< DNumber, DLabel > &d)
Returns the signed in-circle determinant of a query point.
Definition orientation.hpp:857
constexpr bool is_Rational_v
Definition rational.hpp:37
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
bool boundariesCross(const RedRange &red, const BlueRange &blue)
True exactly when some red edge properly crosses some blue edge.
Definition redbluesweep.hpp:482
bool boundariesMeet(const RedRange &red, const BlueRange &blue)
True exactly when some red edge meets some blue edge, crossing or not.
Definition redbluesweep.hpp:494
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
Segment() -> Segment< Point<>, NoLabel >
SweepContact boundaryContactBits(const RedRange &red, const BlueRange &blue)
Definition redbluesweep.hpp:515
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
OrientedLine() -> OrientedLine< Point<>, NoLabel >
Small dispatch traits and geometry helpers reused by the implementations.
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 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 interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:804
constexpr bool crosses(const OtherPoint &) const
Tests whether the two shapes mutually separate each other (each disconnects the other).
Definition crosses.hpp:551
constexpr bool interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition convex.hpp:1340
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
PointType_ PointType
Definition convex.hpp:171
constexpr ResultNumber squaredRadius() const
Returns the squared radius in an explicitly chosen result type.
Definition disk.hpp:402
constexpr Point< ResultNumber, PointLabelType > center() const
Returns the center (circumcenter of the three boundary points) in an explicitly chosen coordinate typ...
Definition disk.hpp:284
constexpr bool isDegenerate() const
Returns whether the three boundary points are collinear.
Definition disk.hpp:348
constexpr const PointType & c() const
Returns the third boundary point in canonical order.
Definition disk.hpp:244
constexpr bool pointInsideInteriorContainedIn(const OtherShape &shape) const
Tests whether some point strictly inside this disk lies in the strict interior of shape.
Definition disk.hpp:1714
constexpr bool interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition disk.hpp:909
detail::floating_result_t< ResultNumber > squaredDistance(const OtherPoint &point) const
Returns the squared Euclidean distance from this disk to a point.
Definition distance.hpp:1194
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:1402
constexpr const PointType & a() const
Returns the first boundary point (lexicographically smallest).
Definition disk.hpp:228
constexpr const PointType & b() const
Returns the second boundary point in canonical order.
Definition disk.hpp:235
constexpr EmptyShape< EmptyPoint > intersection(const EmptyShape< EmptyPoint > &) const
Returns the intersection of the two shapes (A ∩ B), empty when they are disjoint.
Definition halfplaneintersection.hpp:1762
constexpr bool interiorContains(const OtherPoint &point) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition interiorcontains.hpp:1816
constexpr bool isDegenerate() const
Returns whether the region has empty interior (it is empty or lower-dimensional: a line,...
Definition halfplaneintersection.hpp:664
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:2111
constexpr Convex< Point< ResultNumber, typename PointType::LabelType > > asConvex() const
Returns the region as a convex polygon.
Definition halfplaneintersection.hpp:955
constexpr std::variant< Segment< Point< ResultNumber, typename PointType::LabelType > >, Ray< Point< ResultNumber, typename PointType::LabelType > >, Line< Point< ResultNumber, typename PointType::LabelType > > > edge(std::size_t i) const
Returns the boundary contribution of half-plane i as a typed one-dimensional shape.
Definition halfplaneintersection.hpp:919
Closed half-plane defined by an oriented boundary line.
Definition halfplane.hpp:51
constexpr bool interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition halfplane.hpp:562
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:682
constexpr const PointType & target() const
Returns the target boundary point.
Definition halfplane.hpp:193
constexpr Line< PointType > asLine() const
Returns the boundary line without orientation.
Definition halfplane.hpp:318
constexpr const PointType & source() const
Returns the source boundary point.
Definition halfplane.hpp:181
constexpr bool isDegenerate() const
Returns whether the defining points coincide.
Definition predicates.hpp:952
Unoriented infinite line.
Definition line.hpp:52
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 interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:328
constexpr bool interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition line.hpp:574
constexpr bool isDegenerate() const
Returns whether the defining points coincide.
Definition predicates.hpp:451
constexpr std::optional< std::size_t > indexAtX(const OtherNumber &x) const
Locates the vertex or edge of the chain at a given x-coordinate.
Definition atxy.hpp:346
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition interiorsintersect.hpp:1649
constexpr std::size_t size() const
Returns the number of vertices in the chain.
Definition monotonechain.hpp:393
constexpr bool interiorContains(const OtherPoint &point) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition interiorcontains.hpp:1371
constexpr Line< PointType > asLine() const
Returns the line without orientation.
Definition orientedline.hpp:321
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:381
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:364
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:295
constexpr Segment< PointType > asSegment() const
Returns the segment without orientation.
Definition orientedsegment.hpp:322
Two-dimensional point with optional label payload.
Definition point.hpp:129
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:24
static constexpr std::size_t size()
Returns the number of coordinates (always 2).
Definition point.hpp:246
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 interiorsIntersect(const OtherShape &other) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition interiorsintersect.hpp:2764
constexpr const ComponentType & component(std::size_t index) const
Accesses a component by index.
Definition polygonset.hpp:271
PolygonWithHoles< PointType > ComponentType
Definition polygonset.hpp:169
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the region.
Definition polygonwithholes.hpp:1571
constexpr bool interiorsIntersect(const OtherPoint &) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition polygonwithholes.hpp:1660
constexpr bool isDegenerate() const
Tests whether the region has zero area.
Definition polygonwithholes.hpp:441
constexpr const PolygonType & hole(std::size_t index) const
Accesses a hole by index.
Definition polygonwithholes.hpp:196
constexpr bool intersects(const OtherPoint &point) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition polygonwithholes.hpp:1649
constexpr auto end() const
Returns a constant iterator past the last hole.
Definition polygonwithholes.hpp:213
constexpr auto begin() const
Returns a constant iterator to the first hole.
Definition polygonwithholes.hpp:207
auto triangulation() const
Builds the constrained Delaunay triangulation of this region.
Definition triangulation.hpp:6930
constexpr std::optional< PointType > getIfPoint() const
Returns the point the polygon collapses to, if it does.
Definition polygon.hpp:341
PointType::NumberType NumberType
Definition polygon.hpp:61
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 boundaryContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition polygon.hpp:1532
constexpr bool interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition polygon.hpp:1537
constexpr bool crosses(const OtherChain &other) const
Tests whether the two shapes mutually separate each other (each disconnects the other).
Definition crosses.hpp:940
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 bool isPoint() const
Checks whether the polygon covers exactly one point.
Definition polygon.hpp:330
constexpr bool boundariesIntersect(const OtherPolygon &other) const
Tests whether the two polygon boundaries share at least one point (∂A ∩ ∂B ≠ ∅).
Definition interiorsintersect.hpp:1197
constexpr std::size_t size() const
Returns the number of vertices in the polygon.
Definition polygon.hpp:259
constexpr bool boundariesStrongCross(const OtherPolygon &other) const
Tests whether the two polygon boundaries have mononotone chains that strong cross.
Definition interiorsintersect.hpp:1241
constexpr bool interiorsIntersect(const OtherChain &other) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition interiorsintersect.hpp:1833
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
PointType_ PointType
Definition polyline.hpp:70
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition interiorsintersect.hpp:1848
constexpr std::size_t size() const
Returns the number of vertices in the polyline.
Definition polyline.hpp:388
constexpr bool interiorContains(const OtherPoint &point) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition interiorcontains.hpp:1581
constexpr auto edgesView() const
Returns a lazy view over the edges, materializing each Segment on the fly instead of allocating a vec...
Definition polyline.hpp:627
constexpr bool interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition ray.hpp:589
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 boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:254
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 isDegenerate() const
Returns whether the rectangle has empty interior.
Definition predicates.hpp:869
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 bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:551
constexpr std::array< PointType, 4 > vertices() const
Returns the four vertices in counterclockwise order.
Definition bounding.hpp:188
constexpr bool interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition rectangle.hpp:862
constexpr const PointType & max() const
Returns the maximum corner (max x, max y).
Definition rectangle.hpp:359
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
constexpr const PointType & max() const
Returns the largest stored endpoint.
Definition segment.hpp:199
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 interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition segment.hpp:736
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 const PointType & b() const
Returns the second vertex.
Definition triangle.hpp:217
constexpr std::array< PointType, 3 > vertices() const
Returns the vertices in canonical order.
Definition bounding.hpp:235
constexpr const PointType & a() const
Returns the first vertex.
Definition triangle.hpp:208
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:132
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:134
constexpr Rectangle< PointType > bbox() const
Returns the axis-aligned bounding box of the vertices.
Definition bounding.hpp:224
constexpr bool isDegenerate() const
Tests whether the three vertices are collinear.
Definition predicates.hpp:223
constexpr std::array< Segment< PointType >, 3 > edges() const
Returns the three unoriented boundary edges.
Definition bounding.hpp:240
constexpr const PointType & c() const
Returns the third vertex.
Definition triangle.hpp:226
constexpr bool interiorContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition triangle.hpp:806