Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
closestpair.hpp
Go to the documentation of this file.
1#pragma once
2
4
12
13#include <algorithm>
14#include <cassert>
15#include <cstddef>
16#include <iterator>
17#include <type_traits>
18#include <utility>
19#include <vector>
20
21
22namespace pgl {
23
24namespace detail {
25
26template <class Container>
27using closest_pair_input_point_t =
28 std::remove_cvref_t<decltype(*std::begin(std::declval<const Container&>()))>;
29
37template <class PointType>
38using closest_pair_coordinate_t = promoted_number_t<typename PointType::NumberType>;
39
40template <class PointType>
41using closest_pair_distance_t =
42 decltype(std::declval<const PointType&>()
43 .template squaredDistance<closest_pair_coordinate_t<PointType>>(
44 std::declval<const PointType&>()));
45
47template <class PointType>
48struct ClosestPairCandidate {
49 PointType first;
50 PointType second;
51 closest_pair_distance_t<PointType> squaredDistance;
52};
53
92template <class Coordinate>
93inline constexpr std::size_t closestPairBruteForceThreshold =
94 arbitraryPrecision<Coordinate> ? 4 : (is_Rational_v<Coordinate> ? 12 : 6);
95
97template <class PointType>
98constexpr bool closestPairLessY(const PointType& a, const PointType& b) {
99 if (a.y() < b.y())
100 return true;
101 if (b.y() < a.y())
102 return false;
103 return a.x() < b.x();
104}
105
112template <class PointType>
113void closestPairBaseCase(const PointType* points, std::size_t count,
114 ClosestPairCandidate<PointType>& best) {
115 using Coordinate = closest_pair_coordinate_t<PointType>;
116 for (std::size_t i = 0; i < count; ++i) {
117 for (std::size_t j = i + 1; j < count; ++j) {
118 const auto squared = points[i].template squaredDistance<Coordinate>(points[j]);
119 if (squared < best.squaredDistance) {
120 best = {points[i], points[j], squared};
121 }
122 }
123 }
124}
125
148template <class PointType>
149std::size_t closestPairGatherStrip(const PointType* points, std::size_t count, std::size_t half,
150 closest_pair_coordinate_t<PointType> splitX,
151 const ClosestPairCandidate<PointType>& best, PointType* strip) {
152 using Coordinate = closest_pair_coordinate_t<PointType>;
153
154 // Squared throughout, so the half-width is never rooted and the test stays
155 // exact: |x - splitX| < d becomes (x - splitX)^2 < d^2. A best of zero
156 // admits nothing, which is right — no pair can beat a coincident one.
157 const auto inside = [&](std::size_t index) {
158 const Coordinate dx = detail::asNumber<Coordinate>(points[index].x()) - splitX;
159 return dx * dx < best.squaredDistance;
160 };
161
162 std::size_t low = half;
163 while (low > 0 && inside(low - 1)) {
164 --low;
165 }
166 std::size_t high = half;
167 while (high < count && inside(high)) {
168 ++high;
169 }
170
171 std::size_t stripCount = 0;
172 for (std::size_t i = low; i < high; ++i) {
173 strip[stripCount++] = points[i];
174 }
175 return stripCount;
176}
177
185template <class PointType>
186void closestPairScanStrip(const PointType* strip, std::size_t stripCount,
187 ClosestPairCandidate<PointType>& best) {
188 using Coordinate = closest_pair_coordinate_t<PointType>;
189 for (std::size_t i = 0; i < stripCount; ++i) {
190 for (std::size_t j = i + 1; j < stripCount; ++j) {
191 const Coordinate dy =
192 detail::asNumber<Coordinate>(strip[j].y()) - detail::asNumber<Coordinate>(strip[i].y());
193 if (dy * dy >= best.squaredDistance) {
194 break;
195 }
196 const auto squared = strip[i].template squaredDistance<Coordinate>(strip[j]);
197 if (squared < best.squaredDistance) {
198 best = {strip[i], strip[j], squared};
199 }
200 }
201 }
202}
203
244template <std::size_t Threshold, class PointType>
245void closestPairRecursive(const PointType* points, std::size_t count, PointType* scratch,
246 ClosestPairCandidate<PointType>& best) {
247 using Coordinate = closest_pair_coordinate_t<PointType>;
248 static_assert(Threshold >= 3,
249 "a range above the threshold must split into halves of at least two points");
250 assert(count >= 2);
251
252 // Nothing above needs a y-order, so brute force leaves the range untouched.
253 if (count <= Threshold) {
254 closestPairBaseCase(points, count, best);
255 return;
256 }
257
258 // The vertical line through points[half] separates the halves: everything
259 // left of it has a smaller-or-equal abscissa, everything right of it a
260 // greater-or-equal one.
261 const std::size_t half = count / 2;
262 const Coordinate splitX = static_cast<Coordinate>(points[half].x());
263
264 closestPairRecursive<Threshold>(points, half, scratch, best);
265 closestPairRecursive<Threshold>(points + half, count - half, scratch + half, best);
266
267 // The range is in x-order, so the strip comes out in x-order too and has to
268 // be sorted before it can be scanned.
269 const std::size_t stripCount =
270 closestPairGatherStrip(points, count, half, splitX, best, scratch);
271 std::sort(scratch, scratch + stripCount, closestPairLessY<PointType>);
272 closestPairScanStrip(scratch, stripCount, best);
273}
274
281template <std::size_t Threshold, class Container>
283closestPairDriver(const Container& input) {
284 using InputPoint = closest_pair_input_point_t<Container>;
285 using Coordinate = closest_pair_coordinate_t<InputPoint>;
286 static_assert(PointConcept<InputPoint>,
287 "closestPair requires a container of pgl::Point values");
288
289 std::vector<InputPoint> points(std::begin(input), std::end(input));
290 assert(points.size() >= 2);
291
292 std::sort(points.begin(), points.end());
293 std::vector<InputPoint> scratch(points.size());
294
295 ClosestPairCandidate<InputPoint> best{
296 points[0], points[1],
297 points[0].template squaredDistance<Coordinate>(points[1])};
298 closestPairRecursive<Threshold>(points.data(), points.size(), scratch.data(), best);
299
300 return Segment<InputPoint>(best.first, best.second);
301}
302
303} // namespace detail
304
332template <class Container>
334closestPair(const Container& input) {
335 using Coordinate =
336 detail::closest_pair_coordinate_t<detail::closest_pair_input_point_t<Container>>;
337 return detail::closestPairDriver<detail::closestPairBruteForceThreshold<Coordinate>>(input);
338}
339
340} // namespace pgl
Smallest enclosing disk algorithms.
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
constexpr bool is_Rational_v
Definition rational.hpp:37
Segment< detail::closest_pair_input_point_t< Container > > closestPair(const Container &input)
Computes a closest pair of points by divide and conquer.
Definition closestpair.hpp:334
Segment() -> Segment< Point<>, NoLabel >
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58