FastLED 3.9.15
Loading...
Searching...
No Matches
xypath_impls.cpp
Go to the documentation of this file.
1
2#include "fl/xypath_impls.h"
3
4#include <math.h>
5
6#include "fl/assert.h"
7#include "fl/function.h"
8#include "fl/lut.h"
9#include "fl/map_range.h"
10#include "fl/math_macros.h"
11#include "fl/raster.h"
12
13#include "fl/xypath_renderer.h"
14
15namespace fl {
16
17LinePath::LinePath(float x0, float y0, float x1, float y1) {
19 params().x0 = x0;
20 params().y0 = y0;
21 params().x1 = x1;
22 params().y1 = y1;
23}
24
26 // α in [0,1] → (x,y) on the line
27 float x = params().x0 + alpha * (params().x1 - params().x0);
28 float y = params().y0 + alpha * (params().y1 - params().y0);
29 return {x, y};
30}
31
32void LinePath::set(float x0, float y0, float x1, float y1) {
33 params().x0 = x0;
34 params().y0 = y0;
35 params().x1 = x1;
36 params().y1 = y1;
37}
38
39void LinePath::set(const LinePathParams &p) { params() = p; }
40
42 // α in [0,1] → (x,y) on the unit circle [-1, 1]
43 float t = alpha * 2.0f * PI;
44 float x = cosf(t);
45 float y = sinf(t);
46 return vec2f(x, y);
47}
48
50
52
54 // Parametric equation for a heart shape
55 // α in [0,1] → (x,y) on the heart curve
56 float t = alpha * 2.0f * PI;
57
58 // Heart formula based on a modified cardioid with improved aesthetics
59 float x = 16.0f * powf(sinf(t), 3);
60
61 // Modified y formula for a more balanced heart shape
62 // This creates a fuller bottom and more defined top curve
63 float y = -(13.0f * cosf(t) - 5.0f * cosf(2.0f * t) -
64 2.0f * cosf(3.0f * t) - cosf(4.0f * t));
65
66 // Scale to fit in [-1, 1] range
67 // The 16.0f divisor for x ensures x is in [-1, 1]
68 x /= 16.0f;
69
70 // Scale y to ensure it's in [-1, 1]
71 // The 16.0f divisor ensures proper scaling while maintaining proportions
72 y /= -16.0f;
73
74 // Apply a slight vertical stretch to fill the [-1, 1] range better
75 y *= 1.10f;
76
77 y += 0.17f; // Adjust y to fit within the range of [-1, 1]
78
79 return vec2f(x, y);
80}
81
83 : mTurns(turns), mRadius(radius) {}
84
86 // Parametric equation for an Archimedean spiral
87 // α in [0,1] → (x,y) on the spiral curve
88
89 // Calculate the angle based on the number of turns
90 float theta = alpha * 2.0f * PI * mTurns;
91
92 // Calculate the radius at this angle (grows linearly with angle)
93 // Scale by alpha to ensure we start at center and grow outward
94 float r = alpha * mRadius;
95
96 // Convert polar coordinates (r, theta) to Cartesian (x, y)
97 float x = r * cosf(theta);
98 float y = r * sinf(theta);
99
100 // Ensure the spiral fits within [-1, 1] range
101 // No additional scaling needed as we control the radius directly
102
103 return vec2f(x, y);
104}
105
106RosePath::RosePath(uint8_t n, uint8_t d) {
108 params().n = n;
109 params().d = d;
110}
111
113 // Parametric equation for a rose curve (rhodonea)
114 // α in [0,1] → (x,y) on the rose curve
115
116 // Map alpha to the full range needed for the rose
117 // For a complete rose, we need to go through k*PI radians where k is:
118 // - k = n if n is odd and d is 1
119 // - k = 2n if n is even and d is 1
120 // - k = n*d if n and d are coprime
121 // For simplicity, we'll use 2*PI*n as a good approximation
122 float theta = alpha * 2.0f * PI * params().n;
123
124 // Calculate the radius using the rose formula: r = cos(n*θ/d)
125 // We use cosine for a rose that starts with a petal at theta=0
126 float r = cosf(params().n * theta / params().d);
127
128 // Scale to ensure the rose fits within [-1, 1] range
129 // The absolute value ensures we get the proper shape
130 r = fabsf(r);
131
132 // Convert polar coordinates (r, theta) to Cartesian (x, y)
133 float x = r * cosf(theta);
134 float y = r * sinf(theta);
135
136 return vec2f(x, y);
137}
138
140 // total number of points you want in the pattern
141 const float N = static_cast<float>(params().c);
142
143 // continuous “index” from 0…N
144 float n = alpha * N;
145
146 // use the golden angle in radians:
147 // π * (3 – √5) ≈ 2.399963229728653
148 constexpr float goldenAngle = PI * (3.0f - 1.6180339887498948f);
149
150 // normalized radius [0…1]: sqrt(n/N) gives uniform point density
151 float r = sqrtf(n / N);
152
153 // spiral angle
154 float theta = n * goldenAngle;
155
156 // polar → Cartesian
157 float x = r * cosf(theta);
158 float y = r * sinf(theta);
159
160 return vec2f{x, y};
161}
162
164 // 1) map alpha to angle θ ∈ [0 … 2π)
165 constexpr float kTwoPi = 6.283185307179586f;
166 float theta = alpha * kTwoPi;
167
168 // 2) superformula parameters (members of your path)
169 // a, b control the “shape scale” (often both = 1)
170 // m controls symmetry (integer number of lobes)
171 // n1,n2,n3 control curvature/sharpness
172 float a = params().a;
173 float b = params().b;
174 float m = params().m;
175 float n1 = params().n1;
176 float n2 = params().n2;
177 float n3 = params().n3;
178
179 // 3) compute radius from superformula
180 float t2 = m * theta / 4.0f;
181 float part1 = powf(fabsf(cosf(t2) / a), n2);
182 float part2 = powf(fabsf(sinf(t2) / b), n3);
183 float r = powf(part1 + part2, -1.0f / n1);
184
185 // 4) polar → Cartesian in unit circle
186 float x = r * cosf(theta);
187 float y = r * sinf(theta);
188
189 return vec2f{x, y};
190}
191
192const Str CirclePath::name() const { return "CirclePath"; }
193
195 FASTLED_UNUSED(alpha);
196 return mPoint;
197}
198
199const Str PointPath::name() const { return "PointPath"; }
200
201void PointPath::set(float x, float y) { set(vec2f(x, y)); }
202
204
205PointPath::PointPath(float x, float y) : mPoint(x, y) {}
206
208
209const Str LinePath::name() const { return "LinePath"; }
210
212
213const LinePathParams &LinePath::params() const { return *mParams; }
214
215LinePath::LinePath(const LinePathParamsPtr &params) : mParams(params) {}
216
217const Str HeartPath::name() const { return "HeartPath"; }
218
220 return "ArchimedeanSpiralPath";
221}
222
223void ArchimedeanSpiralPath::setTurns(uint8_t turns) { mTurns = turns; }
224
225void ArchimedeanSpiralPath::setRadius(float radius) { mRadius = radius; }
226
227const Str RosePath::name() const { return "RosePath"; }
228
229void RosePath::setN(uint8_t n) { params().n = n; }
230
231void RosePath::setD(uint8_t d) { params().d = d; }
232
234
236
237const RosePathParams &RosePath::params() const { return *mParams; }
238
239const Str PhyllotaxisPath::name() const { return "PhyllotaxisPath"; }
240
243
245
247
250
251const Str GielisCurvePath::name() const { return "GielisCurvePath"; }
252void GielisCurvePath::setA(float a) { params().a = a; }
253void GielisCurvePath::setB(float b) { params().b = b; }
254void GielisCurvePath::setM(float m) { params().m = m; }
255void GielisCurvePath::setN1(float n1) { params().n1 = n1; }
256void GielisCurvePath::setN2(float n2) { params().n2 = n2; }
257void GielisCurvePath::setN3(float n3) { params().n3 = n3; }
260
262
264
265void CatmullRomPath::addPoint(float x, float y) { params().addPoint(x, y); }
266
268
269size_t CatmullRomPath::size() const { return params().size(); }
270
272
274
276 const auto &points = params().points;
277
278 // Need at least 2 points to define a path
279 if (points.size() < 2) {
280 // Return origin if not enough points
281 return vec2f(0.0f, 0.0f);
282 }
283
284 // If only 2 points, do linear interpolation
285 if (points.size() == 2) {
286 return vec2f(points[0].x + alpha * (points[1].x - points[0].x),
287 points[0].y + alpha * (points[1].y - points[0].y));
288 }
289
290 // For Catmull-Rom, we need 4 points to interpolate between the middle two
291 // Scale alpha to the number of segments
292 float scaledAlpha = alpha * (points.size() - 1);
293
294 // Determine which segment we're in
295 int segment = static_cast<int>(scaledAlpha);
296
297 // Clamp to valid range
298 if (segment >= static_cast<int>(points.size()) - 1) {
299 segment = points.size() - 2;
300 scaledAlpha = static_cast<float>(segment) + 1.0f;
301 }
302
303 // Get local alpha within this segment [0,1]
304 float t = scaledAlpha - static_cast<float>(segment);
305
306 // Get the four points needed for interpolation
307 vec2f p0, p1, p2, p3;
308
309 // Handle boundary cases
310 if (segment == 0) {
311 // For the first segment, duplicate the first point
312 p0 = points[0];
313 p1 = points[0];
314 p2 = points[1];
315 p3 = (points.size() > 2) ? points[2] : points[1];
316 } else if (segment == static_cast<int>(points.size()) - 2) {
317 // For the last segment, duplicate the last point
318 p0 = (segment > 0) ? points[segment - 1] : points[0];
319 p1 = points[segment];
320 p2 = points[segment + 1];
321 p3 = points[segment + 1];
322 } else {
323 // Normal case - we have points before and after
324 p0 = points[segment - 1];
325 p1 = points[segment];
326 p2 = points[segment + 1];
327 p3 = points[segment + 2];
328 }
329
330 // Perform Catmull-Rom interpolation
331 auto out = interpolate(p0, p1, p2, p3, t);
332 return out;
333}
334
336 const vec2f &p2, const vec2f &p3,
337 float t) const {
338
339 // Catmull-Rom interpolation formula
340 // Using alpha=0.5 for the "tension" parameter (standard Catmull-Rom)
341 float t2 = t * t;
342 float t3 = t2 * t;
343
344 // Coefficients for x and y
345 float a = -0.5f * p0.x + 1.5f * p1.x - 1.5f * p2.x + 0.5f * p3.x;
346 float b = p0.x - 2.5f * p1.x + 2.0f * p2.x - 0.5f * p3.x;
347 float c = -0.5f * p0.x + 0.5f * p2.x;
348 float d = p1.x;
349
350 float x = a * t3 + b * t2 + c * t + d;
351
352 a = -0.5f * p0.y + 1.5f * p1.y - 1.5f * p2.y + 0.5f * p3.y;
353 b = p0.y - 2.5f * p1.y + 2.0f * p2.y - 0.5f * p3.y;
354 c = -0.5f * p0.y + 0.5f * p2.y;
355 d = p1.y;
356
357 float y = a * t3 + b * t2 + c * t + d;
358
359 return vec2f(x, y);
360}
361
362const Str CatmullRomPath::name() const { return "CatmullRomPath"; }
363
364} // namespace fl
uint32_t x[NUM_LAYERS]
Definition Fire2023.ino:82
uint32_t y[NUM_LAYERS]
Definition Fire2023.ino:83
const Str name() const override
void setRadius(float radius)
ArchimedeanSpiralPath(uint8_t turns=3, float radius=1.0f)
vec2f compute(float alpha) override
void setTurns(uint8_t turns)
HeapVector< vec2f > points
size_t size() const
void addPoint(vec2f p)
CatmullRomParams & params()
void clear()
Clear all control points.
vec2f interpolate(const vec2f &p0, const vec2f &p1, const vec2f &p2, const vec2f &p3, float t) const
CatmullRomPath(const Ptr< CatmullRomParams > &p=NewPtr< CatmullRomParams >())
vec2f compute(float alpha) override
Ptr< CatmullRomParams > mParams
const Str name() const override
size_t size() const
Get the number of control points.
void addPoint(vec2f p)
Add a point in [0,1]² to the path.
const Str name() const override
vec2f compute(float alpha) override
void setN3(float n3)
Ptr< GielisCurveParams > mParams
void setN2(float n2)
GielisCurveParams & params()
void setN1(float n1)
const Str name() const override
vec2f compute(float alpha) override
GielisCurvePath(const Ptr< GielisCurveParams > &p=NewPtr< GielisCurveParams >())
const Str name() const override
vec2f compute(float alpha) override
void set(float x0, float y0, float x1, float y1)
vec2f compute(float alpha) override
const Str name() const override
LinePath(const LinePathParamsPtr &params=NewPtr< LinePathParams >())
LinePathParams & params()
Ptr< LinePathParams > mParams
PhyllotaxisPath(const Ptr< PhyllotaxisParams > &p=NewPtr< PhyllotaxisParams >())
PhyllotaxisParams & params()
vec2f compute(float alpha) override
Ptr< PhyllotaxisParams > mParams
const Str name() const override
vec2f compute(float alpha) override
const Str name() const override
void set(float x, float y)
PointPath(float x, float y)
Definition ptr.h:118
RosePath(const Ptr< RosePathParams > &p=NewPtr< RosePathParams >())
const Str name() const override
RosePathParams & params()
void setD(uint8_t d)
void setN(uint8_t n)
vec2f compute(float alpha) override
Ptr< RosePathParams > mParams
Definition str.h:388
#define PI
Definition math_macros.h:57
vec2< float > vec2f
Definition geometry.h:151
Ptr< T > NewPtr(Args... args)
Definition ptr.h:451
Implements a simple red square effect for 2D LED grids.
Definition crgb.h:16
static FASTLED_NAMESPACE_BEGIN uint8_t const p[]
Definition noise.cpp:30
#define FASTLED_UNUSED(x)
Definition unused.h:3