FastLED 3.9.15
Loading...
Searching...
No Matches
line_simplification.h
Go to the documentation of this file.
1#pragma once
2
3/*
4
5Line simplification based of an improved Douglas-Peucker algorithm with only
6O(n) extra memory. Memory structures are inlined so that most simplifications
7can be done with zero heap allocations.
8
9There are two versions here, one that simplifies using a threshold, and another
10version which will simplify to an exact number of points, however the latter is
11expensive since it must re-run the algorithm multiple times to find the right
12threshold. The first version is much faster and should be used in most cases.
13
14*/
15
16#include "fl/stl/bitset.h"
17#include "fl/math/math.h"
18#include "fl/math/math.h"
19#include "fl/stl/pair.h"
20#include "fl/math/geometry.h"
21#include "fl/stl/vector.h"
22#include "fl/stl/int.h"
23#include "fl/stl/noexcept.h"
24
25namespace fl {
26
27template <typename NumberT = float> class LineSimplifier {
28 public:
29 // This line simplification algorithm will remove vertices that are close
30 // together upto a distance of mMinDistance. The algorithm is based on the
31 // Douglas-Peucker but with some tweaks for memory efficiency. Most common
32 // usage of this class for small sized inputs (~20) will produce no heap
33 // allocations.
36
38 LineSimplifier(const LineSimplifier &other) FL_NOEXCEPT = default;
42
43 explicit LineSimplifier(NumberT e) : mMinDistance(e) {}
44 void setMinimumDistance(NumberT eps) { mMinDistance = eps; }
45
46 // simplifyInPlace.
48 simplifyInplaceT(polyline);
49 }
50 template <typename VectorType> void simplifyInplace(VectorType *polyLine) {
51 simplifyInplaceT(polyLine);
52 }
53
54 // simplify to the output vector.
55 void simplify(const fl::span<const Point> &polyLine,
56 fl::vector<Point> *out) {
57 simplifyT(polyLine, out);
58 }
59 template <typename VectorType>
60 void simplify(const fl::span<Point> &polyLine, VectorType *out) {
61 simplifyT(polyLine, out);
62 }
63
64 template <typename VectorType>
65 static void removeOneLeastError(VectorType *_poly) {
66 bitset<256> keep;
67 VectorType &poly = *_poly;
68 keep.assign(poly.size(), 1);
69 const int n = poly.size();
70 NumberT bestErr = FL_INFINITY_DOUBLE;
71 int bestIdx = -1;
72
73 // scan all interior “alive” points
74 for (int i = 1; i + 1 < n; ++i) {
75 if (!keep[i])
76 continue;
77
78 // find previous alive
79 int L = i - 1;
80 while (L >= 0 && !keep[L])
81 --L;
82 // find next alive
83 int R = i + 1;
84 while (R < n && !keep[R])
85 ++R;
86
87 if (L < 0 || R >= n)
88 continue; // endpoints
89
90 // compute perp‐distance² to the chord L→R
91 NumberT dx = poly[R].x - poly[L].x;
92 NumberT dy = poly[R].y - poly[L].y;
93 NumberT vx = poly[i].x - poly[L].x;
94 NumberT vy = poly[i].y - poly[L].y;
95 NumberT len2 = dx * dx + dy * dy;
96 NumberT err =
97 (len2 > NumberT(0))
98 ? ((dx * vy - dy * vx) * (dx * vy - dy * vx) / len2)
99 : (vx * vx + vy * vy);
100
101 if (err < bestErr) {
102 bestErr = err;
103 bestIdx = i;
104 }
105 }
106
107 // now “remove” that one point
108 if (bestIdx >= 0)
109 // keep[bestIdx] = 0;
110 poly.erase(poly.begin() + bestIdx);
111 }
112
113 private:
114 template <typename VectorType> void simplifyInplaceT(VectorType *polyLine) {
115 // run the simplification algorithm
116 span<Point> slice(polyLine->data(), polyLine->size());
117 simplifyT(slice, polyLine);
118 }
119
120 template <typename VectorType>
121 void simplifyT(const fl::span<const Point> &polyLine, VectorType *out) {
122 // run the simplification algorithm
123 simplifyInternal(polyLine);
124
125 // copy the result to the output slice
126 out->assign(mSimplified.begin(), mSimplified.end());
127 }
128 // Runs in O(n) allocations: one bool‐array + one index stack + one output
129 // vector
131 mSimplified.clear();
132 int n = polyLine.size();
133 if (n < 2) {
134 if (n) {
135 mSimplified.assign(polyLine.data(), polyLine.data() + n);
136 }
137 return;
138 }
139 const NumberT minDist2 = mMinDistance * mMinDistance;
140
141 // mark all points as “kept” initially
142 keep.assign(n, 1);
143
144 // explicit stack of (start,end) index pairs
145 indexStack.clear();
146 // indexStack.reserve(64);
147 indexStack.push_back({0, n - 1});
148
149 // process segments
150 while (!indexStack.empty()) {
151 // auto [i0, i1] = indexStack.back();
152 auto pair = indexStack.back();
153 int i0 = pair.first;
154 int i1 = pair.second;
155 indexStack.pop_back();
156 const bool has_interior = (i1 - i0) > 1;
157 if (!has_interior) {
158 // no interior points, just keep the endpoints
159 // keep[i0] = 1;
160 // keep[i1] = 1;
161 continue;
162 }
163
164 // find farthest point in [i0+1 .. i1-1]
165 NumberT maxDist2 = 0;
166 int split = i0;
167 for (int i = i0 + 1; i < i1; ++i) {
168 if (!keep[i])
169 continue;
170 NumberT d2 = PerpendicularDistance2(polyLine[i], polyLine[i0],
171 polyLine[i1]);
172
173 // FL_WARN("Perpendicular distance2 between "
174 // << polyLine[i] << " and " << polyLine[i0]
175 // << " and " << polyLine[i1] << " is " << d2);
176
177 if (d2 > maxDist2) {
178 maxDist2 = d2;
179 split = i;
180 }
181 }
182
183 if (maxDist2 > minDist2) {
184 // need to keep that split point and recurse on both halves
185 indexStack.push_back({i0, split});
186 indexStack.push_back({split, i1});
187 } else {
188 // drop all interior points in this segment
189 for (int i = i0 + 1; i < i1; ++i) {
190 keep[i] = 0;
191 }
192 }
193 }
194
195 // collect survivors
196 mSimplified.clear();
197 mSimplified.reserve(n);
198 for (int i = 0; i < n; ++i) {
199 if (keep[i])
200 mSimplified.push_back(polyLine[i]);
201 }
202 }
203
204 private:
206
207 // workspace buffers
208 fl::bitset<256> keep; // marks which points survive
210 indexStack; // manual recursion stack
211 VectorPoint mSimplified; // output buffer
212
213 static NumberT PerpendicularDistance2(const Point &pt, const Point &a,
214 const Point &b) {
215 // vector AB
216 NumberT dx = b.x - a.x;
217 NumberT dy = b.y - a.y;
218 // vector AP
219 NumberT vx = pt.x - a.x;
220 NumberT vy = pt.y - a.y;
221
222 // squared length of AB
223 NumberT len2 = dx * dx + dy * dy;
224 if (len2 <= NumberT(0)) {
225 // A and B coincide — just return squared dist from A to P
226 return vx * vx + vy * vy;
227 }
228
229 // cross‐product magnitude (AB × AP) in 2D is (dx*vy − dy*vx)
230 NumberT cross = dx * vy - dy * vx;
231 // |cross|/|AB| is the perpendicular distance; we want squared:
232 return (cross * cross) / len2;
233 }
234};
235
236template <typename NumberT = float> class LineSimplifierExact {
237 public:
239 using Point = vec2<NumberT>;
240
241 LineSimplifierExact(int count) : mCount(count) {}
242
243 void setCount(u32 count) { mCount = count; }
244
245 template <typename VectorType = fl::vector<Point>>
246 void simplifyInplace(VectorType *polyLine) {
247 return simplify(*polyLine, polyLine);
248 }
249
250 template <typename VectorType = fl::vector<Point>>
251 void simplify(const fl::span<const Point> &polyLine, VectorType *out) {
252 if (mCount > polyLine.size()) {
253 safeCopy(polyLine, out);
254 return;
255 } else if (mCount == polyLine.size()) {
256 safeCopy(polyLine, out);
257 return;
258 } else if (mCount < 2) {
260 if (polyLine.size() > 0) {
261 temp.push_back(polyLine[0]);
262 }
263 if (polyLine.size() > 1) {
264 temp.push_back(polyLine[polyLine.size() - 1]);
265 }
266 out->assign(temp.begin(), temp.end());
267 return;
268 }
269 NumberT est_max_dist = estimateMaxDistance(polyLine);
270 NumberT min = 0;
271 NumberT max = est_max_dist;
272 NumberT mid = (min + max) / 2.0f;
273 while (true) {
274 // min < max;
275 auto diff = max - min;
276 const bool done = (diff < 0.01f);
277 out->clear();
278 mLineSimplifier.setMinimumDistance(mid);
279 mLineSimplifier.simplify(polyLine, out);
280
281 fl::size n = out->size();
282
283 if (n == mCount) {
284 return; // we are done
285 }
286
287 // Handle the last few iterations manually. Often the algo will get
288 // stuck here.
289 if (n == mCount + 1) {
290 // Just one more left, so peel it off.
291 mLineSimplifier.removeOneLeastError(out);
292 return;
293 }
294
295 if (n == mCount + 2) {
296 // Just two more left, so peel them off.
297 mLineSimplifier.removeOneLeastError(out);
298 mLineSimplifier.removeOneLeastError(out);
299 return;
300 }
301
302 if (done) {
303 while (out->size() > mCount) {
304 // we have too many points, so we need to increase the
305 // distance
306 mLineSimplifier.removeOneLeastError(out);
307 }
308 return;
309 }
310 if (out->size() < mCount) {
311 max = mid;
312 } else {
313 min = mid;
314 }
315 mid = (min + max) / 2.0f;
316 }
317 }
318
319 private:
320 static NumberT estimateMaxDistance(const fl::span<const Point> &polyLine) {
321 // Rough guess: max distance between endpoints
322 if (polyLine.size() < 2)
323 return 0;
324
325 const Point &first = polyLine[0];
326 const Point &last = polyLine[polyLine.size() - 1];
327 NumberT dx = last.x - first.x;
328 NumberT dy = last.y - first.y;
329 return sqrt(dx * dx + dy * dy);
330 }
331
332 template <typename VectorType>
333 void safeCopy(const fl::span<const Point> &polyLine, VectorType *out) {
334 auto *first_out = out->data();
335 // auto* last_out = first_out + mCount;
336 auto *other_first_out = polyLine.data();
337 // auto* other_last_out = other_first_out + polyLine.size();
338 const bool is_same = first_out == other_first_out;
339 if (is_same) {
340 return;
341 }
342 auto *last_out = first_out + mCount;
343 auto *other_last_out = other_first_out + polyLine.size();
344
345 const bool is_overlapping =
346 (first_out >= other_first_out && first_out < other_last_out) ||
347 (other_first_out >= first_out && other_first_out < last_out);
348
349 if (!is_overlapping) {
350 out->assign(polyLine.data(), polyLine.data() + polyLine.size());
351 return;
352 }
353
354 // allocate a temporary buffer
356 temp.assign(polyLine.begin(), polyLine.end());
357 out->assign(temp.begin(), temp.end());
358 return;
359 }
360
361 u32 mCount = 10;
363};
364
365} // namespace fl
bool done
iterator end() FL_NOEXCEPT
Definition vector.h:395
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:191
iterator begin() FL_NOEXCEPT
Definition vector.h:393
void simplify(const fl::span< const Point > &polyLine, fl::vector< Point > *out)
LineSimplifier(const LineSimplifier &other) FL_NOEXCEPT=default
LineSimplifier & operator=(const LineSimplifier &other) FL_NOEXCEPT=default
LineSimplifier(LineSimplifier &&other) FL_NOEXCEPT=default
void simplifyT(const fl::span< const Point > &polyLine, VectorType *out)
void simplifyInplace(fl::vector< Point > *polyline)
LineSimplifier & operator=(LineSimplifier &&other) FL_NOEXCEPT=default
void simplify(const fl::span< Point > &polyLine, VectorType *out)
static NumberT PerpendicularDistance2(const Point &pt, const Point &a, const Point &b)
LineSimplifier() FL_NOEXCEPT
void simplifyInplaceT(VectorType *polyLine)
void setMinimumDistance(NumberT eps)
void simplifyInternal(const fl::span< const Point > &polyLine)
fl::vector< Point > VectorPoint
fl::bitset< 256 > keep
static void removeOneLeastError(VectorType *_poly)
fl::vector_inlined< fl::pair< int, int >, 64 > indexStack
fl::vec2< NumberT > Point
void simplifyInplace(VectorType *polyLine)
LineSimplifierExact() FL_NOEXCEPT=default
void simplifyInplace(VectorType *polyLine)
void simplify(const fl::span< const Point > &polyLine, VectorType *out)
void safeCopy(const fl::span< const Point > &polyLine, VectorType *out)
LineSimplifier< NumberT > mLineSimplifier
static NumberT estimateMaxDistance(const fl::span< const Point > &polyLine)
iterator begin() FL_NOEXCEPT
Definition span.h:440
const T * data() const FL_NOEXCEPT
Definition span.h:461
iterator end() FL_NOEXCEPT
Definition span.h:444
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
iterator begin() FL_NOEXCEPT
Definition vector.h:655
iterator end() FL_NOEXCEPT
Definition vector.h:661
void assign(InputIt first, InputIt last) FL_NOEXCEPT
Definition vector.h:638
#define FL_EPSILON_F
Definition math.h:38
#define FL_INFINITY_DOUBLE
Definition math.h:50
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NOEXCEPT
Definition math.h:71
constexpr common_type_t< T, U > max(T a, U b) FL_NOEXCEPT
Definition math.h:75
constexpr enable_if< is_fixed_point< T >::value, T >::type sqrt(T x) FL_NOEXCEPT
VectorN< T, INLINED_SIZE > vector_inlined
Definition vector.h:1133
FixedVector< T, INLINED_SIZE > vector_fixed
Definition vector.h:1130
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
T1 first
Definition pair.h:16
T2 second
Definition pair.h:17
value_type y
Definition geometry.h:191
value_type x
Definition geometry.h:190