Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
xysweep.hpp
Go to the documentation of this file.
1#pragma once
2
4
18
19#include <algorithm>
20#include <array>
21#include <cstddef>
22#include <cstdint>
23#include <limits>
24#include <stdexcept>
25#include <type_traits>
26#include <utility>
27#include <vector>
28
29
30namespace pgl::detail {
31
40inline constexpr std::size_t xySweepMinSegments = 128;
41
69template <SegmentConcept Segment, class Visitor>
70bool visitXYSweepPairs(const std::vector<Segment>& segments, Visitor visit) {
71 using PointType = typename Segment::PointType;
72 using NumberType = typename PointType::NumberType;
73 using IndexedSegment = pgl::Segment<PointType, std::uint32_t>;
74
75 const std::size_t count = segments.size();
76 if (count < 2) {
77 return false; // no pair to visit
78 }
79 if (count > static_cast<std::size_t>(std::numeric_limits<std::uint32_t>::max())) {
80 throw std::length_error("xy sweep exceeds its 32-bit segment capacity");
81 }
82
83 // The index travels with the segment as its label, so a segment reported by
84 // the tree names its position in the input.
85 std::vector<IndexedSegment> indexed;
86 indexed.reserve(count);
87 for (std::size_t i = 0; i < count; ++i) {
88 indexed.emplace_back(segments[i].min(), segments[i].max(),
89 static_cast<std::uint32_t>(i));
90 }
91
92 struct Event {
93 NumberType x;
94 bool closing;
95 std::uint32_t index;
96 };
97 std::vector<Event> events;
98 events.reserve(2 * count);
99 for (const IndexedSegment& segment : indexed) {
100 const auto box = segment.bbox();
101 events.push_back({box.min().x(), false, segment.label()});
102 events.push_back({box.max().x(), true, segment.label()});
103 }
104 // Openings come before closings at a shared abscissa, so two boxes meeting
105 // in a single vertical line are still compared, and a vertical segment is
106 // queried before the same abscissa closes it.
107 std::sort(events.begin(), events.end(), [](const Event& a, const Event& b) {
108 if (a.x < b.x) {
109 return true;
110 }
111 if (b.x < a.x) {
112 return false;
113 }
114 return !a.closing && b.closing;
115 });
116
118 for (const Event& event : events) {
119 const IndexedSegment& segment = indexed[event.index];
120 if (event.closing) {
121 active.erase(segment);
122 continue;
123 }
124 // Everything the tree holds already overlaps this segment in x, so the
125 // shapes it reports for the y-extent are exactly the boxes that overlap.
126 const bool stopped =
127 active.visitProjectionsIntersecting(segment, [&](const IndexedSegment& other) {
128 return visit(static_cast<std::size_t>(event.index),
129 static_cast<std::size_t>(other.label()));
130 });
131 if (stopped) {
132 return true;
133 }
134 active.insert(segment);
135 }
136 return false;
137}
138
139} // namespace pgl::detail
140
141
142namespace pgl {
143
159template<class Rational = pgl::Rational<pgl::BigInt>, class Container>
160auto xyCrossings(const Container &segments) {
161 using Point = Container::value_type::PointType;
162 // Rebuilt carrying the input's own label type: defaulting it to NoLabel
163 // here dropped a segment label that the caller had put on to identify the
164 // pairs coming back, and dropped it silently, since the conversion is
165 // well-formed either way.
167 std::vector<Seg> v;
168 for (const auto &s : segments) {
169 Seg converted = s;
170 v.push_back(converted);
171 }
172
173 std::vector<std::array<Seg,2>> ret;
174 pgl::detail::visitXYSweepPairs(v, [&v,&ret](std::size_t i, std::size_t j) {
175 Seg s1 = v[i];
176 Seg s2 = v[j];
177 if (s1.crosses(s2)) {
178 if (s2 < s1)
179 std::swap(s1,s2);
180 ret.push_back({s1,s2});
181 }
182 });
183
184 return ret;
185}
186
202template<class Rational = pgl::Rational<pgl::BigInt>, class Container>
203auto xyIntersections(const Container &segments) {
204 using Point = Container::value_type::PointType;
205 // Rebuilt carrying the input's own label type: defaulting it to NoLabel
206 // here dropped a segment label that the caller had put on to identify the
207 // pairs coming back, and dropped it silently, since the conversion is
208 // well-formed either way.
210 std::vector<Seg> v;
211 for (const auto &s : segments) {
212 Seg converted = s;
213 v.push_back(converted);
214 }
215
216 std::vector<std::array<Seg,2>> ret;
217 pgl::detail::visitXYSweepPairs(v, [&v,&ret](std::size_t i, std::size_t j) {
218 Seg s1 = v[i];
219 Seg s2 = v[j];
220 if (s1.intersects(s2)) {
221 if (s2 < s1)
222 std::swap(s1,s2);
223 ret.push_back({s1,s2});
224 }
225 });
226
227 return ret;
228}
229
244template <class PointType_, class LabelType>
245template <class Rational>
247 using Number = typename PointType::NumberType;
248 const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(size());
249 if (n < 3) {
250 return false;
251 }
252
253 std::vector<pgl::Segment<PointType>> edges;
254 edges.reserve(static_cast<std::size_t>(n));
255 for (std::ptrdiff_t i = 0; i < n; ++i) {
257 if (edge.isDegenerate()) {
258 return false; // zero-length edge / repeated vertex
259 }
260 edges.push_back(edge);
261 }
262
263 // Exact sweep for large integer/rational polygons; the bounding-box sweep
264 // for large floating-point ones, which the exact sweep cannot handle; brute
265 // force for the small ones either way.
266 if constexpr (!std::is_floating_point_v<Number>) {
267 if (n > 8) {
268 pgl::detail::BentleyOttmann<Rational, pgl::Segment<PointType>> bo;
269 return bo.testPolygon(edges);
270 }
271 } else if (edges.size() > detail::xySweepMinSegments) {
272 const std::size_t last = edges.size() - 1;
273 bool notSimple = false;
274 detail::visitXYSweepPairs(edges, [&edges, last, &notSimple](std::size_t a, std::size_t b) {
275 // The ring closes, so the first and last edges are consecutive too.
276 const bool adjacent = (a + 1 == b) || (b + 1 == a) ||
277 (a == 0 && b == last) || (b == 0 && a == last);
278 notSimple = adjacent ? edges[a].interiorsIntersect(edges[b])
279 : edges[a].intersects(edges[b]);
280 return notSimple; // the first violation ends the sweep
281 });
282 return !notSimple;
283 }
284
285 for (std::ptrdiff_t i = 0; i < n; ++i) {
286 for (std::ptrdiff_t j = i + 1; j < n; ++j) {
287 const bool adjacent = (j == i + 1) || (i == 0 && j == n - 1);
288 if (adjacent) {
289 if (edges[i].interiorsIntersect(edges[j])) {
290 return false; // consecutive edges overlap beyond the shared vertex
291 }
292 } else if (edges[i].intersects(edges[j])) {
293 return false; // non-adjacent edges must be disjoint
294 }
295 }
296 }
297 return true;
298}
299
312template <class PointType_, class TLabel>
313template <class Rational>
315 using Number = typename PointType::NumberType;
316 const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(size());
317 if (n < 2) {
318 return true; // no edge: vacuously simple
319 }
320
321 std::vector<pgl::Segment<PointType>> edges;
322 edges.reserve(static_cast<std::size_t>(n - 1));
323 for (std::ptrdiff_t i = 0; i + 1 < n; ++i) {
325 if (edge.isDegenerate()) {
326 return false; // zero-length edge / repeated consecutive vertex
327 }
328 edges.push_back(edge);
329 }
330
331 // Exact sweep for large integer/rational polylines; the bounding-box sweep
332 // for large floating-point ones, which the exact sweep cannot handle; brute
333 // force for the small ones either way.
334 if constexpr (!std::is_floating_point_v<Number>) {
335 if (edges.size() > 8) {
336 pgl::detail::BentleyOttmann<Rational, pgl::Segment<PointType>> bo;
337 return bo.testPolyLine(edges);
338 }
339 } else if (edges.size() > detail::xySweepMinSegments) {
340 bool notSimple = false;
341 detail::visitXYSweepPairs(edges, [&edges, &notSimple](std::size_t a, std::size_t b) {
342 // In an open chain the first and last edges are NOT adjacent, so a
343 // closed polyline (first vertex equal to the last) is not simple.
344 const bool adjacent = (a + 1 == b) || (b + 1 == a);
345 notSimple = adjacent ? edges[a].interiorsIntersect(edges[b])
346 : edges[a].intersects(edges[b]);
347 return notSimple; // the first violation ends the sweep
348 });
349 return !notSimple;
350 }
351
352 const std::ptrdiff_t m = static_cast<std::ptrdiff_t>(edges.size());
353 for (std::ptrdiff_t i = 0; i < m; ++i) {
354 for (std::ptrdiff_t j = i + 1; j < m; ++j) {
355 // In an open chain the first and last edges are NOT adjacent, so a
356 // closed polyline (first vertex equal to the last) is not simple.
357 const bool adjacent = (j == i + 1);
358 if (adjacent) {
359 if (edges[i].interiorsIntersect(edges[j])) {
360 return false; // consecutive edges overlap beyond the shared vertex
361 }
362 } else if (edges[i].intersects(edges[j])) {
363 return false; // non-adjacent edges must be disjoint
364 }
365 }
366 }
367 return true;
368}
369
370} // namespace pgl
void insert(const ShapeType &shape)
Inserts shape and its selected closed bounding-box interval.
Definition intervaltree.hpp:690
bool visitProjectionsIntersecting(const Q &q, Fn fn) const
Visits projected-interval intersections, stopping early if fn returns true.
Definition intervaltree.hpp:790
bool erase(const ShapeType &shape)
Removes one stored shape equal to shape.
Definition intervaltree.hpp:721
Mutable one-dimensional interval tree over projected bounded shapes.
Definition arrangement.hpp:67
@ x
Definition intervaltree.hpp:24
auto xyIntersections(const Container &segments)
Finds all intersecting segment pairs with a bounding-box sweep.
Definition xysweep.hpp:203
auto xyCrossings(const Container &segments)
Finds all crossing segment pairs with a bounding-box sweep.
Definition xysweep.hpp:160
@ edge
Definition bitmatrix.hpp:37
IntervalTree(const Container &) -> IntervalTree< typename Container::value_type >
Segment() -> Segment< Point<>, NoLabel >
Two-dimensional point with optional label payload.
Definition point.hpp:129
constexpr bool intersects(const OtherChain &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:1596
bool isSimple() const
Tests whether the polygon is simple (its boundary does not touch or cross itself).
Definition xysweep.hpp:246
constexpr std::size_t size() const
Returns the number of vertices in the polygon.
Definition polygon.hpp:259
constexpr bool interiorsIntersect(const OtherChain &other) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition interiorsintersect.hpp:1833
constexpr std::vector< Segment< PointType > > edges() const
Returns the edges of the polygon.
Definition polygon.hpp:598
constexpr PointType get(std::ptrdiff_t index) const
Cyclic access: same as operator[] but index is taken modulo size(); negative indices wrap from the en...
Definition polygon.hpp:169
bool isSimple() const
Tests whether the polyline is simple (it does not touch or cross itself).
Definition xysweep.hpp:314
constexpr PointType get(std::ptrdiff_t index) const
Accesses a vertex by index modulo the vertex count.
Definition polyline.hpp:174
constexpr bool interiorsIntersect(const OtherPoint &other) const
Tests whether the interiors of the shapes intersect (A° ∩ B° ≠ ∅).
Definition interiorsintersect.hpp:1848
constexpr bool intersects(const OtherPoint &other) const
Tests whether this shape and the other shape intersect (A ∩ B ≠ ∅).
Definition intersects.hpp:1620
constexpr std::vector< Segment< PointType > > edges() const
Returns the edges of the polyline.
Definition polyline.hpp:585
constexpr std::size_t size() const
Returns the number of vertices in the polyline.
Definition polyline.hpp:388
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
TPoint PointType
Definition segment.hpp:59