Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
boundarycontains.hpp
Go to the documentation of this file.
1#pragma once
2
4
9
10#include <cstddef>
11#include <limits>
12#include "shape/segment.hpp"
14
15
16namespace pgl {
17
23
24template <class Number, class Label>
25template<PointConcept OtherPoint>
26constexpr bool Point<Number, Label>::boundaryContains(const OtherPoint&) const {
27 return false;
28}
29
30template <class Number, class Label>
31template<SegmentConcept OtherSegment>
32constexpr bool Point<Number, Label>::boundaryContains(const OtherSegment&) const {
33 return false;
34}
35
36template <class Number, class Label>
37template<OrientedSegmentConcept OtherOrientedSegment>
38constexpr bool Point<Number, Label>::boundaryContains(const OtherOrientedSegment&) const {
39 return false;
40}
41
42template <class Number, class Label>
43template<LineConcept OtherLine>
44constexpr bool Point<Number, Label>::boundaryContains(const OtherLine&) const {
45 return false;
46}
47
48template <class Number, class Label>
49template<OrientedLineConcept OtherOrientedLine>
50constexpr bool Point<Number, Label>::boundaryContains(const OtherOrientedLine&) const {
51 return false;
52}
53
54template <class Number, class Label>
55template<RayConcept OtherRay>
56constexpr bool Point<Number, Label>::boundaryContains(const OtherRay&) const {
57 return false;
58}
59
60template <class Number, class Label>
61template<HalfplaneConcept OtherHalfplane>
62constexpr bool Point<Number, Label>::boundaryContains(const OtherHalfplane&) const {
63 return false;
64}
65
66template <class Number, class Label>
67template<RectangleConcept OtherRectangle>
68constexpr bool Point<Number, Label>::boundaryContains(const OtherRectangle& other) const {
69 if (other.empty()) {
70 // The empty set is a subset of every shape, its boundary and its
71 // interior alike.
72 return true;
73 }
74 return false;
75}
76
77template <class Number, class Label>
78template<TriangleConcept OtherTriangle>
79constexpr bool Point<Number, Label>::boundaryContains(const OtherTriangle&) const {
80 return false;
81}
82
83template <class Number, class Label>
84template<ConvexConcept OtherConvex>
85constexpr bool Point<Number, Label>::boundaryContains(const OtherConvex&) const {
86 return false;
87}
88
89template <class Number, class Label>
90template<PolygonConcept OtherPolygon>
91constexpr bool Point<Number, Label>::boundaryContains(const OtherPolygon&) const {
92 return false;
93}
94
95template <class Number, class Label>
96template<DiskConcept OtherDisk>
97constexpr bool Point<Number, Label>::boundaryContains(const OtherDisk&) const {
98 return false;
99}
100
107
108template <class PointType, class LabelType>
109template<PointConcept OtherPoint>
110constexpr bool Segment<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
111 return verticesContain(point);
112}
113
114
120
121template <class PointType, class LabelType>
122template<PointConcept OtherPoint>
123constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
124 const auto boundary = edges();
125 return boundary[0].contains(point) || boundary[1].contains(point) || boundary[2].contains(point);
126}
127
128template <class PointType, class LabelType>
129template<SegmentConcept OtherSegment>
130constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherSegment& other) const {
131 return detail::polygonBoundaryContainsSegment(*this, other);
132}
133
134template <class PointType, class LabelType>
135template<OrientedSegmentConcept OtherOrientedSegment>
136constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherOrientedSegment& other) const {
138}
139
140template <class PointType, class LabelType>
141template<LineConcept OtherLine>
142constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherLine& other) const {
143 return other.isDegenerate() && boundaryContains(other.min());
144}
145
146template <class PointType, class LabelType>
147template<OrientedLineConcept OtherOrientedLine>
148constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherOrientedLine& other) const {
149 return other.isDegenerate() && boundaryContains(other.source());
150}
151
152template <class PointType, class LabelType>
153template<RayConcept OtherRay>
154constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherRay& other) const {
155 return other.isDegenerate() && boundaryContains(other.source());
156}
157
158template <class PointType, class LabelType>
159template<HalfplaneConcept OtherHalfplane>
160constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherHalfplane& other) const {
161 return other.isDegenerate() && boundaryContains(other.source());
162}
163
164template <class PointType, class LabelType>
165template<RectangleConcept OtherRectangle>
166constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherRectangle& other) const {
167 if (other.empty()) {
168 // The empty set is a subset of every shape, its boundary and its
169 // interior alike.
170 return true;
171 }
172 if (!other.isDegenerate()) {
173 return false;
174 }
175 if (other.min() == other.max()) {
176 return boundaryContains(other.min());
177 }
178 return boundaryContains(Segment<typename OtherRectangle::PointType>(other.min(), other.max()));
179}
180
181template <class PointType, class LabelType>
182template<TriangleConcept OtherTriangle>
183constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherTriangle& other) const {
184 if (!other.isDegenerate()) {
185 return false;
186 }
187 if (other.a() == other.c()) {
188 return boundaryContains(other.a());
189 }
191}
192
193template <class PointType, class LabelType>
194template<ConvexConcept OtherConvex>
195constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherConvex& other) const {
196 if (other.size() == 0) {
197 return true;
198 }
199 if (other.size() == 1) {
200 return boundaryContains(other[0]);
201 }
202 if (other.size() == 2) {
204 }
205 return false;
206}
207
213
214template <class PointType, class LabelType>
215template<PointConcept OtherPoint>
216constexpr bool OrientedSegment<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
217 return verticesContain(point);
218}
219
225
226template <class PointType, class LabelType>
227template<PointConcept OtherPoint>
228constexpr bool Line<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
229 (void)point;
230 return false;
231}
232
238
239template <class PointType, class LabelType>
240template<PointConcept OtherPoint>
241constexpr bool OrientedLine<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
242 (void)point;
243 return false;
244}
245
251
252template <class PointType, class LabelType>
253template<PointConcept OtherPoint>
254constexpr bool Ray<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
255 return point == source();
256}
257
263
264
265template <class PointType, class LabelType>
266template<PointConcept OtherPoint>
267constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
268 // The empty set has no boundary, and it needs no case of its own: it
269 // contains no point, so the rejection below already fires for it.
270 if (!contains(point)) {
271 return false;
272 }
273 return point.x() == min().x() ||
274 point.x() == max().x() ||
275 point.y() == min().y() ||
276 point.y() == max().y();
277}
278
279template <class PointType, class LabelType>
280template<SegmentConcept OtherSegment>
281constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherSegment& other) const {
282 if (empty()) {
283 // The empty set is a subset of itself and of nothing else.
284 return detail::coversNoPoint(other);
285 }
286 return detail::polygonBoundaryContainsSegment(*this, other);
287}
288
289template <class PointType, class LabelType>
290template<OrientedSegmentConcept OtherOrientedSegment>
291constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherOrientedSegment& other) const {
292 if (empty()) {
293 // The empty set is a subset of itself and of nothing else.
294 return detail::coversNoPoint(other);
295 }
297}
298
299template <class PointType, class LabelType>
300template<LineConcept OtherLine>
301constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherLine& other) const {
302 if (empty()) {
303 // The empty set is a subset of itself and of nothing else.
304 return detail::coversNoPoint(other);
305 }
306 return other.isDegenerate() && boundaryContains(other.min());
307}
308
309template <class PointType, class LabelType>
310template<OrientedLineConcept OtherOrientedLine>
311constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherOrientedLine& other) const {
312 if (empty()) {
313 // The empty set is a subset of itself and of nothing else.
314 return detail::coversNoPoint(other);
315 }
316 return other.isDegenerate() && boundaryContains(other.source());
317}
318
319template <class PointType, class LabelType>
320template<RayConcept OtherRay>
321constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherRay& other) const {
322 if (empty()) {
323 // The empty set is a subset of itself and of nothing else.
324 return detail::coversNoPoint(other);
325 }
326 return other.isDegenerate() && boundaryContains(other.source());
327}
328
329template <class PointType, class LabelType>
330template<HalfplaneConcept OtherHalfplane>
331constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherHalfplane& other) const {
332 if (empty()) {
333 // The empty set is a subset of itself and of nothing else.
334 return detail::coversNoPoint(other);
335 }
336 return other.isDegenerate() && boundaryContains(other.source());
337}
338
339template <class PointType, class LabelType>
340template<RectangleConcept OtherRectangle>
341constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherRectangle& other) const {
342 // A boundary is a curve, so only a degenerate operand can lie on one. That
343 // rejection is the common answer and comes first; an empty operand has
344 // inverted corners, hence is degenerate, and survives it to be answered
345 // below. An empty *this needs no case of its own: it covers no point, so
346 // the point and segment cases already answer false, which is the right
347 // answer for every non-empty operand.
348 if (!other.isDegenerate()) {
349 return false;
350 }
351 if (other.empty()) {
352 // The empty set is a subset of every shape, its boundary and its
353 // interior alike.
354 return true;
355 }
356 if (other.min() == other.max()) {
357 return boundaryContains(other.min());
358 }
359 return boundaryContains(Segment<typename OtherRectangle::PointType>(other.min(), other.max()));
360}
361
362template <class PointType, class LabelType>
363template<TriangleConcept OtherTriangle>
364constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherTriangle& other) const {
365 if (empty()) {
366 // The empty set is a subset of itself and of nothing else.
367 return detail::coversNoPoint(other);
368 }
369 if (!other.isDegenerate()) {
370 return false;
371 }
372 if (other.a() == other.c()) {
373 return boundaryContains(other.a());
374 }
376}
377
378template <class PointType, class LabelType>
379template<ConvexConcept OtherConvex>
380constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherConvex& other) const {
381 if (empty()) {
382 // The empty set is a subset of itself and of nothing else.
383 return detail::coversNoPoint(other);
384 }
385 if (other.size() == 0) {
386 return true;
387 }
388 if (other.size() == 1) {
389 return boundaryContains(other[0]);
390 }
391 if (other.size() == 2) {
393 }
394 return false;
395}
396
402
403template <class PointType, class LabelType>
404template<PointConcept OtherPoint>
405constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
406 if (isDegenerate()) {
407 return point == source();
408 }
409 return pgl::collinear(source(), target(), point);
410}
411
412template <class PointType, class LabelType>
413template<SegmentConcept OtherSegment>
414constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherSegment& other) const {
415 return boundaryContains(other.min()) && boundaryContains(other.max());
416}
417
418template <class PointType, class LabelType>
419template<LineConcept OtherLine>
420constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherLine& other) const {
421 return boundaryContains(other.min()) && boundaryContains(other.max());
422}
423
424template <class PointType, class LabelType>
425template<OrientedSegmentConcept OtherOrientedSegment>
426constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherOrientedSegment& other) const {
427 return boundaryContains(other.source()) && boundaryContains(other.target());
428}
429
430template <class PointType, class LabelType>
431template<OrientedLineConcept OtherOrientedLine>
432constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherOrientedLine& other) const {
433 return boundaryContains(other.source()) && boundaryContains(other.target());
434}
435
436template <class PointType, class LabelType>
437template<RayConcept OtherRay>
438constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherRay& other) const {
439 return boundaryContains(other.source()) && boundaryContains(other.target());
440}
441
442template <class PointType, class LabelType>
443template<RectangleConcept OtherRectangle>
444constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherRectangle& other) const {
445 if (other.empty()) {
446 // The empty set is a subset of every shape, its boundary and its
447 // interior alike.
448 return true;
449 }
450 return other.isDegenerate() &&
451 boundaryContains(other.min()) &&
452 boundaryContains(other.max());
453}
454
455template <class PointType, class LabelType>
456template<TriangleConcept OtherTriangle>
457constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherTriangle& other) const {
458 return other.isDegenerate() &&
459 boundaryContains(other.a()) &&
460 boundaryContains(other.b()) &&
461 boundaryContains(other.c());
462}
463
464template <class PointType, class LabelType>
465template<HalfplaneConcept OtherHalfplane>
466constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherHalfplane& other) const {
467 return other.isDegenerate() &&
468 boundaryContains(other.source()) &&
469 boundaryContains(other.target());
470}
471
472// -----------------------------------------------------------------------------
473// Disk
474//
475// Every overload opens with the two degenerate readings. A disk of radius zero
476// is the point a() — never a segment (doc/raw/shapes.md) — and a shape that has
477// dropped below its natural dimension is entirely boundary, so boundaryContains
478// coincides with contains there. Testing a() == b() alone settles it without
479// ever reading c(): a() == b() == c() is that radius-zero disk, and a() == b()
480// with c() elsewhere is undefined, so answering as if it were the point a() is
481// one of the answers the contract allows. A disk whose three defining points
482// are collinear but distinct determines no circle and is likewise undefined;
483// reading it as the line through them is another terminating answer. That line
484// is degenerate when a() == c(), and then holds every point of the plane —
485// still an answer, still terminating, and only ever reached on undefined input.
486
487template <class PointType, class LabelType>
488template<PointConcept OtherPoint>
489constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
490 if (a() == b()) {
491 return contains(point);
492 }
493 if (isDegenerate()) {
494 return Line<PointType>(a(), c()).contains(point);
495 }
496
497 return inCircleSign(a(), b(), c(), point) == std::partial_ordering::equivalent;
498}
499
500template <class PointType, class LabelType>
501template<SegmentConcept OtherSegment>
502constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherSegment& other) const {
503 if (a() == b()) {
504 return contains(other);
505 }
506 if (isDegenerate()) {
507 return Line<PointType>(a(), c()).contains(other);
508 }
509 return other.isDegenerate() && boundaryContains(other.min());
510}
511
512template <class PointType, class LabelType>
513template<OrientedSegmentConcept OtherOrientedSegment>
514constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherOrientedSegment& other) const {
515 if (a() == b()) {
516 return contains(other);
517 }
518 if (isDegenerate()) {
519 return Line<PointType>(a(), c()).contains(other);
520 }
521 return other.isDegenerate() && boundaryContains(other.source());
522}
523
524template <class PointType, class LabelType>
525template<LineConcept OtherLine>
526constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherLine& other) const {
527 if (a() == b()) {
528 return contains(other);
529 }
530 if (isDegenerate()) {
531 return Line<PointType>(a(), c()).contains(other);
532 }
533 return other.isDegenerate() && boundaryContains(other.min());
534}
535
536template <class PointType, class LabelType>
537template<OrientedLineConcept OtherOrientedLine>
538constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherOrientedLine& other) const {
539 if (a() == b()) {
540 return contains(other);
541 }
542 if (isDegenerate()) {
543 return Line<PointType>(a(), c()).contains(other);
544 }
545 return other.isDegenerate() && boundaryContains(other.source());
546}
547
548template <class PointType, class LabelType>
549template<RayConcept OtherRay>
550constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherRay& other) const {
551 if (a() == b()) {
552 return contains(other);
553 }
554 if (isDegenerate()) {
555 return Line<PointType>(a(), c()).contains(other);
556 }
557 return other.isDegenerate() && boundaryContains(other.source());
558}
559
560template <class PointType, class LabelType>
561template<HalfplaneConcept OtherHalfplane>
562constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherHalfplane& other) const {
563 if (a() == b()) {
564 return contains(other);
565 }
566 if (isDegenerate()) {
567 return Line<PointType>(a(), c()).contains(other);
568 }
569 return other.isDegenerate() && boundaryContains(other.source());
570}
571
572template <class PointType, class LabelType>
573template<TriangleConcept OtherTriangle>
574constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherTriangle& other) const {
575 if (a() == b()) {
576 return contains(other);
577 }
578 if (isDegenerate()) {
579 return Line<PointType>(a(), c()).contains(other);
580 }
581
582 const bool is_point = other.a() == other.b() && other.a() == other.c();
583 return is_point && boundaryContains(other.a());
584}
585
586template <class PointType, class LabelType>
587template<RectangleConcept OtherRectangle>
588constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherRectangle& other) const {
589 if (other.empty()) {
590 // The empty set is a subset of every shape, its boundary and its
591 // interior alike.
592 return true;
593 }
594 if (a() == b()) {
595 return contains(other);
596 }
597 if (isDegenerate()) {
598 return Line<PointType>(a(), c()).contains(other);
599 }
600
601 const bool is_point = other.min() == other.max();
602 return is_point && boundaryContains(other.min());
603}
604
605template <class PointType, class LabelType>
606template<ConvexConcept OtherConvex>
607constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherConvex& other) const {
608 if (a() == b()) {
609 return contains(other);
610 }
611 if (isDegenerate()) {
612 return Line<PointType>(a(), c()).contains(other);
613 }
614
615 return other.size() == 1 && boundaryContains(other[0]);
616}
617
618template <class PointType, class LabelType>
619template<DiskConcept OtherDisk>
620constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherDisk& other) const {
621 // A collapsed disk is the point a(), never a segment; its boundary circle
622 // has shrunk to that same point.
623 if (other.a() == other.b()) {
624 return boundaryContains(other.a());
625 }
626 // Any disk that has not collapsed covers area, and a circle covers none, so
627 // no boundary holds it -- not even the circle bounding it. Testing that the
628 // three points of `other` lie on this circle would only say the two disks
629 // share a boundary, which is not the same as the filled `other` lying on it:
630 // a disk is never a subset of its own boundary. An undefined `other` -- three
631 // collinear but distinct points, read elsewhere as the line through them --
632 // determines no circle either, and false is one of the answers its contract
633 // allows.
634 return false;
635}
636
637template <class PointType, class LabelType>
639 return std::visit(
640 [this](const auto& value) {
641 return this->boundaryContains(value);
642 },
643 other.variant());
644}
645
646
647// ---------------------------------------------------------------------------
648// Convex
649
650template <class PointType, class LabelType>
651template<PointConcept OtherPoint>
652constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
653 if (points_.empty()) {
654 return false;
655 }
656 if (!bbox().contains(point)) {
657 return false;
658 }
659
660 using CommonNumberType = std::common_type_t<NumberType, typename OtherPoint::NumberType>;
661 Point<CommonNumberType> translatedPoint = static_cast<Point<CommonNumberType>>(point) -
662 static_cast<Point<CommonNumberType>>(translation_);
663
664 if (points_.size() == 1) {
665 return translatedPoint == points_[0];
666 }
667 if (points_.size() == 2) {
668 return Segment<PointType>(points_[0], points_[1]).contains(translatedPoint);
669 }
670 if (points_.size() == 3) {
671 return Segment<PointType>(points_[0], points_[1]).contains(translatedPoint) ||
672 Segment<PointType>(points_[1], points_[2]).contains(translatedPoint) ||
673 Segment<PointType>(points_[2], points_[0]).contains(translatedPoint);
674 }
675
676 const size_t max_i = maxIndex();
677
678 auto o = orientationSign(points_[0], points_[max_i], translatedPoint);
679
680 if (o < 0) {
681 auto it_end = points_.begin() + max_i + 1;
682 auto it = std::lower_bound(points_.begin(), it_end, translatedPoint, lexLessCrossType);
683 if (it == it_end) {
684 return false;
685 }
686 if (it == points_.begin()) {
687 return *it == translatedPoint;
688 }
689
690 return Segment<PointType>(*(it - 1), *it).contains(translatedPoint);
691 }
692
693 if (o > 0) {
694 auto it_begin = std::make_reverse_iterator(points_.end());
695 auto it_end = std::make_reverse_iterator(points_.begin() + max_i);
696
697 auto it = std::lower_bound(it_begin, it_end, translatedPoint, lexLessCrossType);
698
699 if (it == it_end) {
700 return false;
701 }
702 if (it == it_begin) { // Check the edge between the first and last vertices
703 return Segment<PointType>(*it, points_[0]).contains(translatedPoint);
704 }
705
706 return Segment<PointType>(*(it - 1), *it).contains(translatedPoint);
707 }
708
709 // o == 0: the point is collinear with the two x-extreme vertices. That
710 // line is an actual boundary edge only when one hull degenerates to the
711 // single edge v[0]-v[max_i] (the extremes are cyclically adjacent);
712 // otherwise it is an interior diagonal and only the endpoints qualify.
713 if (max_i == 1 || max_i + 1 == points_.size()) {
714 return Segment<PointType>(points_[0], points_[max_i]).contains(translatedPoint);
715 }
716 return translatedPoint == points_[0] || translatedPoint == points_[max_i];
717}
718
719template <class PointType, class LabelType>
720template<SegmentConcept OtherSegment>
721constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherSegment& other) const {
722 if (other.isDegenerate()) {
723 return boundaryContains(other.min());
724 }
725 if (isDegenerate()) {
726 // A hull with empty interior is entirely boundary, so boundary
727 // containment coincides with containment.
728 return contains(other);
729 }
730 for (const auto &edgePair : {edgesAtX(other.min().x()), edgesAtX(other.max().x())}) {
731 if (!edgePair) {
732 return false;
733 }
734 if (other.isVertical()) {
735 Segment<PointType> edge1(get(0),get(-1));
736 if (edge1.contains(other)) {
737 return true;
738 }
739
740 ptrdiff_t i = maxIndex();
741 Segment<PointType> edge2(get(i),get(i-1));
742 if (edge2.contains(other)) {
743 return true;
744 }
745 return false;
746 }
747 for (const auto &edge : *edgePair) {
748 if (edge.contains(other)) {
749 return true;
750 }
751 }
752 }
753 return false;
754}
755
756template <class PointType, class LabelType>
757template<OrientedSegmentConcept OtherOrientedSegment>
758constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherOrientedSegment& other) const {
760}
761
762template <class PointType, class LabelType>
763template<LineConcept OtherLine>
764constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherLine& other) const {
765 return other.isDegenerate() && boundaryContains(other.min());
766}
767
768template <class PointType, class LabelType>
769template<OrientedLineConcept OtherOrientedLine>
770constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherOrientedLine& other) const {
771 return other.isDegenerate() && boundaryContains(other.source());
772}
773
774template <class PointType, class LabelType>
775template<RayConcept OtherRay>
776constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherRay& other) const {
777 return other.isDegenerate() && boundaryContains(other.source());
778}
779
780template <class PointType, class LabelType>
781template<HalfplaneConcept OtherHalfplane>
782constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherHalfplane& other) const {
783 return other.isDegenerate() && boundaryContains(other.source());
784}
785
786template <class PointType, class LabelType>
787template<RectangleConcept OtherRectangle>
788constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherRectangle& other) const {
789 if (other.empty()) {
790 // The empty set is a subset of every shape, its boundary and its
791 // interior alike.
792 return true;
793 }
794 if (!other.isDegenerate()) {
795 return false;
796 }
797 if (other.min() == other.max()) {
798 return boundaryContains(other.min());
799 }
800 return boundaryContains(Segment<typename OtherRectangle::PointType>(other.min(), other.max()));
801}
802
803template <class PointType, class LabelType>
804template<TriangleConcept OtherTriangle>
805constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherTriangle& other) const {
806 if (!other.isDegenerate()) {
807 return false;
808 }
809 if (other.a() == other.c()) {
810 return boundaryContains(other.a());
811 }
813}
814
815template <class PointType, class LabelType>
816template<ConvexConcept OtherConvex>
817constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherConvex& other) const {
818 if (other.size() == 0) {
819 return true;
820 }
821 if (other.size() == 1) {
822 return boundaryContains(other[0]);
823 }
824 if (other.size() == 2) {
826 }
827 return false;
828}
829
830template <class PointType, class LabelType>
831template<DiskConcept OtherDisk>
832constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherDisk& other) const {
833 if (other[0] == other[1] && other[0] == other[2]) {
834 return boundaryContains(other[0]);
835 }
836 return false;
837}
838
839template <class PointType, class LabelType>
840template <PointConcept OtherPoint>
842 return std::visit(
843 [this](const auto& value) {
844 return this->boundaryContains(value);
845 },
846 other.variant());
847}
848
849
850// ---------------------------------------------------------------------------
851// Polygon
852
853template <class PointType, class LabelType>
854template<PointConcept OtherPoint>
855constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
856 const std::size_t n = size();
857 if (n == 0) {
858 return false;
859 }
860 // Containment is translation-invariant, so test against the raw points_ in
861 // the polygon's untranslated frame (cf. contains). A single-vertex polygon
862 // is handled by its degenerate edge (a point), so no special case is needed.
863 const auto p = point - translation_;
864 for (std::size_t i = 0; i < n; ++i) {
865 if (Segment<PointType>(points_[i], points_[(i + 1) % n]).contains(p)) {
866 return true;
867 }
868 }
869 return false;
870}
871
872template <class PointType, class LabelType>
873template<SegmentConcept OtherSegment>
874constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherSegment& other) const {
875 if (other.isDegenerate()) {
876 return boundaryContains(other.min());
877 }
878 // A straight segment on the boundary of a simple polygon (one without
879 // straight-angle vertices) lies within a single edge, mirroring the
880 // single-edge containment used by Convex::boundaryContains.
881 for (const auto& edge : edgesView()) {
882 if (edge.contains(other)) {
883 return true;
884 }
885 }
886 return false;
887}
888
889template <class PointType, class LabelType>
890template<OrientedSegmentConcept OtherOrientedSegment>
891constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherOrientedSegment& other) const {
893}
894
895template <class PointType, class LabelType>
896template<LineConcept OtherLine>
897constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherLine& other) const {
898 return other.isDegenerate() && boundaryContains(other.min());
899}
900
901template <class PointType, class LabelType>
902template<OrientedLineConcept OtherOrientedLine>
903constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherOrientedLine& other) const {
904 return other.isDegenerate() && boundaryContains(other.source());
905}
906
907template <class PointType, class LabelType>
908template<RayConcept OtherRay>
909constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherRay& other) const {
910 return other.isDegenerate() && boundaryContains(other.source());
911}
912
913template <class PointType, class LabelType>
914template<HalfplaneConcept OtherHalfplane>
915constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherHalfplane& other) const {
916 return other.isDegenerate() && boundaryContains(other.source());
917}
918
919template <class PointType, class LabelType>
920template<RectangleConcept OtherRectangle>
921constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherRectangle& other) const {
922 if (other.empty()) {
923 // The empty set is a subset of every shape, its boundary and its
924 // interior alike.
925 return true;
926 }
927 if (!other.isDegenerate()) {
928 return false;
929 }
930 if (other.min() == other.max()) {
931 return boundaryContains(other.min());
932 }
933 return boundaryContains(Segment<typename OtherRectangle::PointType>(other.min(), other.max()));
934}
935
936template <class PointType, class LabelType>
937template<TriangleConcept OtherTriangle>
938constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherTriangle& other) const {
939 if (!other.isDegenerate()) {
940 return false;
941 }
942 if (other.a() == other.c()) {
943 return boundaryContains(other.a());
944 }
946}
947
948template <class PointType, class LabelType>
949template<ConvexConcept OtherConvex>
950constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherConvex& other) const {
951 if (other.size() == 0) {
952 return true;
953 }
954 if (other.size() == 1) {
955 return boundaryContains(other[0]);
956 }
957 if (other.size() == 2) {
959 }
960 return false;
961}
962
963template <class PointType, class LabelType>
964template<PolygonConcept OtherPolygon>
965constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherPolygon& other) const {
966 if (other.size() == 0) {
967 return true;
968 }
969 if (other.size() == 1) {
970 return boundaryContains(other[0]);
971 }
972 if (other.size() == 2) {
974 }
975 return false;
976}
977
978template <class PointType, class LabelType>
979template<DiskConcept OtherDisk>
980constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherDisk& other) const {
981 if (other[0] == other[1] && other[0] == other[2]) {
982 return boundaryContains(other[0]);
983 }
984 return false;
985}
986
987template <class PointType, class LabelType>
988template<PointConcept OtherPoint>
990 return std::visit(
991 [this](const auto& value) {
992 return this->boundaryContains(value);
993 },
994 other.variant());
995}
996
997template <class PointType, class LabelType>
998template<PolygonConcept OtherPolygon>
999constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherPolygon& other) const {
1000 if (other.size() == 0) {
1001 return true;
1002 }
1003 if (other.size() == 1) {
1004 return boundaryContains(other[0]);
1005 }
1006 if (other.size() == 2) {
1008 }
1009 return false;
1010}
1011
1012template <class PointType, class LabelType>
1013template<PolygonConcept OtherPolygon>
1014constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherPolygon& other) const {
1015 if (a() == b()) {
1016 return contains(other);
1017 }
1018 if (isDegenerate()) {
1019 return Line<PointType>(a(), c()).contains(other);
1020 }
1021 return other.size() == 1 && boundaryContains(other[0]);
1022}
1023
1024
1025// --- asymmetric Disk/Polygon boundary containment ---
1026//
1027// A triangle and a rectangle share the boundary of their convex-polygon view,
1028// so they defer to it. The Convex implementation captures the only ways a 2D
1029// shape can lie on a 1D boundary: a disk degenerated to a single boundary point,
1030// or a polygon of zero/one/two vertices reducing to a point or boundary segment.
1031
1032template <class PointType, class LabelType>
1033template<DiskConcept OtherDisk>
1034constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherDisk& other) const {
1035 return other[0] == other[1] && other[1]==other[2] && boundaryContains(other[0]);
1036}
1037
1038template <class PointType, class LabelType>
1039template<PolygonConcept OtherPolygon>
1040constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherPolygon& other) const {
1041 return asConvex().boundaryContains(other);
1042}
1043
1044template <class PointType, class LabelType>
1045template<PolygonConcept OtherPolygon>
1046constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherPolygon& other) const {
1047 if (empty()) {
1048 // The empty set is a subset of itself and of nothing else.
1049 return detail::coversNoPoint(other);
1050 }
1051 return asConvex().boundaryContains(other);
1052}
1053
1054template <class PointType, class LabelType>
1055template<DiskConcept OtherDisk>
1056constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherDisk& other) const {
1057 if (empty()) {
1058 // The empty set is a subset of itself and of nothing else.
1059 return detail::coversNoPoint(other);
1060 }
1061 return other[0] == other[1] && other[1]==other[2] && boundaryContains(other[0]);
1062}
1063
1064// ---------------------------------------------------------------------------
1065// boundaryContains(Shape): runtime dispatch over the wrapped alternative, for
1066// the shapes that did not previously expose a Shape overload.
1067// ---------------------------------------------------------------------------
1068
1069template <class Number, class Label>
1070constexpr bool Point<Number, Label>::boundaryContains(const Shape<Point<Number, Label>>& other) const {
1071 return std::visit(
1072 [this](const auto& value) {
1073 return this->boundaryContains(value);
1074 },
1075 other.variant());
1076}
1077
1078template <class PointType, class LabelType>
1079template<PointConcept OtherPoint>
1081 return std::visit(
1082 [this](const auto& value) {
1083 return this->boundaryContains(value);
1084 },
1085 other.variant());
1086}
1087
1088template <class PointType, class LabelType>
1090 return std::visit(
1091 [this](const auto& value) {
1092 return this->boundaryContains(value);
1093 },
1094 other.variant());
1095}
1096
1097template <class PointType, class LabelType>
1099 return std::visit(
1100 [this](const auto& value) {
1101 return this->boundaryContains(value);
1102 },
1103 other.variant());
1104}
1105
1106template <class PointType, class LabelType>
1108 return std::visit(
1109 [this](const auto& value) {
1110 return this->boundaryContains(value);
1111 },
1112 other.variant());
1113}
1114
1115template <class PointType, class LabelType>
1117 return std::visit(
1118 [this](const auto& value) {
1119 return this->boundaryContains(value);
1120 },
1121 other.variant());
1122}
1123
1124template <class PointType, class LabelType>
1126 return std::visit(
1127 [this](const auto& value) {
1128 return this->boundaryContains(value);
1129 },
1130 other.variant());
1131}
1132
1133template <class PointType, class LabelType>
1135 return std::visit(
1136 [this](const auto& value) {
1137 return this->boundaryContains(value);
1138 },
1139 other.variant());
1140}
1141
1142template <class PointType, class LabelType>
1144 return std::visit(
1145 [this](const auto& value) {
1146 return this->boundaryContains(value);
1147 },
1148 other.variant());
1149}
1150
1156
1157template <class PointType, class LabelType, class Storage>
1158template<PointConcept OtherPoint>
1159constexpr bool MonotoneChain<PointType, LabelType, Storage>::boundaryContains(const OtherPoint& point) const {
1160 if (points_.empty()) {
1161 return false;
1162 }
1163 return point == points_.front() + translation_ || point == points_.back() + translation_;
1164}
1165
1166template <class PointType, class LabelType, class Storage>
1167template<PointConcept OtherPoint>
1169 return std::visit(
1170 [this](const auto& value) {
1171 return this->boundaryContains(value);
1172 },
1173 other.variant());
1174}
1175
1176template <class Number, class Label>
1177template<MonotoneChainConcept OtherChain>
1178constexpr bool Point<Number, Label>::boundaryContains(const OtherChain&) const {
1179 return false;
1180}
1181
1182template <class PointType, class LabelType>
1183template<MonotoneChainConcept OtherChain>
1184constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherChain& other) const {
1185 // The boundary is a line (a convex set), so it contains the chain iff it
1186 // contains every vertex.
1187 for (const auto& vertex : other) {
1188 if (!boundaryContains(vertex)) {
1189 return false;
1190 }
1191 }
1192 return true;
1193}
1194
1195template <class PointType, class LabelType>
1196template<MonotoneChainConcept OtherChain>
1197constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherChain& other) const {
1198 if (empty()) {
1199 // The empty set is a subset of itself and of nothing else.
1200 return detail::coversNoPoint(other);
1201 }
1202 return asConvex().boundaryContains(other);
1203}
1204
1205template <class PointType, class LabelType>
1206template<MonotoneChainConcept OtherChain>
1207constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherChain& other) const {
1208 return asConvex().boundaryContains(other);
1209}
1210
1211template <class PointType, class LabelType>
1212template<MonotoneChainConcept OtherChain>
1213constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherChain& other) const {
1214 if (a() == b()) {
1215 return contains(other);
1216 }
1217 if (isDegenerate()) {
1218 return Line<PointType>(a(), c()).contains(other);
1219 }
1220 // Chain edges are straight, so only a single vertex can lie on the circle.
1221 return other.empty() || (other.size() == 1 && boundaryContains(other[0]));
1222}
1223
1224template <class PointType, class LabelType>
1225template<MonotoneChainConcept OtherChain>
1226constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherChain& other) const {
1227 // A chain may run along the convex boundary through many collinear
1228 // vertices, so fold the edges rather than counting vertices.
1229 if (other.empty()) {
1230 return true;
1231 }
1232 if (other.size() == 1) {
1233 return boundaryContains(other[0]);
1234 }
1235 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
1236 if (!boundaryContains(Segment<typename OtherChain::PointType>(other[i], other[i + 1]))) {
1237 return false;
1238 }
1239 }
1240 return true;
1241}
1242
1243template <class PointType, class LabelType>
1244template<MonotoneChainConcept OtherChain>
1245constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherChain& other) const {
1246 if (other.empty()) {
1247 return true;
1248 }
1249 if (other.size() == 1) {
1250 return boundaryContains(other[0]);
1251 }
1252 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
1253 if (!boundaryContains(Segment<typename OtherChain::PointType>(other[i], other[i + 1]))) {
1254 return false;
1255 }
1256 }
1257 return true;
1258}
1259
1265
1266template <class PointType, class LabelType>
1267template<PointConcept OtherPoint>
1268constexpr bool Polyline<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
1269 if (points_.empty()) {
1270 return false;
1271 }
1272 return point == points_.front() + translation_ || point == points_.back() + translation_;
1273}
1274
1275template <class PointType, class LabelType>
1276template<PointConcept OtherPoint>
1278 return std::visit(
1279 [this](const auto& value) {
1280 return this->boundaryContains(value);
1281 },
1282 other.variant());
1283}
1284
1285template <class Number, class Label>
1286template<PolylineConcept OtherPolyline>
1287constexpr bool Point<Number, Label>::boundaryContains(const OtherPolyline&) const {
1288 return false;
1289}
1290
1291template <class PointType, class LabelType>
1292template<PolylineConcept OtherPolyline>
1293constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherPolyline& other) const {
1294 // The boundary is a line (a convex set), so it contains the polyline iff
1295 // it contains every vertex.
1296 for (const auto& vertex : other) {
1297 if (!boundaryContains(vertex)) {
1298 return false;
1299 }
1300 }
1301 return true;
1302}
1303
1304template <class PointType, class LabelType>
1305template<PolylineConcept OtherPolyline>
1306constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherPolyline& other) const {
1307 if (empty()) {
1308 // The empty set is a subset of itself and of nothing else.
1309 return detail::coversNoPoint(other);
1310 }
1311 return asConvex().boundaryContains(other);
1312}
1313
1314template <class PointType, class LabelType>
1315template<PolylineConcept OtherPolyline>
1316constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherPolyline& other) const {
1317 return asConvex().boundaryContains(other);
1318}
1319
1320template <class PointType, class LabelType>
1321template<PolylineConcept OtherPolyline>
1322constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherPolyline& other) const {
1323 if (a() == b()) {
1324 return contains(other);
1325 }
1326 if (isDegenerate()) {
1327 return Line<PointType>(a(), c()).contains(other);
1328 }
1329 // Polyline edges are straight, so only a polyline covering a single point
1330 // can lie on the circle.
1331 return other.empty() || (other.isDegenerate() && boundaryContains(other[0]));
1332}
1333
1334template <class PointType, class LabelType>
1335template<PolylineConcept OtherPolyline>
1336constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherPolyline& other) const {
1337 // A polyline may run along the convex boundary through many collinear
1338 // vertices, so fold the edges rather than counting vertices.
1339 if (other.empty()) {
1340 return true;
1341 }
1342 if (other.size() == 1) {
1343 return boundaryContains(other[0]);
1344 }
1345 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
1346 if (!boundaryContains(Segment<typename OtherPolyline::PointType>(other[i], other[i + 1]))) {
1347 return false;
1348 }
1349 }
1350 return true;
1351}
1352
1353template <class PointType, class LabelType>
1354template<PolylineConcept OtherPolyline>
1355constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherPolyline& other) const {
1356 if (other.empty()) {
1357 return true;
1358 }
1359 if (other.size() == 1) {
1360 return boundaryContains(other[0]);
1361 }
1362 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
1363 if (!boundaryContains(Segment<typename OtherPolyline::PointType>(other[i], other[i + 1]))) {
1364 return false;
1365 }
1366 }
1367 return true;
1368}
1369
1370
1371// ---------------------------------------------------------------------------
1372// HalfplaneIntersection
1373
1374template <class PointType, class LabelType>
1375template <PointConcept OtherPoint>
1376constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
1377 // A degenerate region equals its own boundary, and every point of a
1378 // degenerate region lies on some constraint boundary, so the status test
1379 // covers that case with no special handling.
1380 return pointStatus(point) == 0;
1381}
1382
1383template <class PointType, class LabelType>
1384template <SegmentConcept OtherSegment>
1385constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherSegment& other) const {
1386 if (empty()) {
1387 return false;
1388 }
1389 // A segment on the boundary lies on a single constraint's boundary line
1390 // (at most two stored constraints are parallel to it), inside the region.
1391 // A degenerate region is its own boundary, and it always stores its
1392 // carrier line's constraints, so the same test applies.
1393 const Halfplane<typename OtherSegment::PointType> along(other.min(), other.max());
1394 if (along.isDegenerate()) {
1395 return boundaryContains(other.min());
1396 }
1397 for (const std::ptrdiff_t idx : {sameDirectionIndex(along), sameDirectionIndex(along.opposite())}) {
1398 if (idx >= 0 && constraintSide(static_cast<std::size_t>(idx), other.min()) == 0 &&
1399 constraintSide(static_cast<std::size_t>(idx), other.max()) == 0) {
1400 return contains(other.min()) && contains(other.max());
1401 }
1402 }
1403 return false;
1404}
1405
1406template <class PointType, class LabelType>
1407template <OrientedSegmentConcept OtherOrientedSegment>
1408constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherOrientedSegment& other) const {
1410}
1411
1412template <class PointType, class LabelType>
1413template <LineConcept OtherLine>
1414constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherLine& other) const {
1415 // A full line on the boundary must be a constraint's entire boundary
1416 // line, and the region must contain it (so no other constraint clips it).
1417 if (empty() || !contains(other)) {
1418 return false;
1419 }
1420 for (const auto& halfplane : halfplanes_) {
1421 if (halfplane.boundaryContains(other)) {
1422 return true;
1423 }
1424 }
1425 return false;
1426}
1427
1428template <class PointType, class LabelType>
1429template <OrientedLineConcept OtherOrientedLine>
1430constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherOrientedLine& other) const {
1431 return boundaryContains(other.asLine());
1432}
1433
1434template <class PointType, class LabelType>
1435template <RayConcept OtherRay>
1436constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherRay& other) const {
1437 // The ray must lie inside the region and on some constraint's boundary
1438 // line; a degenerate region containing the ray stores its carrier line's
1439 // constraints, so the boundary-line scan below finds it there too.
1440 if (empty() || !contains(other)) {
1441 return false;
1442 }
1443 const Halfplane<typename OtherRay::PointType> along(other.source(), other.target());
1444 for (const std::ptrdiff_t idx : {sameDirectionIndex(along), sameDirectionIndex(along.opposite())}) {
1445 if (idx >= 0 && halfplanes_[static_cast<std::size_t>(idx)].boundaryContains(other)) {
1446 return true;
1447 }
1448 }
1449 return false;
1450}
1451
1452template <class PointType, class LabelType>
1453template <HalfplaneConcept OtherHalfplane>
1454constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherHalfplane&) const {
1455 // The boundary of the region is at most one-dimensional (and empty for
1456 // the whole plane), so it never contains a two-dimensional half-plane.
1457 return false;
1458}
1459
1460template <class PointType, class LabelType>
1461template <RectangleConcept OtherRectangle>
1462constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherRectangle& other) const {
1463 if (other.empty()) {
1464 // The empty set is a subset of every shape, its boundary and its
1465 // interior alike.
1466 return true;
1467 }
1468 // Only a degenerate rectangle — the segment between its corners — can lie
1469 // on the at most one-dimensional boundary.
1470 if (!other.isDegenerate()) {
1471 return false;
1472 }
1473 return boundaryContains(Segment<typename OtherRectangle::PointType>(other.min(), other.max()));
1474}
1475
1476template <class PointType, class LabelType>
1477template <TriangleConcept OtherTriangle>
1478constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherTriangle&) const {
1479 // A (non-degenerate) triangle is two-dimensional and the boundary of the
1480 // region is at most one-dimensional.
1481 return false;
1482}
1483
1484template <class PointType, class LabelType>
1485template <DiskConcept OtherDisk>
1486constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherDisk& other) const {
1487 // Only a degenerate disk — a single point — can lie on the boundary.
1488 if (!other.isDegenerate()) {
1489 return false;
1490 }
1491 return boundaryContains(other.template center<typename OtherDisk::NumberType>());
1492}
1493
1494template <class PointType, class LabelType>
1495template <ConvexConcept OtherConvex>
1496constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherConvex& other) const {
1497 // Only the empty polygon and a degenerate one — a point or the segment
1498 // between its extremes — can lie on the at most one-dimensional boundary.
1499 if (other.size() == 0) {
1500 return true;
1501 }
1502 if (!other.isDegenerate()) {
1503 return false;
1504 }
1505 if (other.size() == 1) {
1506 return boundaryContains(other[0]);
1507 }
1508 return boundaryContains(Segment<typename OtherConvex::PointType>(other[0], other[other.size() - 1]));
1509}
1510
1511template <class PointType, class LabelType>
1512template <MonotoneChainConcept OtherChain>
1513constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherChain& other) const {
1514 if (other.size() == 0) {
1515 return true;
1516 }
1517 if (other.size() == 1) {
1518 return boundaryContains(other[0]);
1519 }
1520 for (const auto& edge : other.edgesView()) {
1521 if (!boundaryContains(edge)) {
1522 return false;
1523 }
1524 }
1525 return true;
1526}
1527
1528template <class PointType, class LabelType>
1529template <PolylineConcept OtherPolyline>
1530constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherPolyline& other) const {
1531 if (other.size() == 0) {
1532 return true;
1533 }
1534 if (other.size() == 1) {
1535 return boundaryContains(other[0]);
1536 }
1537 for (const auto& edge : other.edgesView()) {
1538 if (!boundaryContains(edge)) {
1539 return false;
1540 }
1541 }
1542 return true;
1543}
1544
1545template <class PointType, class LabelType>
1546template <PolygonConcept OtherPolygon>
1547constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherPolygon& other) const {
1548 // Only the empty polygon and a degenerate (zero-area) one — the union of
1549 // its edges — can lie on the at most one-dimensional boundary.
1550 if (other.size() == 0) {
1551 return true;
1552 }
1553 if (other.size() == 1) {
1554 return boundaryContains(other[0]);
1555 }
1556 if (!other.isDegenerate()) {
1557 return false;
1558 }
1559 for (const auto& edge : other.edgesView()) {
1560 if (!boundaryContains(edge)) {
1561 return false;
1562 }
1563 }
1564 return true;
1565}
1566
1567template <class PointType, class LabelType>
1568template <HalfplaneIntersectionConcept OtherRegion>
1569constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1570 // The empty region lies on every boundary; a full-dimensional region
1571 // never fits in the at most one-dimensional boundary; a degenerate region
1572 // reduces to its carrier shape.
1573 if (other.empty()) {
1574 return true;
1575 }
1576 if (!other.isDegenerate()) {
1577 return false;
1578 }
1579 return std::visit([this](const auto& carrier) { return this->boundaryContains(carrier); },
1580 detail::degenerateRegionCarrier(other));
1581}
1582
1583template <class PointType, class LabelType>
1584template <PointConcept OtherPoint>
1586 return std::visit(
1587 [this](const auto& value) {
1588 return this->boundaryContains(value);
1589 },
1590 other.variant());
1591}
1592
1593
1594// ---------------------------------------------------------------------------
1595// Reverse direction: lower-ranked shapes' boundaries containing a
1596// HalfplaneIntersection.
1597//
1598// The empty region is a subset of every boundary; a full-dimensional region
1599// is never contained in an at most one-dimensional boundary; a degenerate
1600// region reduces to its carrier shape.
1601
1602namespace detail {
1603
1604// Dispatches boundaryContains(carrier) over the degenerate region's carrier,
1605// with per-alternative availability: a bounded shape's boundary never
1606// contains a ray or a line, so those alternatives short-circuit to false
1607// unless the shape declares the corresponding overload.
1608template <class Shape2, class Region>
1609constexpr bool boundaryContainsDegenerateRegion(const Shape2& shape, const Region& region) {
1610 return std::visit(
1611 [&shape](const auto& carrier) {
1612 if constexpr (requires { shape.boundaryContains(carrier); }) {
1613 return shape.boundaryContains(carrier);
1614 } else {
1615 (void)carrier;
1616 return false; // no overload: geometrically impossible containment
1617 }
1618 },
1619 degenerateRegionCarrier(region));
1620}
1621
1622} // namespace detail
1623
1624template <class Number, class Label>
1625template <HalfplaneIntersectionConcept OtherRegion>
1626constexpr bool Point<Number, Label>::boundaryContains(const OtherRegion& other) const {
1627 if (other.empty()) {
1628 return true;
1629 }
1630 if (!other.isDegenerate()) {
1631 return false;
1632 }
1633 return detail::boundaryContainsDegenerateRegion(*this, other);
1634}
1635
1636template <class PointType, class LabelType>
1637template <HalfplaneIntersectionConcept OtherRegion>
1638constexpr bool Segment<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1639 if (other.empty()) {
1640 return true;
1641 }
1642 if (!other.isDegenerate()) {
1643 return false;
1644 }
1645 return detail::boundaryContainsDegenerateRegion(*this, other);
1646}
1647
1648template <class PointType, class LabelType>
1649template <HalfplaneIntersectionConcept OtherRegion>
1650constexpr bool OrientedSegment<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1651 return asSegment().boundaryContains(other);
1652}
1653
1654template <class PointType, class LabelType>
1655template <HalfplaneIntersectionConcept OtherRegion>
1656constexpr bool Line<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1657 if (other.empty()) {
1658 return true;
1659 }
1660 if (!other.isDegenerate()) {
1661 return false;
1662 }
1663 return detail::boundaryContainsDegenerateRegion(*this, other);
1664}
1665
1666template <class PointType, class LabelType>
1667template <HalfplaneIntersectionConcept OtherRegion>
1668constexpr bool OrientedLine<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1669 return asLine().boundaryContains(other);
1670}
1671
1672template <class PointType, class LabelType>
1673template <HalfplaneIntersectionConcept OtherRegion>
1674constexpr bool Ray<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1675 if (other.empty()) {
1676 return true;
1677 }
1678 if (!other.isDegenerate()) {
1679 return false;
1680 }
1681 return detail::boundaryContainsDegenerateRegion(*this, other);
1682}
1683
1684template <class PointType, class LabelType>
1685template <HalfplaneIntersectionConcept OtherRegion>
1686constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1687 if (other.empty()) {
1688 return true;
1689 }
1690 if (!other.isDegenerate()) {
1691 return false;
1692 }
1693 return detail::boundaryContainsDegenerateRegion(*this, other);
1694}
1695
1696template <class PointType, class LabelType>
1697template <HalfplaneIntersectionConcept OtherRegion>
1698constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1699 if (empty()) {
1700 // The empty set is a subset of itself and of nothing else.
1701 return detail::coversNoPoint(other);
1702 }
1703 if (other.empty()) {
1704 return true;
1705 }
1706 if (!other.isDegenerate()) {
1707 return false;
1708 }
1709 return detail::boundaryContainsDegenerateRegion(*this, other);
1710}
1711
1712template <class PointType, class LabelType>
1713template <HalfplaneIntersectionConcept OtherRegion>
1714constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1715 if (other.empty()) {
1716 return true;
1717 }
1718 if (!other.isDegenerate()) {
1719 return false;
1720 }
1721 return detail::boundaryContainsDegenerateRegion(*this, other);
1722}
1723
1724template <class PointType, class LabelType>
1725template <HalfplaneIntersectionConcept OtherRegion>
1726constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1727 if (other.empty()) {
1728 return true;
1729 }
1730 if (!other.isDegenerate()) {
1731 return false;
1732 }
1733 // The circle contains no straight piece of positive length, so only a
1734 // point-carrier region can lie on it.
1735 return std::visit(
1736 [this](const auto& carrier) {
1737 using Carrier = std::remove_cvref_t<decltype(carrier)>;
1738 if constexpr (detail::is_point_v<Carrier>) {
1739 return this->boundaryContains(carrier);
1740 } else {
1741 (void)carrier;
1742 return false;
1743 }
1744 },
1745 detail::degenerateRegionCarrier(other));
1746}
1747
1748template <class PointType, class LabelType>
1749template <HalfplaneIntersectionConcept OtherRegion>
1750constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1751 if (other.empty()) {
1752 return true;
1753 }
1754 if (!other.isDegenerate()) {
1755 return false;
1756 }
1757 return detail::boundaryContainsDegenerateRegion(*this, other);
1758}
1759
1760template <class PointType, class LabelType, class Storage>
1761template <HalfplaneIntersectionConcept OtherRegion>
1762constexpr bool MonotoneChain<PointType, LabelType, Storage>::boundaryContains(const OtherRegion& other) const {
1763 if (other.empty()) {
1764 return true;
1765 }
1766 if (!other.isDegenerate()) {
1767 return false;
1768 }
1769 return detail::boundaryContainsDegenerateRegion(*this, other);
1770}
1771
1772template <class PointType, class LabelType>
1773template <HalfplaneIntersectionConcept OtherRegion>
1774constexpr bool Polyline<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1775 if (other.empty()) {
1776 return true;
1777 }
1778 if (!other.isDegenerate()) {
1779 return false;
1780 }
1781 return detail::boundaryContainsDegenerateRegion(*this, other);
1782}
1783
1784template <class PointType, class LabelType>
1785template <HalfplaneIntersectionConcept OtherRegion>
1786constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1787 if (other.empty()) {
1788 return true;
1789 }
1790 if (!other.isDegenerate()) {
1791 return false;
1792 }
1793 return detail::boundaryContainsDegenerateRegion(*this, other);
1794}
1795
1796
1797// ---------------------------------------------------------------------------
1798// PolygonWithHoles
1799
1800template <class PointType, class LabelType>
1801template <PointConcept OtherPoint>
1802constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherPoint& point) const {
1803 if (outer_.boundaryContains(point)) {
1804 return true;
1805 }
1806 for (const auto& hole : holes_) {
1807 if (hole.boundaryContains(point)) {
1808 return true;
1809 }
1810 }
1811 return false;
1812}
1813
1814template <class PointType, class LabelType>
1815template <SegmentConcept OtherSegment>
1816constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherSegment& other) const {
1817 if (other.isDegenerate()) {
1818 return boundaryContains(other.min());
1819 }
1820 // ∂A = A ∖ A°, so a segment lies on the boundary exactly when the region
1821 // contains it while its relative interior never reaches the region
1822 // interior. Testing it this way rather than edge by edge also accepts a
1823 // segment covered jointly by several collinear ring edges.
1824 return contains(other) && !interiorsIntersect(other);
1825}
1826
1827template <class PointType, class LabelType>
1828template <OrientedSegmentConcept OtherOrientedSegment>
1829constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherOrientedSegment& other) const {
1830 return boundaryContains(other.asSegment());
1831}
1832
1833// The boundary of a bounded region is bounded, so an unbounded operand fits on
1834// it only after collapsing to a point.
1835template <class PointType, class LabelType>
1836template <LineConcept OtherLine>
1837constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherLine& other) const {
1838 return other.isDegenerate() && boundaryContains(other.min());
1839}
1840
1841template <class PointType, class LabelType>
1842template <OrientedLineConcept OtherOrientedLine>
1843constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherOrientedLine& other) const {
1844 return other.isDegenerate() && boundaryContains(other.source());
1845}
1846
1847template <class PointType, class LabelType>
1848template <RayConcept OtherRay>
1849constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherRay& other) const {
1850 return other.isDegenerate() && boundaryContains(other.source());
1851}
1852
1853template <class PointType, class LabelType>
1854template <HalfplaneConcept OtherHalfplane>
1855constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherHalfplane& other) const {
1856 return other.isDegenerate() && boundaryContains(other.source());
1857}
1858
1859// ∂A is a finite union of segments and so has no area, which rules out any
1860// operand that has some. A collapsed operand is exactly the union of its edges,
1861// so the segment overload settles it one edge at a time — and that overload
1862// already accepts an edge covered jointly by several collinear ring edges.
1863template <class PointType, class LabelType>
1864template <class OtherArea>
1865constexpr bool PolygonWithHoles<PointType, LabelType>::areaBoundaryContains(const OtherArea& other) const {
1866 if (!other.isDegenerate()) {
1867 return false;
1868 }
1869 for (const auto& edge : other.edges()) {
1870 if (!boundaryContains(edge)) {
1871 return false;
1872 }
1873 }
1874 return true;
1875}
1876
1877template <class PointType, class LabelType>
1878template <RectangleConcept OtherRectangle>
1879constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherRectangle& other) const {
1880 if (other.empty()) {
1881 // The empty set is a subset of every shape, its boundary and its
1882 // interior alike.
1883 return true;
1884 }
1885 return areaBoundaryContains(other);
1886}
1887
1888template <class PointType, class LabelType>
1889template <TriangleConcept OtherTriangle>
1890constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherTriangle& other) const {
1891 return areaBoundaryContains(other);
1892}
1893
1894template <class PointType, class LabelType>
1895template <ConvexConcept OtherConvex>
1896constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherConvex& other) const {
1897 return areaBoundaryContains(other);
1898}
1899
1900template <class PointType, class LabelType>
1901template <PolygonConcept OtherPolygon>
1902constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherPolygon& other) const {
1903 return areaBoundaryContains(other);
1904}
1905
1906template <class PointType, class LabelType>
1907template <PolygonWithHolesConcept OtherRegion>
1908constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1909 return areaBoundaryContains(other);
1910}
1911
1912// A chain is the union of its edges, so it lies on ∂A exactly when every edge
1913// does (see @ref chainRelation).
1914template <class PointType, class LabelType>
1915template <MonotoneChainConcept OtherChain>
1916constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherChain& other) const {
1917 return chainRelation(other, true,
1918 [this](const auto& edge) { return this->boundaryContains(edge); });
1919}
1920
1921template <class PointType, class LabelType>
1922template <PolylineConcept OtherPolyline>
1923constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherPolyline& other) const {
1924 return chainRelation(other, true,
1925 [this](const auto& edge) { return this->boundaryContains(edge); });
1926}
1927
1928// ∂A is a finite union of segments, so it holds no disk with any area; a
1929// degenerate disk is the point a() (radius zero) or undefined.
1930template <class PointType, class LabelType>
1931template <DiskConcept OtherDisk>
1932constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherDisk& other) const {
1933 if (!other.isDegenerate()) {
1934 return false;
1935 }
1936 return boundaryContains(other.a());
1937}
1938
1939template <class PointType, class LabelType>
1940template <HalfplaneIntersectionConcept OtherIntersection>
1941constexpr bool PolygonWithHoles<PointType, LabelType>::boundaryContains(const OtherIntersection& other) const {
1942 if (other.empty()) {
1943 return true;
1944 }
1945 if (!other.isDegenerate()) {
1946 return false; // it has area, and ∂A has none
1947 }
1948 return degenerateIntersectionRelation(
1949 other, [this](const auto& carrier) { return this->boundaryContains(carrier); });
1950}
1951
1952
1953// ---------------------------------------------------------------------------
1954// Reverse direction: lower-ranked shapes' boundaries containing a
1955// PolygonWithHoles.
1956//
1957// One rewriting settles every shape here, because it asks nothing of the
1958// shape at all. A boundary is at most one-dimensional, so it can hold the
1959// region only when the region has no area — and a region with no area is
1960// exactly the union of its ring edges (detail::everyHoledRegionEdge). So the
1961// question is edge by edge, and each edge goes to the shape's own
1962// boundaryContains(Segment).
1963//
1964// Note what this does *not* do: forward to the outer polygon. The outer
1965// polygon of a zero-area region need not be zero-area itself — a hole may
1966// cover it entirely, leaving the ring as the whole region — and a boundary
1967// that holds the ring does not hold the polygon the ring bounds.
1968
1969namespace detail {
1970
1971// ∂shape ⊇ region, for any shape offering boundaryContains(Segment).
1972template <class Shape2, class HoledRegion>
1973constexpr bool boundaryContainsHoledRegion(const Shape2& shape, const HoledRegion& region) {
1974 if (!region.isDegenerate()) {
1975 return false; // the region has area; the boundary has none
1976 }
1977 return everyHoledRegionEdge(
1978 region, [&shape](const auto& edge) { return shape.boundaryContains(edge); });
1979}
1980
1981} // namespace detail
1982
1983template <class Number, class Label>
1984template <PolygonWithHolesConcept OtherRegion>
1985constexpr bool Point<Number, Label>::boundaryContains(const OtherRegion& other) const {
1986 return detail::boundaryContainsHoledRegion(*this, other);
1987}
1988
1989template <class PointType, class LabelType>
1990template <PolygonWithHolesConcept OtherRegion>
1991constexpr bool Segment<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1992 return detail::boundaryContainsHoledRegion(*this, other);
1993}
1994
1995template <class PointType, class LabelType>
1996template <PolygonWithHolesConcept OtherRegion>
1997constexpr bool OrientedSegment<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
1998 return asSegment().boundaryContains(other);
1999}
2000
2001template <class PointType, class LabelType>
2002template <PolygonWithHolesConcept OtherRegion>
2003constexpr bool Line<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2004 return detail::boundaryContainsHoledRegion(*this, other);
2005}
2006
2007template <class PointType, class LabelType>
2008template <PolygonWithHolesConcept OtherRegion>
2009constexpr bool OrientedLine<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2010 return asLine().boundaryContains(other);
2011}
2012
2013template <class PointType, class LabelType>
2014template <PolygonWithHolesConcept OtherRegion>
2015constexpr bool Ray<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2016 return detail::boundaryContainsHoledRegion(*this, other);
2017}
2018
2019template <class PointType, class LabelType>
2020template <PolygonWithHolesConcept OtherRegion>
2021constexpr bool Halfplane<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2022 return detail::boundaryContainsHoledRegion(*this, other);
2023}
2024
2025template <class PointType, class LabelType>
2026template <PolygonWithHolesConcept OtherRegion>
2027constexpr bool Rectangle<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2028 if (empty()) {
2029 // The empty set is a subset of itself and of nothing else.
2030 return detail::coversNoPoint(other);
2031 }
2032 return detail::boundaryContainsHoledRegion(*this, other);
2033}
2034
2035template <class PointType, class LabelType>
2036template <PolygonWithHolesConcept OtherRegion>
2037constexpr bool Triangle<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2038 return detail::boundaryContainsHoledRegion(*this, other);
2039}
2040
2041template <class PointType, class LabelType>
2042template <PolygonWithHolesConcept OtherRegion>
2043constexpr bool Disk<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2044 return detail::boundaryContainsHoledRegion(*this, other);
2045}
2046
2047template <class PointType, class LabelType>
2048template <PolygonWithHolesConcept OtherRegion>
2049constexpr bool Convex<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2050 return detail::boundaryContainsHoledRegion(*this, other);
2051}
2052
2053template <class PointType, class LabelType, class Storage>
2054template <PolygonWithHolesConcept OtherRegion>
2055constexpr bool MonotoneChain<PointType, LabelType, Storage>::boundaryContains(const OtherRegion& other) const {
2056 return detail::boundaryContainsHoledRegion(*this, other);
2057}
2058
2059template <class PointType, class LabelType>
2060template <PolygonWithHolesConcept OtherRegion>
2061constexpr bool Polyline<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2062 return detail::boundaryContainsHoledRegion(*this, other);
2063}
2064
2065template <class PointType, class LabelType>
2066template <PolygonWithHolesConcept OtherRegion>
2067constexpr bool Polygon<PointType, LabelType>::boundaryContains(const OtherRegion& other) const {
2068 return detail::boundaryContainsHoledRegion(*this, other);
2069}
2070
2071template <class PointType, class LabelType>
2072template <PolygonWithHolesConcept OtherHoledRegion>
2073constexpr bool HalfplaneIntersection<PointType, LabelType>::boundaryContains(const OtherHoledRegion& other) const {
2074 return detail::boundaryContainsHoledRegion(*this, other);
2075}
2076
2077// ---------------------------------------------------------------------------
2078// Runtime Shape argument: unwrap the stored alternative and re-dispatch. Every
2079// alternative has a per-shape overload above, so no fallback is needed.
2080
2081template <class PointType, class LabelType>
2082template <PointConcept OtherPoint>
2084 return std::visit(
2085 [this](const auto& value) {
2086 return this->boundaryContains(value);
2087 },
2088 other.variant());
2089}
2090
2091
2092// ---------------------------------------------------------------------------
2093// PolygonSet
2094//
2095// `∂A = ⋃ ∂Aᵢ`: a point on a component's boundary is in the set's interior only
2096// if another component fills the far side of it, which would mean a shared
2097// stretch of edge — what PolygonSet::isValid rules out. The union has no area,
2098// so only a collapsed operand can lie on it, and the one-dimensional case is
2099// the same one PolygonSet::contains has: a segment may run from one component's
2100// boundary onto another's through a point where the two touch.
2101
2102template <class PointType, class LabelType>
2103template <detail::SetOperandConcept OtherShape>
2104bool PolygonSet<PointType, LabelType>::boundaryContains(const OtherShape& other) const {
2105 if constexpr (PointConcept<OtherShape>) {
2106 return anyComponent([&](const ComponentType& c) { return c.boundaryContains(other); });
2107 } else if constexpr (LineConcept<OtherShape>) {
2108 return other.isDegenerate() && boundaryContains(other.min());
2109 } else if constexpr (OrientedLineConcept<OtherShape>) {
2110 return other.isDegenerate() && boundaryContains(other.source());
2111 } else if constexpr (RayConcept<OtherShape>) {
2112 return other.isDegenerate() && boundaryContains(other.source());
2113 } else if constexpr (HalfplaneConcept<OtherShape>) {
2114 // A half-plane is unbounded unless it has collapsed onto its own
2115 // boundary line, which the line path then settles.
2116 return other.isDegenerate() && boundaryContains(other.source());
2118 if (anyComponent([&](const ComponentType& c) { return c.boundaryContains(other); })) {
2119 return true;
2120 }
2121 if (!isPinched() || !intersects(other)) {
2122 return false;
2123 }
2124 return segmentIn(other, /*boundaryOnly=*/true);
2126 if (anyComponent([&](const ComponentType& c) { return c.boundaryContains(other); })) {
2127 return true;
2128 }
2129 if (!isPinched()) {
2130 return false;
2131 }
2132 return chainIn(other, /*boundaryOnly=*/true);
2133 } else {
2134 if (anyComponent([&](const ComponentType& c) { return c.boundaryContains(other); })) {
2135 return true;
2136 }
2137 return detail::reduceDegenerate(
2138 other, [this](const auto& carrier) { return this->boundaryContains(carrier); });
2139 }
2140}
2141
2142template <class PointType, class LabelType>
2143template <PolygonSetConcept OtherSet>
2145 for (const auto& component : other) {
2147 return false;
2148 }
2149 }
2150 return true;
2151}
2152
2153template <class PointType, class LabelType>
2154template <PointConcept OtherPoint>
2156 return std::visit([this](const auto& value) { return this->boundaryContains(value); },
2157 other.variant());
2158}
2159
2160} // namespace pgl
Definition forward.hpp:312
Definition forward.hpp:309
Definition forward.hpp:320
Definition forward.hpp:310
Definition forward.hpp:308
Definition forward.hpp:306
Definition forward.hpp:321
Definition forward.hpp:311
Definition forward.hpp:307
Definition arrangement.hpp:67
constexpr std::partial_ordering inCircleSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c, const Point< DNumber, DLabel > &d)
Classifies a point with respect to the circumcircle of three others.
Definition orientation.hpp:894
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
Line() -> Line< Point<>, NoLabel >
constexpr std::partial_ordering orientationSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Classifies the orientation of three points.
Definition orientation.hpp:544
constexpr bool collinear(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Tests whether three points are collinear.
Definition orientation.hpp:651
Small dispatch traits and geometry helpers reused by the implementations.
Public declaration of pgl::Segment.
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the convex polygon.
Definition bounding.hpp:374
constexpr PointType get(std::ptrdiff_t index) const
Cyclic access: same as operator[] but index is taken modulo size(); negative indices wrap from the en...
Definition convex.hpp:289
constexpr bool isDegenerate() const
Checks if the convex polygon is degenerate (has zero area).
Definition predicates.hpp:982
constexpr size_t maxIndex() const
Returns the index of the maximum vertex (rightmost and highest in case of ties).
Definition predicates.hpp:1022
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1135
constexpr std::optional< std::array< Segment< PointType >, 2 > > edgesAtX(OtherNumberType x) const
Returns two edges of the convex polygon that intersect with the vertical line at x.
Definition atxy.hpp:289
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:652
constexpr bool boundaryContains(const EmptyShape< EmptyPoint > &) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition disk.hpp:904
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 contains(const OtherPoint &other) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1015
constexpr const PointType & a() const
Returns the first boundary point (lexicographically smallest).
Definition disk.hpp:228
constexpr const PointType & b() const
Returns the second boundary point in canonical order.
Definition disk.hpp:235
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:2511
constexpr bool empty() const
Returns whether the region is the empty set.
Definition halfplaneintersection.hpp:649
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:1376
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 Halfplane opposite() const
Returns the complementary half-plane with reversed boundary orientation.
Definition halfplane.hpp:223
constexpr const PointType & target() const
Returns the target boundary point.
Definition halfplane.hpp:193
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:405
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
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:228
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:1159
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:241
constexpr Line< PointType > asLine() const
Returns the line without orientation.
Definition orientedline.hpp:321
constexpr bool verticesContain(const OtherPoint &point) const
Returns whether one endpoint equals the given point.
Definition predicates.hpp:315
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:216
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 boundaryContains(const Shape< Point< TNumber, TLabel > > &other) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
bool intersects(const OtherShape &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:2234
constexpr const ComponentType & component(std::size_t index) const
Accesses a component by index.
Definition polygonset.hpp:271
bool isPinched() const
Tests whether two components touch each other anywhere.
Definition contains.hpp:3368
PolygonWithHoles< PointType > ComponentType
Definition polygonset.hpp:169
bool boundaryContains(const OtherShape &other) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:2104
constexpr bool interiorsIntersect(const OtherPoint &) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition polygonwithholes.hpp:1660
constexpr const PolygonType & hole(std::size_t index) const
Accesses a hole by index.
Definition polygonwithholes.hpp:196
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:1802
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:2945
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 contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1296
constexpr auto edgesView() const
Returns a lazy view over the edges, materializing each Segment on the fly instead of allocating a vec...
Definition polygon.hpp:782
constexpr std::size_t size() const
Returns the number of vertices in the polygon.
Definition polygon.hpp:259
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:1268
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:254
constexpr const PointType & source() const
Returns the source point of the ray.
Definition ray.hpp:181
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 Convex< PointType > asConvex() const
Returns the rectangle as a convex polygon.
Definition rectangle.hpp:690
constexpr bool boundaryContains(const Shape< PointType > &other) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:1134
constexpr const PointType & max() const
Returns the maximum corner (max x, max y).
Definition rectangle.hpp:359
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:728
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
constexpr bool verticesContain(const OtherPoint &point) const
Returns whether one endpoint equals the given point.
Definition predicates.hpp:149
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:119
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:110
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
constexpr bool boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:123
constexpr Convex< PointType > asConvex() const
Returns the triangle as a convex polygon.
Definition triangle.hpp:490
constexpr std::array< Segment< PointType >, 3 > edges() const
Returns the three unoriented boundary edges.
Definition bounding.hpp:240