Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
contains.hpp
Go to the documentation of this file.
1#pragma once
2
4
9
10#include <limits>
13
14
15namespace pgl {
16
22
23template <class Number, class Label>
24template<PointConcept OtherPoint>
25constexpr bool Point<Number, Label>::contains(const OtherPoint& other) const {
26 using Compare = std::common_type_t<Number, typename OtherPoint::NumberType>;
27 return detail::asNumber<Compare>(x()) == detail::asNumber<Compare>(other.x()) &&
28 detail::asNumber<Compare>(y()) == detail::asNumber<Compare>(other.y());
29}
30
31template <class Number, class Label>
32template<SegmentConcept OtherSegment>
33constexpr bool Point<Number, Label>::contains(const OtherSegment& other) const {
34 return other.isDegenerate() && contains(other.min());
35}
36
37template <class Number, class Label>
38template<OrientedSegmentConcept OtherOrientedSegment>
39constexpr bool Point<Number, Label>::contains(const OtherOrientedSegment& other) const {
40 return other.isDegenerate() && contains(other.source());
41}
42
43template <class Number, class Label>
44template<LineConcept OtherLine>
45constexpr bool Point<Number, Label>::contains(const OtherLine& other) const {
46 return other.isDegenerate() && contains(other.min());
47}
48
49template <class Number, class Label>
50template<OrientedLineConcept OtherOrientedLine>
51constexpr bool Point<Number, Label>::contains(const OtherOrientedLine& other) const {
52 return other.isDegenerate() && contains(other.source());
53}
54
55template <class Number, class Label>
56template<RayConcept OtherRay>
57constexpr bool Point<Number, Label>::contains(const OtherRay& other) const {
58 return other.isDegenerate() && contains(other.source());
59}
60
61template <class Number, class Label>
62template<HalfplaneConcept OtherHalfplane>
63constexpr bool Point<Number, Label>::contains(const OtherHalfplane& other) const {
64 return other.isDegenerate() && contains(other.source());
65}
66
67template <class Number, class Label>
68template<RectangleConcept OtherRectangle>
69constexpr bool Point<Number, Label>::contains(const OtherRectangle& other) const {
70 if (other.empty()) {
71 // The empty set is a subset of every shape, its boundary and its
72 // interior alike.
73 return true;
74 }
75 return contains(other.min()) && contains(other.max());
76}
77
78template <class Number, class Label>
79template<TriangleConcept OtherTriangle>
80constexpr bool Point<Number, Label>::contains(const OtherTriangle& other) const {
81 const auto vertices = other.vertices();
82 return contains(vertices[0]) && contains(vertices[1]) && contains(vertices[2]);
83}
84
85template <class Number, class Label>
86template<ConvexConcept OtherConvex>
87constexpr bool Point<Number, Label>::contains(const OtherConvex& other) const {
88 return other.size()== 0 || (other.size() == 1 && other[0]==*this);
89}
90
91template <class Number, class Label>
92template<DiskConcept OtherDisk>
93constexpr bool Point<Number, Label>::contains(const OtherDisk& other) const {
94 // A non-degenerate disk has positive area, so a single point can only
95 // contain a disk that has collapsed to a point, which is the case a() == b()
96 // (a disk is never a segment). A disk with a() == b() != c() is undefined,
97 // and reading it as the point a() is one answer the contract allows.
98 return other.a() == other.b() && contains(other.a());
99}
100
101template <class Number, class Label>
102constexpr bool Point<Number, Label>::contains(const Shape<Point<Number, Label>>& other) const {
103 return std::visit(
104 [this](const auto& value) {
105 return this->contains(value);
106 },
107 other.variant());
108}
109
116
117template <class PointType, class LabelType>
118template<PointConcept OtherPoint>
119constexpr bool Segment<PointType, LabelType>::contains(const OtherPoint& point) const {
120 if (point < min() || max() < point) {
121 return false;
122 }
123 return pgl::collinear(min(), max(), point);
124}
125
126template <class PointType, class LabelType>
127template<SegmentConcept OtherSegment>
128constexpr bool Segment<PointType, LabelType>::contains(const OtherSegment& other) const {
129 return contains(other.min()) && contains(other.max());
130}
131
132template <class PointType, class LabelType>
133template<OrientedSegmentConcept OtherOrientedSegment>
134constexpr bool Segment<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
135 return contains(other.source()) && contains(other.target());
136}
137
138template <class PointType, class LabelType>
139template<LineConcept OtherLine>
140constexpr bool Segment<PointType, LabelType>::contains(const OtherLine& other) const {
141 return other.isDegenerate() && contains(other.min());
142}
143
144template <class PointType, class LabelType>
145template<OrientedLineConcept OtherOrientedLine>
146constexpr bool Segment<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
147 return other.isDegenerate() && contains(other.source());
148}
149
150template <class PointType, class LabelType>
151template<RayConcept OtherRay>
152constexpr bool Segment<PointType, LabelType>::contains(const OtherRay& other) const {
153 return other.isDegenerate() && contains(other.source());
154}
155
156template <class PointType, class LabelType>
157template<HalfplaneConcept OtherHalfplane>
158constexpr bool Segment<PointType, LabelType>::contains(const OtherHalfplane& other) const {
159 return other.isDegenerate() && contains(other.source());
160}
161
162template <class PointType, class LabelType>
163template<RectangleConcept OtherRectangle>
164constexpr bool Segment<PointType, LabelType>::contains(const OtherRectangle& other) const {
165 if (other.empty()) {
166 // The empty set is a subset of every shape, its boundary and its
167 // interior alike.
168 return true;
169 }
170 if (!other.isDegenerate()) {
171 return false;
172 }
173 if (other.min() == other.max()) {
174 return contains(other.min());
175 }
176 return contains(Segment<typename OtherRectangle::PointType>(other.min(), other.max()));
177}
178
179template <class PointType, class LabelType>
180template<TriangleConcept OtherTriangle>
181constexpr bool Segment<PointType, LabelType>::contains(const OtherTriangle& other) const {
182 return contains(other.a()) && contains(other.b()) && contains(other.c());
183}
184
185template <class PointType, class LabelType>
186template<ConvexConcept OtherConvex>
187constexpr bool Segment<PointType, LabelType>::contains(const OtherConvex& other) const {
188 if (other.size() > 2) {
189 return false;
190 }
191 for (std::size_t i = 0; i < other.size(); ++i) {
192 if (!contains(other[i])) {
193 return false;
194 }
195 }
196 return true;
197}
198
199template <class PointType, class LabelType>
200template<DiskConcept OtherDisk>
201constexpr bool Segment<PointType, LabelType>::contains(const OtherDisk& other) const {
202 return other.a() == other.b() && other.b() == other.c() && contains(other.a());
203}
204
205template <class PointType, class LabelType>
206template<PointConcept OtherPoint>
208 return std::visit(
209 [this](const auto& value) {
210 return this->contains(value);
211 },
212 other.variant());
213}
214
220
221template <class PointType, class LabelType>
222template<PointConcept OtherPoint>
223constexpr bool Triangle<PointType, LabelType>::contains(const OtherPoint& point) const {
224 if (isDegenerate()) {
225 return boundaryContains(point);
226 }
227 const auto o1 = orientationSign(a(), b(), point);
228 const auto o2 = orientationSign(b(), c(), point);
229 const auto o3 = orientationSign(c(), a(), point);
230 const bool has_negative =
231 o1 == std::partial_ordering::less ||
232 o2 == std::partial_ordering::less ||
233 o3 == std::partial_ordering::less;
234 const bool has_positive =
235 o1 == std::partial_ordering::greater ||
236 o2 == std::partial_ordering::greater ||
237 o3 == std::partial_ordering::greater;
238 return !(has_negative && has_positive);
239}
240
241template <class PointType, class LabelType>
242template<SegmentConcept OtherSegment>
243constexpr bool Triangle<PointType, LabelType>::contains(const OtherSegment& other) const {
244 return contains(other.min()) && contains(other.max());
245}
246
247template <class PointType, class LabelType>
248template<OrientedSegmentConcept OtherOrientedSegment>
249constexpr bool Triangle<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
250 return contains(other.source()) && contains(other.target());
251}
252
253template <class PointType, class LabelType>
254template<LineConcept OtherLine>
255constexpr bool Triangle<PointType, LabelType>::contains(const OtherLine& other) const {
256 return other.isDegenerate() && contains(other.min());
257}
258
259template <class PointType, class LabelType>
260template<OrientedLineConcept OtherOrientedLine>
261constexpr bool Triangle<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
262 return other.isDegenerate() && contains(other.source());
263}
264
265template <class PointType, class LabelType>
266template<RayConcept OtherRay>
267constexpr bool Triangle<PointType, LabelType>::contains(const OtherRay& other) const {
268 return other.isDegenerate() && contains(other.source());
269}
270
271template <class PointType, class LabelType>
272template<HalfplaneConcept OtherHalfplane>
273constexpr bool Triangle<PointType, LabelType>::contains(const OtherHalfplane& other) const {
274 return other.isDegenerate() && contains(other.source());
275}
276
277template <class PointType, class LabelType>
278template<RectangleConcept OtherRectangle>
279constexpr bool Triangle<PointType, LabelType>::contains(const OtherRectangle& other) const {
280 if (other.empty()) {
281 // The empty set is a subset of every shape, its boundary and its
282 // interior alike.
283 return true;
284 }
285 const auto vertices = other.vertices();
286 for (const auto& vertex : vertices) {
287 if (!contains(vertex)) {
288 return false;
289 }
290 }
291 return true;
292}
293
294template <class PointType, class LabelType>
295template<TriangleConcept OtherTriangle>
296constexpr bool Triangle<PointType, LabelType>::contains(const OtherTriangle& other) const {
297 return contains(other.a()) && contains(other.b()) && contains(other.c());
298}
299
300template <class PointType, class LabelType>
301template<ConvexConcept OtherConvex>
302constexpr bool Triangle<PointType, LabelType>::contains(const OtherConvex& other) const {
303 if (other.size() == 0) {
304 return true;
305 }
306 if (!bbox().contains(other.bbox())) {
307 return false;
308 }
309 for (std::size_t i = 0; i < other.size(); ++i) {
310 if (!contains(other[i])) {
311 return false;
312 }
313 }
314 return true;
315}
316
317template <class PointType, class LabelType>
318template<DiskConcept OtherDisk>
319constexpr bool Triangle<PointType, LabelType>::contains(const OtherDisk& other) const {
320 if (const auto center = other.getIfPoint()) {
321 // A radius-zero disk is its center, and contains(Point) already reads a
322 // degenerate triangle as its carrier segment.
323 return contains(*center);
324 }
325 // A non-degenerate triangle behaves exactly like its convex view.
326 return asConvex().contains(other);
327}
328
329template <class PointType, class LabelType>
331 return std::visit(
332 [this](const auto& value) {
333 return this->contains(value);
334 },
335 other.variant());
336}
337
343
344template <class PointType, class LabelType>
345template<PointConcept OtherPoint>
346constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherPoint& point) const {
347 return asSegment().contains(point);
348}
349
350template <class PointType, class LabelType>
351template<SegmentConcept OtherSegment>
352constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherSegment& other) const {
353 return asSegment().contains(other);
354}
355
356template <class PointType, class LabelType>
357template<OrientedSegmentConcept OtherOrientedSegment>
358constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
359 return asSegment().contains(other);
360}
361
362template <class PointType, class LabelType>
363template<LineConcept OtherLine>
364constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherLine& other) const {
365 return asSegment().contains(other);
366}
367
368template <class PointType, class LabelType>
369template<OrientedLineConcept OtherOrientedLine>
370constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
371 return asSegment().contains(other);
372}
373
374template <class PointType, class LabelType>
375template<RayConcept OtherRay>
376constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherRay& other) const {
377 return asSegment().contains(other);
378}
379
380template <class PointType, class LabelType>
381template<HalfplaneConcept OtherHalfplane>
382constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherHalfplane& other) const {
383 return asSegment().contains(other);
384}
385
386template <class PointType, class LabelType>
387template<RectangleConcept OtherRectangle>
388constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherRectangle& other) const {
389 if (other.empty()) {
390 // The empty set is a subset of every shape, its boundary and its
391 // interior alike.
392 return true;
393 }
394 return asSegment().contains(other);
395}
396
397template <class PointType, class LabelType>
398template<TriangleConcept OtherTriangle>
399constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherTriangle& other) const {
400 return asSegment().contains(other);
401}
402
403template <class PointType, class LabelType>
404template<ConvexConcept OtherConvex>
405constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherConvex& other) const {
406 return asSegment().contains(other);
407}
408
409template <class PointType, class LabelType>
410template<DiskConcept OtherDisk>
411constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherDisk& other) const {
412 return asSegment().contains(other);
413}
414
415template <class PointType, class LabelType>
417 return asSegment().contains(other);
418}
419
425
426template <class PointType, class LabelType>
427template<PointConcept OtherPoint>
428constexpr bool Line<PointType, LabelType>::contains(const OtherPoint& point) const {
429 if (isDegenerate()) {
430 return point == min();
431 }
432 return pgl::collinear(min(), max(), point);
433}
434
435template <class PointType, class LabelType>
436template<LineConcept OtherLine>
437constexpr bool Line<PointType, LabelType>::contains(const OtherLine& other) const {
438 return contains(other.min()) && contains(other.max());
439}
440
441template <class PointType, class LabelType>
442template<SegmentConcept OtherSegment>
443constexpr bool Line<PointType, LabelType>::contains(const OtherSegment& other) const {
444 return contains(other.min()) && contains(other.max());
445}
446
447template <class PointType, class LabelType>
448template<OrientedSegmentConcept OtherOrientedSegment>
449constexpr bool Line<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
450 return contains(other.source()) && contains(other.target());
451}
452
453template <class PointType, class LabelType>
454template<OrientedLineConcept OtherOrientedLine>
455constexpr bool Line<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
456 return contains(other.asLine());
457}
458
459template <class PointType, class LabelType>
460template<RayConcept OtherRay>
461constexpr bool Line<PointType, LabelType>::contains(const OtherRay& other) const {
462 return contains(other.source()) && contains(other.target());
463}
464
465template <class PointType, class LabelType>
466template<HalfplaneConcept OtherHalfplane>
467constexpr bool Line<PointType, LabelType>::contains(const OtherHalfplane& other) const {
468 return other.isDegenerate() && contains(other.source());
469}
470
471template <class PointType, class LabelType>
472template<RectangleConcept OtherRectangle>
473constexpr bool Line<PointType, LabelType>::contains(const OtherRectangle& other) const {
474 if (other.empty()) {
475 // The empty set is a subset of every shape, its boundary and its
476 // interior alike.
477 return true;
478 }
479 if (!other.isDegenerate()) {
480 return false;
481 }
482 return contains(other.min()) && contains(other.max());
483}
484
485template <class PointType, class LabelType>
486template<TriangleConcept OtherTriangle>
487constexpr bool Line<PointType, LabelType>::contains(const OtherTriangle& other) const {
488 return contains(other.a()) && contains(other.b()) && contains(other.c());
489}
490
491template <class PointType, class LabelType>
492template<ConvexConcept OtherConvex>
493constexpr bool Line<PointType, LabelType>::contains(const OtherConvex& other) const {
494 if (other.size() > 2) {
495 return false;
496 }
497 for (std::size_t i = 0; i < other.size(); ++i) {
498 if (!contains(other[i])) {
499 return false;
500 }
501 }
502 return true;
503}
504
505template <class PointType, class LabelType>
506template<DiskConcept OtherDisk>
507constexpr bool Line<PointType, LabelType>::contains(const OtherDisk& other) const {
508 // A non-degenerate disk has positive area; only a disk collapsed to a point,
509 // the case a() == b(), can lie in a one-dimensional line. See
510 // Point::contains(Disk) for the undefined a() == b() != c() case.
511 return other.a() == other.b() && contains(other.a());
512}
513
514template <class PointType, class LabelType>
515constexpr bool Line<PointType, LabelType>::contains(const Shape<PointType>& other) const {
516 return std::visit(
517 [this](const auto& value) {
518 return this->contains(value);
519 },
520 other.variant());
521}
522
528
529template <class PointType, class LabelType>
530template<PointConcept OtherPoint>
531constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherPoint& point) const {
532 return this->asLine().contains(point);
533}
534
535template <class PointType, class LabelType>
536template<LineConcept OtherLine>
537constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherLine& other) const {
538 return this->asLine().contains(other);
539}
540
541template <class PointType, class LabelType>
542template<OrientedLineConcept OtherOrientedLine>
543constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
544 return this->asLine().contains(other.asLine());
545}
546
547template <class PointType, class LabelType>
548template<SegmentConcept OtherSegment>
549constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherSegment& other) const {
550 return this->asLine().contains(other);
551}
552
553template <class PointType, class LabelType>
554template<OrientedSegmentConcept OtherOrientedSegment>
555constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
556 return this->asLine().contains(other);
557}
558
559template <class PointType, class LabelType>
560template<RayConcept OtherRay>
561constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherRay& other) const {
562 return this->asLine().contains(other);
563}
564
565template <class PointType, class LabelType>
566template<HalfplaneConcept OtherHalfplane>
567constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherHalfplane& other) const {
568 return this->asLine().contains(other);
569}
570
571template <class PointType, class LabelType>
572template<RectangleConcept OtherRectangle>
573constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherRectangle& other) const {
574 if (other.empty()) {
575 // The empty set is a subset of every shape, its boundary and its
576 // interior alike.
577 return true;
578 }
579 return this->asLine().contains(other);
580}
581
582template <class PointType, class LabelType>
583template<TriangleConcept OtherTriangle>
584constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherTriangle& other) const {
585 return this->asLine().contains(other);
586}
587
588template <class PointType, class LabelType>
589template<ConvexConcept OtherConvex>
590constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherConvex& other) const {
591 if (other.size() > 2) {
592 return false;
593 }
594 for (std::size_t i = 0; i < other.size(); ++i) {
595 if (!contains(other[i])) {
596 return false;
597 }
598 }
599 return true;
600}
601
602template <class PointType, class LabelType>
603template<DiskConcept OtherDisk>
604constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherDisk& other) const {
605 return this->asLine().contains(other);
606}
607
608template <class PointType, class LabelType>
610 return std::visit(
611 [this](const auto& value) {
612 return this->contains(value);
613 },
614 other.variant());
615}
616
622
623template <class PointType, class LabelType>
624template<PointConcept OtherPoint>
625constexpr bool Ray<PointType, LabelType>::contains(const OtherPoint& point) const {
626 if (isDegenerate()) {
627 return point == source();
628 }
629 return pgl::collinear(source(), target(), point) && containsCollinear(point);
630}
631
632template <class PointType, class LabelType>
633template<LineConcept OtherLine>
634constexpr bool Ray<PointType, LabelType>::contains(const OtherLine& other) const {
635 return other.isDegenerate() && contains(other.min());
636}
637
638template <class PointType, class LabelType>
639template<OrientedLineConcept OtherOrientedLine>
640constexpr bool Ray<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
641 return other.isDegenerate() && contains(other.source());
642}
643
644template <class PointType, class LabelType>
645template<SegmentConcept OtherSegment>
646constexpr bool Ray<PointType, LabelType>::contains(const OtherSegment& other) const {
647 return contains(other.min()) && contains(other.max());
648}
649
650template <class PointType, class LabelType>
651template<OrientedSegmentConcept OtherOrientedSegment>
652constexpr bool Ray<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
653 return contains(other.source()) && contains(other.target());
654}
655
656template <class PointType, class LabelType>
657template<RayConcept OtherRay>
658constexpr bool Ray<PointType, LabelType>::contains(const OtherRay& other) const {
659 return contains(other.source()) && contains(other.target());
660}
661
662template <class PointType, class LabelType>
663template<HalfplaneConcept OtherHalfplane>
664constexpr bool Ray<PointType, LabelType>::contains(const OtherHalfplane& other) const {
665 return other.isDegenerate() && contains(other.source());
666}
667
668template <class PointType, class LabelType>
669template<RectangleConcept OtherRectangle>
670constexpr bool Ray<PointType, LabelType>::contains(const OtherRectangle& other) const {
671 if (other.empty()) {
672 // The empty set is a subset of every shape, its boundary and its
673 // interior alike.
674 return true;
675 }
676 if (!other.isDegenerate()) {
677 return false;
678 }
679 return contains(other.min()) && contains(other.max());
680}
681
682template <class PointType, class LabelType>
683template<TriangleConcept OtherTriangle>
684constexpr bool Ray<PointType, LabelType>::contains(const OtherTriangle& other) const {
685 return contains(other.a()) && contains(other.b()) && contains(other.c());
686}
687
688template <class PointType, class LabelType>
689template<ConvexConcept OtherConvex>
690constexpr bool Ray<PointType, LabelType>::contains(const OtherConvex& other) const {
691 if (other.size() > 2) {
692 return false;
693 }
694 for (std::size_t i = 0; i < other.size(); ++i) {
695 if (!contains(other[i])) {
696 return false;
697 }
698 }
699 return true;
700}
701
702template <class PointType, class LabelType>
703template<DiskConcept OtherDisk>
704constexpr bool Ray<PointType, LabelType>::contains(const OtherDisk& other) const {
705 // A non-degenerate disk has positive area; only a disk collapsed to a point,
706 // the case a() == b(), can lie in a one-dimensional ray. See
707 // Point::contains(Disk) for the undefined a() == b() != c() case.
708 return other.a() == other.b() && contains(other.a());
709}
710
711template <class PointType, class LabelType>
712constexpr bool Ray<PointType, LabelType>::contains(const Shape<PointType>& other) const {
713 return std::visit(
714 [this](const auto& value) {
715 return this->contains(value);
716 },
717 other.variant());
718}
719
725
726template <class PointType, class LabelType>
727template<PointConcept OtherPoint>
728constexpr bool Rectangle<PointType, LabelType>::contains(const OtherPoint& point) const {
729 // The empty set contains no point, and it needs no case of its own: its
730 // corners are inverted, so the pair of tests on the inverted axis cannot
731 // both hold and the answer is already false.
732 return !(point.x() < min().x()) &&
733 !(max().x() < point.x()) &&
734 !(point.y() < min().y()) &&
735 !(max().y() < point.y());
736}
737
738template <class PointType, class LabelType>
739template<LineConcept OtherLine>
740constexpr bool Rectangle<PointType, LabelType>::contains(const OtherLine& other) const {
741 if (empty()) {
742 // The empty set is a subset of itself and of nothing else.
743 return detail::coversNoPoint(other);
744 }
745 return other.isDegenerate() && contains(other.min());
746}
747
748template <class PointType, class LabelType>
749template<OrientedLineConcept OtherOrientedLine>
750constexpr bool Rectangle<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
751 if (empty()) {
752 // The empty set is a subset of itself and of nothing else.
753 return detail::coversNoPoint(other);
754 }
755 return other.isDegenerate() && contains(other.source());
756}
757
758template <class PointType, class LabelType>
759template<SegmentConcept OtherSegment>
760constexpr bool Rectangle<PointType, LabelType>::contains(const OtherSegment& other) const {
761 if (empty()) {
762 // The empty set is a subset of itself and of nothing else.
763 return detail::coversNoPoint(other);
764 }
765 return contains(other.min()) && contains(other.max());
766}
767
768template <class PointType, class LabelType>
769template<OrientedSegmentConcept OtherOrientedSegment>
770constexpr bool Rectangle<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
771 if (empty()) {
772 // The empty set is a subset of itself and of nothing else.
773 return detail::coversNoPoint(other);
774 }
775 return contains(other.source()) && contains(other.target());
776}
777
778template <class PointType, class LabelType>
779template<RayConcept OtherRay>
780constexpr bool Rectangle<PointType, LabelType>::contains(const OtherRay& other) const {
781 if (empty()) {
782 // The empty set is a subset of itself and of nothing else.
783 return detail::coversNoPoint(other);
784 }
785 return other.isDegenerate() && contains(other.source());
786}
787
788template <class PointType, class LabelType>
789template<HalfplaneConcept OtherHalfplane>
790constexpr bool Rectangle<PointType, LabelType>::contains(const OtherHalfplane& other) const {
791 if (empty()) {
792 // The empty set is a subset of itself and of nothing else.
793 return detail::coversNoPoint(other);
794 }
795 return other.isDegenerate() && contains(other.source());
796}
797
798template <class PointType, class LabelType>
799template<RectangleConcept OtherRectangle>
800constexpr bool Rectangle<PointType, LabelType>::contains(const OtherRectangle& other) const {
801 // The empty set is a subset of every shape, so an empty operand is
802 // contained whatever its inverted corners do to the tests below. An empty
803 // *this needs no case of its own: it covers no point, so the corner tests
804 // already answer false, which is the right answer for every non-empty
805 // operand. The emptiness test trails the geometry because containment is
806 // usually decided without it.
807 return (contains(other.min()) && contains(other.max())) || other.empty();
808}
809
810template <class PointType, class LabelType>
811template<TriangleConcept OtherTriangle>
812constexpr bool Rectangle<PointType, LabelType>::contains(const OtherTriangle& other) const {
813 if (empty()) {
814 // The empty set is a subset of itself and of nothing else.
815 return detail::coversNoPoint(other);
816 }
817 return contains(other.a()) && contains(other.b()) && contains(other.c());
818}
819
820template <class PointType, class LabelType>
821template<ConvexConcept OtherConvex>
822constexpr bool Rectangle<PointType, LabelType>::contains(const OtherConvex& other) const {
823 if (empty()) {
824 // The empty set is a subset of itself and of nothing else.
825 return detail::coversNoPoint(other);
826 }
827 return other.size()==0 || contains(other.bbox());
828}
829
830template <class PointType, class LabelType>
831template<DiskConcept OtherDisk>
832constexpr bool Rectangle<PointType, LabelType>::contains(const OtherDisk& other) const {
833 if (empty()) {
834 // The empty set is a subset of itself and of nothing else.
835 return detail::coversNoPoint(other);
836 }
837 if (const auto center = other.getIfPoint()) {
838 // A radius-zero disk is its center; it has no interior point for the
839 // witness test below to find, which would reject it on the boundary.
840 return contains(*center);
841 }
842 // The closed rectangle contains the closed disk iff no edge passes through
843 // the open disk (the disk does not poke across the boundary) and the disk
844 // lies on the inside (a point strictly inside the disk is interior to the
845 // rectangle). Both tests are exact in integer arithmetic, avoiding the
846 // disk's center and radius, which are generally rational.
847 for (const auto& edge : edges()) {
848 if (edge.interiorsIntersect(other)) {
849 return false;
850 }
851 }
852 return other.pointInsideInteriorContainedIn(*this);
853}
854
855template <class PointType, class LabelType>
857 return std::visit(
858 [this](const auto& value) {
859 return this->contains(value);
860 },
861 other.variant());
862}
863
869
870template <class PointType, class LabelType>
871template<PointConcept OtherPoint>
872constexpr bool Halfplane<PointType, LabelType>::contains(const OtherPoint& point) const {
873 if (isDegenerate()) {
874 return point == source();
875 }
876 const auto side = orientationSign(source(), target(), point);
877 return side == std::partial_ordering::greater || side == std::partial_ordering::equivalent;
878}
879
880template <class PointType, class LabelType>
881template<LineConcept OtherLine>
882constexpr bool Halfplane<PointType, LabelType>::contains(const OtherLine& other) const {
883 if (isDegenerate() || other.isDegenerate()) {
884 return other.isDegenerate() && contains(other.min());
885 }
886 return contains(other.min()) && this->asLine().parallel(other);
887}
888
889template <class PointType, class LabelType>
890template<OrientedLineConcept OtherOrientedLine>
891constexpr bool Halfplane<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
892 return contains(other.asLine());
893}
894
895template <class PointType, class LabelType>
896template<SegmentConcept OtherSegment>
897constexpr bool Halfplane<PointType, LabelType>::contains(const OtherSegment& other) const {
898 if (isDegenerate()) {
899 return other.isDegenerate() && contains(other.min());
900 }
901 return contains(other.min()) && contains(other.max());
902}
903
904template <class PointType, class LabelType>
905template<OrientedSegmentConcept OtherOrientedSegment>
906constexpr bool Halfplane<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
907 if (isDegenerate()) {
908 return other.isDegenerate() && contains(other.source());
909 }
910 return contains(other.source()) && contains(other.target());
911}
912
913template <class PointType, class LabelType>
914template<RayConcept OtherRay>
915constexpr bool Halfplane<PointType, LabelType>::contains(const OtherRay& other) const {
916 if (isDegenerate() || other.isDegenerate()) {
917 return other.isDegenerate() && contains(other.source());
918 }
919 if (orientationSign(source(), target(), other.source()) < 0) {
920 return false;
921 }
922 // The ray runs on into the half-plane exactly when its direction does not
923 // turn away from the boundary. That is the sign of the difference of the
924 // two endpoint determinants, which is one cross product of the two
925 // directions -- no need to evaluate either determinant.
926 return !(crossSign(source(), target(), other.source(), other.target()) < 0);
927}
928
929template <class PointType, class LabelType>
930template<RectangleConcept OtherRectangle>
931constexpr bool Halfplane<PointType, LabelType>::contains(const OtherRectangle& other) const {
932 if (other.empty()) {
933 // The empty set is a subset of every shape, its boundary and its
934 // interior alike.
935 return true;
936 }
937 const auto vertices = other.vertices();
938 for (const auto& vertex : vertices) {
939 if (!contains(vertex)) {
940 return false;
941 }
942 }
943 return true;
944}
945
946template <class PointType, class LabelType>
947template<HalfplaneConcept OtherHalfplane>
948constexpr bool Halfplane<PointType, LabelType>::contains(const OtherHalfplane& other) const {
949 if (isDegenerate() || other.isDegenerate()) {
950 return false;
951 }
952
953 // Boundaries must be parallel AND face the same interior side; otherwise
954 // part of `other` always lies outside `*this`. `parallel` is orientation
955 // insensitive, so the dot-product sign disambiguates same vs opposite
956 // facing. Then `other` is the nested half-plane iff its boundary line lies
957 // on the closed side of `*this`, which contains(other.source()) tests
958 // exactly (one point stands in for the whole parallel line).
959 if (!asLine().parallel(other.asLine()) ||
960 dotSign(target() - source(), other.target() - other.source()) !=
961 std::partial_ordering::greater) {
962 return false;
963 }
964 return contains(other.source());
965}
966
967template <class PointType, class LabelType>
968template<TriangleConcept OtherTriangle>
969constexpr bool Halfplane<PointType, LabelType>::contains(const OtherTriangle& other) const {
970 return contains(other.a()) && contains(other.b()) && contains(other.c());
971}
972
973template <class PointType, class LabelType>
974template<ConvexConcept OtherConvex>
975constexpr bool Halfplane<PointType, LabelType>::contains(const OtherConvex& other) const {
976 if (other.size() == 0) {
977 return true;
978 }
979 if (other.size() == 1) {
980 return contains(other[0]);
981 }
982 if (other.size() == 2) {
983 return contains(other[0]) && contains(other[1]);
984 }
985
986 return contains(other[0]) && contains(other[1]) && contains(other[2]) && !static_cast<Line<PointType>>(*this).interiorsIntersect(other);
987}
988
989template <class PointType, class LabelType>
990template<DiskConcept OtherDisk>
991constexpr bool Halfplane<PointType, LabelType>::contains(const OtherDisk& other) const {
992 if (const auto center = other.getIfPoint()) {
993 // A radius-zero disk is its center; it has no interior point for the
994 // witness test below to find, which would reject it on the boundary.
995 // This mirrors the same guard in interiorContains(Disk).
996 return contains(*center);
997 }
998 return !asLine().interiorsIntersect(other) && other.pointInsideInteriorContainedIn(*this);
999}
1000
1001template <class PointType, class LabelType>
1003 return std::visit(
1004 [this](const auto& value) {
1005 return this->contains(value);
1006 },
1007 other.variant());
1008}
1009
1010// -----------------------------------------------------------------------------
1011// Disk
1012
1013template <class PointType, class LabelType>
1014template<PointConcept OtherPoint>
1015constexpr bool Disk<PointType, LabelType>::contains(const OtherPoint& point) const {
1016 // A disk that has collapsed covers the single point a(); it is never a
1017 // segment. The remaining degenerate disks -- three collinear points that
1018 // determine no circle -- are undefined, and fall through to the in-circle
1019 // determinant, which is division-free and terminates on any input.
1020 if (a() == b()) {
1021 return a() == point;
1022 }
1023
1024 return inCircleSign(a(), b(), c(), point) != std::partial_ordering::less;
1025}
1026
1027template <class PointType, class LabelType>
1028template<SegmentConcept OtherSegment>
1029constexpr bool Disk<PointType, LabelType>::contains(const OtherSegment& other) const {
1030 return contains(other.min()) && contains(other.max());
1031}
1032
1033template <class PointType, class LabelType>
1034template<OrientedSegmentConcept OtherOrientedSegment>
1035constexpr bool Disk<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
1036 return contains(other.source()) && contains(other.target());
1037}
1038
1039template <class PointType, class LabelType>
1040template<LineConcept OtherLine>
1041constexpr bool Disk<PointType, LabelType>::contains(const OtherLine& other) const {
1042 return other.isDegenerate() && contains(other.min());
1043}
1044
1045template <class PointType, class LabelType>
1046template<OrientedLineConcept OtherOrientedLine>
1047constexpr bool Disk<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
1048 return other.isDegenerate() && contains(other.source());
1049}
1050
1051template <class PointType, class LabelType>
1052template<RayConcept OtherRay>
1053constexpr bool Disk<PointType, LabelType>::contains(const OtherRay& other) const {
1054 return other.isDegenerate() && contains(other.source());
1055}
1056
1057template <class PointType, class LabelType>
1058template<HalfplaneConcept OtherHalfplane>
1059constexpr bool Disk<PointType, LabelType>::contains(const OtherHalfplane& other) const {
1060 return other.isDegenerate() && contains(other.source());
1061}
1062
1063template <class PointType, class LabelType>
1064template<TriangleConcept OtherTriangle>
1065constexpr bool Disk<PointType, LabelType>::contains(const OtherTriangle& other) const {
1066 return contains(other.a()) && contains(other.b()) && contains(other.c());
1067}
1068
1069template <class PointType, class LabelType>
1070template<RectangleConcept OtherRectangle>
1071constexpr bool Disk<PointType, LabelType>::contains(const OtherRectangle& other) const {
1072 if (other.empty()) {
1073 // The empty set is a subset of every shape, its boundary and its
1074 // interior alike.
1075 return true;
1076 }
1077 const auto vertices = other.vertices();
1078 return contains(vertices[0]) && contains(vertices[1]) && contains(vertices[2]) && contains(vertices[3]);
1079}
1080
1081template <class PointType, class LabelType>
1082template<ConvexConcept OtherConvex>
1083constexpr bool Disk<PointType, LabelType>::contains(const OtherConvex& other) const {
1084 for (const auto& point : other) {
1085 if (!contains(point)) {
1086 return false;
1087 }
1088 }
1089 return true;
1090}
1091
1092template <class PointType, class LabelType>
1093template<DiskConcept OtherDisk>
1094constexpr bool Disk<PointType, LabelType>::contains(const OtherDisk& other) const {
1095 // A collapsed disk is the point a(), never a segment.
1096 if (other.a() == other.b()) {
1097 return contains(other.a());
1098 }
1099 if (isDegenerate()) {
1100 return false;
1101 }
1102
1103 using R = std::conditional_t<
1104 std::is_floating_point_v<NumberType> ||
1105 std::is_floating_point_v<typename OtherDisk::NumberType>,
1106 long double,
1108
1109 const R r1_sq = squaredRadius<R>();
1110 const R r2_sq = other.template squaredRadius<R>();
1111 if (r1_sq < r2_sq) {
1112 return false;
1113 }
1114
1115 const R d2 = center<R>().template squaredDistance<R>(other.template center<R>());
1116 const R A = d2 - r1_sq - r2_sq;
1117 return A <= R{} && A * A >= R{4} * r1_sq * r2_sq;
1118}
1119
1120template <class PointType, class LabelType>
1121constexpr bool Disk<PointType, LabelType>::contains(const Shape<PointType>& other) const {
1122 return std::visit(
1123 [this](const auto& value) {
1124 return this->contains(value);
1125 },
1126 other.variant());
1127}
1128
1129
1130// ---------------------------------------------------------------------------
1131// Convex
1132
1133template <class PointType, class LabelType>
1134template<PointConcept OtherPoint>
1135constexpr bool Convex<PointType, LabelType>::contains(const OtherPoint& point) const {
1136 if (isDegenerate()) {
1137 // Fewer than three vertices: there is no lower/upper chain for
1138 // edgesAtX to split, so test the carrier point or segment directly.
1139 if (const auto vertex = getIfPoint()) {
1140 return *vertex == point;
1141 }
1142 if (const auto carrier = getIfSegment()) {
1143 return carrier->contains(point);
1144 }
1145 return false; // no vertices at all
1146 }
1147 if (!bbox().contains(point)) {
1148 return false;
1149 }
1150 auto edges = edgesAtX(point.x());
1151 if (!edges) {
1152 return false;
1153 }
1154 const auto& lower = (*edges)[0];
1155 const auto& upper = (*edges)[1];
1156 // Both edges are non-vertical and stored left-to-right, so the interior
1157 // lies above the lower edge and below the upper edge (boundary included).
1158 return orientationSign(lower[0], lower[1], point) >= 0 &&
1159 orientationSign(upper[0], upper[1], point) <= 0;
1160}
1161
1162template <class PointType, class LabelType>
1163template<SegmentConcept OtherSegment>
1164constexpr bool Convex<PointType, LabelType>::contains(const OtherSegment& other) const {
1165 return contains(other[0]) && contains(other[1]);
1166}
1167
1168template <class PointType, class LabelType>
1169template<OrientedSegmentConcept OtherOrientedSegment>
1170constexpr bool Convex<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
1171 return contains(other[0]) && contains(other[1]);
1172}
1173
1174template <class PointType, class LabelType>
1175template<LineConcept OtherLine>
1176constexpr bool Convex<PointType, LabelType>::contains(const OtherLine&) const {
1177 return false;
1178}
1179
1180template <class PointType, class LabelType>
1181template<OrientedLineConcept OtherOrientedLine>
1182constexpr bool Convex<PointType, LabelType>::contains(const OtherOrientedLine&) const {
1183 return false;
1184}
1185
1186template <class PointType, class LabelType>
1187template<RayConcept OtherRay>
1188constexpr bool Convex<PointType, LabelType>::contains(const OtherRay&) const {
1189 return false;
1190}
1191
1192template <class PointType, class LabelType>
1193template<HalfplaneConcept OtherHalfplane>
1194constexpr bool Convex<PointType, LabelType>::contains(const OtherHalfplane&) const {
1195 return false;
1196}
1197
1198template <class PointType, class LabelType>
1199template<RectangleConcept OtherRectangle>
1200constexpr bool Convex<PointType, LabelType>::contains(const OtherRectangle& other) const {
1201 if (other.empty()) {
1202 // The empty set is a subset of every shape, its boundary and its
1203 // interior alike.
1204 return true;
1205 }
1206 if (!bbox().contains(other)) {
1207 return false;
1208 }
1209 for (size_t i = 0; i < 4; ++i) {
1210 if (!contains(other[i])) {
1211 return false;
1212 }
1213 }
1214 return true;
1215}
1216
1217template <class PointType, class LabelType>
1218template<TriangleConcept OtherTriangle>
1219constexpr bool Convex<PointType, LabelType>::contains(const OtherTriangle& other) const {
1220 if (!bbox().contains(other)) {
1221 return false;
1222 }
1223 for (size_t i = 0; i < 3; ++i) {
1224 if (!contains(other[i])) {
1225 return false;
1226 }
1227 }
1228 return true;
1229}
1230
1231template <class PointType, class LabelType>
1232template<ConvexConcept OtherConvex>
1233constexpr bool Convex<PointType, LabelType>::contains(const OtherConvex& other) const {
1234 // The empty set is a subset of every shape, so it is tested first: an empty
1235 // polygon does contain another empty one.
1236 if (other.empty()) {
1237 return true;
1238 }
1239 if (empty()) {
1240 return false;
1241 }
1242 if (!bbox().contains(other.bbox())) {
1243 return false;
1244 }
1245
1246 if (size() == 1) {
1247 return other.size() == 1 && (*this)[0] == other[0];
1248 }
1249 if (size() == 2 && other.size() == 1) {
1250 return Segment<PointType>((*this)[0], (*this)[1]).contains(other[0]);
1251 }
1252 if (size() == 2 && other.size() == 2) {
1253 return Segment<PointType>((*this)[0], (*this)[1]).contains(Segment<typename OtherConvex::PointType>(other[0], other[1]));
1254 }
1255
1256 if (other.size() <= 2*size()) {
1257 for (size_t i = 0; i < other.size(); ++i) {
1258 if (!contains(other[i])) {
1259 return false;
1260 }
1261 }
1262 } else {
1263 for (const auto& edge : orientedEdgesView()) {
1264 if (!edge.leftHalfplane().contains(other)) {
1265 return false;
1266 }
1267 }
1268 }
1269 return true;
1270}
1271
1272template <class PointType, class LabelType>
1273template<DiskConcept OtherDisk>
1274constexpr bool Convex<PointType, LabelType>::contains(const OtherDisk& other) const {
1275 if (const auto center = other.getIfPoint()) {
1276 // A radius-zero disk is its center. The edge loop below cannot answer
1277 // for it: a degenerate convex hull has no pair of bounding half-planes
1278 // to cut the carrier line down to the point or segment it really is,
1279 // whereas contains(Point) reads that carrier directly.
1280 return contains(*center);
1281 }
1282 for (const auto& edge : orientedEdgesView()) {
1283 if (!edge.leftHalfplane().contains(other)) {
1284 return false;
1285 }
1286 }
1287 return true;
1288}
1289
1290
1291// ---------------------------------------------------------------------------
1292// Polygon
1293
1294template <class PointType, class LabelType>
1295template<PointConcept OtherPoint>
1296constexpr bool Polygon<PointType, LabelType>::contains(const OtherPoint& point) const {
1297 const std::size_t n = size();
1298 if (n == 0) {
1299 return false;
1300 }
1301
1302 // Orientation signs, lexicographic order, and Segment::contains are all
1303 // translation-invariant, so translate the query once into the polygon's
1304 // untranslated frame and compare against the raw points_ — instead of
1305 // adding the translation to every vertex (cheaper, notably for rationals).
1306 const auto p = point - translation_;
1307
1308 if (n == 1) {
1309 return points_[0] == p;
1310 }
1311 if (n == 2) {
1312 return Segment<PointType>(points_[0], points_[1]).contains(p);
1313 }
1314
1315 // The closed polygon includes its boundary, so an explicit edge check
1316 // comes first (the winding number is unreliable on the boundary).
1317 for (std::size_t i = 0; i < n; ++i) {
1318 if (Segment<PointType>(points_[i], points_[(i + 1) % n]).contains(p)) {
1319 return true;
1320 }
1321 }
1322
1323 // Interior test via the winding number, using exact orientation signs for
1324 // the left/right classification of each upward/downward crossing edge.
1325 int winding = 0;
1326 for (std::size_t i = 0; i < n; ++i) {
1327 const PointType& a = points_[i];
1328 const PointType& b = points_[(i + 1) % n];
1329 if (!(a.y() > p.y())) { // a.y() <= p.y()
1330 if (b.y() > p.y()) { // upward crossing
1331 if (orientationSign(a, b, p) > 0) {
1332 ++winding; // p strictly left of the edge
1333 }
1334 }
1335 } else { // a.y() > p.y()
1336 if (!(b.y() > p.y())) { // downward crossing
1337 if (orientationSign(a, b, p) < 0) {
1338 --winding; // p strictly right of the edge
1339 }
1340 }
1341 }
1342 }
1343 return winding != 0;
1344}
1345
1346template <class PointType, class LabelType>
1347template<SegmentConcept OtherSegment>
1348constexpr bool Polygon<PointType, LabelType>::contains(const OtherSegment& other) const {
1349 if (size() == 0) {
1350 return false;
1351 }
1352 if (!contains(other.min()) || !contains(other.max())) {
1353 return false;
1354 }
1355 if (other.isDegenerate()) {
1356 return contains(other.min());
1357 }
1358
1359 const std::size_t n = size();
1360
1361 // Translate the segment once into the polygon's untranslated frame and
1362 // work against the raw points_, rather than adding the translation to
1363 // every vertex; all the predicates below are translation-invariant.
1364 const auto a = other.min() - translation_;
1365 const auto b = other.max() - translation_;
1366
1367 // Does the ray from polygon vertex p[i] toward q head into the closed
1368 // polygon? The interior at a vertex is the intersection of the two edge
1369 // half-planes when the vertex is convex, and their union when reflex
1370 // (the polygon is CCW, so the interior lies to the left of each edge).
1371 auto entersClosedAtVertex = [&](std::size_t i, const auto& q) -> bool {
1372 const PointType& u = points_[(i + n - 1) % n];
1373 const PointType& w = points_[i];
1374 const PointType& x = points_[(i + 1) % n];
1375 const auto incoming = orientationSign(u, w, q);
1376 const auto outgoing = orientationSign(w, x, q);
1377 const auto turn = orientationSign(u, w, x);
1378 if (turn > 0) { // convex vertex: between both edges
1379 return incoming >= 0 && outgoing >= 0;
1380 }
1381 if (turn < 0) { // reflex vertex: outside the small wedge
1382 return incoming >= 0 || outgoing >= 0;
1383 }
1384 return incoming >= 0; // straight pass-through
1385 };
1386
1387 for (std::size_t i = 0; i < n; ++i) {
1388 const PointType& c = points_[i];
1389 const PointType& d = points_[(i + 1) % n];
1390
1391 const auto cSide = orientationSign(a, b, c);
1392 const auto dSide = orientationSign(a, b, d);
1393 const auto aSide = orientationSign(c, d, a);
1394 const auto bSide = orientationSign(c, d, b);
1395
1396 // (A) The edge interior crosses the segment transversally, so the
1397 // segment passes from one side of the boundary to the other.
1398 const bool straddleEdge = (cSide < 0 && dSide > 0) || (cSide > 0 && dSide < 0);
1399 const bool straddleSeg = (aSide < 0 && bSide > 0) || (aSide > 0 && bSide < 0);
1400 if (straddleEdge && straddleSeg) {
1401 return false;
1402 }
1403
1404 // (B) Vertex c lies strictly inside the open segment: the segment
1405 // passes straight through it and must stay inside on both sides.
1406 if (collinear(a, b, c) && a < c && c < b) {
1407 if (!entersClosedAtVertex(i, a) || !entersClosedAtVertex(i, b)) {
1408 return false;
1409 }
1410 }
1411
1412 // (C) A segment endpoint lies strictly inside this edge: the segment
1413 // must continue to the interior (left) side, not the exterior.
1414 if (aSide == 0 && a != c && a != d && Segment<PointType>(c, d).contains(a)) {
1415 if (bSide < 0) {
1416 return false;
1417 }
1418 }
1419 if (bSide == 0 && b != c && b != d && Segment<PointType>(c, d).contains(b)) {
1420 if (aSide < 0) {
1421 return false;
1422 }
1423 }
1424
1425 // (D) A segment endpoint coincides with vertex c: the segment must
1426 // leave the vertex into the closed polygon.
1427 if (a == c && !entersClosedAtVertex(i, b)) {
1428 return false;
1429 }
1430 if (b == c && !entersClosedAtVertex(i, a)) {
1431 return false;
1432 }
1433 }
1434
1435 return true;
1436}
1437
1438template <class PointType, class LabelType>
1439template<OrientedSegmentConcept OtherOrientedSegment>
1440constexpr bool Polygon<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
1441 return contains(Segment<typename OtherOrientedSegment::PointType>(other.source(), other.target()));
1442}
1443
1444template <class PointType, class LabelType>
1445template<LineConcept OtherLine>
1446constexpr bool Polygon<PointType, LabelType>::contains(const OtherLine& other) const {
1447 return other.isDegenerate() && contains(other.min());
1448}
1449
1450template <class PointType, class LabelType>
1451template<OrientedLineConcept OtherOrientedLine>
1452constexpr bool Polygon<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
1453 return other.isDegenerate() && contains(other.source());
1454}
1455
1456template <class PointType, class LabelType>
1457template<RayConcept OtherRay>
1458constexpr bool Polygon<PointType, LabelType>::contains(const OtherRay& other) const {
1459 return other.isDegenerate() && contains(other.source());
1460}
1461
1462template <class PointType, class LabelType>
1463template<HalfplaneConcept OtherHalfplane>
1464constexpr bool Polygon<PointType, LabelType>::contains(const OtherHalfplane& other) const {
1465 return other.isDegenerate() && contains(other.source());
1466}
1467
1468// For a simple polygon (no holes) a bounded shape is contained iff every one
1469// of its edges is contained, so the region overloads reduce to edge checks.
1470template <class PointType, class LabelType>
1471template<RectangleConcept OtherRectangle>
1472constexpr bool Polygon<PointType, LabelType>::contains(const OtherRectangle& other) const {
1473 if (other.empty()) {
1474 // The empty set is a subset of every shape, its boundary and its
1475 // interior alike.
1476 return true;
1477 }
1478 for (std::size_t i = 0; i < other.size(); ++i) {
1479 if (!contains(Segment<typename OtherRectangle::PointType>(other[i], other[(i + 1) % other.size()]))) {
1480 return false;
1481 }
1482 }
1483 return true;
1484}
1485
1486template <class PointType, class LabelType>
1487template<TriangleConcept OtherTriangle>
1488constexpr bool Polygon<PointType, LabelType>::contains(const OtherTriangle& other) const {
1489 for (std::size_t i = 0; i < other.size(); ++i) {
1490 if (!contains(Segment<typename OtherTriangle::PointType>(other[i], other[(i + 1) % other.size()]))) {
1491 return false;
1492 }
1493 }
1494 return true;
1495}
1496
1497// A convex polygon's boundary is exactly two lex-monotone chains — its lower and
1498// upper hull — so we can run the edgesCross fast path of contains(Polygon)
1499// without building a BoundaryChains decomposition (or an asPolygon copy) of the
1500// convex: just test this polygon's chains against those two known hull chains.
1501template <class PointType, class LabelType>
1502template<ConvexConcept OtherConvex>
1503constexpr bool Polygon<PointType, LabelType>::contains(const OtherConvex& other) const {
1504 using OtherPoint = typename OtherConvex::PointType;
1505 if (other.size() == 0) {
1506 return true;
1507 }
1508 if (!bbox().contains(other.bbox())) {
1509 return false;
1510 }
1511 if (other.size() == 1) {
1512 return contains(other[0]);
1513 }
1514
1515 if (!contains(other[0])) {
1516 return false;
1517 }
1518
1519 const MonotoneChain<OtherPoint> lower = other.lowerHull();
1520 const MonotoneChain<OtherPoint> upper = other.upperHull();
1521
1522 bool boundaries_intersect = false;
1523 BoundaryChains<Polygon> mine(*this);
1524 while (!mine.exhausted()) {
1525 const auto& chain = mine.produceNext();
1526 for (const MonotoneChain<OtherPoint>* their : {&lower, &upper}) {
1527 if (chain.intersects(*their)) {
1528 boundaries_intersect = true;
1529 if (chain.edgesCross(*their)) {
1530 return false;
1531 }
1532 }
1533 }
1534 }
1535
1536 if (!boundaries_intersect) {
1537 return true;
1538 }
1539
1540 // The boundaries intersect without crossing, check everything quadratically
1541 for (std::size_t i = 0; i < other.size(); ++i) {
1542 if (!contains(Segment<OtherPoint>(other[i], other[(i + 1) % other.size()]))) {
1543 return false;
1544 }
1545 }
1546
1547 return true;
1548}
1549
1550// Two implementations of one contract, picked between per call: a combined
1551// red-blue plane sweep (sweepContains, in algorithm/redbluesweep.hpp) and the
1552// pairwise test of the two boundaries' lexicographically monotone chains
1553// (containsChainBased, below — the same argument contains(Convex) runs against
1554// a convex hull's fixed two chains). Neither dominates: the chain test costs
1555// the product of the two chain counts, cheap for near-convex boundaries and
1556// O(n*m) for a comb or a star, while the sweep is O((n+m) log(n+m)) always but
1557// carries setup a small input never earns back. preferSweep is where that
1558// tradeoff is decided, once, for every predicate built on it.
1559//
1560// The two agree on every input: sweepContains follows the same three steps as
1561// containsChainBased — bbox and degenerate-point rejects, one vertex of `other`
1562// inside `this`, no boundary crossing — and falls back to the same quadratic
1563// edge scan when the boundaries touch without a crossing being found.
1564template <class PointType, class LabelType>
1565template<PolygonConcept OtherPolygon>
1566constexpr bool Polygon<PointType, LabelType>::contains(const OtherPolygon& other) const {
1567 // The bbox reject both implementations open with is cheap (bbox() is
1568 // cached) and, for the common case of two polygons that plainly do not
1569 // overlap, settles the call outright. Doing it here too, before
1570 // preferSweep, means that common case never pays for chainCount()'s O(n)
1571 // pass on either operand — real money for Rational or BigInt coordinates,
1572 // where a single lexicographic comparison is not cheap.
1573 if (other.size() == 0) {
1574 return true;
1575 }
1576 if (!bbox().contains(other.bbox())) {
1577 return false;
1578 }
1579 if (preferSweep(*this, other)) {
1580 return sweepContains(*this, other);
1581 }
1582 return containsChainBased(other);
1583}
1584
1585template <class PointType, class LabelType>
1586template<PolygonConcept OtherPolygon>
1587constexpr bool Polygon<PointType, LabelType>::containsChainBased(const OtherPolygon& other) const {
1588 if (other.size() == 0) {
1589 return true;
1590 }
1591 if (!bbox().contains(other.bbox())) {
1592 return false;
1593 }
1594 if (other.size() == 1) {
1595 return contains(other[0]);
1596 }
1597 // A polygon collapsed to a single point is exactly that point, and its
1598 // boundary has no lexicographic break for BoundaryChains to split on.
1599 if (const auto vertex = other.getIfPoint()) {
1600 return contains(*vertex);
1601 }
1602 if (isPoint()) {
1603 // `other` has two distinct vertices by the test above, so a single
1604 // point cannot contain it.
1605 return false;
1606 }
1607
1608 if (!contains(other.get(0))) {
1609 return false;
1610 }
1611
1612 bool boundaries_intersect = false;
1613
1614 BoundaryChains<Polygon> mine(*this);
1615 BoundaryChains<OtherPolygon> theirs(other);
1616 while (!mine.exhausted() || !theirs.exhausted()) {
1617 if (!mine.exhausted()) {
1618 const auto& chain = mine.produceNext();
1619 for (const auto& their : theirs.produced()) {
1620 if (chain.intersects(their)) {
1621 boundaries_intersect = true;
1622 if (chain.edgesCross(their)) {
1623 return false;
1624 }
1625 }
1626 }
1627 }
1628 if (!theirs.exhausted()) {
1629 const auto& chain = theirs.produceNext();
1630 for (const auto& my : mine.produced()) {
1631 if (chain.intersects(my)) {
1632 boundaries_intersect = true;
1633 if (chain.edgesCross(my)) {
1634 return false;
1635 }
1636 }
1637 }
1638 }
1639 }
1640
1641 if (!boundaries_intersect) {
1642 return true;
1643 }
1644
1645 // The boundaries intersect without crossing, check everything quadratically
1646 for (std::size_t i = 0; i < other.size(); ++i) {
1647 if (!contains(Segment<typename OtherPolygon::PointType>(other[i], other[(i + 1) % other.size()]))) {
1648 return false;
1649 }
1650 }
1651
1652 return true;
1653}
1654
1655template <class PointType, class LabelType>
1656template<DiskConcept OtherDisk>
1657constexpr bool Polygon<PointType, LabelType>::contains(const OtherDisk& other) const {
1658 if (other.isDegenerate()) {
1659 return contains(other.a());
1660 }
1661
1662 if (!other.pointInsideInteriorContainedIn(*this)) {
1663 return false;
1664 }
1665
1666 for (const auto& edge : edgesView()) {
1667 if (other.interiorsIntersect(edge)) {
1668 return false;
1669 }
1670 }
1671 return true;
1672}
1673
1674template <class PointType, class LabelType>
1676 return std::visit(
1677 [this](const auto& value) {
1678 return this->contains(value);
1679 },
1680 other.variant());
1681}
1682
1683template <class PointType, class LabelType>
1684template <PointConcept OtherPoint>
1686 return std::visit(
1687 [this](const auto& value) {
1688 return this->contains(value);
1689 },
1690 other.variant());
1691}
1692
1693template <class Number, class Label>
1694template<PolygonConcept OtherPolygon>
1695constexpr bool Point<Number, Label>::contains(const OtherPolygon& other) const {
1696 for (const auto& vertex : other) {
1697 if (!contains(vertex)) {
1698 return false;
1699 }
1700 }
1701 return true;
1702}
1703
1704template <class PointType, class LabelType>
1705template<PolygonConcept OtherPolygon>
1706constexpr bool Segment<PointType, LabelType>::contains(const OtherPolygon& other) const {
1707 for (const auto& vertex : other) {
1708 if (!contains(vertex)) {
1709 return false;
1710 }
1711 }
1712 return true;
1713}
1714
1715template <class PointType, class LabelType>
1716template<PolygonConcept OtherPolygon>
1717constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherPolygon& other) const {
1718 return asSegment().contains(other);
1719}
1720
1721template <class PointType, class LabelType>
1722template<PolygonConcept OtherPolygon>
1723constexpr bool Line<PointType, LabelType>::contains(const OtherPolygon& other) const {
1724 for (const auto& vertex : other) {
1725 if (!contains(vertex)) {
1726 return false;
1727 }
1728 }
1729 return true;
1730}
1731
1732template <class PointType, class LabelType>
1733template<PolygonConcept OtherPolygon>
1734constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherPolygon& other) const {
1735 for (const auto& vertex : other) {
1736 if (!contains(vertex)) {
1737 return false;
1738 }
1739 }
1740 return true;
1741}
1742
1743template <class PointType, class LabelType>
1744template<PolygonConcept OtherPolygon>
1745constexpr bool Ray<PointType, LabelType>::contains(const OtherPolygon& other) const {
1746 for (const auto& vertex : other) {
1747 if (!contains(vertex)) {
1748 return false;
1749 }
1750 }
1751 return true;
1752}
1753
1754template <class PointType, class LabelType>
1755template<PolygonConcept OtherPolygon>
1756constexpr bool Halfplane<PointType, LabelType>::contains(const OtherPolygon& other) const {
1757 for (const auto& vertex : other) {
1758 if (!contains(vertex)) {
1759 return false;
1760 }
1761 }
1762 return true;
1763}
1764
1765template <class PointType, class LabelType>
1766template<PolygonConcept OtherPolygon>
1767constexpr bool Rectangle<PointType, LabelType>::contains(const OtherPolygon& other) const {
1768 if (empty()) {
1769 // The empty set is a subset of itself and of nothing else.
1770 return detail::coversNoPoint(other);
1771 }
1772 for (const auto& vertex : other) {
1773 if (!contains(vertex)) {
1774 return false;
1775 }
1776 }
1777 return true;
1778}
1779
1780template <class PointType, class LabelType>
1781template<PolygonConcept OtherPolygon>
1782constexpr bool Triangle<PointType, LabelType>::contains(const OtherPolygon& other) const {
1783 for (const auto& vertex : other) {
1784 if (!contains(vertex)) {
1785 return false;
1786 }
1787 }
1788 return true;
1789}
1790
1791template <class PointType, class LabelType>
1792template<PolygonConcept OtherPolygon>
1793constexpr bool Convex<PointType, LabelType>::contains(const OtherPolygon& other) const {
1794 for (const auto& vertex : other) {
1795 if (!contains(vertex)) {
1796 return false;
1797 }
1798 }
1799 return true;
1800}
1801
1802template <class PointType, class LabelType>
1803template<PolygonConcept OtherPolygon>
1804constexpr bool Disk<PointType, LabelType>::contains(const OtherPolygon& other) const {
1805 for (const auto& vertex : other) {
1806 if (!contains(vertex)) {
1807 return false;
1808 }
1809 }
1810 return true;
1811}
1812
1819
1820template <class PointType, class LabelType, class Storage>
1821template<PointConcept OtherPoint>
1822constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherPoint& point) const {
1823 // The slice of the chain at any x is a single vertical segment (possibly a
1824 // point), so the point is on the chain iff the chain passes both weakly
1825 // below and weakly above it.
1826 return isBelow(point).has_value() && isAbove(point).has_value();
1827}
1828
1829template <class PointType, class LabelType, class Storage>
1830template<SegmentConcept OtherSegment>
1831constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherSegment& other) const {
1832 if (!contains(other.min()) || !contains(other.max())) {
1833 return false;
1834 }
1835 // Both endpoints lie on the chain, which is a monotone arc in the
1836 // lexicographic order, so the sub-arc between them equals the segment iff
1837 // it never bends: every chain vertex lexicographically between the
1838 // endpoints must be collinear with the segment. The scan stops at the
1839 // first bend or at the far endpoint.
1840 const auto first = std::upper_bound(
1841 points_.begin(), points_.end(), other.min(),
1842 [this](const auto& value, const PointType& p) { return value < p + translation_; });
1843 for (auto it = first; it != points_.end(); ++it) {
1844 const PointType vertex = *it + translation_;
1845 if (!(vertex < other.max())) {
1846 break;
1847 }
1848 if (orientationSign(other.min(), other.max(), vertex) != 0) {
1849 return false;
1850 }
1851 }
1852 return true;
1853}
1854
1855template <class PointType, class LabelType, class Storage>
1856template<OrientedSegmentConcept OtherOrientedSegment>
1857constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherOrientedSegment& other) const {
1859}
1860
1861template <class PointType, class LabelType, class Storage>
1862template<LineConcept OtherLine>
1863constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherLine& other) const {
1864 return other.isDegenerate() && contains(other.min());
1865}
1866
1867template <class PointType, class LabelType, class Storage>
1868template<OrientedLineConcept OtherOrientedLine>
1869constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherOrientedLine& other) const {
1870 return other.isDegenerate() && contains(other.source());
1871}
1872
1873template <class PointType, class LabelType, class Storage>
1874template<RayConcept OtherRay>
1875constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherRay& other) const {
1876 return other.isDegenerate() && contains(other.source());
1877}
1878
1879template <class PointType, class LabelType, class Storage>
1880template<HalfplaneConcept OtherHalfplane>
1881constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherHalfplane& other) const {
1882 return other.isDegenerate() && contains(other.source());
1883}
1884
1885template <class PointType, class LabelType, class Storage>
1886template<RectangleConcept OtherRectangle>
1887constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherRectangle& other) const {
1888 if (other.empty()) {
1889 // The empty set is a subset of every shape, its boundary and its
1890 // interior alike.
1891 return true;
1892 }
1893 if (!other.isDegenerate()) {
1894 return false;
1895 }
1896 if (other.min() == other.max()) {
1897 return contains(other.min());
1898 }
1899 return contains(Segment<typename OtherRectangle::PointType>(other.min(), other.max()));
1900}
1901
1902template <class PointType, class LabelType, class Storage>
1903template<TriangleConcept OtherTriangle>
1904constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherTriangle& other) const {
1905 if (!other.isDegenerate()) {
1906 return false;
1907 }
1908 if (other.a() == other.c()) {
1909 return contains(other.a());
1910 }
1911 return contains(Segment<typename OtherTriangle::PointType>(other.a(), other.c()));
1912}
1913
1914template <class PointType, class LabelType, class Storage>
1915template<ConvexConcept OtherConvex>
1916constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherConvex& other) const {
1917 if (other.size() == 0) {
1918 return true;
1919 }
1920 if (other.size() == 1) {
1921 return contains(other[0]);
1922 }
1923 if (other.size() == 2) {
1924 return contains(Segment<typename OtherConvex::PointType>(other[0], other[1]));
1925 }
1926 return false;
1927}
1928
1929template <class PointType, class LabelType, class Storage>
1930template<PolygonConcept OtherPolygon>
1931constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherPolygon& other) const {
1932 // A polygon with area is never on the 1-dimensional chain. Without area it
1933 // is exactly the union of its edges, so the edge fold decides -- and it has
1934 // to be the edges: a bent chain may pass through every vertex without
1935 // containing the edges between them, so a vertex fold would not be enough.
1936 if (!other.isDegenerate()) {
1937 return false;
1938 }
1939 if (other.size() == 0) {
1940 return true;
1941 }
1942 if (other.size() == 1) {
1943 return contains(other[0]);
1944 }
1945 for (const auto& edge : other.edgesView()) {
1946 if (!contains(edge)) {
1947 return false;
1948 }
1949 }
1950 return true;
1951}
1952
1953template <class PointType, class LabelType, class Storage>
1954template<DiskConcept OtherDisk>
1955constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherDisk& other) const {
1956 return other.a() == other.b() && other.b() == other.c() && contains(other.a());
1957}
1958
1959template <class PointType, class LabelType, class Storage>
1960template<MonotoneChainConcept OtherChain>
1961constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherChain& other) const {
1962 if (other.empty()) {
1963 return true;
1964 }
1965 if (other.size() == 1) {
1966 return contains(other[0]);
1967 }
1968 // The other chain is exactly the union of its edges, and each edge must be
1969 // a straight sub-path of this chain.
1970 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
1971 if (!contains(Segment<typename OtherChain::PointType>(other[i], other[i + 1]))) {
1972 return false;
1973 }
1974 }
1975 return true;
1976}
1977
1978template <class PointType, class LabelType, class Storage>
1979template<PointConcept OtherPoint>
1981 return std::visit(
1982 [this](const auto& value) {
1983 return this->contains(value);
1984 },
1985 other.variant());
1986}
1987
1988// Every shape below is a convex point set, so it contains the chain iff it
1989// contains all of the chain's vertices (an empty chain is trivially contained).
1990
1991template <class Number, class Label>
1992template<MonotoneChainConcept OtherChain>
1993constexpr bool Point<Number, Label>::contains(const OtherChain& other) const {
1994 for (const auto& vertex : other) {
1995 if (!contains(vertex)) {
1996 return false;
1997 }
1998 }
1999 return true;
2000}
2001
2002template <class PointType, class LabelType>
2003template<MonotoneChainConcept OtherChain>
2004constexpr bool Segment<PointType, LabelType>::contains(const OtherChain& other) const {
2005 for (const auto& vertex : other) {
2006 if (!contains(vertex)) {
2007 return false;
2008 }
2009 }
2010 return true;
2011}
2012
2013template <class PointType, class LabelType>
2014template<MonotoneChainConcept OtherChain>
2015constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherChain& other) const {
2016 return asSegment().contains(other);
2017}
2018
2019template <class PointType, class LabelType>
2020template<MonotoneChainConcept OtherChain>
2021constexpr bool Line<PointType, LabelType>::contains(const OtherChain& other) const {
2022 for (const auto& vertex : other) {
2023 if (!contains(vertex)) {
2024 return false;
2025 }
2026 }
2027 return true;
2028}
2029
2030template <class PointType, class LabelType>
2031template<MonotoneChainConcept OtherChain>
2032constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherChain& other) const {
2033 return asLine().contains(other);
2034}
2035
2036template <class PointType, class LabelType>
2037template<MonotoneChainConcept OtherChain>
2038constexpr bool Ray<PointType, LabelType>::contains(const OtherChain& other) const {
2039 for (const auto& vertex : other) {
2040 if (!contains(vertex)) {
2041 return false;
2042 }
2043 }
2044 return true;
2045}
2046
2047template <class PointType, class LabelType>
2048template<MonotoneChainConcept OtherChain>
2049constexpr bool Halfplane<PointType, LabelType>::contains(const OtherChain& other) const {
2050 for (const auto& vertex : other) {
2051 if (!contains(vertex)) {
2052 return false;
2053 }
2054 }
2055 return true;
2056}
2057
2058template <class PointType, class LabelType>
2059template<MonotoneChainConcept OtherChain>
2060constexpr bool Rectangle<PointType, LabelType>::contains(const OtherChain& other) const {
2061 if (empty()) {
2062 // The empty set is a subset of itself and of nothing else.
2063 return detail::coversNoPoint(other);
2064 }
2065 for (const auto& vertex : other) {
2066 if (!contains(vertex)) {
2067 return false;
2068 }
2069 }
2070 return true;
2071}
2072
2073template <class PointType, class LabelType>
2074template<MonotoneChainConcept OtherChain>
2075constexpr bool Triangle<PointType, LabelType>::contains(const OtherChain& other) const {
2076 for (const auto& vertex : other) {
2077 if (!contains(vertex)) {
2078 return false;
2079 }
2080 }
2081 return true;
2082}
2083
2084template <class PointType, class LabelType>
2085template<MonotoneChainConcept OtherChain>
2086constexpr bool Disk<PointType, LabelType>::contains(const OtherChain& other) const {
2087 for (const auto& vertex : other) {
2088 if (!contains(vertex)) {
2089 return false;
2090 }
2091 }
2092 return true;
2093}
2094
2095template <class PointType, class LabelType>
2096template<MonotoneChainConcept OtherChain>
2097constexpr bool Convex<PointType, LabelType>::contains(const OtherChain& other) const {
2098 for (const auto& vertex : other) {
2099 if (!contains(vertex)) {
2100 return false;
2101 }
2102 }
2103 return true;
2104}
2105
2106// A polygon is generally not convex, so it must contain every chain edge.
2107template <class PointType, class LabelType>
2108template<MonotoneChainConcept OtherChain>
2109constexpr bool Polygon<PointType, LabelType>::contains(const OtherChain& other) const {
2110 if (other.empty()) {
2111 return true;
2112 }
2113 if (other.size() == 1) {
2114 return contains(other[0]);
2115 }
2116 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
2117 if (!contains(Segment<typename OtherChain::PointType>(other[i], other[i + 1]))) {
2118 return false;
2119 }
2120 }
2121 return true;
2122}
2123
2131
2132template <class PointType, class LabelType>
2133template<PointConcept OtherPoint>
2134constexpr bool Polyline<PointType, LabelType>::contains(const OtherPoint& point) const {
2135 if (empty()) {
2136 return false;
2137 }
2138 if (size() == 1) {
2139 return (*this)[0] == point;
2140 }
2141 for (const auto& edge : edgesView()) {
2142 if (edge.contains(point)) {
2143 return true;
2144 }
2145 }
2146 return false;
2147}
2148
2149template <class PointType, class LabelType>
2150template<SegmentConcept OtherSegment>
2151constexpr bool Polyline<PointType, LabelType>::contains(const OtherSegment& other) const {
2152 if (other.min() == other.max()) {
2153 return contains(other.min());
2154 }
2155 if (size() < 2) {
2156 // Without an edge the polyline cannot cover a positive-length segment.
2157 return false;
2158 }
2159 // A self-intersecting polyline can cover the segment with several,
2160 // possibly non-consecutive collinear edges, so collect the overlap of
2161 // every edge lying on the segment's supporting line and check that the
2162 // union leaves no gap. Collinear points compare consistently along the
2163 // line in the lexicographic point order, so the overlaps are ordinary
2164 // closed intervals in that order.
2165 using CommonPoint =
2167 std::vector<std::pair<CommonPoint, CommonPoint>> overlaps;
2168 for (const auto& edge : edgesView()) {
2169 if (orientationSign(other.min(), other.max(), edge.min()) != 0 ||
2170 orientationSign(other.min(), other.max(), edge.max()) != 0) {
2171 continue; // the edge leaves the segment's supporting line
2172 }
2173 const CommonPoint lo =
2174 (edge.min() < other.min()) ? CommonPoint(other.min()) : CommonPoint(edge.min());
2175 const CommonPoint hi =
2176 (other.max() < edge.max()) ? CommonPoint(other.max()) : CommonPoint(edge.max());
2177 if (hi < lo) {
2178 continue; // collinear but disjoint from the segment
2179 }
2180 overlaps.emplace_back(lo, hi);
2181 }
2182 std::sort(overlaps.begin(), overlaps.end());
2183 CommonPoint covered(other.min());
2184 for (const auto& [lo, hi] : overlaps) {
2185 if (covered < lo) {
2186 return false; // the part between covered and lo is off the polyline
2187 }
2188 if (covered < hi) {
2189 covered = hi;
2190 }
2191 }
2192 return !(covered < CommonPoint(other.max()));
2193}
2194
2195template <class PointType, class LabelType>
2196template<OrientedSegmentConcept OtherOrientedSegment>
2197constexpr bool Polyline<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
2199}
2200
2201template <class PointType, class LabelType>
2202template<LineConcept OtherLine>
2203constexpr bool Polyline<PointType, LabelType>::contains(const OtherLine& other) const {
2204 return other.isDegenerate() && contains(other.min());
2205}
2206
2207template <class PointType, class LabelType>
2208template<OrientedLineConcept OtherOrientedLine>
2209constexpr bool Polyline<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
2210 return other.isDegenerate() && contains(other.source());
2211}
2212
2213template <class PointType, class LabelType>
2214template<RayConcept OtherRay>
2215constexpr bool Polyline<PointType, LabelType>::contains(const OtherRay& other) const {
2216 return other.isDegenerate() && contains(other.source());
2217}
2218
2219template <class PointType, class LabelType>
2220template<HalfplaneConcept OtherHalfplane>
2221constexpr bool Polyline<PointType, LabelType>::contains(const OtherHalfplane& other) const {
2222 return other.isDegenerate() && contains(other.source());
2223}
2224
2225template <class PointType, class LabelType>
2226template<RectangleConcept OtherRectangle>
2227constexpr bool Polyline<PointType, LabelType>::contains(const OtherRectangle& other) const {
2228 if (other.empty()) {
2229 // The empty set is a subset of every shape, its boundary and its
2230 // interior alike.
2231 return true;
2232 }
2233 if (!other.isDegenerate()) {
2234 return false;
2235 }
2236 if (other.min() == other.max()) {
2237 return contains(other.min());
2238 }
2239 return contains(Segment<typename OtherRectangle::PointType>(other.min(), other.max()));
2240}
2241
2242template <class PointType, class LabelType>
2243template<TriangleConcept OtherTriangle>
2244constexpr bool Polyline<PointType, LabelType>::contains(const OtherTriangle& other) const {
2245 if (!other.isDegenerate()) {
2246 return false;
2247 }
2248 if (other.a() == other.c()) {
2249 return contains(other.a());
2250 }
2251 return contains(Segment<typename OtherTriangle::PointType>(other.a(), other.c()));
2252}
2253
2254template <class PointType, class LabelType>
2255template<ConvexConcept OtherConvex>
2256constexpr bool Polyline<PointType, LabelType>::contains(const OtherConvex& other) const {
2257 if (other.size() == 0) {
2258 return true;
2259 }
2260 if (other.size() == 1) {
2261 return contains(other[0]);
2262 }
2263 if (other.size() == 2) {
2264 return contains(Segment<typename OtherConvex::PointType>(other[0], other[1]));
2265 }
2266 return false;
2267}
2268
2269template <class PointType, class LabelType>
2270template<PolygonConcept OtherPolygon>
2271constexpr bool Polyline<PointType, LabelType>::contains(const OtherPolygon& other) const {
2272 // A polygon with area is never on the 1-dimensional polyline, however much
2273 // of its boundary is: a polyline tracing the whole boundary still misses
2274 // everything inside it. Without area the polygon is exactly the union of
2275 // its edges, and the fold decides.
2276 if (!other.isDegenerate()) {
2277 return false;
2278 }
2279 if (other.size() == 0) {
2280 return true;
2281 }
2282 if (other.size() == 1) {
2283 return contains(other[0]);
2284 }
2285 for (const auto& edge : other.edgesView()) {
2286 if (!contains(edge)) {
2287 return false;
2288 }
2289 }
2290 return true;
2291}
2292
2293template <class PointType, class LabelType>
2294template<DiskConcept OtherDisk>
2295constexpr bool Polyline<PointType, LabelType>::contains(const OtherDisk& other) const {
2296 return other.a() == other.b() && other.b() == other.c() && contains(other.a());
2297}
2298
2299template <class PointType, class LabelType>
2300template<MonotoneChainConcept OtherChain>
2301constexpr bool Polyline<PointType, LabelType>::contains(const OtherChain& other) const {
2302 if (other.empty()) {
2303 return true;
2304 }
2305 if (other.size() == 1) {
2306 return contains(other[0]);
2307 }
2308 // The chain is exactly the union of its edges.
2309 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
2310 if (!contains(Segment<typename OtherChain::PointType>(other[i], other[i + 1]))) {
2311 return false;
2312 }
2313 }
2314 return true;
2315}
2316
2317template <class PointType, class LabelType>
2318template<PolylineConcept OtherPolyline>
2319constexpr bool Polyline<PointType, LabelType>::contains(const OtherPolyline& other) const {
2320 if (other.empty()) {
2321 return true;
2322 }
2323 if (other.size() == 1) {
2324 return contains(other[0]);
2325 }
2326 // The other polyline is exactly the union of its edges.
2327 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
2328 if (!contains(Segment<typename OtherPolyline::PointType>(other[i], other[i + 1]))) {
2329 return false;
2330 }
2331 }
2332 return true;
2333}
2334
2335template <class PointType, class LabelType>
2336template<PointConcept OtherPoint>
2338 return std::visit(
2339 [this](const auto& value) {
2340 return this->contains(value);
2341 },
2342 other.variant());
2343}
2344
2345// A point and a segment are convex point sets, so they contain the polyline
2346// iff they contain all of its vertices (an empty polyline is trivially
2347// contained).
2348
2349template <class Number, class Label>
2350template<PolylineConcept OtherPolyline>
2351constexpr bool Point<Number, Label>::contains(const OtherPolyline& other) const {
2352 for (const auto& vertex : other) {
2353 if (!contains(vertex)) {
2354 return false;
2355 }
2356 }
2357 return true;
2358}
2359
2360template <class PointType, class LabelType>
2361template<PolylineConcept OtherPolyline>
2362constexpr bool Segment<PointType, LabelType>::contains(const OtherPolyline& other) const {
2363 for (const auto& vertex : other) {
2364 if (!contains(vertex)) {
2365 return false;
2366 }
2367 }
2368 return true;
2369}
2370
2371template <class PointType, class LabelType>
2372template<PolylineConcept OtherPolyline>
2373constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherPolyline& other) const {
2374 return asSegment().contains(other);
2375}
2376
2377// Every shape below is a convex point set, so it contains the polyline iff it
2378// contains all of the polyline's vertices (an empty polyline is trivially
2379// contained).
2380
2381template <class PointType, class LabelType>
2382template<PolylineConcept OtherPolyline>
2383constexpr bool Line<PointType, LabelType>::contains(const OtherPolyline& other) const {
2384 for (const auto& vertex : other) {
2385 if (!contains(vertex)) {
2386 return false;
2387 }
2388 }
2389 return true;
2390}
2391
2392template <class PointType, class LabelType>
2393template<PolylineConcept OtherPolyline>
2394constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherPolyline& other) const {
2395 return asLine().contains(other);
2396}
2397
2398template <class PointType, class LabelType>
2399template<PolylineConcept OtherPolyline>
2400constexpr bool Ray<PointType, LabelType>::contains(const OtherPolyline& other) const {
2401 for (const auto& vertex : other) {
2402 if (!contains(vertex)) {
2403 return false;
2404 }
2405 }
2406 return true;
2407}
2408
2409template <class PointType, class LabelType>
2410template<PolylineConcept OtherPolyline>
2411constexpr bool Halfplane<PointType, LabelType>::contains(const OtherPolyline& other) const {
2412 for (const auto& vertex : other) {
2413 if (!contains(vertex)) {
2414 return false;
2415 }
2416 }
2417 return true;
2418}
2419
2420template <class PointType, class LabelType>
2421template<PolylineConcept OtherPolyline>
2422constexpr bool Rectangle<PointType, LabelType>::contains(const OtherPolyline& other) const {
2423 if (empty()) {
2424 // The empty set is a subset of itself and of nothing else.
2425 return detail::coversNoPoint(other);
2426 }
2427 for (const auto& vertex : other) {
2428 if (!contains(vertex)) {
2429 return false;
2430 }
2431 }
2432 return true;
2433}
2434
2435template <class PointType, class LabelType>
2436template<PolylineConcept OtherPolyline>
2437constexpr bool Triangle<PointType, LabelType>::contains(const OtherPolyline& other) const {
2438 for (const auto& vertex : other) {
2439 if (!contains(vertex)) {
2440 return false;
2441 }
2442 }
2443 return true;
2444}
2445
2446template <class PointType, class LabelType>
2447template<PolylineConcept OtherPolyline>
2448constexpr bool Disk<PointType, LabelType>::contains(const OtherPolyline& other) const {
2449 for (const auto& vertex : other) {
2450 if (!contains(vertex)) {
2451 return false;
2452 }
2453 }
2454 return true;
2455}
2456
2457template <class PointType, class LabelType>
2458template<PolylineConcept OtherPolyline>
2459constexpr bool Convex<PointType, LabelType>::contains(const OtherPolyline& other) const {
2460 for (const auto& vertex : other) {
2461 if (!contains(vertex)) {
2462 return false;
2463 }
2464 }
2465 return true;
2466}
2467
2468// A monotone chain is 1-dimensional and generally bent, so it must contain
2469// every polyline edge as a straight sub-path.
2470template <class PointType, class LabelType, class Storage>
2471template<PolylineConcept OtherPolyline>
2472constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherPolyline& other) const {
2473 if (other.empty()) {
2474 return true;
2475 }
2476 if (other.size() == 1) {
2477 return contains(other[0]);
2478 }
2479 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
2480 if (!contains(Segment<typename OtherPolyline::PointType>(other[i], other[i + 1]))) {
2481 return false;
2482 }
2483 }
2484 return true;
2485}
2486
2487// A polygon is generally not convex, so it must contain every polyline edge.
2488template <class PointType, class LabelType>
2489template<PolylineConcept OtherPolyline>
2490constexpr bool Polygon<PointType, LabelType>::contains(const OtherPolyline& other) const {
2491 if (other.empty()) {
2492 return true;
2493 }
2494 if (other.size() == 1) {
2495 return contains(other[0]);
2496 }
2497 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
2498 if (!contains(Segment<typename OtherPolyline::PointType>(other[i], other[i + 1]))) {
2499 return false;
2500 }
2501 }
2502 return true;
2503}
2504
2505
2506// ---------------------------------------------------------------------------
2507// HalfplaneIntersection
2508
2509template <class PointType, class LabelType>
2510template <PointConcept OtherPoint>
2511constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherPoint& point) const {
2512 return pointStatus(point) >= 0;
2513}
2514
2515template <class PointType, class LabelType>
2516template <SegmentConcept OtherSegment>
2517constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherSegment& other) const {
2518 // The region is convex, so containing both endpoints contains the segment.
2519 return contains(other[0]) && contains(other[1]);
2520}
2521
2522template <class PointType, class LabelType>
2523template <OrientedSegmentConcept OtherOrientedSegment>
2524constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
2525 return contains(other[0]) && contains(other[1]);
2526}
2527
2528template <class PointType, class LabelType>
2529template <LineConcept OtherLine>
2530constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherLine& other) const {
2531 // A half-plane contains a line only when its boundary is parallel to it,
2532 // and the canonical form stores at most one half-plane per direction, so
2533 // at most two constraints (one per orientation) can contain a line.
2534 if (empty() || size() > 2) {
2535 return false;
2536 }
2537 for (const auto& halfplane : halfplanes_) {
2538 if (!halfplane.contains(other)) {
2539 return false;
2540 }
2541 }
2542 return true;
2543}
2544
2545template <class PointType, class LabelType>
2546template <OrientedLineConcept OtherOrientedLine>
2547constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
2548 return contains(other.asLine());
2549}
2550
2551template <class PointType, class LabelType>
2552template <RayConcept OtherRay>
2553constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherRay& other) const {
2554 // Containing the source and the ray's direction lying in the recession
2555 // cone keeps the whole ray inside (the distance to each boundary line is
2556 // affine along the ray and stays nonnegative).
2557 if (empty()) {
2558 return false;
2559 }
2560 if (!contains(other.source())) {
2561 return false;
2562 }
2563 const Halfplane<typename OtherRay::PointType> forward(other.source(), other.target());
2564 return recessionContains(forward);
2565}
2566
2567template <class PointType, class LabelType>
2568template <HalfplaneConcept OtherHalfplane>
2569constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherHalfplane& other) const {
2570 // Every stored constraint must contain the half-plane, which requires the
2571 // same boundary direction; the canonical form therefore admits at most one
2572 // stored constraint (or none: the whole plane).
2573 if (empty() || size() > 1) {
2574 return false;
2575 }
2576 return halfplanes_.empty() || halfplanes_[0].contains(other);
2577}
2578
2579template <class PointType, class LabelType>
2580template <RectangleConcept OtherRectangle>
2581constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherRectangle& other) const {
2582 if (other.empty()) {
2583 // The empty set is a subset of every shape, its boundary and its
2584 // interior alike.
2585 return true;
2586 }
2587 // The region is convex, so containing the vertices contains the rectangle.
2588 const auto vertices = other.vertices();
2589 for (const auto& vertex : vertices) {
2590 if (!contains(vertex)) {
2591 return false;
2592 }
2593 }
2594 return true;
2595}
2596
2597template <class PointType, class LabelType>
2598template <TriangleConcept OtherTriangle>
2599constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherTriangle& other) const {
2600 return contains(other.a()) && contains(other.b()) && contains(other.c());
2601}
2602
2603template <class PointType, class LabelType>
2604template <DiskConcept OtherDisk>
2605constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherDisk& other) const {
2606 // The region contains the disk exactly when every stored constraint does.
2607 if (empty()) {
2608 return false;
2609 }
2610 if (const auto center = other.getIfPoint()) {
2611 // A radius-zero disk is its center, and contains(Point) is the
2612 // authoritative membership test for the region.
2613 return contains(*center);
2614 }
2615 for (const auto& halfplane : halfplanes_) {
2616 if (!halfplane.contains(other)) {
2617 return false;
2618 }
2619 }
2620 return true;
2621}
2622
2623template <class PointType, class LabelType>
2624template <ConvexConcept OtherConvex>
2625constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherConvex& other) const {
2626 // The region is convex, so containing the vertices contains the polygon.
2627 for (std::size_t i = 0; i < other.size(); ++i) {
2628 if (!contains(other[i])) {
2629 return false;
2630 }
2631 }
2632 return true;
2633}
2634
2635template <class PointType, class LabelType>
2636template <MonotoneChainConcept OtherChain>
2637constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherChain& other) const {
2638 // The region is convex, so containing the vertices contains the chain.
2639 for (std::size_t i = 0; i < other.size(); ++i) {
2640 if (!contains(other[i])) {
2641 return false;
2642 }
2643 }
2644 return true;
2645}
2646
2647template <class PointType, class LabelType>
2648template <PolylineConcept OtherPolyline>
2649constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherPolyline& other) const {
2650 // The region is convex, so containing the vertices contains the polyline.
2651 for (std::size_t i = 0; i < other.size(); ++i) {
2652 if (!contains(other[i])) {
2653 return false;
2654 }
2655 }
2656 return true;
2657}
2658
2659template <class PointType, class LabelType>
2660template <PolygonConcept OtherPolygon>
2661constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherPolygon& other) const {
2662 // The region is convex, so containing the vertices contains the polygon
2663 // (its region is inside the vertices' convex hull).
2664 for (std::size_t i = 0; i < other.size(); ++i) {
2665 if (!contains(other[i])) {
2666 return false;
2667 }
2668 }
2669 return true;
2670}
2671
2672template <class PointType, class LabelType>
2673template <HalfplaneIntersectionConcept OtherRegion>
2674constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherRegion& other) const {
2675 // The region contains the other region exactly when every stored
2676 // constraint does; the whole plane (no constraints) contains everything.
2677 if (other.empty()) {
2678 return true;
2679 }
2680 if (empty()) {
2681 return false;
2682 }
2683 for (const auto& halfplane : halfplanes_) {
2684 if (!halfplane.contains(other)) {
2685 return false;
2686 }
2687 }
2688 return true;
2689}
2690
2691template <class PointType, class LabelType>
2692template <PointConcept OtherPoint>
2694 return std::visit(
2695 [this](const auto& value) {
2696 return this->contains(value);
2697 },
2698 other.variant());
2699}
2700
2701
2702// ---------------------------------------------------------------------------
2703// Reverse direction: lower-ranked shapes containing a HalfplaneIntersection.
2704//
2705// The empty region is a subset of every shape. A degenerate region reduces to
2706// its carrier shape (point, segment, ray, or line, with exact coordinates); a
2707// full-dimensional region can only be contained in two-dimensional shapes,
2708// where it reduces to half-plane redundancy tests or, for a bounded region,
2709// to its convex-polygon form.
2710
2711template <class Number, class Label>
2712template <HalfplaneIntersectionConcept OtherRegion>
2713constexpr bool Point<Number, Label>::contains(const OtherRegion& other) const {
2714 // The region is inside the point exactly when it is inside all four
2715 // axis-aligned half-planes through it.
2716 const Point right(x() + Number(1), y());
2717 const Point up(x(), y() + Number(1));
2718 return detail::regionInsideHalfplane(other, Halfplane<Point>(*this, right)) &&
2719 detail::regionInsideHalfplane(other, Halfplane<Point>(right, *this)) &&
2720 detail::regionInsideHalfplane(other, Halfplane<Point>(*this, up)) &&
2721 detail::regionInsideHalfplane(other, Halfplane<Point>(up, *this));
2722}
2723
2724template <class PointType, class LabelType>
2725template <HalfplaneIntersectionConcept OtherRegion>
2726constexpr bool Segment<PointType, LabelType>::contains(const OtherRegion& other) const {
2727 if (min() == max()) {
2728 // The clamps below are built from the segment's own direction, which a
2729 // collapsed segment does not have: every one of them would come out
2730 // undefined, and `insert` drops an undefined half-plane instead of
2731 // cutting with it, so the region would test as inside all four. The
2732 // point the segment covers answers the question with axis-aligned
2733 // clamps of its own.
2734 return min().contains(other);
2735 }
2736 // Inside the supporting line's slab and the two perpendicular clamps
2737 // through the endpoints.
2738 const auto a = min();
2739 const auto b = max();
2740 const auto dx = b.x() - a.x();
2741 const auto dy = b.y() - a.y();
2742 const PointType aClamp(a.x() + dy, a.y() - dx);
2743 const PointType bClamp(b.x() - dy, b.y() + dx);
2744 return detail::regionInsideHalfplane(other, Halfplane<PointType>(a, b)) &&
2745 detail::regionInsideHalfplane(other, Halfplane<PointType>(b, a)) &&
2746 detail::regionInsideHalfplane(other, Halfplane<PointType>(a, aClamp)) &&
2747 detail::regionInsideHalfplane(other, Halfplane<PointType>(b, bClamp));
2748}
2749
2750template <class PointType, class LabelType>
2751template <HalfplaneIntersectionConcept OtherRegion>
2752constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherRegion& other) const {
2753 return asSegment().contains(other);
2754}
2755
2756template <class PointType, class LabelType>
2757template <HalfplaneIntersectionConcept OtherRegion>
2758constexpr bool Line<PointType, LabelType>::contains(const OtherRegion& other) const {
2759 return detail::regionInsideHalfplane(other, Halfplane<PointType>((*this)[0], (*this)[1])) &&
2760 detail::regionInsideHalfplane(other, Halfplane<PointType>((*this)[1], (*this)[0]));
2761}
2762
2763template <class PointType, class LabelType>
2764template <HalfplaneIntersectionConcept OtherRegion>
2765constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherRegion& other) const {
2766 return asLine().contains(other);
2767}
2768
2769template <class PointType, class LabelType>
2770template <HalfplaneIntersectionConcept OtherRegion>
2771constexpr bool Ray<PointType, LabelType>::contains(const OtherRegion& other) const {
2772 // Inside the supporting line's slab and the perpendicular clamp through
2773 // the source.
2774 const auto a = source();
2775 const auto b = target();
2776 const auto dx = b.x() - a.x();
2777 const auto dy = b.y() - a.y();
2778 const PointType clamp(a.x() + dy, a.y() - dx);
2779 return detail::regionInsideHalfplane(other, Halfplane<PointType>(a, b)) &&
2780 detail::regionInsideHalfplane(other, Halfplane<PointType>(b, a)) &&
2781 detail::regionInsideHalfplane(other, Halfplane<PointType>(a, clamp));
2782}
2783
2784template <class PointType, class LabelType>
2785template <HalfplaneIntersectionConcept OtherRegion>
2786constexpr bool Halfplane<PointType, LabelType>::contains(const OtherRegion& other) const {
2787 return detail::regionInsideHalfplane(other, *this);
2788}
2789
2790template <class PointType, class LabelType>
2791template <HalfplaneIntersectionConcept OtherRegion>
2792constexpr bool Rectangle<PointType, LabelType>::contains(const OtherRegion& other) const {
2793 if (empty()) {
2794 // The empty set is a subset of itself and of nothing else.
2795 return detail::coversNoPoint(other);
2796 }
2797 if (min() == max()) {
2798 return Point<typename PointType::NumberType>(min().x(), min().y()).contains(other);
2799 }
2800 if (isDegenerate()) {
2801 return Segment<PointType>(min(), max()).contains(other);
2802 }
2803 const PointType lo(min());
2804 const PointType hi(max());
2805 const PointType lohi(lo.x(), hi.y());
2806 const PointType hilo(hi.x(), lo.y());
2807 return detail::regionInsideHalfplane(other, Halfplane<PointType>(lo, hilo)) &&
2808 detail::regionInsideHalfplane(other, Halfplane<PointType>(hilo, hi)) &&
2809 detail::regionInsideHalfplane(other, Halfplane<PointType>(hi, lohi)) &&
2810 detail::regionInsideHalfplane(other, Halfplane<PointType>(lohi, lo));
2811}
2812
2813template <class PointType, class LabelType>
2814template <HalfplaneIntersectionConcept OtherRegion>
2815constexpr bool Triangle<PointType, LabelType>::contains(const OtherRegion& other) const {
2816 if (other.empty()) {
2817 return true;
2818 }
2819 if (const auto vertex = getIfPoint()) {
2820 return vertex->contains(other);
2821 }
2822 if (const auto carrier = getIfSegment()) {
2823 return carrier->contains(other);
2824 }
2825 // Vertices are counterclockwise when non-degenerate, so each edge's
2826 // half-plane has the interior on its left.
2827 return detail::regionInsideHalfplane(other, Halfplane<PointType>(a(), b())) &&
2828 detail::regionInsideHalfplane(other, Halfplane<PointType>(b(), c())) &&
2829 detail::regionInsideHalfplane(other, Halfplane<PointType>(c(), a()));
2830}
2831
2832template <class PointType, class LabelType>
2833template <HalfplaneIntersectionConcept OtherRegion>
2834constexpr bool Disk<PointType, LabelType>::contains(const OtherRegion& other) const {
2835 if (other.empty()) {
2836 return true;
2837 }
2838 if (!other.isBounded()) {
2839 return false;
2840 }
2841 // The disk is convex and the bounded region is the hull of its vertices.
2842 using E = detail::region_exact_number_t<typename OtherRegion::NumberType>;
2843 const auto vertices = other.template vertices<E>();
2844 for (const auto& vertex : vertices) {
2845 if (!contains(vertex)) {
2846 return false;
2847 }
2848 }
2849 return true;
2850}
2851
2852template <class PointType, class LabelType>
2853template <HalfplaneIntersectionConcept OtherRegion>
2854constexpr bool Convex<PointType, LabelType>::contains(const OtherRegion& other) const {
2855 if (size() == 0) {
2856 return other.empty();
2857 }
2858 if (size() == 1) {
2859 return Point<typename PointType::NumberType>((*this)[0].x(), (*this)[0].y()).contains(other);
2860 }
2861 if (isDegenerate()) {
2862 return Segment<PointType>((*this)[0], (*this)[size() - 1]).contains(other);
2863 }
2864 for (std::size_t i = 0; i < size(); ++i) {
2865 if (!detail::regionInsideHalfplane(other, Halfplane<PointType>((*this)[i], get(static_cast<std::ptrdiff_t>(i) + 1)))) {
2866 return false;
2867 }
2868 }
2869 return true;
2870}
2871
2872template <class PointType, class LabelType, class Storage>
2873template <HalfplaneIntersectionConcept OtherRegion>
2874constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherRegion& other) const {
2875 if (other.empty()) {
2876 return true;
2877 }
2878 if (!other.isDegenerate()) {
2879 return false; // a chain is one-dimensional
2880 }
2881 return std::visit(
2882 [this](const auto& carrier) {
2883 using Carrier = std::remove_cvref_t<decltype(carrier)>;
2884 if constexpr (detail::is_point_v<Carrier> || detail::is_segment_v<Carrier>) {
2885 return this->contains(carrier);
2886 } else {
2887 return false; // a bounded chain never contains a ray or a line
2888 }
2889 },
2890 detail::degenerateRegionCarrier(other));
2891}
2892
2893template <class PointType, class LabelType>
2894template <HalfplaneIntersectionConcept OtherRegion>
2895constexpr bool Polyline<PointType, LabelType>::contains(const OtherRegion& other) const {
2896 if (other.empty()) {
2897 return true;
2898 }
2899 if (!other.isDegenerate()) {
2900 return false; // a polyline is one-dimensional
2901 }
2902 return std::visit(
2903 [this](const auto& carrier) {
2904 using Carrier = std::remove_cvref_t<decltype(carrier)>;
2905 if constexpr (detail::is_point_v<Carrier> || detail::is_segment_v<Carrier>) {
2906 return this->contains(carrier);
2907 } else {
2908 return false; // a bounded polyline never contains a ray or a line
2909 }
2910 },
2911 detail::degenerateRegionCarrier(other));
2912}
2913
2914template <class PointType, class LabelType>
2915template <HalfplaneIntersectionConcept OtherRegion>
2916constexpr bool Polygon<PointType, LabelType>::contains(const OtherRegion& other) const {
2917 if (other.empty()) {
2918 return true;
2919 }
2920 if (!other.isBounded()) {
2921 return false;
2922 }
2923 using E = detail::region_exact_number_t<typename OtherRegion::NumberType>;
2924 if (other.isDegenerate()) {
2925 return std::visit(
2926 [this](const auto& carrier) {
2927 using Carrier = std::remove_cvref_t<decltype(carrier)>;
2928 if constexpr (detail::is_point_v<Carrier> || detail::is_segment_v<Carrier>) {
2929 return this->contains(carrier);
2930 } else {
2931 return false;
2932 }
2933 },
2934 detail::degenerateRegionCarrier(other));
2935 }
2936 return contains(other.template asConvex<E>());
2937}
2938
2939
2940// ---------------------------------------------------------------------------
2941// PolygonWithHoles
2942
2943template <class PointType, class LabelType>
2944template <PointConcept OtherPoint>
2945constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherPoint& point) const {
2946 if (!outer_.contains(point)) {
2947 return false;
2948 }
2949 // A hole removes only its interior, so a point on a hole boundary stays in
2950 // the region; only a strictly interior one is carved out.
2951 for (const auto& hole : holes_) {
2952 if (hole.interiorContains(point)) {
2953 return false;
2954 }
2955 }
2956 return true;
2957}
2958
2959template <class PointType, class LabelType>
2960template <SegmentConcept OtherSegment>
2961constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherSegment& other) const {
2962 // A segment collapsed to a point has no relative interior for the hole test
2963 // below to see, so it goes through the point path.
2964 if (other.isDegenerate()) {
2965 return contains(other.min());
2966 }
2967 if (!outer_.contains(other)) {
2968 return false;
2969 }
2970 // A hole removes only its interior. A segment that reaches the open hole at
2971 // all reaches it along its own relative interior — the hole interior is
2972 // open, so a contact at an endpoint drags the neighbouring segment points in
2973 // with it — which is exactly what interiorsIntersect detects.
2974 for (const auto& hole : holes_) {
2975 if (hole.interiorsIntersect(other)) {
2976 return false;
2977 }
2978 }
2979 return true;
2980}
2981
2982template <class PointType, class LabelType>
2983template <OrientedSegmentConcept OtherOrientedSegment>
2984constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherOrientedSegment& other) const {
2985 return contains(other.asSegment());
2986}
2987
2988// The region is bounded, so it swallows an unbounded operand only when that
2989// operand has collapsed to a point.
2990template <class PointType, class LabelType>
2991template <LineConcept OtherLine>
2992constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherLine& other) const {
2993 return other.isDegenerate() && contains(other.min());
2994}
2995
2996template <class PointType, class LabelType>
2997template <OrientedLineConcept OtherOrientedLine>
2998constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherOrientedLine& other) const {
2999 return other.isDegenerate() && contains(other.source());
3000}
3001
3002template <class PointType, class LabelType>
3003template <RayConcept OtherRay>
3004constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherRay& other) const {
3005 return other.isDegenerate() && contains(other.source());
3006}
3007
3008template <class PointType, class LabelType>
3009template <HalfplaneConcept OtherHalfplane>
3010constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherHalfplane& other) const {
3011 return other.isDegenerate() && contains(other.source());
3012}
3013
3014// Containment of a bounded operand splits cleanly in two along the operand's
3015// own boundary.
3016//
3017// Everything the boundary can reach is settled edge by edge, and that is more
3018// than it looks. Leaving the outer polygon is caught even when no edge does:
3019// a simple polygon leaves a connected unbounded complement, so a path from an
3020// escaped point of the operand to infinity stays outside the outer polygon and
3021// has to cross the operand's boundary on its way out.
3022//
3023// What the edges cannot reach is a hole swallowed whole. Its boundary belongs
3024// to the region, its interior does not, so an operand closing over one holds
3025// points the region lacks while every one of its edges still lies in the
3026// region. That is the second test — and once the edge scan has passed, the
3027// hole's interior misses ∂B, so it lies wholly inside the operand or wholly
3028// outside it and one witness point of the hole decides.
3029//
3030// A collapsed operand is exactly the union of its edges and has no interior to
3031// swallow anything with, so the first half alone answers it.
3032template <class PointType, class LabelType>
3033template <class OtherArea>
3034constexpr bool PolygonWithHoles<PointType, LabelType>::areaContains(const OtherArea& other) const {
3035 // Without holes the region is exactly its outer polygon, which answers
3036 // directly — and for a convex or a simple-polygon operand it has a
3037 // boundary-chain fast path the edge scan below does not. Polygon::contains
3038 // has no overload for a region operand (that direction is a later phase), so
3039 // that one keeps going edge by edge.
3040 if constexpr (!PolygonWithHolesConcept<OtherArea>) {
3041 if (holes_.empty()) {
3042 return outer_.contains(other);
3043 }
3044 }
3045 // A sweep pre-check was tried here (any crossing between the operand's
3046 // boundary and this region's full boundary settles the question outright,
3047 // in O((n + m) log(n + m)) instead of the O(n * m) the edge scan below
3048 // costs in the worst case). Measured against the shape-pair benchmark
3049 // cube it was a net loss, not a win: this loop already returns on the
3050 // first violating edge, so on the ordinary (non-adversarially-jagged)
3051 // regions that cube generates it was already fast, and the pre-check's
3052 // own cost — chainCount() over every ring, plus materializing edges()
3053 // for both operands a second time (the loop below materializes them
3054 // again) — was pure overhead on top of that, regressing PolygonWithHoles
3055 // containment up to 7x. Left out until there is a real dispatch for it,
3056 // the way Polygon::contains has one in containsChainBased/sweepContains,
3057 // rather than a pre-check bolted onto an algorithm with no fast branch of
3058 // its own to skip.
3059 for (const auto& edge : other.edges()) {
3060 if (!contains(edge)) {
3061 return false;
3062 }
3063 }
3064 if (other.isDegenerate()) {
3065 return true;
3066 }
3067 for (const auto& hole : holes_) {
3068 if (hole.pointInsideInteriorContainedIn(other)) {
3069 return false;
3070 }
3071 }
3072 return true;
3073}
3074
3075template <class PointType, class LabelType>
3076template <RectangleConcept OtherRectangle>
3077constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherRectangle& other) const {
3078 if (other.empty()) {
3079 // The empty set is a subset of every shape, its boundary and its
3080 // interior alike.
3081 return true;
3082 }
3083 return areaContains(other);
3084}
3085
3086template <class PointType, class LabelType>
3087template <TriangleConcept OtherTriangle>
3088constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherTriangle& other) const {
3089 return areaContains(other);
3090}
3091
3092template <class PointType, class LabelType>
3093template <ConvexConcept OtherConvex>
3094constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherConvex& other) const {
3095 return areaContains(other);
3096}
3097
3098template <class PointType, class LabelType>
3099template <PolygonConcept OtherPolygon>
3100constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherPolygon& other) const {
3101 return areaContains(other);
3102}
3103
3104template <class PointType, class LabelType>
3105template <PolygonWithHolesConcept OtherRegion>
3106constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherRegion& other) const {
3107 return areaContains(other);
3108}
3109
3110// A chain — monotone or not — is exactly the union of its edges, so every
3111// set-level relation between it and the region is the conjunction (or, for
3112// intersects, the disjunction) of the same relation over the edges. The segment
3113// overloads carry all the hole bookkeeping already. A chain covering a single
3114// point becomes a degenerate segment, which those overloads route to the point
3115// path themselves.
3116template <class PointType, class LabelType>
3117template <class OtherChain, class EdgeRelation>
3118constexpr bool PolygonWithHoles<PointType, LabelType>::chainRelation(const OtherChain& other,
3119 bool all,
3120 EdgeRelation&& relation) const {
3121 using ChainSegment = Segment<typename OtherChain::PointType>;
3122 if (other.empty()) {
3123 // The empty set is contained in everything and meets nothing.
3124 return all;
3125 }
3126 if (other.size() == 1) {
3127 return relation(ChainSegment(other[0], other[0]));
3128 }
3129 for (std::size_t i = 0; i + 1 < other.size(); ++i) {
3130 const bool holds = relation(ChainSegment(other[i], other[i + 1]));
3131 if (holds != all) {
3132 return holds; // an edge that fails a conjunction, or carries a disjunction
3133 }
3134 }
3135 return all;
3136}
3137
3138template <class PointType, class LabelType>
3139template <MonotoneChainConcept OtherChain>
3140constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherChain& other) const {
3141 return chainRelation(other, true, [this](const auto& edge) { return this->contains(edge); });
3142}
3143
3144template <class PointType, class LabelType>
3145template <PolylineConcept OtherPolyline>
3146constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherPolyline& other) const {
3147 return chainRelation(other, true, [this](const auto& edge) { return this->contains(edge); });
3148}
3149
3150// A non-degenerate disk is the closure of its own interior, which is exactly
3151// what the area operands could not be relied on to be (§3): a disk reaching a
3152// hole interior at all reaches it with interior of its own, so the per-hole
3153// rewriting of A = outer ∖ ⋃ hole° applies directly and no edge scan is needed.
3154//
3155// A degenerate disk is either a disk of radius zero, which is the point a(), or
3156// undefined — three collinear points determine no circle. Reading it as a() is
3157// exact in the first case and an arbitrary but terminating answer in the second,
3158// which is all the contract asks for.
3159template <class PointType, class LabelType>
3160template <DiskConcept OtherDisk>
3161constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherDisk& other) const {
3162 if (other.isDegenerate()) {
3163 return contains(other.a());
3164 }
3165 if (!outer_.contains(other)) {
3166 return false;
3167 }
3168 for (const auto& hole : holes_) {
3169 if (hole.interiorsIntersect(other)) {
3170 return false;
3171 }
3172 }
3173 return true;
3174}
3175
3176template <class PointType, class LabelType>
3177template <class OtherIntersection>
3178constexpr auto PolygonWithHoles<PointType, LabelType>::asConvexOperand(const OtherIntersection& other) {
3179 using E = detail::region_exact_number_t<typename OtherIntersection::NumberType>;
3180 return other.template asConvex<E>();
3181}
3182
3183template <class PointType, class LabelType>
3184template <class OtherIntersection, class Relation>
3185constexpr bool PolygonWithHoles<PointType, LabelType>::degenerateIntersectionRelation(
3186 const OtherIntersection& other, Relation&& relation) const {
3187 return std::visit([&relation](const auto& carrier) { return relation(carrier); },
3188 detail::degenerateRegionCarrier(other));
3189}
3190
3191// A bounded half-plane intersection with area is a convex polygon and goes to
3192// the area path; a degenerate one is a point, a segment, a ray or a line, and
3193// goes to the overload for that carrier — which, for the two unbounded ones,
3194// answers false because the region is bounded.
3195template <class PointType, class LabelType>
3196template <HalfplaneIntersectionConcept OtherIntersection>
3197constexpr bool PolygonWithHoles<PointType, LabelType>::contains(const OtherIntersection& other) const {
3198 if (other.empty()) {
3199 return true;
3200 }
3201 if (other.isDegenerate()) {
3202 return degenerateIntersectionRelation(
3203 other, [this](const auto& carrier) { return this->contains(carrier); });
3204 }
3205 if (!other.isBounded()) {
3206 return false;
3207 }
3208 return areaContains(asConvexOperand(other));
3209}
3210
3211
3212// ---------------------------------------------------------------------------
3213// Reverse direction: lower-ranked shapes containing a PolygonWithHoles.
3214//
3215// Every shape here but a Polyline answers the region exactly as it answers the
3216// region's *outer polygon*, holes and all:
3217//
3218// B ⊇ A ⟺ B ⊇ outer.
3219//
3220// (⇐) is free, since A ⊆ outer. For (⇒), start from A ⊇ ∂outer: a hole
3221// interior touching ∂outer would carry points beyond it, which the closed
3222// containment of a hole in the outer polygon forbids, so the whole outer ring
3223// belongs to the region however the rings meet. Then split on the ring:
3224//
3225// - if the outer polygon has no area it *is* ∂outer, and it has no room for a
3226// hole either (zero-area rings are dropped at construction), so A = outer
3227// and the two questions are the same one;
3228//
3229// - otherwise ∂outer is a Jordan curve. A shape that is at most
3230// one-dimensional cannot hold it and cannot hold the polygon either, so
3231// both sides are false. A two-dimensional B holding it holds everything
3232// inside it too: every shape in the library is closed with a *connected*
3233// complement, so a point of outer° outside B would reach infinity along a
3234// path avoiding B — hence avoiding ∂outer ⊆ B — while going from inside the
3235// curve to outside it.
3236//
3237// A Polyline is the one exception, because it is the one shape here that can
3238// close a loop, and then its complement is not connected. It gets the
3239// zero-area rewriting of detail::everyHoledRegionEdge instead: a
3240// one-dimensional shape holds the region only when the region has no area, and
3241// then the region is exactly the union of its ring edges. The region whose
3242// hole equals its outer ring — material with no area anywhere — is precisely
3243// the case the two readings disagree on.
3244
3245template <class Number, class Label>
3246template <PolygonWithHolesConcept OtherRegion>
3247constexpr bool Point<Number, Label>::contains(const OtherRegion& other) const {
3248 return contains(other.outer());
3249}
3250
3251template <class PointType, class LabelType>
3252template <PolygonWithHolesConcept OtherRegion>
3253constexpr bool Segment<PointType, LabelType>::contains(const OtherRegion& other) const {
3254 return contains(other.outer());
3255}
3256
3257template <class PointType, class LabelType>
3258template <PolygonWithHolesConcept OtherRegion>
3259constexpr bool OrientedSegment<PointType, LabelType>::contains(const OtherRegion& other) const {
3260 return asSegment().contains(other);
3261}
3262
3263template <class PointType, class LabelType>
3264template <PolygonWithHolesConcept OtherRegion>
3265constexpr bool Line<PointType, LabelType>::contains(const OtherRegion& other) const {
3266 return contains(other.outer());
3267}
3268
3269template <class PointType, class LabelType>
3270template <PolygonWithHolesConcept OtherRegion>
3271constexpr bool OrientedLine<PointType, LabelType>::contains(const OtherRegion& other) const {
3272 return asLine().contains(other);
3273}
3274
3275template <class PointType, class LabelType>
3276template <PolygonWithHolesConcept OtherRegion>
3277constexpr bool Ray<PointType, LabelType>::contains(const OtherRegion& other) const {
3278 return contains(other.outer());
3279}
3280
3281template <class PointType, class LabelType>
3282template <PolygonWithHolesConcept OtherRegion>
3283constexpr bool Halfplane<PointType, LabelType>::contains(const OtherRegion& other) const {
3284 return contains(other.outer());
3285}
3286
3287template <class PointType, class LabelType>
3288template <PolygonWithHolesConcept OtherRegion>
3289constexpr bool Rectangle<PointType, LabelType>::contains(const OtherRegion& other) const {
3290 if (empty()) {
3291 // The empty set is a subset of itself and of nothing else.
3292 return detail::coversNoPoint(other);
3293 }
3294 return contains(other.outer());
3295}
3296
3297template <class PointType, class LabelType>
3298template <PolygonWithHolesConcept OtherRegion>
3299constexpr bool Triangle<PointType, LabelType>::contains(const OtherRegion& other) const {
3300 return contains(other.outer());
3301}
3302
3303template <class PointType, class LabelType>
3304template <PolygonWithHolesConcept OtherRegion>
3305constexpr bool Disk<PointType, LabelType>::contains(const OtherRegion& other) const {
3306 return contains(other.outer());
3307}
3308
3309template <class PointType, class LabelType>
3310template <PolygonWithHolesConcept OtherRegion>
3311constexpr bool Convex<PointType, LabelType>::contains(const OtherRegion& other) const {
3312 return contains(other.outer());
3313}
3314
3315template <class PointType, class LabelType, class Storage>
3316template <PolygonWithHolesConcept OtherRegion>
3317constexpr bool MonotoneChain<PointType, LabelType, Storage>::contains(const OtherRegion& other) const {
3318 return contains(other.outer());
3319}
3320
3321// The exception; see the note above.
3322template <class PointType, class LabelType>
3323template <PolygonWithHolesConcept OtherRegion>
3324constexpr bool Polyline<PointType, LabelType>::contains(const OtherRegion& other) const {
3325 if (!other.isDegenerate()) {
3326 return false; // the region has area; the polyline has none
3327 }
3328 return detail::everyHoledRegionEdge(
3329 other, [this](const auto& edge) { return this->contains(edge); });
3330}
3331
3332template <class PointType, class LabelType>
3333template <PolygonWithHolesConcept OtherRegion>
3334constexpr bool Polygon<PointType, LabelType>::contains(const OtherRegion& other) const {
3335 return contains(other.outer());
3336}
3337
3338template <class PointType, class LabelType>
3339template <PolygonWithHolesConcept OtherHoledRegion>
3340constexpr bool HalfplaneIntersection<PointType, LabelType>::contains(const OtherHoledRegion& other) const {
3341 return contains(other.outer());
3342}
3343
3344// ---------------------------------------------------------------------------
3345// Runtime Shape argument: unwrap the stored alternative and re-dispatch. Every
3346// alternative has a per-shape overload above, so no fallback is needed.
3347
3348template <class PointType, class LabelType>
3349template <PointConcept OtherPoint>
3351 return std::visit(
3352 [this](const auto& value) {
3353 return this->contains(value);
3354 },
3355 other.variant());
3356}
3357
3358
3359// ---------------------------------------------------------------------------
3360// PolygonSet
3361//
3362// One definition per relation, stated over every operand the set outranks: the
3363// argument for `A = ⋃ Aᵢ` does not vary from one operand family to the next, and
3364// where it does — the one-dimensional operands — the difference is a branch
3365// inside, not an overload of its own.
3366
3367template <class PointType, class LabelType>
3369 if (pinched_ >= 0) {
3370 return pinched_ != 0;
3371 }
3372 pinched_ = 0;
3373 for (std::size_t i = 0; i < components_.size() && pinched_ == 0; ++i) {
3374 for (std::size_t j = i + 1; j < components_.size(); ++j) {
3375 if (!components_[i].bbox().intersects(components_[j].bbox())) {
3376 continue; // the boxes prefilter the quadratic scan
3377 }
3378 if (components_[i].intersects(components_[j])) {
3379 pinched_ = 1;
3380 break;
3381 }
3382 }
3383 }
3384 return pinched_ != 0;
3385}
3386
3387template <class PointType, class LabelType>
3388template <class OtherSegment>
3389bool PolygonSet<PointType, LabelType>::segmentIn(const OtherSegment& segment,
3390 bool boundaryOnly) const {
3391 using ExactNumber = detail::Exact1DNumber<NumberType, typename OtherSegment::NumberType>;
3392 using ExactPoint = Point<ExactNumber>;
3393 using ExactSegment = Segment<ExactPoint>;
3394
3395 // One component holding the whole segment is the common case, and it costs
3396 // a scan of the components against the general answer's exact crossing per
3397 // component edge. It is what the segment operand of `contains` asks before
3398 // coming here; a chain and a region reach this per edge, and would pay the
3399 // general price on every edge that never needed it.
3400 if (anyComponent([&](const ComponentType& component) {
3401 return boundaryOnly ? component.boundaryContains(segment) : component.contains(segment);
3402 })) {
3403 return true;
3404 }
3405
3406 const ExactSegment probe(ExactPoint(segment.min()), ExactPoint(segment.max()));
3407 const auto holds = [this, boundaryOnly](const ExactPoint& point) {
3408 return anyComponent([&](const ComponentType& component) {
3409 return boundaryOnly ? component.boundaryContains(point) : component.contains(point);
3410 });
3411 };
3412 if (probe.min() == probe.max()) {
3413 return holds(probe.min());
3414 }
3415
3416 std::vector<ExactPoint> cuts{probe.min(), probe.max()};
3417 for (const auto& component : components_) {
3418 for (const auto& edge : component.edges()) {
3419 const auto piece = probe.template intersection<ExactNumber>(
3420 ExactSegment(ExactPoint(edge.min()), ExactPoint(edge.max())));
3421 if (!piece) {
3422 continue;
3423 }
3424 if (const auto* touch = std::get_if<0>(&*piece)) {
3425 cuts.push_back(*touch);
3426 } else {
3427 const auto& overlap = std::get<1>(*piece);
3428 cuts.push_back(overlap.min());
3429 cuts.push_back(overlap.max());
3430 }
3431 }
3432 }
3433 // All the cuts lie on one line, so the lexicographic point order is the
3434 // linear order along it — the same argument splitCutSegments uses.
3435 std::sort(cuts.begin(), cuts.end());
3436 cuts.erase(std::unique(cuts.begin(), cuts.end()), cuts.end());
3437
3438 for (const ExactPoint& cut : cuts) {
3439 if (!holds(cut)) {
3440 return false;
3441 }
3442 }
3443 const ExactNumber two(2);
3444 for (std::size_t i = 0; i + 1 < cuts.size(); ++i) {
3445 const ExactPoint middle((cuts[i].x() + cuts[i + 1].x()) / two,
3446 (cuts[i].y() + cuts[i + 1].y()) / two);
3447 if (!holds(middle)) {
3448 return false;
3449 }
3450 }
3451 return true;
3452}
3453
3454template <class PointType, class LabelType>
3455template <class OtherChain>
3456bool PolygonSet<PointType, LabelType>::chainIn(const OtherChain& chain, bool boundaryOnly) const {
3457 if (chain.size() == 0) {
3458 return true; // an empty chain is contained in everything
3459 }
3460 if (chain.size() == 1) {
3461 return anyComponent([&](const ComponentType& component) {
3462 return boundaryOnly ? component.boundaryContains(chain[0]) : component.contains(chain[0]);
3463 });
3464 }
3465 for (const auto& edge : chain.edgesView()) {
3466 if (!segmentIn(edge, boundaryOnly)) {
3467 return false;
3468 }
3469 }
3470 return true;
3471}
3472
3473template <class PointType, class LabelType>
3474template <class OtherRegion>
3475bool PolygonSet<PointType, LabelType>::regionIn(const OtherRegion& region) const {
3476 using ExactNumber = detail::Exact1DNumber<NumberType, typename OtherRegion::NumberType>;
3477 using ExactPoint = Point<ExactNumber>;
3478 const ExactNumber two(2);
3479
3480 // The vertices before the edges they bound. This settles nothing on its
3481 // own — the vertices of a region may all be in the set while an edge
3482 // between two of them leaves it — but it is a point location per vertex
3483 // against an exact segment intersection per edge per component boundary,
3484 // so it is worth spending on an operand the next loop would reject anyway.
3485 for (const auto& vertex : region.vertices()) {
3486 if (!anyComponent([&](const ComponentType& component) {
3487 return component.contains(vertex);
3488 })) {
3489 return false;
3490 }
3491 }
3492
3493 // The boundary itself: it is one-dimensional, so the components are free to
3494 // share an edge of it between them.
3495 const auto boundary = region.edges();
3496 for (const auto& edge : boundary) {
3497 if (!segmentIn(edge, /*boundaryOnly=*/false)) {
3498 return false;
3499 }
3500 }
3501
3502 // No component boundary may reach the operand's interior. It would take a
3503 // stretch of it with it — a segment meets an open set in an open piece of
3504 // itself — and the far side of that stretch lies in no component, since a
3505 // second component there would share it with the first.
3506 for (const auto& component : components_) {
3507 for (const auto& edge : component.edges()) {
3508 if (region.interiorsIntersect(edge)) {
3509 return false;
3510 }
3511 }
3512 }
3513
3514 // What is left is one witness per connected piece of the operand's
3515 // interior, taken on the vertical line down the middle of each strip
3516 // between consecutive vertex abscissas. No vertex of the operand sits on
3517 // such a line, so every edge that spans the strip crosses it exactly once,
3518 // and between two consecutive crossings the line is wholly inside the
3519 // operand or wholly outside it.
3520 std::vector<ExactNumber> abscissas;
3521 abscissas.reserve(2 * boundary.size());
3522 for (const auto& edge : boundary) {
3523 abscissas.emplace_back(edge.min().x());
3524 abscissas.emplace_back(edge.max().x());
3525 }
3526 std::sort(abscissas.begin(), abscissas.end());
3527 abscissas.erase(std::unique(abscissas.begin(), abscissas.end()), abscissas.end());
3528
3529 std::vector<ExactNumber> ordinates;
3530 for (std::size_t strip = 0; strip + 1 < abscissas.size(); ++strip) {
3531 const ExactNumber x = (abscissas[strip] + abscissas[strip + 1]) / two;
3532 ordinates.clear();
3533 for (const auto& edge : boundary) {
3534 // An edge is stored with its endpoints in lexicographic order, so
3535 // min() is the left one whenever the two abscissas differ at all.
3536 const ExactNumber left(edge.min().x());
3537 const ExactNumber right(edge.max().x());
3538 if (!(left < x) || !(x < right)) {
3539 continue; // vertical, or entirely on one side of the line
3540 }
3541 const ExactNumber low(edge.min().y());
3542 const ExactNumber high(edge.max().y());
3543 ordinates.push_back(low + (high - low) * (x - left) / (right - left));
3544 }
3545 std::sort(ordinates.begin(), ordinates.end());
3546 ordinates.erase(std::unique(ordinates.begin(), ordinates.end()), ordinates.end());
3547 for (std::size_t i = 0; i + 1 < ordinates.size(); ++i) {
3548 const ExactPoint witness(x, (ordinates[i] + ordinates[i + 1]) / two);
3549 if (!region.contains(witness)) {
3550 continue; // the piece is a hole of the operand, not part of it
3551 }
3552 if (!anyComponent([&](const ComponentType& component) {
3553 return component.contains(witness);
3554 })) {
3555 return false;
3556 }
3557 }
3558 }
3559 return true;
3560}
3561
3562template <class PointType, class LabelType>
3563template <detail::SetOperandConcept OtherShape>
3564bool PolygonSet<PointType, LabelType>::contains(const OtherShape& other) const {
3565 // A single-component set is exactly that component, so the component's
3566 // own answer is already exact, not just a fast necessary condition. Every
3567 // branch below exists for the genuinely multi-component case — disjoint
3568 // components whose interiors may still meet at finitely many points, or
3569 // come apart at a hole-to-boundary pinch — and falling into one here would
3570 // only re-derive the same answer through machinery (isPinched, segmentIn,
3571 // chainIn, regionIn) built for that harder problem.
3572 if (componentCount() == 1) {
3573 return component(0).contains(other);
3574 }
3575 if constexpr (PointConcept<OtherShape>) {
3576 return anyComponent([&](const ComponentType& c) { return c.contains(other); });
3577 } else if constexpr (LineConcept<OtherShape>) {
3578 return other.isDegenerate() && contains(other.min());
3579 } else if constexpr (OrientedLineConcept<OtherShape>) {
3580 return other.isDegenerate() && contains(other.source());
3581 } else if constexpr (RayConcept<OtherShape>) {
3582 return other.isDegenerate() && contains(other.source());
3583 } else if constexpr (HalfplaneConcept<OtherShape>) {
3584 // A half-plane is unbounded unless it has collapsed onto its own
3585 // boundary line, which the line path then settles.
3586 return other.isDegenerate() && contains(other.source());
3588 // The one-dimensional case: a single component need not hold the whole
3589 // segment even when the set does.
3590 if (anyComponent([&](const ComponentType& c) { return c.contains(other); })) {
3591 return true;
3592 }
3593 if (!isPinched() || !intersects(other)) {
3594 return false;
3595 }
3596 return segmentIn(other, /*boundaryOnly=*/false);
3598 if (anyComponent([&](const ComponentType& c) { return c.contains(other); })) {
3599 return true;
3600 }
3601 if (!isPinched()) {
3602 return false;
3603 }
3604 return chainIn(other, /*boundaryOnly=*/false);
3605 } else if constexpr (PolygonWithHolesConcept<OtherShape>) {
3606 // The two-dimensional case a single component need not settle. A region
3607 // is connected, so a set whose components stay apart holds it in one of
3608 // them or not at all — that is the argument below, and it is all the
3609 // other operands with area need. What breaks it here is that a region's
3610 // *interior* may still come apart, at the points and the slits where a
3611 // hole reaches the outer boundary, and the pieces it comes apart into
3612 // may go to different components.
3613 if (anyComponent([&](const ComponentType& c) { return c.contains(other); })) {
3614 return true;
3615 }
3616 if (!isPinched() || other.vertexCount() == 0) {
3617 return false;
3618 }
3619 // Two necessary conditions stand in front of the general test, which
3620 // costs orders of magnitude more than the fold it backs up and answers
3621 // `false` for almost every operand that reaches it: the operand's box
3622 // must sit inside the set's, and its first vertex, like every other
3623 // point of it, must be in the set. Both are what the general test would
3624 // establish anyway, at O(1) and at one point location.
3625 if (!bbox().contains(other.bbox()) || !contains(other.outer()[0])) {
3626 return false;
3627 }
3628 return regionIn(other);
3629 } else {
3630 // Every remaining operand has connected interior, and the components
3631 // have disjoint interiors meeting at finitely many points at most. So
3632 // the operand's interior, minus those points, still connected, lies in
3633 // one component's interior, and the operand — its closure, plus a
3634 // boundary the closed component then holds — lies in that component:
3635 // one component holds it or none does. A collapsed operand is
3636 // one-dimensional again and reduces to its carrier.
3637 if (anyComponent([&](const ComponentType& c) { return c.contains(other); })) {
3638 return true;
3639 }
3640 return detail::reduceDegenerate(other,
3641 [this](const auto& carrier) { return this->contains(carrier); });
3642 }
3643}
3644
3645template <class PointType, class LabelType>
3646template <PolygonSetConcept OtherSet>
3647bool PolygonSet<PointType, LabelType>::contains(const OtherSet& other) const {
3648 // A set operand is the one that need not be connected, so it goes in one
3649 // component at a time.
3650 for (const auto& component : other) {
3651 if (!contains(component)) {
3652 return false;
3653 }
3654 }
3655 return true;
3656}
3657
3658template <class PointType, class LabelType>
3659template <PointConcept OtherPoint>
3661 return std::visit([this](const auto& value) { return this->contains(value); }, other.variant());
3662}
3663
3664} // namespace pgl
Implementations of the 'boundaryContains' predicate.
Exact rational number class template.
Definition rational.hpp:106
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:317
Definition forward.hpp:321
Definition forward.hpp:311
Definition forward.hpp:307
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
Definition arrangement.hpp:67
bool sweepContains(const OuterPolygon &outer, const InnerPolygon &inner)
Sweep-based counterpart of Polygon::contains(Polygon).
Definition redbluesweep.hpp:716
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
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
Point() -> Point< int >
constexpr std::partial_ordering dotSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b)
Tells if the angle between two vectors is acute, right, or obtuse.
Definition orientation.hpp:688
constexpr std::partial_ordering orientationSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Classifies the orientation of three points.
Definition orientation.hpp:544
constexpr std::partial_ordering crossSign(const Point< UNumber, ULabel > &u, const Point< VNumber, VLabel > &v)
Classifies the turn from one vector to another.
Definition orientation.hpp:583
constexpr bool collinear(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Tests whether three points are collinear.
Definition orientation.hpp:651
Exact low-level orientation and incircle predicates.
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 empty() const
Returns whether the convex polygon is the empty set of points.
Definition convex.hpp:390
constexpr std::optional< PointType > getIfPoint() const
Returns the point the convex polygon collapses to, if it does.
Definition predicates.hpp:994
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 std::vector< Segment< PointType > > edges() const
Returns the edges of the convex polygon.
Definition convex.hpp:529
constexpr std::optional< BoundaryType< false > > getIfSegment() const
Returns the segment the convex polygon collapses to, if it does.
Definition predicates.hpp:1008
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1135
constexpr auto orientedEdgesView() const
Lazy view counterpart of orientedEdges(); see edgesView().
Definition convex.hpp:583
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
size_t size() const
Returns the number of vertices in the convex polygon.
Definition convex.hpp:840
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 contains(const OtherPoint &other) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1015
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 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 std::vector< Point< ResultNumber, typename PointType::LabelType > > vertices() const
Returns every vertex of the region, in pair-index order (for a bounded region: counterclockwise).
Definition halfplaneintersection.hpp:890
constexpr std::size_t size() const
Returns the number of stored (non-redundant) half-planes.
Definition halfplaneintersection.hpp:596
constexpr Point< ResultNumber, typename PointType::LabelType > vertex(std::size_t i) const
Returns the vertex between half-planes i and i+1 (cyclically).
Definition halfplaneintersection.hpp:875
Closed half-plane defined by an oriented boundary line.
Definition halfplane.hpp:51
constexpr const PointType & target() const
Returns the target boundary point.
Definition halfplane.hpp:193
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:872
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 bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:428
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 interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition interiorsintersect.hpp:328
constexpr bool isDegenerate() const
Returns whether the defining points coincide.
Definition predicates.hpp:451
Weakly x-monotone polyline stored by lexicographically sorted vertices.
Definition monotonechain.hpp:146
constexpr std::optional< std::size_t > isBelow(const OtherPoint &point) const
Tests whether the chain passes weakly below a point.
Definition atxy.hpp:462
PointType_ PointType
Definition monotonechain.hpp:147
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1822
constexpr std::optional< std::size_t > isAbove(const OtherPoint &point) const
Tests whether the chain passes weakly above a point.
Definition atxy.hpp:489
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:531
constexpr Line< PointType > asLine() const
Returns the line without orientation.
Definition orientedline.hpp:321
constexpr Segment< PointType > asSegment() const
Returns the segment without orientation.
Definition orientedsegment.hpp:322
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:346
Two-dimensional point with optional label payload.
Definition point.hpp:129
constexpr Point()=default
Creates the origin point (0, 0).
constexpr std::array< Point, 1 > vertices() const
Returns the unique vertex of the point-shaped object.
Definition bounding.hpp:34
constexpr bool contains(const OtherPoint &other) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:25
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 contains(const OtherShape &other) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:3564
bool isPinched() const
Tests whether two components touch each other anywhere.
Definition contains.hpp:3368
constexpr std::size_t componentCount() const
Returns the number of components.
Definition polygonset.hpp:263
PolygonWithHoles< PointType > ComponentType
Definition polygonset.hpp:169
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the set.
Definition bounding.hpp:469
constexpr const PolygonType & hole(std::size_t index) const
Accesses a hole by index.
Definition polygonwithholes.hpp:196
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:2945
constexpr const Rectangle< PointType > & bbox() const
Computes the bounding box of the polygon.
Definition bounding.hpp:449
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1296
constexpr auto edgesView() const
Returns a lazy view over the edges, materializing each Segment on the fly instead of allocating a vec...
Definition polygon.hpp:782
constexpr bool isPoint() const
Checks whether the polygon covers exactly one point.
Definition polygon.hpp:330
constexpr bool containsChainBased(const OtherPolygon &other) const
Same contract as contains(const OtherPolygon&) const, by the chain-pair strategy alone.
Definition contains.hpp:1587
constexpr std::size_t size() const
Returns the number of vertices in the polygon.
Definition polygon.hpp:259
PointType_ PointType
Definition polygon.hpp:60
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:2134
constexpr bool empty() const
Checks whether the polyline has no vertex.
Definition polyline.hpp:395
constexpr std::size_t size() const
Returns the number of vertices in the polyline.
Definition polyline.hpp:388
constexpr 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 containsCollinear(const OtherPoint &point) const
Returns whether the ray contains the given point that is collinear with the ray.
Definition predicates.hpp:754
PointType_ PointType
Definition ray.hpp:52
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 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 std::array< Segment< PointType >, 4 > edges() const
Returns the four edges as unordered segments.
Definition bounding.hpp:199
constexpr Point< ResultNumber > center() const
Returns the center of the rectangle.
Definition measures.hpp:325
PointType_ PointType
Definition rectangle.hpp:76
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 contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:119
constexpr const PointType & max() const
Returns the largest stored endpoint.
Definition segment.hpp:199
TPoint PointType
Definition segment.hpp:59
constexpr const PointType & min() const
Returns the smallest stored endpoint.
Definition segment.hpp:190
constexpr Segment()=default
Creates the degenerate segment (0,0)--(0,0).
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 contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:223
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 boundaryContains(const OtherPoint &point) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition boundarycontains.hpp:123
constexpr Rectangle< PointType > bbox() const
Returns the axis-aligned bounding box of the vertices.
Definition bounding.hpp:224
constexpr std::optional< BoundaryType< false > > getIfSegment() const
Returns the segment the triangle collapses to, if it does.
Definition predicates.hpp:255
constexpr Convex< PointType > asConvex() const
Returns the triangle as a convex polygon.
Definition triangle.hpp:490
constexpr bool isDegenerate() const
Tests whether the three vertices are collinear.
Definition predicates.hpp:223
constexpr std::optional< PointType > getIfPoint() const
Returns the point the triangle collapses to, if it does.
Definition predicates.hpp:241
constexpr const PointType & c() const
Returns the third vertex.
Definition triangle.hpp:226