Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
redbluesweep.hpp
Go to the documentation of this file.
1#pragma once
2
4
25
26#include <algorithm>
27#include <cstddef>
28#include <ranges>
29#include <set>
30#include <type_traits>
31#include <utility>
32#include <vector>
33
34
35namespace pgl {
36
60
61namespace detail {
62
70template <class Number>
71class RedBlueSweeper {
72 public:
73 using SweepPoint = pgl::Point<Number>;
74 using SweepSegment = pgl::Segment<SweepPoint>;
75
76 private:
78 struct Edge {
79 SweepPoint lo;
80 SweepPoint hi;
81 bool blue = false;
82
84 bool upright() const { return lo.x() == hi.x(); }
85 SweepSegment segment() const { return SweepSegment(lo, hi); }
86 };
87
92 enum class Kind : unsigned char {
93 Right = 0,
94 Upper = 1,
95 UprightLow = 2,
96 Left = 3
97 };
98
99 struct Event {
100 SweepPoint at;
101 std::size_t edge = 0;
102 Kind kind = Kind::Left;
103
104 bool operator<(const Event& other) const {
105 if (at < other.at) return true;
106 if (other.at < at) return false;
107 if (kind != other.kind) return kind < other.kind;
108 return edge < other.edge;
109 }
110 };
111
131 struct Below {
132 const std::vector<Edge>* edges = nullptr;
133 using is_transparent = void;
134
136 static int sideOf(const Edge& one, const Edge& that) {
137 int side = signOf(orientationSign(one.lo, one.hi, that.lo));
138 if (side == 0) {
139 side = signOf(orientationSign(one.lo, one.hi, that.hi));
140 }
141 // one.lo -> one.hi points right, so counterclockwise means above.
142 return side > 0 ? -1 : (side < 0 ? 1 : 0);
143 }
144
145 int compare(std::size_t first, std::size_t second) const {
146 if (first == second) {
147 return 0;
148 }
149 const Edge& one = (*edges)[first];
150 const Edge& other = (*edges)[second];
151 const int side =
152 one.lo.x() <= other.lo.x() ? sideOf(one, other) : -sideOf(other, one);
153 if (side != 0) {
154 return side;
155 }
156 return first < second ? -1 : 1;
157 }
158
159 bool operator()(std::size_t first, std::size_t second) const {
160 return compare(first, second) < 0;
161 }
162
163 // A bare point probes the status at the sweep line: it sorts below every
164 // edge it lies on, so lower_bound(p) is the first edge at or above p.
165 bool operator()(std::size_t first, const SweepPoint& probe) const {
166 const Edge& one = (*edges)[first];
167 return signOf(orientationSign(one.lo, one.hi, probe)) > 0;
168 }
169 bool operator()(const SweepPoint& probe, std::size_t second) const {
170 const Edge& other = (*edges)[second];
171 return signOf(orientationSign(other.lo, other.hi, probe)) <= 0;
172 }
173 };
174
175 std::vector<Edge> edges_;
176 std::vector<Event> events_;
177 std::set<std::size_t, Below> status_;
178 bool touching_ = false;
179
181 struct Extent {
182 Number xlo{}, ylo{}, xhi{}, yhi{};
183 bool any = false;
184
185 void insert(const SweepPoint& point) {
186 if (!any) {
187 xlo = xhi = point.x();
188 ylo = yhi = point.y();
189 any = true;
190 return;
191 }
192 if (point.x() < xlo) xlo = point.x();
193 if (xhi < point.x()) xhi = point.x();
194 if (point.y() < ylo) ylo = point.y();
195 if (yhi < point.y()) yhi = point.y();
196 }
197
198 bool overlaps(const SweepPoint& lo, const SweepPoint& hi) const {
199 return any && !(hi.x() < xlo) && !(xhi < lo.x()) &&
200 !(std::max(lo.y(), hi.y()) < ylo) && !(yhi < std::min(lo.y(), hi.y()));
201 }
202 };
203
204 template <class Range>
205 void gather(const Range& range, bool blue, Extent& extent) {
206 for (const auto& edge : range) {
207 Edge stored{SweepPoint(edge.min()), SweepPoint(edge.max()), blue};
208 extent.insert(stored.lo);
209 extent.insert(stored.hi);
210 edges_.push_back(std::move(stored));
211 }
212 }
213
215 bool adjacent(std::size_t first, std::size_t second) {
216 const Edge& one = edges_[first];
217 const Edge& other = edges_[second];
218 if (one.blue == other.blue) {
219 return false;
220 }
221 const SweepSegment mine = one.segment();
222 const SweepSegment theirs = other.segment();
223 if (mine.crosses(theirs)) {
224 return true;
225 }
226 if (mine.intersects(theirs)) {
227 touching_ = true;
228 }
229 return false;
230 }
231
244 bool scanUpright(std::size_t index) {
245 const Edge& upright = edges_[index];
246 const SweepSegment bar = upright.segment();
247 for (auto it = status_.lower_bound(upright.lo); it != status_.end(); ++it) {
248 const Edge& other = edges_[*it];
249 if (signOf(orientationSign(other.lo, other.hi, upright.hi)) < 0) {
250 break; // past the top of the vertical
251 }
252 if (other.blue == upright.blue) {
253 continue;
254 }
255 if (other.segment().crosses(bar)) {
256 return true;
257 }
258 touching_ = true;
259 break;
260 }
261 return false;
262 }
263
272 void uprightPairs(std::size_t begin, std::size_t end) {
273 std::vector<std::size_t> red;
274 std::vector<std::size_t> blue;
275 for (std::size_t k = begin; k < end; ++k) {
276 if (events_[k].kind != Kind::UprightLow) {
277 continue;
278 }
279 (edges_[events_[k].edge].blue ? blue : red).push_back(events_[k].edge);
280 }
281 std::size_t i = 0;
282 std::size_t j = 0;
283 while (i < red.size() && j < blue.size()) {
284 const Edge& one = edges_[red[i]];
285 const Edge& other = edges_[blue[j]];
286 if (one.hi.y() < other.lo.y()) {
287 ++i;
288 } else if (other.hi.y() < one.lo.y()) {
289 ++j;
290 } else {
291 touching_ = true;
292 return;
293 }
294 }
295 }
296
298 bool batch(std::size_t begin, std::size_t end) {
299 // A point that is an endpoint of edges of both colours is a meeting the
300 // status structure cannot see: an edge ending there is erased before one
301 // starting there is inserted, so such a pair is never adjacent. It is
302 // never a crossing either — a shared endpoint makes one of the four
303 // orientations in Segment::crosses vanish — so recognizing it here, from
304 // the events alone, closes the only gap in the adjacency argument.
305 for (std::size_t k = begin; k < end;) {
306 std::size_t run = k;
307 bool red = false;
308 bool blue = false;
309 while (run < end && !(events_[k].at < events_[run].at)) {
310 (edges_[events_[run].edge].blue ? blue : red) = true;
311 ++run;
312 }
313 if (red && blue) {
314 touching_ = true;
315 }
316 k = run;
317 }
318
319 // Verticals see the status twice, since neither snapshot alone holds
320 // every edge spanning this x: before the deletions it still has the ones
321 // ending here, after the insertions it has the ones starting here.
322 for (std::size_t k = begin; k < end; ++k) {
323 if (events_[k].kind == Kind::UprightLow && scanUpright(events_[k].edge)) {
324 return true;
325 }
326 }
327 uprightPairs(begin, end);
328
329 for (std::size_t k = begin; k < end; ++k) {
330 if (events_[k].kind != Kind::Right) {
331 continue;
332 }
333 const auto it = status_.find(events_[k].edge);
334 if (it == status_.end()) {
335 continue;
336 }
337 const bool hasBelow = it != status_.begin();
338 auto below = it;
339 if (hasBelow) {
340 --below;
341 }
342 const auto above = std::next(it);
343 status_.erase(it);
344 if (hasBelow && above != status_.end() && adjacent(*below, *above)) {
345 return true;
346 }
347 }
348
349 for (std::size_t k = begin; k < end; ++k) {
350 if (events_[k].kind != Kind::Left) {
351 continue;
352 }
353 const auto [it, fresh] = status_.insert(events_[k].edge);
354 if (!fresh) {
355 continue;
356 }
357 if (it != status_.begin() && adjacent(*std::prev(it), *it)) {
358 return true;
359 }
360 const auto above = std::next(it);
361 if (above != status_.end() && adjacent(*it, *above)) {
362 return true;
363 }
364 }
365
366 for (std::size_t k = begin; k < end; ++k) {
367 if (events_[k].kind == Kind::UprightLow && scanUpright(events_[k].edge)) {
368 return true;
369 }
370 }
371 return false;
372 }
373
374 public:
375 template <class RedRange, class BlueRange>
376 RedBlueSweeper(const RedRange& red, const BlueRange& blue) : status_(Below{&edges_}) {
377 Extent redExtent;
378 Extent blueExtent;
379 gather(red, false, redExtent);
380 const std::size_t split = edges_.size();
381 gather(blue, true, blueExtent);
382
383 // An edge whose box misses the other colour's box cannot meet anything
384 // of that colour, and dropping it can only remove intersections, never
385 // create one. On two boundaries that overlap everywhere this costs one
386 // extra pass; on two that barely touch it removes almost all the work.
387 std::vector<Edge> kept;
388 kept.reserve(edges_.size());
389 for (std::size_t i = 0; i < edges_.size(); ++i) {
390 const Extent& against = i < split ? blueExtent : redExtent;
391 if (against.overlaps(edges_[i].lo, edges_[i].hi)) {
392 kept.push_back(edges_[i]);
393 }
394 }
395 edges_ = std::move(kept);
396
397 events_.reserve(2 * edges_.size());
398 for (std::size_t i = 0; i < edges_.size(); ++i) {
399 const Edge& edge = edges_[i];
400 if (edge.upright()) {
401 events_.push_back(Event{edge.lo, i, Kind::UprightLow});
402 events_.push_back(Event{edge.hi, i, Kind::Upper});
403 } else {
404 events_.push_back(Event{edge.lo, i, Kind::Left});
405 events_.push_back(Event{edge.hi, i, Kind::Right});
406 }
407 }
408 }
409
410 BoundaryContact run() {
411 std::sort(events_.begin(), events_.end());
412 std::size_t i = 0;
413 while (i < events_.size()) {
414 std::size_t j = i;
415 while (j < events_.size() && !(events_[i].at.x() < events_[j].at.x())) {
416 ++j;
417 }
418 if (batch(i, j)) {
420 }
421 i = j;
422 }
424 }
425};
426
427} // namespace detail
428
459template <class RedRange, class BlueRange>
460BoundaryContact redBlueSweep(const RedRange& red, const BlueRange& blue) {
461 using RedNumber = typename std::ranges::range_value_t<RedRange>::PointType::NumberType;
462 using BlueNumber = typename std::ranges::range_value_t<BlueRange>::PointType::NumberType;
463 using Number = std::common_type_t<RedNumber, BlueNumber>;
464 return detail::RedBlueSweeper<Number>(red, blue).run();
465}
466
481template <class RedRange, class BlueRange>
482bool boundariesCross(const RedRange& red, const BlueRange& blue) {
483 return redBlueSweep(red, blue) == BoundaryContact::Crossing;
484}
485
493template <class RedRange, class BlueRange>
494bool boundariesMeet(const RedRange& red, const BlueRange& blue) {
495 return redBlueSweep(red, blue) != BoundaryContact::Disjoint;
496}
497
511 bool met;
512};
513
514template <class RedRange, class BlueRange>
515SweepContact boundaryContactBits(const RedRange& red, const BlueRange& blue) {
516 const BoundaryContact contact = redBlueSweep(red, blue);
517 return {contact == BoundaryContact::Crossing, contact != BoundaryContact::Disjoint};
518}
519
551
560#ifndef PGL_BOUNDARY_STRATEGY
561#define PGL_BOUNDARY_STRATEGY 0
562#endif
563
564namespace detail {
565
567template <class Shape>
568constexpr std::size_t boundaryEdgeCount(const Shape& shape) {
569 if constexpr (PolygonWithHolesConcept<Shape>) {
570 return shape.vertexCount();
571 } else {
572 return shape.size();
573 }
574}
575
619constexpr bool clearsEdgeFloor(std::size_t redEdges, std::size_t blueEdges) {
620 constexpr std::size_t kMinEdges = 128;
621 return redEdges >= kMinEdges && blueEdges >= kMinEdges;
622}
623
647constexpr bool sweepBeatsChains(std::size_t redEdges, std::size_t redChains,
648 std::size_t blueEdges, std::size_t blueChains) {
649 const std::size_t edges = redEdges + blueEdges;
650 return 3 * redChains * blueChains > edges;
651}
652
653} // namespace detail
654
672template <class RedShape, class BlueShape>
673constexpr bool preferSweep([[maybe_unused]] const RedShape& red,
674 [[maybe_unused]] const BlueShape& blue) {
675#if PGL_BOUNDARY_STRATEGY == 1
676 return false;
677#elif PGL_BOUNDARY_STRATEGY == 2
678 return true;
679#else
680 const std::size_t redEdges = detail::boundaryEdgeCount(red);
681 const std::size_t blueEdges = detail::boundaryEdgeCount(blue);
682 // Settled on edge counts alone — O(1), read straight off size()/vertexCount()
683 // — before either chainCount() is read, so an operand pair too small to be
684 // worth a sweep never pays for that O(n) pass (see clearsEdgeFloor).
685 if (!detail::clearsEdgeFloor(redEdges, blueEdges)) {
686 return false;
687 }
688 return detail::sweepBeatsChains(redEdges, red.chainCount(), blueEdges, blue.chainCount());
689#endif
690}
691
693
715template <PolygonConcept OuterPolygon, PolygonConcept InnerPolygon>
716bool sweepContains(const OuterPolygon& outer, const InnerPolygon& inner) {
717 using InnerPoint = typename InnerPolygon::PointType;
718
719 if (inner.size() == 0) {
720 return true;
721 }
722 if (!outer.bbox().contains(inner.bbox())) {
723 return false;
724 }
725 if (inner.size() == 1) {
726 return outer.contains(inner[0]);
727 }
728 if (const auto vertex = inner.getIfPoint()) {
729 return outer.contains(*vertex);
730 }
731 if (outer.isPoint()) {
732 // `inner` has two distinct vertices by the test above.
733 return false;
734 }
735 if (!outer.contains(inner.get(0))) {
736 return false;
737 }
738
739 const BoundaryContact contact = redBlueSweep(outer.edgesView(), inner.edgesView());
740 if (contact == BoundaryContact::Crossing) {
741 return false;
742 }
743 if (contact == BoundaryContact::Disjoint) {
744 // One vertex inside and no boundary contact leaves the whole of `inner`
745 // inside: its boundary cannot reach out without meeting `outer`'s.
746 return true;
747 }
748
749 // The boundaries meet without a crossing having been found. Whether one is
750 // hiding behind the touchings no longer matters: checking every edge settles
751 // containment outright.
752 for (std::size_t i = 0; i < inner.size(); ++i) {
753 if (!outer.contains(Segment<InnerPoint>(inner[i], inner[(i + 1) % inner.size()]))) {
754 return false;
755 }
756 }
757 return true;
758}
759
760} // namespace pgl
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
Segment intersection and crossing algorithms.
Definition arrangement.hpp:67
bool sweepContains(const OuterPolygon &outer, const InnerPolygon &inner)
Sweep-based counterpart of Polygon::contains(Polygon).
Definition redbluesweep.hpp:716
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
bool boundariesCross(const RedRange &red, const BlueRange &blue)
True exactly when some red edge properly crosses some blue edge.
Definition redbluesweep.hpp:482
bool boundariesMeet(const RedRange &red, const BlueRange &blue)
True exactly when some red edge meets some blue edge, crossing or not.
Definition redbluesweep.hpp:494
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
Shape(const std::variant< T, Ts... > &) -> Shape< detail::shape_point_type_t< T > >
BoundaryContact
How two edge sets meet, as classified by redBlueSweep.
Definition redbluesweep.hpp:55
@ Disjoint
No red edge meets any blue edge.
Definition redbluesweep.hpp:56
@ Touching
The two edge sets meet; no crossing pair was found.
Definition redbluesweep.hpp:57
@ Crossing
A red edge properly crosses a blue edge.
Definition redbluesweep.hpp:58
SweepContact boundaryContactBits(const RedRange &red, const BlueRange &blue)
Definition redbluesweep.hpp:515
BoundaryContact redBlueSweep(const RedRange &red, const BlueRange &blue)
Classifies how two edge sets meet, in one combined left-to-right sweep.
Definition redbluesweep.hpp:460
Two-dimensional point with optional label payload.
Definition point.hpp:129
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
Both boundariesCross and boundariesMeet, from one sweep.
Definition redbluesweep.hpp:509
bool crossed
Definition redbluesweep.hpp:510
bool met
Definition redbluesweep.hpp:511