Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
lattice.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "core/hash.hpp"
4
14
15#include <algorithm>
16#include <array>
17#include <cmath>
18#include <cstddef>
19#include <cstdint>
20#include <limits>
21#include <ranges>
22#include <stdexcept>
23#include <type_traits>
24#include <unordered_set>
25#include <vector>
26
27namespace pgl {
28
29namespace detail {
30
32template <class Number>
33[[nodiscard]] bool isWholeCoordinate(const Number& value) {
34 if constexpr (is_Rational_v<Number>) {
35 return value.isInteger();
36 } else if constexpr (std::is_floating_point_v<Number>) {
37 return std::isfinite(value) && value == std::floor(value);
38 } else {
39 return true;
40 }
41}
42
50template <class Int, class Integer>
51[[nodiscard]] bool latticeFits(const Integer& value) {
52 if constexpr (std::same_as<Int, Integer>) {
53 return true;
54 } else if constexpr (numeric_limits<Integer>::is_specialized
55 && numeric_limits<Integer>::digits < numeric_limits<Int>::digits) {
56 return true;
57 } else {
58 return representableAs<Int>(value);
59 }
60}
61
68template <class Int, class Integer>
69void requireLatticeFits(const Integer& value) {
70 if (!latticeFits<Int>(value)) {
71 throw std::logic_error("pgl::latticePoints: a lattice point does not fit the result type");
72 }
73}
74
76template <class Int, class Integer>
77[[nodiscard]] Int latticeNarrow(const Integer& value) {
78 requireLatticeFits<Int>(value);
79 return narrowTo<Int>(value);
80}
81
83template <class Int, class Float>
84[[nodiscard]] Int latticeFromFloat(const Float& value) {
85 if (!std::isfinite(value)) {
86 throw std::logic_error("pgl::latticePoints: a coordinate is not finite");
87 }
88 if constexpr (numeric_limits<Int>::is_bounded) {
89 // The bound is a power of two, so it and its negation are both exact in
90 // any binary floating-point type: this range test never rounds.
91 const Float low = static_cast<Float>(numeric_limits<Int>::min());
92 if (value < low || value >= -low) {
93 throw std::logic_error("pgl::latticePoints: a lattice point does not fit the result type");
94 }
95 return static_cast<Int>(value);
96 } else {
97 return Int(value);
98 }
99}
100
102template <class Int, class Number>
103[[nodiscard]] Int latticeFloor(const Number& value) {
104 if constexpr (is_Rational_v<Number>) {
105 // Both parts are wanted, and a deferred fraction reduces itself anew on
106 // each read, so reduce once and read the reduced form twice.
107 const Number reduced = value.simplified();
108 using Integer = rational_int_t<Number>;
109 const Integer n = reduced.numerator();
110 const Integer d = reduced.denominator(); // positive
111 return latticeNarrow<Int>(n >= Integer(0) ? n / d : -((-n + d - Integer(1)) / d));
112 } else if constexpr (std::is_floating_point_v<Number>) {
113 return latticeFromFloat<Int>(std::floor(value));
114 } else {
115 return latticeNarrow<Int>(value);
116 }
117}
118
120template <class Int, class Number>
121[[nodiscard]] Int latticeCeil(const Number& value) {
122 if constexpr (is_Rational_v<Number>) {
123 const Number reduced = value.simplified();
124 using Integer = rational_int_t<Number>;
125 const Integer n = reduced.numerator();
126 const Integer d = reduced.denominator(); // positive
127 return latticeNarrow<Int>(n > Integer(0) ? (n + d - Integer(1)) / d : -((-n) / d));
128 } else if constexpr (std::is_floating_point_v<Number>) {
129 return latticeFromFloat<Int>(std::ceil(value));
130 } else {
131 return latticeNarrow<Int>(value);
132 }
133}
134
142template <class Number>
143[[nodiscard]] std::array<BigInt, 2> exactFraction(const Number& value) {
144 if constexpr (is_Rational_v<Number>) {
145 const Number reduced = value.simplified();
146 return {BigInt(reduced.numerator()), BigInt(reduced.denominator())};
147 } else if constexpr (std::is_floating_point_v<Number>) {
148 if (!std::isfinite(value)) {
149 throw std::logic_error("pgl::latticePoints: a coordinate is not finite");
150 }
151 // A finite float is exactly significand * 2^shift, with the significand
152 // a whole number of at most `digits` bits.
153 int exponent = 0;
154 const Number fraction = std::frexp(value, &exponent);
155 const int digits = numeric_limits<Number>::digits;
156 const BigInt significand(std::ldexp(fraction, digits));
157 const int shift = exponent - digits;
158 if (shift >= 0) {
159 return {significand * pow2(shift), BigInt(1)};
160 }
161 return {significand, pow2(-shift)};
162 } else {
163 return {BigInt(value), BigInt(1)};
164 }
165}
166
168template <class Integer>
169[[nodiscard]] std::size_t latticeCount(const Integer& first, const Integer& last) {
170 // Half the addressable range: a bound no allocation clears anyway, and one
171 // both this conversion and a 32-bit std::size_t are exact for.
172 constexpr std::int64_t limit =
173 static_cast<std::int64_t>(std::numeric_limits<std::size_t>::max() / 2);
174 const Integer count = last - first + Integer(1);
175 if (!latticeFits<std::int64_t>(count) || narrowTo<std::int64_t>(count) > limit) {
176 throw std::length_error("pgl::latticePoints: too many lattice points");
177 }
178 return static_cast<std::size_t>(narrowTo<std::int64_t>(count));
179}
180
182template <class Int, class Number>
183[[nodiscard]] Int strictlyBelow(const Number& value) {
184 const Int below = latticeFloor<Int>(value);
185 return isWholeCoordinate(value) ? Int(below - Int(1)) : below;
186}
187
189template <class Number, class Int>
190[[nodiscard]] Number coordinateAt(const Int& index) {
191 if constexpr (std::same_as<Number, Int>) {
192 return index;
193 } else if constexpr (is_Rational_v<Number>) {
194 return Number(narrowTo<rational_int_t<Number>>(index));
195 } else if constexpr (std::is_floating_point_v<Number>) {
196 return static_cast<Number>(narrowTo<std::int64_t>(index));
197 } else {
198 return narrowTo<Number>(index);
199 }
200}
201
211template <class ResultNumber, class SegmentType>
212[[nodiscard]] ResultNumber crossingFloor(const SegmentType& edge, const ResultNumber& column) {
213 using Number = typename SegmentType::NumberType;
214 const auto& lower = edge.min(); // lower.x() < upper.x(): the edge is not vertical
215 const auto& upper = edge.max();
216 if constexpr (extended_integral<Number> || std::same_as<Number, BigInt>) {
217 using Wide = promoted_number_t<Number>;
218 const Wide run = Wide(upper.x()) - Wide(lower.x()); // positive
219 const Wide rise = Wide(upper.y()) - Wide(lower.y());
220 const Wide offset = (narrowTo<Wide>(column) - Wide(lower.x())) * rise;
221 const Wide quotient = offset >= Wide(0) ? offset / run
222 : -((-offset + run - Wide(1)) / run);
223 return latticeNarrow<ResultNumber>(Wide(lower.y()) + quotient);
224 } else {
225 const Number crossing =
226 lower.y() + (coordinateAt<Number>(column) - lower.x()) * (upper.y() - lower.y())
227 / (upper.x() - lower.x());
228 return latticeFloor<ResultNumber>(crossing);
229 }
230}
231
248template <class ResultPoint, class EdgeRange>
249[[nodiscard]] std::vector<ResultPoint> regionLatticePoints(const EdgeRange& edges) {
250 using ResultNumber = typename ResultPoint::NumberType;
251 using EdgeType = std::ranges::range_value_t<EdgeRange>;
252
254 struct Crossed {
255 EdgeType edge;
256 ResultNumber first;
257 ResultNumber last;
258 };
259
260 std::vector<Crossed> crossed;
261 std::vector<ResultPoint> boundary;
262 for (const EdgeType& edge : edges) {
263 const std::vector<ResultPoint> own = edge.template latticePoints<ResultNumber>();
264 boundary.insert(boundary.end(), own.begin(), own.end());
265 if (edge.isVertical()) {
266 continue; // no column crosses it; it is all boundary anyway
267 }
268 // Half-open in x, so a vertex shared by two edges is crossed by one of
269 // them: that is what makes the parity right where the boundary turns.
270 const ResultNumber first = latticeCeil<ResultNumber>(edge.min().x());
271 const ResultNumber last = strictlyBelow<ResultNumber>(edge.max().x());
272 if (last < first) {
273 continue;
274 }
275 crossed.push_back(Crossed{edge, first, last});
276 }
277 std::sort(boundary.begin(), boundary.end());
278 boundary.erase(std::unique(boundary.begin(), boundary.end()), boundary.end());
279 if (crossed.empty()) {
280 return boundary; // nothing has an interior: a point, a segment, no shape at all
281 }
282 std::sort(crossed.begin(), crossed.end(),
283 [](const Crossed& left, const Crossed& right) { return left.first < right.first; });
284
285 std::vector<ResultPoint> inside;
286 std::vector<const Crossed*> active;
287 std::vector<ResultNumber> crossings;
288 std::size_t pending = 0;
289 ResultNumber column = crossed.front().first;
290 while (pending < crossed.size() || !active.empty()) {
291 if (active.empty() && pending < crossed.size() && column < crossed[pending].first) {
292 column = crossed[pending].first; // no edge here: on to the next one that has some
293 }
294 while (pending < crossed.size() && !(column < crossed[pending].first)) {
295 active.push_back(&crossed[pending++]);
296 }
297 std::erase_if(active, [&](const Crossed* edge) { return edge->last < column; });
298 if (active.empty()) {
299 continue;
300 }
301 crossings.clear();
302 for (const Crossed* edge : active) {
303 crossings.push_back(crossingFloor<ResultNumber>(edge->edge, column));
304 }
305 std::sort(crossings.begin(), crossings.end());
306 for (std::size_t i = 0; i + 1 < crossings.size(); i += 2) {
307 for (ResultNumber row = crossings[i] + ResultNumber(1); !(crossings[i + 1] < row); ++row) {
308 inside.push_back(ResultPoint(column, row));
309 }
310 }
311 ++column;
312 }
313
314 std::vector<ResultPoint> points;
315 points.reserve(inside.size() + boundary.size());
316 std::set_union(inside.begin(), inside.end(), boundary.begin(), boundary.end(),
317 std::back_inserter(points));
318 return points;
319}
320
322template <class Region, class SegmentVector>
323void appendRegionEdges(const Region& region, SegmentVector& edges) {
324 for (const auto& edge : region.outer().edgesView()) {
325 edges.push_back(edge);
326 }
327 for (const auto& hole : region.holes()) {
328 for (const auto& edge : hole.edgesView()) {
329 edges.push_back(edge);
330 }
331 }
332}
333
341template <class ResultPoint, class Chain>
342[[nodiscard]] std::vector<ResultPoint> chainLatticePoints(const Chain& chain) {
343 using ResultNumber = typename ResultPoint::NumberType;
344 if (chain.size() == 1) {
345 // No edge to walk, so the single vertex answers for itself.
346 return Segment<typename Chain::PointType>(chain[0], chain[0])
347 .template latticePoints<ResultNumber>();
348 }
349 std::vector<ResultPoint> points;
350 std::unordered_set<ResultPoint> reached;
351 for (const auto& edge : chain.orientedEdgesView()) {
352 for (const ResultPoint& point : edge.template latticePoints<ResultNumber>()) {
353 if (reached.insert(point).second) {
354 points.push_back(point);
355 }
356 }
357 }
358 return points;
359}
360
361} // namespace detail
362
363// -----------------------------------------------------------------------------
364// Segment
365
366// The parameter is named as the declaration spells it: the return type reaches
367// through it for a nested type, and MSVC matches such a definition to its
368// declaration by spelling rather than by parameter position.
369template <class PointType, class LabelType>
370template <class ResultNumber>
371 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
375 // The walk from one lattice point to the next runs one type wider than the
376 // points themselves, so the step off the last one cannot overflow.
377 using Step = detail::promoted_number_t<ResultNumber>;
378
379 std::vector<ResultPoint> points;
380
381 const NumberType& x1 = min().x();
382 const NumberType& y1 = min().y();
383 const NumberType& x2 = max().x();
384 const NumberType& y2 = max().y();
385
386 // The three paths below agree on this much: the answer is `count` points
387 // from `first`, each a fixed step further along the segment.
388 ResultNumber firstX{}, firstY{};
389 Step stepX{}, stepY{};
390 std::size_t count = 0;
391
392 if (isVertical() || isHorizontal()) {
393 // One coordinate is constant and the other sweeps an interval, so the
394 // lattice points are the integers of that interval -- no direction to
395 // reduce, and no fraction to solve. A single point takes this path too.
396 const bool vertical = isVertical();
397 const NumberType& fixed = vertical ? x1 : y1;
398 const NumberType& low = vertical ? y1 : x1;
399 const NumberType& high = vertical ? y2 : x2;
400 if (!detail::isWholeCoordinate(fixed)) {
401 return points; // the whole supporting line lies between two grid lines
402 }
403 const ResultNumber constant = detail::latticeFloor<ResultNumber>(fixed);
404 const ResultNumber lowest = detail::latticeCeil<ResultNumber>(low);
405 const ResultNumber highest = detail::latticeFloor<ResultNumber>(high);
406 if (highest < lowest) {
407 return points; // the interval holds no integer
408 }
409 count = detail::latticeCount(Step(lowest), Step(highest));
410 firstX = vertical ? constant : lowest;
411 firstY = vertical ? lowest : constant;
412 stepX = vertical ? Step(0) : Step(1);
413 stepY = vertical ? Step(1) : Step(0);
414 } else if constexpr (detail::extended_integral<NumberType> || std::same_as<NumberType, BigInt>) {
415 // Integer endpoints are lattice points themselves, so the progression
416 // starts at one of them: it steps by the primitive direction, which is
417 // the difference divided by its own gcd, and lands on the other end.
418 using Wide = detail::promoted_number_t<NumberType>;
419 const Wide deltaX = Wide(x2) - Wide(x1);
420 const Wide deltaY = Wide(y2) - Wide(y1);
421 const Wide steps = detail::gcd(detail::abs(deltaX), detail::abs(deltaY));
422 firstX = detail::latticeNarrow<ResultNumber>(x1);
423 firstY = detail::latticeNarrow<ResultNumber>(y1);
424 detail::requireLatticeFits<ResultNumber>(x2); // the far end is a lattice point too
425 detail::requireLatticeFits<ResultNumber>(y2);
426 count = detail::latticeCount(Wide(0), steps);
427 stepX = detail::narrowTo<Step>(deltaX / steps);
428 stepY = detail::narrowTo<Step>(deltaY / steps);
429 } else {
430 // Fractional endpoints: the supporting line carries a progression of
431 // lattice points -- or none at all -- and the segment holds the part of
432 // it inside its own x range. Both come out of an exact integral line.
433 const auto line = [&] {
434 if constexpr (is_Rational_v<NumberType>) {
435 return OrientedLine<PointType>(min(), max()).template integralLine<BigInt>();
436 } else {
437 using ExactPoint = Point<Rational<BigInt>>;
438 const auto exactPoint = [](const NumberType& x, const NumberType& y) {
439 const std::array<BigInt, 2> fx = detail::exactFraction(x);
440 const std::array<BigInt, 2> fy = detail::exactFraction(y);
441 return ExactPoint(Rational<BigInt>(fx[0], fx[1]), Rational<BigInt>(fy[0], fy[1]));
442 };
443 return OrientedLine<ExactPoint>(exactPoint(x1, y1), exactPoint(x2, y2))
444 .template integralLine<BigInt>();
445 }
446 }();
447 if (!line) {
448 return points; // the supporting line misses the grid entirely
449 }
450 const BigInt baseX = line->source().x();
451 const BigInt baseY = line->source().y();
452 const BigInt directionX = line->target().x() - baseX; // > 0: the segment is not vertical
453 const BigInt directionY = line->target().y() - baseY;
454
455 // The lattice points of the line are base + t * direction, and x grows
456 // with t, so the segment keeps the t whose x it spans. Both bounds are
457 // exact fractions, which turns each into one rounded integer division.
458 const auto boundIndex = [&](const NumberType& x, bool upwards) {
459 const std::array<BigInt, 2> fraction = detail::exactFraction(x);
460 const BigInt numerator = fraction[0] - baseX * fraction[1];
461 const BigInt denominator = fraction[1] * directionX; // positive
462 if (upwards) {
463 return numerator > BigInt(0)
464 ? (numerator + denominator - BigInt(1)) / denominator
465 : -((-numerator) / denominator);
466 }
467 return numerator >= BigInt(0)
468 ? numerator / denominator
469 : -((-numerator + denominator - BigInt(1)) / denominator);
470 };
471 const BigInt lowest = boundIndex(x1, true);
472 const BigInt highest = boundIndex(x2, false);
473 if (highest < lowest) {
474 return points; // the line meets the grid, but not within this segment
475 }
476 firstX = detail::latticeNarrow<ResultNumber>(baseX + lowest * directionX);
477 firstY = detail::latticeNarrow<ResultNumber>(baseY + lowest * directionY);
478 detail::requireLatticeFits<ResultNumber>(baseX + highest * directionX);
479 detail::requireLatticeFits<ResultNumber>(baseY + highest * directionY);
480 count = detail::latticeCount(lowest, highest);
481 if (count > 1) {
482 // With two points in range the step is shorter than the span, which
483 // the endpoints above already fit; a single point never steps.
484 stepX = detail::narrowTo<Step>(directionX);
485 stepY = detail::narrowTo<Step>(directionY);
486 }
487 }
488
489 points.reserve(count);
490 Step x(firstX), y(firstY);
491 for (std::size_t i = 0; i < count; ++i) {
492 points.push_back(ResultPoint(detail::narrowTo<ResultNumber>(x),
493 detail::narrowTo<ResultNumber>(y)));
494 x += stepX;
495 y += stepY;
496 }
497 return points;
498}
499
500
501// -----------------------------------------------------------------------------
502// OrientedSegment
503
504template <class PointType, class LabelType>
505template <class ResultNumber>
506 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
509 auto points = static_cast<Segment<PointType>>(*this).template latticePoints<ResultNumber>();
510 if (target() < source()) {
511 std::reverse(points.begin(), points.end()); // the segment answered the other way round
512 }
513 return points;
514}
515
516// -----------------------------------------------------------------------------
517// MonotoneChain
518
519template <class PointType, class LabelType, class Storage>
520template <class ResultNumber>
521 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
524 return detail::chainLatticePoints<Point<ResultNumber, typename PointType::LabelType>>(*this);
525}
526
527// -----------------------------------------------------------------------------
528// Polyline
529
530template <class PointType, class LabelType>
531template <class ResultNumber>
532 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
535 return detail::chainLatticePoints<Point<ResultNumber, typename PointType::LabelType>>(*this);
536}
537
538
539// -----------------------------------------------------------------------------
540// Rectangle
541
542template <class PointType, class LabelType>
543template <class ResultNumber>
544 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
548 std::vector<ResultPoint> points;
549 const ResultNumber firstX = detail::latticeCeil<ResultNumber>(min().x());
550 const ResultNumber lastX = detail::latticeFloor<ResultNumber>(max().x());
551 const ResultNumber firstY = detail::latticeCeil<ResultNumber>(min().y());
552 const ResultNumber lastY = detail::latticeFloor<ResultNumber>(max().y());
553 if (lastX < firstX || lastY < firstY) {
554 return points; // a side spans no integer, so neither does the box
555 }
556 // The two sides are independent: their integers are the whole answer, with
557 // no direction to reduce and no crossing to sort. They are counted and
558 // walked one type wider than the points, so a side that reaches the edge
559 // of the coordinate range neither wraps its count nor steps past its end.
560 using Step = detail::promoted_number_t<ResultNumber>;
561 const std::size_t columns = detail::latticeCount(Step(firstX), Step(lastX));
562 const std::size_t rows = detail::latticeCount(Step(firstY), Step(lastY));
563 if (columns > std::numeric_limits<std::size_t>::max() / rows) {
564 throw std::length_error("pgl::latticePoints: too many lattice points");
565 }
566 points.reserve(columns * rows);
567 for (Step x = Step(firstX); !(Step(lastX) < x); ++x) {
568 for (Step y = Step(firstY); !(Step(lastY) < y); ++y) {
569 points.push_back(ResultPoint(detail::narrowTo<ResultNumber>(x),
570 detail::narrowTo<ResultNumber>(y)));
571 }
572 }
573 return points;
574}
575
576// -----------------------------------------------------------------------------
577// Triangle
578
579template <class PointType, class LabelType>
580template <class ResultNumber>
581 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
584 return detail::regionLatticePoints<Point<ResultNumber, typename PointType::LabelType>>(edges());
585}
586
587// -----------------------------------------------------------------------------
588// Disk
589
590template <class PointType, class LabelType>
591template <class ResultNumber>
592 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
596 if (const std::optional<PointType> single = getIfPoint()) {
597 // No circle to sweep, and no centre to derive: the point answers alone.
598 return Segment<PointType>(*single, *single).template latticePoints<ResultNumber>();
599 }
600 std::vector<ResultPoint> points;
601 const Rectangle<PointType> box = bbox();
602 const ResultNumber firstX = detail::latticeCeil<ResultNumber>(box.min().x());
603 const ResultNumber lastX = detail::latticeFloor<ResultNumber>(box.max().x());
604 if (lastX < firstX) {
605 return points;
606 }
607 // A column meets the disk in an interval centred on the centre's own row, so
608 // when it holds a lattice point at all, one of the rows around that centre
609 // is among them: that is the seed the interval grows from, and every step of
610 // the growth is the disk's own exact predicate.
611 const ResultNumber middle =
612 detail::latticeFloor<ResultNumber>(center<division_result_t<NumberType>>().y());
613 // Walked one type wider than the points, so a disk that reaches the edge
614 // of the coordinate range does not step past its last column.
615 using Step = detail::promoted_number_t<ResultNumber>;
616 for (Step column = Step(firstX); !(Step(lastX) < column); ++column) {
617 const ResultNumber x = detail::narrowTo<ResultNumber>(column);
618 ResultNumber seed = middle;
619 bool inside = contains(ResultPoint(x, seed));
620 for (int step = -1; !inside && step <= 1; step += 2) {
621 seed = middle + ResultNumber(step);
622 inside = contains(ResultPoint(x, seed));
623 }
624 if (!inside) {
625 continue; // the column passes beside the disk, or between two rows
626 }
627 ResultNumber low = seed;
628 ResultNumber high = seed;
629 while (contains(ResultPoint(x, low - ResultNumber(1)))) {
630 --low;
631 }
632 while (contains(ResultPoint(x, high + ResultNumber(1)))) {
633 ++high;
634 }
635 for (ResultNumber y = low; !(high < y); ++y) {
636 points.push_back(ResultPoint(x, y));
637 }
638 }
639 return points;
640}
641
642// -----------------------------------------------------------------------------
643// Convex
644
645template <class PointType, class LabelType>
646template <class ResultNumber>
647 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
650 return detail::regionLatticePoints<Point<ResultNumber, typename PointType::LabelType>>(
651 edgesView());
652}
653
654// -----------------------------------------------------------------------------
655// Polygon
656
657template <class PointType, class LabelType>
658template <class ResultNumber>
659 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
662 return detail::regionLatticePoints<Point<ResultNumber, typename PointType::LabelType>>(
663 edgesView());
664}
665
666// -----------------------------------------------------------------------------
667// HalfplaneIntersection
668
669template <class PointType, class LabelType>
670template <class ResultNumber>
671 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
674 // The vertices are crossings of the constraints, so they need not be on the
675 // grid and generally are not: the exact convex polygon they form is what the
676 // sweep reads, and it refuses an unbounded region as every vertex list does.
678}
679
680// -----------------------------------------------------------------------------
681// PolygonWithHoles
682
683template <class PointType, class LabelType>
684template <class ResultNumber>
685 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
688 // A hole's ring crosses every column that runs through it exactly as the
689 // outer ring does, so counting both makes the parity odd only where the
690 // region is: the sweep needs no separate notion of a hole.
691 std::vector<Segment<PointType>> edges;
692 detail::appendRegionEdges(*this, edges);
693 return detail::regionLatticePoints<Point<ResultNumber, typename PointType::LabelType>>(edges);
694}
695
696// -----------------------------------------------------------------------------
697// PolygonSet
698
699template <class PointType, class LabelType>
700template <class ResultNumber>
701 requires(detail::extended_integral<ResultNumber> || std::same_as<ResultNumber, BigInt>)
704 // The components have disjoint interiors, so one sweep over all their rings
705 // answers for the set: a column crosses each component an even number of
706 // times, which leaves the parity of the one it is inside.
707 std::vector<Segment<PointType>> edges;
708 for (const auto& component : components()) {
709 detail::appendRegionEdges(component, edges);
710 }
711 return detail::regionLatticePoints<Point<ResultNumber, typename PointType::LabelType>>(edges);
712}
713
714} // namespace pgl
Arbitrary precision signed integer.
Definition bigint.hpp:157
Exact rational number class template.
Definition rational.hpp:106
Hash support for Pangolin value types.
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
constexpr bool is_Rational_v
Definition rational.hpp:37
@ edge
Definition bitmatrix.hpp:37
typename DivisionResult< Number >::type division_result_t
Convenience alias for DivisionResult.
Definition rational.hpp:1175
typename rational_int< T >::type rational_int_t
Definition rational.hpp:61
Segment() -> Segment< Point<>, NoLabel >
OrientedLine() -> OrientedLine< Point<>, NoLabel >
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the convex polygon contains.
Definition lattice.hpp:649
constexpr auto edgesView() const
Returns a lazy view over the edges, materializing each Segment on the fly instead of allocating a vec...
Definition convex.hpp:575
constexpr Point< ResultNumber, PointLabelType > center() const
Returns the center (circumcenter of the three boundary points) in an explicitly chosen coordinate typ...
Definition disk.hpp:284
constexpr std::optional< PointType > getIfPoint() const
Returns the point the disk collapses to, if it does.
Definition disk.hpp:372
constexpr bool contains(const OtherPoint &other) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1015
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the disk contains.
Definition lattice.hpp:594
constexpr Rectangle< PointType > bbox() const
Returns an axis-aligned bounding box in the coordinate type.
Definition disk.hpp:457
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the region contains.
Definition lattice.hpp:673
constexpr Convex< Point< ResultNumber, typename PointType::LabelType > > asConvex() const
Returns the region as a convex polygon.
Definition halfplaneintersection.hpp:955
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the chain contains.
Definition lattice.hpp:523
constexpr const PointType & source() const
Returns the source endpoint.
Definition orientedsegment.hpp:178
constexpr const PointType & target() const
Returns the target endpoint.
Definition orientedsegment.hpp:190
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the oriented segment contains.
Definition lattice.hpp:508
Two-dimensional point with optional label payload.
Definition point.hpp:129
constexpr std::vector< EdgeType > edges() const
Returns the boundary edges of every ring of every component.
Definition polygonset.hpp:420
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the set contains.
Definition lattice.hpp:703
constexpr const std::vector< ComponentType > & components() const
Returns the components in canonical order.
Definition polygonset.hpp:277
constexpr const ComponentType & component(std::size_t index) const
Accesses a component by index.
Definition polygonset.hpp:271
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the region contains.
Definition lattice.hpp:687
constexpr std::vector< EdgeType > edges() const
Returns the boundary edges of every ring, outer boundary first.
Definition polygonwithholes.hpp:343
constexpr auto edgesView() const
Returns a lazy view over the edges, materializing each Segment on the fly instead of allocating a vec...
Definition polygon.hpp:782
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the polygon contains.
Definition lattice.hpp:661
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the polyline contains.
Definition lattice.hpp:534
Axis-aligned rectangle stored by minimum and maximum corners.
Definition rectangle.hpp:75
constexpr const PointType & min() const
Returns the minimum corner (min x, min y).
Definition rectangle.hpp:347
constexpr const PointType & max() const
Returns the maximum corner (max x, max y).
Definition rectangle.hpp:359
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the rectangle contains.
Definition lattice.hpp:546
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
constexpr bool isHorizontal() const
Returns whether the segment is horizontal.
Definition predicates.hpp:82
constexpr const PointType & max() const
Returns the largest stored endpoint.
Definition segment.hpp:199
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the segment contains.
Definition lattice.hpp:373
constexpr const PointType & min() const
Returns the smallest stored endpoint.
Definition segment.hpp:190
PointType::NumberType NumberType
Definition segment.hpp:60
constexpr bool isVertical() const
Returns whether the segment is vertical.
Definition predicates.hpp:77
std::vector< Point< ResultNumber, typename PointType::LabelType > > latticePoints() const
Returns the integer points the triangle contains.
Definition lattice.hpp:583
constexpr std::array< Segment< PointType >, 3 > edges() const
Returns the three unoriented boundary edges.
Definition bounding.hpp:240