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