Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
convexhull.hpp
Go to the documentation of this file.
1#pragma once
2
4
12
13#include <algorithm>
14#include <cstddef>
15#include <iterator>
16#include <type_traits>
17#include <vector>
18
19
20
21namespace pgl {
22
23namespace detail {
24
43template <class Container>
44auto hullCandidates(const Container &points_) {
45 using Point = std::remove_cvref_t<decltype(*std::begin(points_))>;
46 using Number = typename Point::NumberType;
47
48 // Materialized first: a shape's vertex iterator hands out points by value,
49 // and an approximation holds the point it approximates by address.
50 std::vector<Point> points(std::begin(points_), std::end(points_));
51 // Four distinct corners are needed before the filter can drop anything, and
52 // a handful of points more before the orientation signs it spends cost less
53 // than the sort they save.
54 if (points.size() < 16) {
55 return points;
56 }
57
58 std::vector<decltype(filtered<Number>(points[0]))> approximations;
59 approximations.reserve(points.size());
60 for (const Point &p : points) {
61 approximations.push_back(filtered<Number>(p));
62 }
63
64 std::size_t lowest = 0, rightmost = 0, highest = 0, leftmost = 0;
65 ApproximatePoint first = approximationOf(approximations[0]);
66 double lowY = first.y.value, highY = first.y.value;
67 double lowX = first.x.value, highX = first.x.value;
68 for (std::size_t i = 1; i < approximations.size(); ++i) {
69 const ApproximatePoint a = approximationOf(approximations[i]);
70 if (a.y.value < lowY) { lowY = a.y.value; lowest = i; }
71 if (a.x.value > highX) { highX = a.x.value; rightmost = i; }
72 if (a.y.value > highY) { highY = a.y.value; highest = i; }
73 if (a.x.value < lowX) { lowX = a.x.value; leftmost = i; }
74 }
75
76 const std::size_t corner[4] = {lowest, rightmost, highest, leftmost};
77 for (int i = 0; i < 4; ++i) {
78 const auto turn = orientationSignOf(approximations[corner[i]],
79 approximations[corner[(i + 1) % 4]],
80 approximations[corner[(i + 2) % 4]]).value();
81 if (!(turn > 0)) {
82 return points;
83 }
84 }
85
86 std::vector<Point> kept;
87 kept.reserve(points.size() / 8 + 16);
88 for (std::size_t i = 0; i < approximations.size(); ++i) {
89 bool inside = true;
90 for (int e = 0; e < 4 && inside; ++e) {
91 inside = orientationSignOf(approximations[corner[e]],
92 approximations[corner[(e + 1) % 4]],
93 approximations[i]).value() > 0;
94 }
95 if (!inside) {
96 kept.push_back(points[i]);
97 }
98 }
99 return kept;
100}
101
113template <class Point>
114std::vector<Point> grahamScanOf(const std::vector<Point> &points, bool keepCollinear) {
115 using Number = typename Point::NumberType;
116 std::vector<Point> hull;
117 if (points.empty()) {
118 return hull;
119 }
120
121 std::vector<decltype(filtered<Number>(points[0]))> approximations;
122 approximations.reserve(points.size());
123 for (const Point &p : points) {
124 approximations.push_back(filtered<Number>(p));
125 }
126
127 std::vector<std::size_t> stack;
128 const auto turnsBack = [&](std::size_t candidate) {
129 const auto sign = orientationSignOf(approximations[stack[stack.size() - 2]],
130 approximations[stack.back()],
131 approximations[candidate]).value();
132 return keepCollinear ? sign < 0 : sign <= 0;
133 };
134
135 // Build lower hull
136 for (std::size_t i = 0; i < points.size(); ++i) {
137 while (stack.size() >= 2 && turnsBack(i)) {
138 stack.pop_back();
139 }
140 stack.push_back(i);
141 }
142
143 // Build upper hull
144 const std::size_t lower_size = stack.size();
145 for (std::size_t i = points.size() - 1; i-- != 0;) {
146 while (stack.size() > lower_size && turnsBack(i)) {
147 stack.pop_back();
148 }
149 stack.push_back(i);
150 }
151
152 if (stack.size() >= 2) {
153 stack.pop_back();
154 }
155
156 hull.reserve(stack.size());
157 for (std::size_t i : stack) {
158 hull.push_back(points[i]);
159 }
160 return hull;
161}
162
163} // namespace detail
164
165
176template<class Container>
177auto grahamScan(const Container &points_) {
178 std::vector points = detail::hullCandidates(points_);
179
180 std::sort(points.begin(), points.end());
181 // Drop duplicate points: coincident inputs are never hull vertices and would
182 // otherwise survive as degenerate (zero-length) hull edges.
183 points.erase(std::unique(points.begin(), points.end()), points.end());
184
185 return detail::grahamScanOf(points, false);
186}
187
198template<class Container>
199auto grahamScanExtended(const Container &points_) {
200 std::vector points = detail::hullCandidates(points_);
201
202 std::sort(points.begin(), points.end());
203 // Drop duplicate points: coincident inputs are never hull vertices and would
204 // otherwise survive as degenerate (zero-length) hull edges.
205 points.erase(std::unique(points.begin(), points.end()), points.end());
206
207 return detail::grahamScanOf(points, true);
208}
209
210
221template<class Container>
222auto convexHull(const Container &points_) {
223 return grahamScan(points_);
224}
225
236template<class Container>
237auto convexHullExtended (const Container &points_) {
238 return grahamScanExtended(points_);
239}
240
241} // namespace pgl
Definition arrangement.hpp:67
auto grahamScanExtended(const Container &points_)
Computes the convex hull of a point container using Graham's scan.
Definition convexhull.hpp:199
auto convexHull(const Container &points_)
Computes the convex hull of a point container.
Definition convexhull.hpp:222
Point() -> Point< int >
auto grahamScan(const Container &points_)
Computes the convex hull of a point container using Graham's scan.
Definition convexhull.hpp:177
auto convexHullExtended(const Container &points_)
Computes the convex hull of a point container.
Definition convexhull.hpp:237
Bichromatic (red-blue) boundary contact by one combined plane sweep.
TNumber NumberType
Definition point.hpp:131