Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
shape.hpp
Go to the documentation of this file.
1#pragma once
2
4
12
13#include <compare>
14#include <concepts>
15#include <functional>
16#include <optional>
17#include <ostream>
18#include <stdexcept>
19#include <type_traits>
20#include <utility>
21#include <variant>
22#include <vector>
23
24
25namespace pgl {
26
27namespace detail {
28
29template <class PointType, class T>
30struct is_shape_alternative : std::false_type {};
31
32template <class PointType>
33struct is_shape_alternative<PointType, EmptyShape<PointType>> : std::true_type {};
34
35template <class PointType, class Number, class Label>
36struct is_shape_alternative<PointType, Point<Number, Label>> : std::bool_constant<std::same_as<PointType, Point<Number, Label>>> {};
37
38template <class PointType, class Label>
39struct is_shape_alternative<PointType, Segment<PointType, Label>> : std::true_type {};
40
41template <class PointType>
42struct is_shape_alternative<PointType, OrientedSegment<PointType>> : std::true_type {};
43
44template <class PointType>
45struct is_shape_alternative<PointType, Line<PointType>> : std::true_type {};
46
47template <class PointType>
48struct is_shape_alternative<PointType, OrientedLine<PointType>> : std::true_type {};
49
50template <class PointType>
51struct is_shape_alternative<PointType, Ray<PointType>> : std::true_type {};
52
53template <class PointType>
54struct is_shape_alternative<PointType, Halfplane<PointType>> : std::true_type {};
55
56template <class PointType>
57struct is_shape_alternative<PointType, Rectangle<PointType>> : std::true_type {};
58
59template <class PointType>
60struct is_shape_alternative<PointType, Triangle<PointType>> : std::true_type {};
61
62template <class PointType>
63struct is_shape_alternative<PointType, Convex<PointType>> : std::true_type {};
64
65template <class PointType, class Label>
66struct is_shape_alternative<PointType, Disk<PointType, Label>> : std::true_type {};
67
68template <class PointType>
69struct is_shape_alternative<PointType, MonotoneChain<PointType>> : std::true_type {};
70
71template <class PointType>
72struct is_shape_alternative<PointType, Polyline<PointType>> : std::true_type {};
73
74template <class PointType>
75struct is_shape_alternative<PointType, Polygon<PointType>> : std::true_type {};
76
77template <class PointType, class Label>
78struct is_shape_alternative<PointType, HalfplaneIntersection<PointType, Label>> : std::true_type {};
79
80template <class PointType, class Label>
81struct is_shape_alternative<PointType, PolygonWithHoles<PointType, Label>> : std::true_type {};
82
83template <class PointType, class Label>
84struct is_shape_alternative<PointType, PolygonSet<PointType, Label>> : std::true_type {};
85
86template <class PointType, class T>
87inline constexpr bool is_shape_alternative_v = is_shape_alternative<PointType, std::remove_cvref_t<T>>::value;
88
89template <class PointType, class T>
90concept ShapeAlternative = is_shape_alternative_v<PointType, T>;
91
92// Minimal shape detectors for the standard wrappers an intersection may return.
93template <class T>
94struct is_std_optional : std::false_type {};
95template <class T>
96struct is_std_optional<std::optional<T>> : std::true_type {};
97
98template <class T>
99struct is_std_vector : std::false_type {};
100template <class T, class A>
101struct is_std_vector<std::vector<T, A>> : std::true_type {};
102
103template <class T>
104struct is_std_variant : std::false_type {};
105template <class... Ts>
106struct is_std_variant<std::variant<Ts...>> : std::true_type {};
107
108// True iff T is a std::variant whose every alternative is a supported shape for
109// PointType. Gates the variant-unwrapping Shape constructor so a variant that
110// could hold a non-shape is rejected at compile time.
111template <class PointType, class T>
112struct is_shape_variant : std::false_type {};
113
114template <class PointType, class... Ts>
115struct is_shape_variant<PointType, std::variant<Ts...>>
116 : std::bool_constant<(is_shape_alternative_v<PointType, Ts> && ...)> {};
117
118template <class PointType, class T>
119inline constexpr bool is_shape_variant_v = is_shape_variant<PointType, std::remove_cvref_t<T>>::value;
120
121// True iff T is a std::optional wrapping such a shape variant.
122template <class PointType, class T>
123struct is_shape_optional_variant : std::false_type {};
124
125template <class PointType, class V>
126struct is_shape_optional_variant<PointType, std::optional<V>>
127 : std::bool_constant<is_shape_variant_v<PointType, V>> {};
128
129template <class PointType, class T>
130inline constexpr bool is_shape_optional_variant_v =
131 is_shape_optional_variant<PointType, std::remove_cvref_t<T>>::value;
132
133// Point type carried by a shape alternative: a Point is its own point type;
134// every other alternative exposes it as a nested PointType. Used by the Shape
135// deduction guides to recover the wrapper's point type from a result variant.
136template <class T>
137struct shape_point_type {
138 using type = typename T::PointType;
139};
140
141template <class Number, class Label>
142struct shape_point_type<Point<Number, Label>> {
143 using type = Point<Number, Label>;
144};
145
146template <class T>
147using shape_point_type_t = typename shape_point_type<T>::type;
148
149} // namespace detail
150
159template <class PointType = Point<>>
160struct Shape {
162 using PointType_ = PointType;
164 using NumberType = PointType::NumberType;
166 using LabelType = PointType::LabelType;
169 using Variant = std::variant<
171 PointType,
188
192 constexpr Shape() = default;
193
200 template <class T>
201 requires(detail::ShapeAlternative<PointType, T>)
202 constexpr Shape(T&& value)
203 : value_(std::forward<T>(value)) {}
204
217 template <class Result>
218 requires(detail::is_shape_variant_v<PointType, Result> ||
219 detail::is_shape_optional_variant_v<PointType, Result>)
220 constexpr Shape(const Result& result) {
221 if constexpr (detail::is_shape_optional_variant_v<PointType, Result>) {
222 if (result) {
223 *this = Shape(*result);
224 }
225 } else {
226 std::visit(
227 [this](const auto& alternative) { *this = Shape(alternative); }, result);
228 }
229 }
230
238 template <class T>
239 requires(detail::ShapeAlternative<PointType, T>)
240 constexpr Shape& operator=(T&& value) {
241 value_ = std::forward<T>(value);
242 return *this;
243 }
244
248 constexpr bool operator==(const Shape&) const = default;
249
251 template<AnyShapeConcept OtherShape>
252 [[nodiscard]] constexpr bool samePointSet(const OtherShape& other) const;
253
257 constexpr auto operator<=>(const Shape&) const = default;
258
264 constexpr const Variant& variant() const {
265 return value_;
266 }
267
273 constexpr Variant& variant() {
274 return value_;
275 }
276
293 [[nodiscard]] constexpr bool empty() const {
294 return std::visit([](const auto& value) { return detail::coversNoPoint(value); },
295 value_);
296 }
297
303 [[nodiscard]] constexpr bool isDegenerate() const {
304 return std::visit(
305 [](const auto& value) {
306 if constexpr (requires { value.isDegenerate(); }) {
307 return value.isDegenerate();
308 } else {
309 return false;
310 }
311 },
312 value_);
313 }
314
325 [[nodiscard]] constexpr Rectangle<PointType> bbox() const {
326 return std::visit(
327 [](const auto& value) -> Rectangle<PointType> {
328 if constexpr (requires { value.template bbox<NumberType>(); }) {
329 return value.template bbox<NumberType>();
330 } else if constexpr (requires { value.bbox(); }) {
331 return value.bbox();
332 } else {
333 throw std::logic_error("Shape::bbox is not defined for this unbounded alternative");
334 }
335 },
336 value_);
337 }
338
352 [[nodiscard]] constexpr std::size_t size() const {
353 return std::visit(
354 [](const auto& value) -> std::size_t {
355 using S = std::decay_t<decltype(value)>;
356 if constexpr (detail::is_polygon_with_holes_v<S>) {
357 throw std::logic_error("Shape::size is not defined for the PolygonWithHoles alternative");
358 } else if constexpr (detail::is_polygon_set_v<S>) {
359 throw std::logic_error("Shape::size is not defined for the PolygonSet alternative");
360 } else {
361 return value.size();
362 }
363 },
364 value_);
365 }
366
378 [[nodiscard]] constexpr PointType_ get(std::ptrdiff_t index) const {
379 return std::visit(
380 [index](const auto& value) -> PointType_ {
381 using S = std::decay_t<decltype(value)>;
382 if constexpr (std::same_as<S, PointType_>) {
383 throw std::logic_error("Shape::get is not defined for the Point alternative");
384 } else if constexpr (detail::is_halfplane_intersection_v<S>) {
385 throw std::logic_error("Shape::get is not defined for the HalfplaneIntersection alternative");
386 } else if constexpr (detail::is_polygon_with_holes_v<S>) {
387 throw std::logic_error("Shape::get is not defined for the PolygonWithHoles alternative");
388 } else if constexpr (detail::is_polygon_set_v<S>) {
389 throw std::logic_error("Shape::get is not defined for the PolygonSet alternative");
390 } else {
391 return value.get(index);
392 }
393 },
394 value_);
395 }
396
408 [[nodiscard]] constexpr PointType_ operator[](std::size_t index) const {
409 return std::visit(
410 [index](const auto& value) -> PointType_ {
411 using S = std::decay_t<decltype(value)>;
412 if constexpr (std::same_as<S, PointType_>) {
413 throw std::logic_error("Shape::operator[] is not defined for the Point alternative");
414 } else if constexpr (detail::is_halfplane_intersection_v<S>) {
415 throw std::logic_error("Shape::operator[] is not defined for the HalfplaneIntersection alternative");
416 } else if constexpr (detail::is_polygon_with_holes_v<S>) {
417 throw std::logic_error("Shape::operator[] is not defined for the PolygonWithHoles alternative");
418 } else if constexpr (detail::is_polygon_set_v<S>) {
419 throw std::logic_error("Shape::operator[] is not defined for the PolygonSet alternative");
420 } else {
421 return value[index];
422 }
423 },
424 value_);
425 }
426
439 [[nodiscard]] constexpr std::ptrdiff_t index(const PointType_& point) const {
440 return std::visit(
441 [&point](const auto& value) -> std::ptrdiff_t {
442 using S = std::decay_t<decltype(value)>;
443 if constexpr (std::same_as<S, PointType_>) {
444 throw std::logic_error("Shape::index(Point) is not defined for the Point alternative");
445 } else if constexpr (detail::is_halfplane_intersection_v<S>) {
446 throw std::logic_error("Shape::index(Point) is not defined for the HalfplaneIntersection alternative");
447 } else if constexpr (detail::is_polygon_with_holes_v<S>) {
448 throw std::logic_error("Shape::index(Point) is not defined for the PolygonWithHoles alternative");
449 } else if constexpr (detail::is_polygon_set_v<S>) {
450 throw std::logic_error("Shape::index(Point) is not defined for the PolygonSet alternative");
451 } else {
452 return value.index(point);
453 }
454 },
455 value_);
456 }
457
468 [[nodiscard]] constexpr std::ptrdiff_t index(const NumberType& value) const {
469 return std::visit(
470 [&value](const auto& shape) -> std::ptrdiff_t {
471 using S = std::decay_t<decltype(shape)>;
472 if constexpr (std::same_as<S, PointType_>) {
473 return shape.index(value);
474 } else {
475 throw std::logic_error("Shape::index(NumberType) is only defined for the Point alternative");
476 }
477 },
478 value_);
479 }
480
487 template <class T>
488 requires(detail::ShapeAlternative<PointType, T>)
489 constexpr bool holdsAlternative() const {
490 return std::holds_alternative<std::remove_cvref_t<T>>(value_);
491 }
492
499 template <class T>
500 requires(detail::ShapeAlternative<PointType, T>)
501 constexpr const std::remove_cvref_t<T>* getIf() const {
502 return std::get_if<std::remove_cvref_t<T>>(&value_);
503 }
504
511 template <class T>
512 requires(detail::ShapeAlternative<PointType, T>)
513 constexpr std::remove_cvref_t<T>* getIf() {
514 return std::get_if<std::remove_cvref_t<T>>(&value_);
515 }
516
538// The doc comments below are part of the expansion on purpose: a description on
539// the enclosing @name group documents the group, not its members, which would
540// leave all 51 generated methods without a @brief of their own.
541#define PGL_SHAPE_ALTERNATIVE(Name, Type) \
542 \
543 [[nodiscard]] constexpr bool is##Name() const { \
544 return std::holds_alternative<Type>(value_); \
545 } \
546 \
548 [[nodiscard]] constexpr const Type* getIf##Name() const { \
549 return std::get_if<Type>(&value_); \
550 } \
551 \
553 [[nodiscard]] constexpr Type* getIf##Name() { \
554 return std::get_if<Type>(&value_); \
574
575#undef PGL_SHAPE_ALTERNATIVE
577
588 template <class T>
589 requires(detail::ShapeAlternative<PointType, T>)
590 constexpr explicit operator T() const {
591 return std::get<std::remove_cvref_t<T>>(value_);
592 }
593
601 template <class Other>
602 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
603 constexpr bool contains(const Other& other) const {
604 return applyPredicate(
605 [](const auto& left, const auto& right) {
606 if constexpr (requires { left.contains(right); }) {
607 return left.contains(right);
608 } else {
609 return false;
610 }
611 },
612 other);
613 }
614
622 template <class Other>
623 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
624 constexpr bool boundaryContains(const Other& other) const {
625 return applyPredicate(
626 [](const auto& left, const auto& right) {
627 if constexpr (requires { left.boundaryContains(right); }) {
628 return left.boundaryContains(right);
629 } else {
630 return false;
631 }
632 },
633 other);
634 }
635
643 template <class Other>
644 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
645 constexpr bool interiorContains(const Other& other) const {
646 return applyPredicate(
647 [](const auto& left, const auto& right) {
648 if constexpr (requires { left.interiorContains(right); }) {
649 return left.interiorContains(right);
650 } else {
651 return false;
652 }
653 },
654 other);
655 }
656
664 template <class Other>
665 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
666 constexpr bool intersects(const Other& other) const {
667 return applyPredicate(
668 [](const auto& left, const auto& right) {
669 if constexpr (requires { left.intersects(right); }) {
670 return left.intersects(right);
671 } else {
672 return false;
673 }
674 },
675 other);
676 }
677
685 template <class Other>
686 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
687 constexpr bool interiorsIntersect(const Other& other) const {
688 return applyPredicate(
689 [](const auto& left, const auto& right) {
690 if constexpr (requires { left.interiorsIntersect(right); }) {
691 return left.interiorsIntersect(right);
692 } else {
693 return false;
694 }
695 },
696 other);
697 }
698
706 template <class Other>
707 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
708 constexpr bool separates(const Other& other) const {
709 return applyPredicate(
710 [](const auto& left, const auto& right) {
711 if constexpr (requires { left.separates(right); }) {
712 return left.separates(right);
713 } else {
714 return false;
715 }
716 },
717 other);
718 }
719
727 template <class Other>
728 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
729 constexpr bool crosses(const Other& other) const {
730 return applyPredicate(
731 [](const auto& left, const auto& right) {
732 if constexpr (requires { left.crosses(right); }) {
733 return left.crosses(right);
734 } else {
735 return false;
736 }
737 },
738 other);
739 }
740
783 template <class ResultNumber = division_result_t<NumberType>, class Other>
784 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
785 constexpr Shape<Point<ResultNumber, LabelType>> intersection(const Other& other) const {
786 if constexpr (detail::is_shape_v<Other>) {
787 return std::visit(
788 [](const auto& left, const auto& right) {
789 return intersectionOf<ResultNumber>(left, right);
790 },
791 value_,
792 other.variant());
793 } else {
794 return std::visit(
795 [&other](const auto& left) {
796 return intersectionOf<ResultNumber>(left, other);
797 },
798 value_);
799 }
800 }
801
825 template <class ResultNumber = division_result_t<NumberType>, class Other>
826 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
827 [[nodiscard]] PolygonSet<Point<ResultNumber, LabelType>> regularizedIntersection(const Other& other) const {
828 if constexpr (detail::is_shape_v<Other>) {
829 return std::visit(
830 [](const auto& left, const auto& right) {
831 return regularizedIntersectionOf<ResultNumber>(left, right);
832 },
833 value_,
834 other.variant());
835 } else {
836 return std::visit(
837 [&other](const auto& left) {
838 return regularizedIntersectionOf<ResultNumber>(left, other);
839 },
840 value_);
841 }
842 }
843
874 template <class ResultNumber = division_result_t<NumberType>, class Other>
875 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
876 [[nodiscard]] PolygonSet<Point<ResultNumber, LabelType>> regularizedUnion(const Other& other) const {
877 if constexpr (detail::is_shape_v<Other>) {
878 return std::visit(
879 [](const auto& left, const auto& right) {
880 return regularizedUnionOf<ResultNumber>(left, right);
881 },
882 value_,
883 other.variant());
884 } else {
885 return std::visit(
886 [&other](const auto& left) {
887 return regularizedUnionOf<ResultNumber>(left, other);
888 },
889 value_);
890 }
891 }
892
924 template <class ResultNumber = division_result_t<NumberType>, class Other>
925 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
926 [[nodiscard]] PolygonSet<Point<ResultNumber, LabelType>> difference(const Other& other) const {
927 if constexpr (detail::is_shape_v<Other>) {
928 return std::visit(
929 [](const auto& left, const auto& right) {
930 return differenceOf<ResultNumber>(left, right);
931 },
932 value_,
933 other.variant());
934 } else {
935 return std::visit(
936 [&other](const auto& left) {
937 return differenceOf<ResultNumber>(left, other);
938 },
939 value_);
940 }
941 }
942
962 template <class ResultNumber = division_result_t<NumberType>, class Other>
963 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
964 [[nodiscard]] PolygonSet<Point<ResultNumber, LabelType>> symmetricDifference(const Other& other) const {
965 if constexpr (detail::is_shape_v<Other>) {
966 return std::visit(
967 [](const auto& left, const auto& right) {
968 return symmetricDifferenceOf<ResultNumber>(left, right);
969 },
970 value_,
971 other.variant());
972 } else {
973 return std::visit(
974 [&other](const auto& left) {
975 return symmetricDifferenceOf<ResultNumber>(left, other);
976 },
977 value_);
978 }
979 }
980
1004 template <class ResultNumber = double, class Other>
1005 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
1006 constexpr ResultNumber squaredDistance(const Other& other) const {
1007 if constexpr (detail::is_shape_v<Other>) {
1008 return std::visit(
1009 [](const auto& left, const auto& right) {
1010 return squaredDistanceOf<ResultNumber>(left, right);
1011 },
1012 value_,
1013 other.variant());
1014 } else {
1015 return std::visit(
1016 [&other](const auto& left) {
1017 return squaredDistanceOf<ResultNumber>(left, other);
1018 },
1019 value_);
1020 }
1021 }
1022
1048 template <class ResultNumber = division_result_t<NumberType>, class Other>
1049 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
1050 constexpr ResultNumber squaredHausdorffDistance(const Other& other) const {
1051 if constexpr (detail::is_shape_v<Other>) {
1052 return std::visit(
1053 [](const auto& left, const auto& right) {
1054 return squaredHausdorffDistanceOf<ResultNumber>(left, right);
1055 },
1056 value_,
1057 other.variant());
1058 } else {
1059 return std::visit(
1060 [&other](const auto& left) {
1061 return squaredHausdorffDistanceOf<ResultNumber>(left, other);
1062 },
1063 value_);
1064 }
1065 }
1066
1093 template <class ResultNumber = double, class Other>
1094 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
1095 constexpr ResultNumber distanceL1(const Other& other) const {
1096 if constexpr (detail::is_shape_v<Other>) {
1097 return std::visit(
1098 [](const auto& left, const auto& right) {
1099 return distanceL1Of<ResultNumber>(left, right);
1100 },
1101 value_,
1102 other.variant());
1103 } else {
1104 return std::visit(
1105 [&other](const auto& left) {
1106 return distanceL1Of<ResultNumber>(left, other);
1107 },
1108 value_);
1109 }
1110 }
1111
1117 template <class ResultNumber = double, class Other>
1118 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
1119 constexpr ResultNumber distanceLInf(const Other& other) const {
1120 if constexpr (detail::is_shape_v<Other>) {
1121 return std::visit(
1122 [](const auto& left, const auto& right) {
1123 return distanceLInfOf<ResultNumber>(left, right);
1124 },
1125 value_,
1126 other.variant());
1127 } else {
1128 return std::visit(
1129 [&other](const auto& left) {
1130 return distanceLInfOf<ResultNumber>(left, other);
1131 },
1132 value_);
1133 }
1134 }
1135
1159 template <class ResultNumber = division_result_t<NumberType>, class Other>
1160 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
1161 constexpr ResultNumber hausdorffDistanceL1(const Other& other) const {
1162 if constexpr (detail::is_shape_v<Other>) {
1163 return std::visit(
1164 [](const auto& left, const auto& right) {
1165 return hausdorffDistanceL1Of<ResultNumber>(left, right);
1166 },
1167 value_,
1168 other.variant());
1169 } else {
1170 return std::visit(
1171 [&other](const auto& left) {
1172 return hausdorffDistanceL1Of<ResultNumber>(left, other);
1173 },
1174 value_);
1175 }
1176 }
1177
1183 template <class ResultNumber = division_result_t<NumberType>, class Other>
1184 requires(std::same_as<std::remove_cvref_t<Other>, Shape> || detail::ShapeAlternative<PointType, Other>)
1185 constexpr ResultNumber hausdorffDistanceLInf(const Other& other) const {
1186 if constexpr (detail::is_shape_v<Other>) {
1187 return std::visit(
1188 [](const auto& left, const auto& right) {
1189 return hausdorffDistanceLInfOf<ResultNumber>(left, right);
1190 },
1191 value_,
1192 other.variant());
1193 } else {
1194 return std::visit(
1195 [&other](const auto& left) {
1196 return hausdorffDistanceLInfOf<ResultNumber>(left, other);
1197 },
1198 value_);
1199 }
1200 }
1201
1215 template <class OtherShape>
1216 requires MinkowskiSummableConcept<Shape<PointType>, OtherShape>
1217 [[nodiscard]] constexpr auto minkowskiSum(const OtherShape& other) const;
1218
1241 template <class OtherShape>
1242 requires MinkowskiSummableConcept<Shape<PointType>, OtherShape>
1243 [[nodiscard]] constexpr auto minkowskiErosion(const OtherShape& other) const;
1244
1255 template <PointConcept OtherPoint>
1256 constexpr Shape& operator+=(const OtherPoint& translation) {
1257 std::visit([&translation](auto& alternative) { alternative += translation; }, value_);
1258 return *this;
1259 }
1260
1271 template <PointConcept OtherPoint>
1272 constexpr Shape& operator-=(const OtherPoint& translation) {
1273 std::visit([&translation](auto& alternative) { alternative -= translation; }, value_);
1274 return *this;
1275 }
1276
1287 template <class Scalar>
1288 requires(!detail::is_point_v<Scalar> && !TransformationConcept<Scalar>)
1289 constexpr Shape& operator*=(const Scalar& scalar) {
1290 std::visit([&scalar](auto& alternative) { alternative *= scalar; }, value_);
1291 return *this;
1292 }
1293
1304 template <class Scalar>
1305 requires(!detail::is_point_v<Scalar> && !TransformationConcept<Scalar>)
1306 constexpr Shape& operator/=(const Scalar& scalar) {
1307 std::visit([&scalar](auto& alternative) { alternative /= scalar; }, value_);
1308 return *this;
1309 }
1310
1315 * @return Rotated shape, preserving the stored alternative type.
1316 */
1317 [[nodiscard]] constexpr Shape rotated90(int k = 1) const {
1318 return std::visit(
1319 [k](const auto& value) -> Shape { return Shape(value.rotated90(k)); },
1320 value_);
1321 }
1322
1326 * @param k Number of 90-degree CCW rotations (may be negative).
1327 */
1328 constexpr void rotate90(int k = 1) {
1329 std::visit([k](auto& value) { value.rotate90(k); }, value_);
1330 }
1331
1338 template <class OtherNumber>
1339 [[nodiscard]] constexpr Shape scaledUpX(const OtherNumber scalar) const {
1340 return std::visit(
1341 [scalar](const auto& value) -> Shape {
1342 if constexpr (requires { value.scaledUpX(scalar); }) {
1343 return Shape(value.scaledUpX(scalar));
1344 } else {
1345 throw std::logic_error("Shape::scaledUpX is not defined for the Disk alternative");
1346 }
1347 },
1348 value_);
1349 }
1350
1357 template <class OtherNumber>
1358 constexpr void scaleUpX(const OtherNumber scalar) {
1359 std::visit(
1360 [scalar](auto& value) {
1361 if constexpr (requires { value.scaleUpX(scalar); }) {
1362 value.scaleUpX(scalar);
1363 } else {
1364 throw std::logic_error("Shape::scaleUpX is not defined for the Disk alternative");
1365 }
1366 },
1367 value_);
1368 }
1369
1376 template <class OtherNumber>
1377 [[nodiscard]] constexpr Shape scaledUpY(const OtherNumber scalar) const {
1378 return std::visit(
1379 [scalar](const auto& value) -> Shape {
1380 if constexpr (requires { value.scaledUpY(scalar); }) {
1381 return Shape(value.scaledUpY(scalar));
1382 } else {
1383 throw std::logic_error("Shape::scaledUpY is not defined for the Disk alternative");
1384 }
1385 },
1386 value_);
1387 }
1388
1395 template <class OtherNumber>
1396 constexpr void scaleUpY(const OtherNumber scalar) {
1397 std::visit(
1398 [scalar](auto& value) {
1399 if constexpr (requires { value.scaleUpY(scalar); }) {
1400 value.scaleUpY(scalar);
1401 } else {
1402 throw std::logic_error("Shape::scaleUpY is not defined for the Disk alternative");
1403 }
1404 },
1405 value_);
1406 }
1407
1414 template <class OtherNumber>
1415 [[nodiscard]] constexpr Shape scaledDownX(const OtherNumber scalar) const {
1416 return std::visit(
1417 [scalar](const auto& value) -> Shape {
1418 if constexpr (requires { value.scaledDownX(scalar); }) {
1419 return Shape(value.scaledDownX(scalar));
1420 } else {
1421 throw std::logic_error("Shape::scaledDownX is not defined for the Disk alternative");
1422 }
1423 },
1424 value_);
1425 }
1426
1433 template <class OtherNumber>
1434 constexpr void scaleDownX(const OtherNumber scalar) {
1435 std::visit(
1436 [scalar](auto& value) {
1437 if constexpr (requires { value.scaleDownX(scalar); }) {
1438 value.scaleDownX(scalar);
1439 } else {
1440 throw std::logic_error("Shape::scaleDownX is not defined for the Disk alternative");
1441 }
1442 },
1443 value_);
1444 }
1445
1452 template <class OtherNumber>
1453 [[nodiscard]] constexpr Shape scaledDownY(const OtherNumber scalar) const {
1454 return std::visit(
1455 [scalar](const auto& value) -> Shape {
1456 if constexpr (requires { value.scaledDownY(scalar); }) {
1457 return Shape(value.scaledDownY(scalar));
1458 } else {
1459 throw std::logic_error("Shape::scaledDownY is not defined for the Disk alternative");
1460 }
1461 },
1462 value_);
1463 }
1464
1471 template <class OtherNumber>
1472 constexpr void scaleDownY(const OtherNumber scalar) {
1473 std::visit(
1474 [scalar](auto& value) {
1475 if constexpr (requires { value.scaleDownY(scalar); }) {
1476 value.scaleDownY(scalar);
1477 } else {
1478 throw std::logic_error("Shape::scaleDownY is not defined for the Disk alternative");
1479 }
1480 },
1481 value_);
1482 }
1483
1484 private:
1496 template <class Dispatch, class Other>
1497 constexpr bool applyPredicate(Dispatch dispatch, const Other& other) const {
1498 if constexpr (detail::is_shape_v<Other>) {
1499 return std::visit(
1500 [&dispatch](const auto& left, const auto& right) {
1501 return dispatch(left, right);
1502 },
1503 value_,
1504 other.variant());
1505 } else {
1506 return std::visit(
1507 [&dispatch, &other](const auto& self) {
1508 return dispatch(self, other);
1509 },
1510 value_);
1511 }
1512 }
1513
1514 // Intersect two unwrapped alternatives and wrap the result as a Shape over
1515 // the result point type. The empty set on either side gives the empty shape;
1516 // otherwise the pair is dispatched to the concrete `intersection` when one
1517 // exists. Thanks to the rank-constrained fallbacks, the `requires` probe is
1518 // SFINAE-safe and self-maintaining: a pair with no intersection (Disk, or
1519 // two shapes neither of which implements the other) simply takes the throw.
1520 template <class ResultNumber, class Left, class Right>
1521 static constexpr Shape<Point<ResultNumber, LabelType>> intersectionOf(const Left& left, const Right& right) {
1522 using ResultPoint = Point<ResultNumber, LabelType>;
1523 if constexpr (std::same_as<Left, EmptyShape<PointType>> ||
1524 std::same_as<Right, EmptyShape<PointType>>) {
1525 return Shape<ResultPoint>{EmptyShape<ResultPoint>{}};
1526 } else if constexpr (requires { left.template intersection<ResultNumber>(right); }) {
1527 return resultToShape<ResultNumber>(left.template intersection<ResultNumber>(right));
1528 } else {
1529 throw std::logic_error("Shape::intersection is not defined for this shape pair");
1530 }
1531 }
1532
1533 // Compute a regularized region intersection. Unlike literal intersection,
1534 // the empty shape is not a specially supported operand: this dispatcher
1535 // mirrors the concrete regularizedIntersection overload grid exactly.
1536 template <class ResultNumber, class Left, class Right>
1537 static PolygonSet<Point<ResultNumber, LabelType>> regularizedIntersectionOf(
1538 const Left& left, const Right& right) {
1539 if constexpr (requires { left.template regularizedIntersection<ResultNumber>(right); }) {
1540 return left.template regularizedIntersection<ResultNumber>(right);
1541 } else {
1542 throw std::logic_error(
1543 "Shape::regularizedIntersection is not defined for this shape pair");
1544 }
1545 }
1546
1547 // Unite two unwrapped alternatives. The probe is SFINAE-safe and
1548 // self-maintaining in the same way intersectionOf's is, and here it lands
1549 // exactly on the pairs of bounded polygonal regions: those define
1550 // regularizedUnion for every ordered pair between them, each on the
1551 // higher-ranked operand with the lower-ranked one forwarding, and nothing
1552 // else defines it at all.
1553 //
1554 // There is no EmptyShape short circuit as there is above. The empty set is
1555 // the identity of a union rather than its absorber, so `empty ∪ A` would have
1556 // to answer with A itself — which is only a PolygonSet when A is a region,
1557 // and would then be a special case reachable no other way. It takes the
1558 // throw with everything else instead.
1559 template <class ResultNumber, class Left, class Right>
1560 static PolygonSet<Point<ResultNumber, LabelType>> regularizedUnionOf(const Left& left, const Right& right) {
1561 if constexpr (requires { left.template regularizedUnion<ResultNumber>(right); }) {
1562 return left.template regularizedUnion<ResultNumber>(right);
1563 } else {
1564 throw std::logic_error("Shape::regularizedUnion is not defined for this shape pair");
1565 }
1566 }
1567
1568 // Remove one unwrapped alternative from another, and take the same
1569 // regularized parts as the two above. The probe lands on the same grid of
1570 // bounded polygonal regions regularizedUnionOf's does, but by a different
1571 // route: a difference is not symmetric, so nothing forwards, and every one
1572 // of the thirty-six ordered pairs is stated on its own receiver.
1573 //
1574 // The empty set is no more a special case here than it is above, and for a
1575 // sharper reason: `A ∖ empty` is A, which is a PolygonSet only when A is a
1576 // region, and `empty ∖ A` is the empty shape rather than the empty set of
1577 // regions. Both take the throw.
1578 template <class ResultNumber, class Left, class Right>
1579 static PolygonSet<Point<ResultNumber, LabelType>> differenceOf(const Left& left, const Right& right) {
1580 if constexpr (requires { left.template difference<ResultNumber>(right); }) {
1581 return left.template difference<ResultNumber>(right);
1582 } else {
1583 throw std::logic_error("Shape::difference is not defined for this shape pair");
1584 }
1585 }
1586
1587 // The symmetric difference of two unwrapped alternatives, probed as above.
1588 template <class ResultNumber, class Left, class Right>
1589 static PolygonSet<Point<ResultNumber, LabelType>> symmetricDifferenceOf(const Left& left,
1590 const Right& right) {
1591 if constexpr (requires { left.template symmetricDifference<ResultNumber>(right); }) {
1592 return left.template symmetricDifference<ResultNumber>(right);
1593 } else {
1594 throw std::logic_error("Shape::symmetricDifference is not defined for this shape pair");
1595 }
1596 }
1597
1598 // Measure the squared distance between two unwrapped alternatives. A pair with
1599 // no defined squaredDistance (anything against an EmptyShape) takes the throw.
1600 // The requires probes are SFINAE-safe and self-maintaining: a pair gains
1601 // support here as soon as either side implements squaredDistance for the
1602 // other (directly or via forwarding).
1603 //
1604 // Both probes convert explicitly, because a pair involving a Disk answers in
1605 // detail::floating_result_t<ResultNumber> rather than in ResultNumber itself
1606 // — an exact request cannot be honoured for a distance realized on a circle.
1607 // That conversion is the point at which an exact caller learns it is getting
1608 // a rounded answer, and it is why the cast cannot be left implicit: Rational
1609 // is only explicitly constructible from a floating-point value.
1610 template <class ResultNumber, class Left, class Right>
1611 static constexpr ResultNumber squaredDistanceOf(const Left& left, const Right& right) {
1612 if constexpr (requires { left.template squaredDistance<ResultNumber>(right); }) {
1613 return static_cast<ResultNumber>(left.template squaredDistance<ResultNumber>(right));
1614 } else if constexpr (requires { left.squaredDistance(right); }) {
1615 return static_cast<ResultNumber>(left.squaredDistance(right));
1616 } else {
1617 throw std::logic_error("Shape::squaredDistance is not defined for this shape pair");
1618 }
1619 }
1620
1621 // Measure the squared Hausdorff distance between two unwrapped alternatives.
1622 // Defined only for Point, Segment, OrientedSegment, Rectangle, Triangle, and
1623 // Convex, so any other pair (including anything against Line, OrientedLine,
1624 // Ray, Halfplane, Disk, MonotoneChain, Polyline, Polygon, or EmptyShape)
1625 // takes the throw. Unlike
1626 // squaredDistance there is no untemplated double-returning overload to probe
1627 // for: Disk has no squaredHausdorffDistance at all.
1628 template <class ResultNumber, class Left, class Right>
1629 static constexpr ResultNumber squaredHausdorffDistanceOf(const Left& left, const Right& right) {
1630 if constexpr (requires { left.template squaredHausdorffDistance<ResultNumber>(right); }) {
1631 return left.template squaredHausdorffDistance<ResultNumber>(right);
1632 } else {
1633 throw std::logic_error("Shape::squaredHausdorffDistance is not defined for this shape pair");
1634 }
1635 }
1636
1637 // Measure the L1 distance between two unwrapped alternatives. Every
1638 // supported concrete overload accepts ResultNumber; an EmptyShape or a Disk
1639 // paired with anything but a Point takes the throw. Convert explicitly for
1640 // the reason given on squaredDistanceOf: a Disk pair answers in a floating
1641 // type.
1642 template <class ResultNumber, class Left, class Right>
1643 static constexpr ResultNumber distanceL1Of(const Left& left, const Right& right) {
1644 if constexpr (requires { left.template distanceL1<ResultNumber>(right); }) {
1645 return static_cast<ResultNumber>(left.template distanceL1<ResultNumber>(right));
1646 } else {
1647 throw std::logic_error("Shape::distanceL1 is not defined for this shape pair");
1648 }
1649 }
1650
1651 // LInf counterpart of distanceL1Of.
1652 template <class ResultNumber, class Left, class Right>
1653 static constexpr ResultNumber distanceLInfOf(const Left& left, const Right& right) {
1654 if constexpr (requires { left.template distanceLInf<ResultNumber>(right); }) {
1655 return static_cast<ResultNumber>(left.template distanceLInf<ResultNumber>(right));
1656 } else {
1657 throw std::logic_error("Shape::distanceLInf is not defined for this shape pair");
1658 }
1659 }
1660
1661 // Measure the L1 Hausdorff distance between two unwrapped alternatives.
1662 // Defined only for Point, Segment, OrientedSegment, Rectangle, Triangle, and
1663 // Convex (same coverage as squaredHausdorffDistanceOf, and for the same
1664 // reason: Disk has no hausdorffDistanceL1 overload to probe for either).
1665 template <class ResultNumber, class Left, class Right>
1666 static constexpr ResultNumber hausdorffDistanceL1Of(const Left& left, const Right& right) {
1667 if constexpr (requires { left.template hausdorffDistanceL1<ResultNumber>(right); }) {
1668 return left.template hausdorffDistanceL1<ResultNumber>(right);
1669 } else {
1670 throw std::logic_error("Shape::hausdorffDistanceL1 is not defined for this shape pair");
1671 }
1672 }
1673
1674 // LInf counterpart of hausdorffDistanceL1Of; see there for the coverage.
1675 template <class ResultNumber, class Left, class Right>
1676 static constexpr ResultNumber hausdorffDistanceLInfOf(const Left& left, const Right& right) {
1677 if constexpr (requires { left.template hausdorffDistanceLInf<ResultNumber>(right); }) {
1678 return left.template hausdorffDistanceLInf<ResultNumber>(right);
1679 } else {
1680 throw std::logic_error("Shape::hausdorffDistanceLInf is not defined for this shape pair");
1681 }
1682 }
1683
1684 // Unwrap a concrete intersection result (optional<T> or vector<T>, where T is
1685 // a single alternative or a variant over alternatives) into a Shape. An
1686 // absent/empty result is the empty shape; a disconnected result (more than
1687 // one component) cannot be a single Shape and throws. The actual wrapping of
1688 // a value or variant is handled by the Shape constructors.
1689 template <class ResultNumber, class Result>
1690 static constexpr Shape<Point<ResultNumber, LabelType>> resultToShape(const Result& result) {
1691 using ResultShape = Shape<Point<ResultNumber, LabelType>>;
1692 if constexpr (detail::is_std_optional<Result>::value) {
1693 return result ? ResultShape(*result) : ResultShape{};
1694 } else if constexpr (detail::is_std_vector<Result>::value) {
1695 if (result.size() > 1) {
1696 throw std::logic_error(
1697 "Shape::intersection: disconnected result cannot be a single Shape");
1698 }
1699 return result.empty() ? ResultShape{} : ResultShape(result.front());
1700 } else if constexpr (detail::is_polygon_set_v<Result>) {
1701 // A set of regions is an alternative of its own, so unlike a vector
1702 // it survives coming apart. A single component is still unwrapped to
1703 // the tighter `PolygonWithHoles` alternative, which is the answer
1704 // this pair has always given when the intersection stayed in one
1705 // piece.
1706 if (result.empty()) {
1707 return ResultShape{};
1708 }
1709 return result.componentCount() == 1 ? ResultShape(result.component(0))
1710 : ResultShape(result);
1711 } else {
1712 return ResultShape(result);
1713 }
1714 }
1715
1716 Variant value_{};
1717};
1718
1719// Deduce the wrapper's point type from the alternatives of a result variant (or
1720// an optional thereof), so `Shape s = a.intersection<N>(b);` names the right
1721// type. The point type is taken from the variant's first alternative; every
1722// alternative shares it.
1723template <class T, class... Ts>
1724Shape(const std::variant<T, Ts...>&) -> Shape<detail::shape_point_type_t<T>>;
1726template <class T, class... Ts>
1727Shape(const std::optional<std::variant<T, Ts...>>&) -> Shape<detail::shape_point_type_t<T>>;
1728
1740template <class PointType, class TranslationNumber, class TranslationLabel>
1741constexpr auto operator-(const Shape<PointType>& shape,
1742 const Point<TranslationNumber, TranslationLabel>& translation) {
1743 using ResultPoint = std::decay_t<decltype(std::declval<const PointType&>() - translation)>;
1744 return std::visit(
1745 [&translation](const auto& alternative) {
1746 return Shape<ResultPoint>(alternative - translation);
1747 },
1748 shape.variant());
1749}
1750
1762template <class PointType, class Scalar>
1763 requires(!detail::is_point_v<Scalar> && !TransformationConcept<Scalar>)
1764constexpr auto operator*(const Shape<PointType>& shape, const Scalar& scalar) {
1765 using ResultPoint = std::decay_t<decltype(std::declval<const PointType&>() * scalar)>;
1766 return std::visit(
1767 [&scalar](const auto& alternative) {
1768 return Shape<ResultPoint>(alternative * scalar);
1769 },
1770 shape.variant());
1771}
1772
1774template <class Scalar, class PointType>
1775 requires(!detail::is_point_v<Scalar> && !TransformationConcept<Scalar>)
1776constexpr auto operator*(const Scalar& scalar, const Shape<PointType>& shape) {
1777 return shape * scalar;
1778}
1779
1791template <class PointType, class Scalar>
1792 requires(!detail::is_point_v<Scalar> && !TransformationConcept<Scalar>)
1793constexpr auto operator/(const Shape<PointType>& shape, const Scalar& scalar) {
1794 using ResultPoint = std::decay_t<decltype(std::declval<const PointType&>() / scalar)>;
1795 return std::visit(
1796 [&scalar](const auto& alternative) {
1797 return Shape<ResultPoint>(alternative / scalar);
1798 },
1799 shape.variant());
1800}
1801
1810template <class PointType>
1811std::ostream& operator<<(std::ostream& stream, const Shape<PointType>& shape) {
1812 std::visit(
1813 [&stream](const auto& value) {
1814 stream << value;
1815 },
1816 shape.variant());
1817 return stream;
1818}
1819
1820} // namespace pgl
Shape pairs whose Minkowski sum Pangolin can represent.
Definition forward.hpp:476
Definition forward.hpp:324
Definition arrangement.hpp:67
HalfplaneIntersection() -> HalfplaneIntersection< Point<>, NoLabel >
Definition halfplaneintersection.hpp:2308
Rectangle() -> Rectangle< Point<>, NoLabel >
Definition rectangle.hpp:2384
Line() -> Line< Point<>, NoLabel >
Point() -> Point< int >
constexpr auto operator-(const Point< LeftNumber, LeftLabel > &left, const Point< RightNumber, RightLabel > &right)
Translates a point by the opposite of another point.
Definition transformations.hpp:130
PolygonSet() -> PolygonSet< Point<>, NoLabel >
Definition polygonset.hpp:1699
OrientedSegment() -> OrientedSegment< Point<>, NoLabel >
MonotoneChain() -> MonotoneChain< Point<>, NoLabel >
Definition monotonechain.hpp:2439
Shape(const std::variant< T, Ts... > &) -> Shape< detail::shape_point_type_t< T > >
PolygonWithHoles() -> PolygonWithHoles< Point<>, NoLabel >
Definition polygonwithholes.hpp:3093
Convex() -> Convex< Point<>, NoLabel >
Definition convex.hpp:3311
std::ostream & operator<<(std::ostream &stream, const Point< Number, Label > &point)
Streams a point as (x,y) or label:(x,y).
Definition io.hpp:27
Segment() -> Segment< Point<>, NoLabel >
Halfplane() -> Halfplane< Point<>, NoLabel >
Polyline() -> Polyline< Point<>, NoLabel >
Definition polyline.hpp:2369
Ray() -> Ray< Point<>, NoLabel >
Polygon() -> Polygon< Point<>, NoLabel >
Definition polygon.hpp:3200
Disk() -> Disk< Point<>, NoLabel >
Deduces a default disk with Point<> boundary points and no label.
Definition disk.hpp:1691
OrientedLine() -> OrientedLine< Point<>, NoLabel >
Triangle() -> Triangle< Point<>, NoLabel >
Definition triangle.hpp:2029
#define PGL_SHAPE_ALTERNATIVE(Name, Type)
Definition shape.hpp:541
Closed convex polygon stored by its vertices.
Definition convex.hpp:170
Closed Euclidean disk stored by boundary points plus optional disk label.
Definition disk.hpp:66
The empty set of points in the plane.
Definition emptyshape.hpp:33
Intersection of closed half-planes; convex but possibly unbounded or empty.
Definition halfplaneintersection.hpp:244
Closed half-plane defined by an oriented boundary line.
Definition halfplane.hpp:51
Unoriented infinite line.
Definition line.hpp:52
Weakly x-monotone polyline stored by lexicographically sorted vertices.
Definition monotonechain.hpp:146
Directed infinite line with left/right side semantics plus optional line label.
Definition orientedline.hpp:53
Directed segment preserving source-to-target order plus optional segment label.
Definition orientedsegment.hpp:44
Two-dimensional point with optional label payload.
Definition point.hpp:129
Set of closed regions with pairwise disjoint interiors.
Definition polygonset.hpp:165
Closed region bounded by one outer simple polygon minus disjoint polygonal holes.
Definition polygonwithholes.hpp:89
Closed simple polygon stored by its vertices.
Definition polygon.hpp:59
Open polygonal chain stored in traversal order; may self-intersect.
Definition polyline.hpp:69
Half-infinite line starting from one source point plus optional ray label.
Definition ray.hpp:51
Axis-aligned rectangle stored by minimum and maximum corners.
Definition rectangle.hpp:75
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
Runtime variant wrapper over the supported primitive shapes.
Definition shape.hpp:160
constexpr Shape scaledDownX(const OtherNumber scalar) const
Returns the wrapped shape with its x-coordinates scaled down.
Definition shape.hpp:1413
constexpr const Variant & variant() const
Returns the underlying variant.
Definition shape.hpp:264
constexpr bool holdsAlternative() const
Tests whether the wrapper currently stores a given alternative.
Definition shape.hpp:489
EPoint::NumberType NumberType
Definition shape.hpp:164
constexpr auto operator<=>(const Shape &) const =default
Orders wrapped values by the underlying variant ordering.
constexpr Shape scaledUpY(const OtherNumber scalar) const
Returns the wrapped shape with its y-coordinates scaled up.
Definition shape.hpp:1375
constexpr Shape & operator+=(const OtherPoint &translation)
Translates the stored shape in place.
Definition shape.hpp:1254
constexpr ResultNumber distanceL1(const Other &other) const
Returns the Manhattan (L1) distance to the given shape.
Definition shape.hpp:1093
constexpr Variant & variant()
Returns the underlying variant.
Definition shape.hpp:273
constexpr PointType_ operator[](std::size_t index) const
Returns the vertex at index of the wrapped shape.
Definition shape.hpp:408
constexpr bool boundaryContains(const Other &other) const
Tests whether this shape's boundary contains the other shape (∂A ⊇ B).
Definition shape.hpp:622
constexpr void scaleUpX(const OtherNumber scalar)
Scales the wrapped shape's x-coordinates up in place.
Definition shape.hpp:1356
constexpr bool intersects(const Other &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition shape.hpp:664
constexpr Shape()=default
Creates a point-valued default shape.
constexpr Shape rotated90(int k=1) const
Returns the wrapped shape rotated by 90k degrees around the origin.
Definition shape.hpp:1315
constexpr Shape(T &&value)
Constructs a shape from one supported alternative.
Definition shape.hpp:202
constexpr bool isDegenerate() const
Tests whether the wrapped shape is degenerate.
Definition shape.hpp:303
constexpr std::ptrdiff_t index(const PointType_ &point) const
Definition shape.hpp:439
PolygonSet< Point< ResultNumber, LabelType > > regularizedIntersection(const Other &other) const
Returns the regularized intersection of two region-valued shapes.
Definition shape.hpp:825
constexpr PointType_ get(std::ptrdiff_t index) const
Returns the i-th vertex (modulo size()) of the wrapped shape.
Definition shape.hpp:378
constexpr void scaleDownX(const OtherNumber scalar)
Scales the wrapped shape's x-coordinates down in place.
Definition shape.hpp:1432
constexpr bool contains(const Other &other) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition shape.hpp:601
constexpr Rectangle< PointType > bbox() const
Returns the wrapped shape's axis-aligned bounding box.
Definition shape.hpp:325
PolygonSet< Point< ResultNumber, LabelType > > regularizedUnion(const Other &other) const
Returns the regularized union closure(A° ∪ B°).
Definition shape.hpp:874
constexpr bool separates(const Other &other) const
Tests whether removing this shape disconnects the other shape (B∖A is disconnected).
Definition shape.hpp:706
constexpr bool interiorsIntersect(const Other &other) const
Tests whether the interiors of the two shapes intersect ((A∖∂A) ∩ (B∖∂B) ≠ ∅).
Definition shape.hpp:685
constexpr ResultNumber hausdorffDistanceLInf(const Other &other) const
Returns the Chebyshev (LInf) Hausdorff distance to the given shape.
Definition shape.hpp:1183
constexpr std::size_t size() const
Returns the number of indexable elements of the wrapped shape.
Definition shape.hpp:352
constexpr bool interiorContains(const Other &other) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition shape.hpp:643
constexpr bool empty() const
Tests whether the wrapped shape covers no point at all.
Definition shape.hpp:293
constexpr void scaleDownY(const OtherNumber scalar)
Scales the wrapped shape's y-coordinates down in place.
Definition shape.hpp:1470
constexpr bool crosses(const Other &other) const
Tests whether the two shapes mutually separate each other (each disconnects the other).
Definition shape.hpp:727
constexpr ResultNumber squaredHausdorffDistance(const Other &other) const
Returns the squared Euclidean Hausdorff distance to the given shape.
Definition shape.hpp:1048
constexpr ResultNumber hausdorffDistanceL1(const Other &other) const
Returns the Manhattan (L1) Hausdorff distance to the given shape.
Definition shape.hpp:1159
constexpr Shape scaledDownY(const OtherNumber scalar) const
Returns the wrapped shape with its y-coordinates scaled down.
Definition shape.hpp:1451
PolygonSet< Point< ResultNumber, LabelType > > difference(const Other &other) const
Returns the regularized set difference of the two shapes (A ∖ B).
Definition shape.hpp:924
EPoint::LabelType LabelType
Definition shape.hpp:166
constexpr auto minkowskiErosion(const OtherShape &other) const
Returns the Minkowski erosion of this shape by another (A ⊖ B).
Definition minkowskierosion.hpp:670
constexpr Shape< Point< ResultNumber, LabelType > > intersection(const Other &other) const
Returns the intersection of the two shapes (A ∩ B), empty when they are disjoint.
Definition shape.hpp:783
std::variant< EmptyShape< EPoint >, EPoint, Segment< EPoint >, OrientedSegment< EPoint >, Line< EPoint >, OrientedLine< EPoint >, Ray< EPoint >, Halfplane< EPoint >, Rectangle< EPoint >, Triangle< EPoint >, Disk< EPoint >, Convex< EPoint >, MonotoneChain< EPoint >, Polyline< EPoint >, Polygon< EPoint >, HalfplaneIntersection< EPoint >, PolygonWithHoles< EPoint >, PolygonSet< EPoint > > Variant
Definition shape.hpp:169
constexpr std::remove_cvref_t< T > * getIf()
Returns a mutable pointer to the stored alternative when it matches T.
Definition shape.hpp:513
constexpr ResultNumber distanceLInf(const Other &other) const
Returns the Chebyshev (LInf) distance to the given shape.
Definition shape.hpp:1117
constexpr auto minkowskiSum(const OtherShape &other) const
Returns the Minkowski sum of this shape and another (A ⊕ B).
Definition minkowski.hpp:895
PolygonSet< Point< ResultNumber, LabelType > > symmetricDifference(const Other &other) const
Returns the regularized symmetric difference of the two shapes (A △ B).
Definition shape.hpp:962
constexpr Shape(const Result &result)
Constructs a shape from a variant over supported alternatives.
Definition shape.hpp:220
EPoint PointType_
Definition shape.hpp:162
constexpr bool operator==(const Shape &) const =default
Compares wrapped values.
constexpr bool samePointSet(const OtherShape &other) const
Tests whether another shape defines exactly the same point set.
Definition samepointset.hpp:2043
constexpr void scaleUpY(const OtherNumber scalar)
Scales the wrapped shape's y-coordinates up in place.
Definition shape.hpp:1394
constexpr std::ptrdiff_t index(const NumberType &value) const
Returns the smallest index i with (*this)[i] == value, or -1 if no coordinate equals value.
Definition shape.hpp:468
constexpr Shape scaledUpX(const OtherNumber scalar) const
Returns the wrapped shape with its x-coordinates scaled up.
Definition shape.hpp:1337
constexpr ResultNumber squaredDistance(const Other &other) const
Returns the squared Euclidean distance to the given shape.
Definition shape.hpp:1004
constexpr Shape & operator-=(const OtherPoint &translation)
Translates the stored shape in place by a negated point.
Definition shape.hpp:1270
constexpr void rotate90(int k=1)
Rotates the wrapped shape by 90k degrees around the origin in place.
Definition shape.hpp:1326
constexpr const std::remove_cvref_t< T > * getIf() const
Returns a pointer to the stored alternative when it matches T.
Definition shape.hpp:501
Closed triangle stored by three vertices.
Definition triangle.hpp:53