Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
predicates_helpers.hpp
Go to the documentation of this file.
1#pragma once
2
4
9
10
11namespace pgl {
12
13namespace detail {
14
22template <class ANumber, class BNumber>
23using Exact1DNumber = std::conditional_t<
24 std::is_floating_point_v<ANumber> || std::is_floating_point_v<BNumber>,
25 double, Rational<BigInt>>;
26
34
35template <class T>
36struct is_area_cut_target : std::false_type {};
37
38template <class PointType, class Label>
39struct is_area_cut_target<Rectangle<PointType, Label>> : std::true_type {};
40
41template <class PointType, class Label>
42struct is_area_cut_target<Triangle<PointType, Label>> : std::true_type {};
43
44template <class T>
45inline constexpr bool is_area_cut_target_v = is_area_cut_target<std::remove_cvref_t<T>>::value;
46
52
53template <class Polygon, class OtherSegment>
54constexpr bool polygonBoundaryContainsSegment(const Polygon& polygon, const OtherSegment& other) {
55 const auto boundary = polygon.edges();
56 for (const auto& edge : boundary) {
57 if (edge.contains(other)) {
58 return true;
59 }
60 }
61 return false;
62}
63
70template <class RectangleType, class FirstPoint, class SecondPoint>
71constexpr bool lineIntersectsRectangle(const RectangleType& rectangle, const FirstPoint& first, const SecondPoint& second) {
72 // The line's two points appear in all four signs, so filtering them once
73 // converts each of their coordinates a single time instead of four.
74 using Coordinate = sign_coordinate_t<typename FirstPoint::NumberType,
75 typename SecondPoint::NumberType,
76 typename RectangleType::NumberType>;
77 const auto a = filtered<Coordinate>(first);
78 const auto b = filtered<Coordinate>(second);
79 bool has_positive = false;
80 bool has_negative = false;
81 const auto vertices = rectangle.vertices();
82 for (const auto& vertex : vertices) {
83 const auto side = orientationSignOf(a, b, filtered<Coordinate>(vertex)).value();
84 if (side == std::partial_ordering::equivalent) {
85 return true;
86 }
87 has_positive = has_positive || side == std::partial_ordering::greater;
88 has_negative = has_negative || side == std::partial_ordering::less;
89 }
90 return has_positive && has_negative;
91}
92
109template <class RectangleType, class FirstPoint, class SecondPoint>
110constexpr bool segmentIntersectsRectangle(const RectangleType& rectangle, const FirstPoint& first, const SecondPoint& second) {
111 const auto& low = rectangle.min();
112 const auto& high = rectangle.max();
113
114 const bool rising_x = first.x() < second.x();
115 if ((rising_x ? first.x() : second.x()) > high.x() ||
116 (rising_x ? second.x() : first.x()) < low.x()) {
117 return false;
118 }
119 const bool rising_y = first.y() < second.y();
120 if ((rising_y ? first.y() : second.y()) > high.y() ||
121 (rising_y ? second.y() : first.y()) < low.y()) {
122 return false;
123 }
124 return lineIntersectsRectangle(rectangle, first, second);
125}
126
133template <class RectangleType, class FirstPoint, class SecondPoint>
134constexpr bool lineIntersectsRectangleInterior(const RectangleType& rectangle, const FirstPoint& first, const SecondPoint& second) {
135 bool has_positive = false;
136 bool has_negative = false;
137 const auto vertices = rectangle.vertices();
138 for (const auto& vertex : vertices) {
139 const auto side = orientationSign(first, second, vertex);
140 has_positive = has_positive || side == std::partial_ordering::greater;
141 has_negative = has_negative || side == std::partial_ordering::less;
142 }
143 return has_positive && has_negative;
144}
145
155template <class RectangleType, class FirstPoint, class SecondPoint>
156constexpr bool segmentIntersectsRectangleInteriorExact(const RectangleType& rectangle, const FirstPoint& first, const SecondPoint& second) {
157 using Coordinate = promoted_number_t<std::common_type_t<
158 std::remove_cvref_t<decltype(rectangle.min().x())>,
159 std::remove_cvref_t<decltype(first.x())>,
160 std::remove_cvref_t<decltype(second.x())>>>;
161 using SegmentPoint = Point<Coordinate>;
162 using TestSegment = Segment<SegmentPoint>;
163
164 const TestSegment segment{SegmentPoint(first), SegmentPoint(second)};
165 if (segment.isDegenerate()) {
166 return false;
167 }
168
169 if (rectangle.interiorContains(first) || rectangle.interiorContains(second)) {
170 return true;
171 }
172
173 if (!lineIntersectsRectangleInterior(rectangle, first, second)) {
174 return false;
175 }
176
177 const auto edges = rectangle.edges();
178 std::array<bool, 4> edge_hits{};
179 int distinct_boundary_contacts = 0;
180 for (std::size_t i = 0; i < edges.size(); ++i) {
181 edge_hits[i] = segment.intersects(edges[i]);
182 distinct_boundary_contacts += edge_hits[i] ? 1 : 0;
183 }
184
185 if (distinct_boundary_contacts < 2) {
186 return false;
187 }
188
189 const auto vertices = rectangle.vertices();
190 if (segment.contains(vertices[0]) && edge_hits[0] && edge_hits[3]) {
191 --distinct_boundary_contacts;
192 }
193 if (segment.contains(vertices[1]) && edge_hits[0] && edge_hits[1]) {
194 --distinct_boundary_contacts;
195 }
196 if (segment.contains(vertices[2]) && edge_hits[1] && edge_hits[2]) {
197 --distinct_boundary_contacts;
198 }
199 if (segment.contains(vertices[3]) && edge_hits[2] && edge_hits[3]) {
200 --distinct_boundary_contacts;
201 }
202
203 return distinct_boundary_contacts >= 2;
204}
205
219template <class Remover, MonotoneChainConcept Chain>
220constexpr bool separatesChain(const Remover& remover, const Chain& chain) {
221 const std::size_t n = chain.size();
222 if (n < 2) {
223 // Removing anything from at most one point cannot disconnect it.
224 return false;
225 }
226 bool active = false; // a component is in progress
227 bool touched = false; // ... and it touches an extreme vertex
228 for (std::size_t i = 0; i + 1 < n; ++i) {
229 const Segment<typename Chain::PointType> edge(chain[i], chain[i + 1]);
230 if (edge.intersects(remover)) {
231 const bool connected = active && remover.contains(chain[i]);
232 if (!connected) {
233 if (active && !touched) {
234 return true;
235 }
236 touched = false;
237 }
238 active = true;
239 if (i == 0 && remover.contains(chain[0])) {
240 touched = true;
241 }
242 if (i + 2 == n && remover.contains(chain[n - 1])) {
243 touched = true;
244 }
245 } else {
246 if (active && !touched) {
247 return true;
248 }
249 active = false;
250 touched = false;
251 }
252 }
253 return active && !touched;
254}
255
265
271template <class Number>
272using region_exact_number_t =
273 std::conditional_t<std::is_floating_point_v<Number>, double, Rational<BigInt>>;
274
282template <class Region, HalfplaneConcept OtherHalfplane>
283constexpr bool regionInsideHalfplane(const Region& region, const OtherHalfplane& halfplane) {
284 std::remove_cvref_t<Region> copy(region);
285 return !copy.insert(halfplane);
286}
287
292template <class Region, HalfplaneConcept OtherHalfplane>
293constexpr bool regionInsideHalfplaneInterior(const Region& region, const OtherHalfplane& halfplane) {
294 return regionInsideHalfplane(region, halfplane) && !region.intersects(halfplane.asLine());
295}
296
303template <class Region, PointConcept FirstPoint, PointConcept SecondPoint>
304constexpr bool regionStrictlyOnBothSides(const Region& region, const FirstPoint& first, const SecondPoint& second) {
305 const Halfplane<std::remove_cvref_t<FirstPoint>> left(first, second);
306 return !regionInsideHalfplane(region, left) && !regionInsideHalfplane(region, left.opposite());
307}
308
315template <class Region>
316constexpr auto degenerateRegionCarrier(const Region& region) {
317 using E = region_exact_number_t<typename Region::NumberType>;
319 using Carrier = std::variant<EPoint, Segment<EPoint>, Ray<EPoint>, Line<EPoint>>;
320 if (region.isBounded()) {
321 // A point or a segment: the convex hull of the (partly coincident)
322 // implicit vertices.
323 const auto verts = region.template vertices<E>();
324 EPoint lo = verts[0];
325 EPoint hi = verts[0];
326 for (const auto& v : verts) {
327 if (v < lo) {
328 lo = v;
329 }
330 if (hi < v) {
331 hi = v;
332 }
333 }
334 if (lo == hi) {
335 return Carrier(lo);
336 }
337 return Carrier(Segment<EPoint>(lo, hi));
338 }
339 // An unbounded degenerate region is a ray or a line, and then some
340 // boundary edge equals the whole region.
341 for (std::size_t i = 0; i < region.size(); ++i) {
342 const auto e = region.template edge<E>(i);
343 if (const auto* ray = std::get_if<Ray<EPoint>>(&e)) {
344 return Carrier(*ray);
345 }
346 if (const auto* line = std::get_if<Line<EPoint>>(&e)) {
347 return Carrier(*line);
348 }
349 }
350 // Unreachable for a degenerate region; keep a deterministic fallback.
351 return Carrier(EPoint(region[0].source()));
352}
353
363template <class Region, RectangleConcept BoundingRectangle>
364constexpr std::remove_cvref_t<Region> regionClippedToBox(const Region& region, const BoundingRectangle& bounds) {
365 using Result = std::remove_cvref_t<Region>;
366 using RegionPoint = typename Result::PointType;
367 using N = typename Result::NumberType;
368 using RegionHalfplane = typename Result::HalfplaneType;
369 const N margin(2);
370 // A fractional bound is rounded away from the box before the margin is
371 // added, so the corners are whole numbers whatever N is. The box only has
372 // to contain @p bounds, and rounding outward keeps that; what it buys is
373 // the depth of everything downstream. Clipping against a corner that is
374 // itself a deep fraction — a disk's bounding box under rational
375 // coordinates carries twelve-digit numerators over eight-digit
376 // denominators — hands every clipped vertex those denominators, and the
377 // degree-four predicates over them then run on numbers with hundreds of
378 // digits. An integral corner leaves the clipped vertices as shallow as the
379 // region's own. For an integral N this is what the toward-zero cast
380 // already did.
381 const auto roundOutward = [](const auto& value, bool up) -> N {
382 if constexpr (pgl::is_Rational_v<std::remove_cvref_t<decltype(value)>>) {
383 // Both parts are wanted, so reduce once and read them off that copy:
384 // numerator() and denominator() each run their own gcd otherwise.
385 const auto reduced = value.simplified();
386 const auto numerator = reduced.numerator();
387 const auto denominator = reduced.denominator(); // always positive
388 auto quotient = numerator / denominator; // truncates toward zero
389 const bool exact = quotient * denominator == numerator;
390 if (!exact && (numerator < 0) == !up) {
391 quotient = up ? quotient + 1 : quotient - 1;
392 }
393 return N(quotient);
394 } else {
395 return N(value);
396 }
397 };
398 const RegionPoint lo(roundOutward(bounds.min().x(), false) - margin,
399 roundOutward(bounds.min().y(), false) - margin);
400 const RegionPoint hi(roundOutward(bounds.max().x(), true) + margin,
401 roundOutward(bounds.max().y(), true) + margin);
402 const RegionPoint lohi(lo.x(), hi.y());
403 const RegionPoint hilo(hi.x(), lo.y());
404 Result result(region);
405 result.insert(RegionHalfplane(lo, hilo));
406 result.insert(RegionHalfplane(hilo, hi));
407 result.insert(RegionHalfplane(hi, lohi));
408 result.insert(RegionHalfplane(lohi, lo));
409 return result;
410}
411
423template <class HoledRegion, class EdgePredicate>
424constexpr bool everyHoledRegionEdge(const HoledRegion& region, EdgePredicate&& predicate) {
425 for (const auto& edge : region.outer().edgesView()) {
426 if (!predicate(edge)) {
427 return false;
428 }
429 }
430 for (const auto& hole : region.holes()) {
431 for (const auto& edge : hole.edgesView()) {
432 if (!predicate(edge)) {
433 return false;
434 }
435 }
436 }
437 return true;
438}
439
440} // namespace detail
441
442} // namespace pgl
Projective duality and polar-transform helpers.
Definition arrangement.hpp:67
@ x
Definition intervaltree.hpp:24
Rectangle() -> Rectangle< Point<>, NoLabel >
Definition rectangle.hpp:2384
constexpr bool is_Rational_v
Definition rational.hpp:37
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
Point< ERational > EPoint
Definition pgl.hpp:98
Point() -> Point< int >
constexpr std::partial_ordering orientationSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Classifies the orientation of three points.
Definition orientation.hpp:544
Segment() -> Segment< Point<>, NoLabel >
Halfplane() -> Halfplane< Point<>, NoLabel >
Polygon() -> Polygon< Point<>, NoLabel >
Definition polygon.hpp:3200
Triangle() -> Triangle< Point<>, NoLabel >
Definition triangle.hpp:2029