Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
sortpoints.hpp
Go to the documentation of this file.
1#pragma once
2
4
12
13#include <algorithm>
14#include <type_traits>
15#include <vector>
16
17
18namespace pgl {
19
47template <class Number, class Label, class CenterNumber, class CenterLabel>
48void sortAround(std::vector<Point<Number, Label>>& points,
50 if (points.size() < 2)
51 return;
52
53 // A point equal to p has no direction around it, and would compare tied
54 // with every other point. Park those at the end and sort the rest.
55 const auto first = points.begin();
56 const auto last = std::partition(first, points.end(),
57 [&p](const auto& q) { return q != p; });
58 if (last - first < 2)
59 return;
60
61 // The smallest point defines the reference direction (angle zero).
62 const auto reference = *std::min_element(first, last);
63
64 // Splits off the directions in the half turn [0, pi) measured
65 // counterclockwise from the +x direction, that is the points above the
66 // horizontal line through p, ties on it broken by the side of p they fall
67 // on. The coordinates are promoted the way the orientation predicate
68 // promotes them, so the split and the sort agree on every direction.
69 using Compare = std::common_type_t<Number, CenterNumber>;
70 const auto firstHalf = [&p](const auto& q) {
71 const auto vertical = detail::strongOrder(detail::asNumber<Compare>(q.y()),
72 detail::asNumber<Compare>(p.y()));
73 if (vertical != 0)
74 return vertical > 0;
75 return detail::strongOrder(detail::asNumber<Compare>(q.x()),
76 detail::asNumber<Compare>(p.x())) > 0;
77 };
78 const auto middle = std::partition(first, last, firstHalf);
79
80 // The center is an operand of every orientation sign the sort takes —
81 // O(n log n) of them — so its coordinates are converted for the filter once
82 // here rather than once per comparison. The two points being compared are
83 // different ones each time and are converted as they come.
84 using SignCoordinate = detail::orientation_coordinate_t<CenterNumber, Number, Number>;
85 const auto center = detail::filtered<SignCoordinate>(p);
86
87 // Neither part spans more than half a turn, so within one of them the
88 // orientation sign is a consistent order on the directions.
89 const auto less = [&p, &center](const auto& a, const auto& b) {
90 const auto turn = detail::orientationSignOf(
91 center, detail::filtered<SignCoordinate>(a), detail::filtered<SignCoordinate>(b))
92 .value();
93 if (turn > 0)
94 return true; // b is counterclockwise of a, so a has the smaller angle.
95 if (turn < 0)
96 return false;
97
98 // Same direction from p: the farther point comes first. The distance
99 // is taken in the promoted type, so points carrying more precision
100 // than the center still compare exactly.
101 return p.template squaredDistance<Compare>(a) >
102 p.template squaredDistance<Compare>(b);
103 };
104 std::sort(first, middle, less);
105 std::sort(middle, last, less);
106
107 // The points now run counterclockwise from the +x direction: rotate the
108 // block sharing the reference direction to the front. The search compares
109 // directions alone, so it lands on the first point of that block rather
110 // than on the reference itself, which the distance tie-break may have put
111 // behind others pointing the same way.
112 const auto above = firstHalf(reference);
113 const auto start = std::lower_bound(
114 above ? first : middle, above ? middle : last, reference,
115 [&center](const auto& a, const auto& b) {
116 return detail::orientationSignOf(center,
117 detail::filtered<SignCoordinate>(a),
118 detail::filtered<SignCoordinate>(b))
119 .value() > 0;
120 });
121 std::rotate(first, start, last);
122}
123
124namespace detail {
125
126// Recursive median Hilbert sort (CGAL's median policy): split the range into the
127// four quadrants the Hilbert curve visits, using nested medians, then recurse
128// into each quadrant with the rotated/reflected curve state. `xAxis` selects the
129// primary split axis; `upX`/`upY` give the curve's direction along each axis.
130// Only the two coordinate comparators are used, so it is exact for any numeric
131// type and never constructs intermediate coordinates.
132template <class RandomIt, class LessX, class LessY>
133void hilbertSortMedian(RandomIt begin, RandomIt end, bool xAxis, bool upX, bool upY,
134 const LessX& lessX, const LessY& lessY) {
135 if (end - begin <= 1) {
136 return;
137 }
138
139 const RandomIt m0 = begin, m4 = end;
140 const RandomIt m2 = m0 + (m4 - m0) / 2; // primary-axis median of the whole range
141 const RandomIt m1 = m0 + (m2 - m0) / 2; // secondary-axis median of the lower half
142 const RandomIt m3 = m2 + (m4 - m2) / 2; // secondary-axis median of the upper half
143
144 // Rearranges [first,last) so *mid is its median under `less`, ascending when
145 // `up`, descending otherwise.
146 const auto split = [](RandomIt first, RandomIt mid, RandomIt last, const auto& less,
147 bool up) {
148 if (up) {
149 std::nth_element(first, mid, last, less);
150 } else {
151 std::nth_element(first, mid, last,
152 [&less](const auto& a, const auto& b) { return less(b, a); });
153 }
154 };
155
156 if (xAxis) {
157 split(m0, m2, m4, lessX, upX);
158 split(m0, m1, m2, lessY, upY);
159 split(m2, m3, m4, lessY, !upY);
160 } else {
161 split(m0, m2, m4, lessY, upY);
162 split(m0, m1, m2, lessX, upX);
163 split(m2, m3, m4, lessX, !upX);
164 }
165
166 hilbertSortMedian(m0, m1, !xAxis, upY, upX, lessX, lessY);
167 hilbertSortMedian(m1, m2, xAxis, upX, upY, lessX, lessY);
168 hilbertSortMedian(m2, m3, xAxis, upX, upY, lessX, lessY);
169 hilbertSortMedian(m3, m4, !xAxis, !upY, !upX, lessX, lessY);
170}
171
172} // namespace detail
173
191template <class Number, class Label>
192void hilbertSort(std::vector<Point<Number, Label>>& points) {
193 const auto lessX = [](const Point<Number, Label>& a, const Point<Number, Label>& b) {
194 return a.x() < b.x();
195 };
196 const auto lessY = [](const Point<Number, Label>& a, const Point<Number, Label>& b) {
197 return a.y() < b.y();
198 };
199 detail::hilbertSortMedian(points.begin(), points.end(), true, false, false, lessX, lessY);
200}
201
202} // namespace pgl
Definition arrangement.hpp:67
void hilbertSort(std::vector< Point< Number, Label > > &points)
Sorts points along a Hilbert space-filling curve.
Definition sortpoints.hpp:192
void sortAround(std::vector< Point< Number, Label > > &points, const Point< CenterNumber, CenterLabel > &p)
Sorts points counterclockwise around a center point.
Definition sortpoints.hpp:48
Two-dimensional point with optional label payload.
Definition point.hpp:129
constexpr const NumberType & x() const
Returns the x coordinate.
Definition point.hpp:193
constexpr const NumberType & y() const
Returns the y coordinate.
Definition point.hpp:205
Bounding-box sweep over pairs of segments.