FastLED 3.10.8
Loading...
Searching...
No Matches
flowfield.cpp.hpp
Go to the documentation of this file.
1
3
5
7#include "fl/gfx/tile2x2.h"
8#include "fl/math/math.h"
10#include "fl/stl/noexcept.h"
11
12namespace fl {
13
14// ===========================================================================
15// FlowField base class
16// ===========================================================================
17
18FlowField::FlowField(const XYMap &xyMap, const Params &params)
19 FL_NO_EXCEPT : Fx2d(xyMap), mParams(params),
20 mNoiseBias(xyMap.getWidth(), xyMap.getHeight(), 0.01f, 0.5f) {}
21
23 u32 t_ms = mTimeWarp.update(context.now);
24 u32 dt_ms = t_ms - mLastWarpedMs;
25 mLastWarpedMs = t_ms;
26 // Cap dt to prevent huge jumps when effect was inactive (mode switch).
27 if (dt_ms > 500) {
28 dt_ms = 0;
29 }
30 mNoiseBias.update(dt_ms * 0.001f);
31 drawImpl(context, dt_ms, t_ms);
32}
33
34void FlowField::noisePunch(float amplitude, BumpShape shape) FL_NO_EXCEPT {
35 float cx = getWidth() * 0.5f;
36 float cy = getHeight() * 0.5f;
37 float wx = getWidth() * 0.4f;
38 float wy = getHeight() * 0.4f;
39 mNoiseBias.triggerX(cx, wx, amplitude, shape);
40 mNoiseBias.triggerY(cy, wy, amplitude, shape);
41}
42
43void FlowField::noisePunchX(float center, float width, float amplitude,
44 BumpShape shape) FL_NO_EXCEPT {
45 mNoiseBias.triggerX(center, width, amplitude, shape);
46}
47
48void FlowField::noisePunchY(float center, float width, float amplitude,
49 BumpShape shape) FL_NO_EXCEPT {
50 mNoiseBias.triggerY(center, width, amplitude, shape);
51}
52
53// ===========================================================================
54// FlowFieldFloat
55// ===========================================================================
56
57// ---------------------------------------------------------------------------
58// Perlin2D
59// ---------------------------------------------------------------------------
60
62 u8 p[256];
63 for (int i = 0; i < 256; i++)
64 p[i] = (u8)i;
65 u32 s = seed;
66 for (int i = 255; i > 0; i--) {
67 s = s * 1664525u + 1013904223u;
68 int j = (int)((s >> 16) % (u32)(i + 1));
69 u8 tmp = p[i];
70 p[i] = p[j];
71 p[j] = tmp;
72 }
73 for (int i = 0; i < 256; i++) {
74 perm[i] = p[i];
75 perm[i + 256] = p[i];
76 }
77}
78
80 int xi = ((int)floorf(x)) & 255;
81 int yi = ((int)floorf(y)) & 255;
82 float xf = x - floorf(x);
83 float yf = y - floorf(y);
84 float u = fade(xf);
85 float v = fade(yf);
86 int aa = perm[perm[xi] + yi];
87 int ab = perm[perm[xi] + yi + 1];
88 int ba = perm[perm[xi + 1] + yi];
89 int bb = perm[perm[xi + 1] + yi + 1];
90 float x1 = lerp(grad(aa, xf, yf), grad(ba, xf - 1.0f, yf), u);
91 float x2 = lerp(grad(ab, xf, yf - 1.0f),
92 grad(bb, xf - 1.0f, yf - 1.0f), u);
93 return lerp(x1, x2, v);
94}
95
96float FlowFieldFloat::Perlin2D::grad(int h, float x, float y) FL_NO_EXCEPT {
97 switch (h & 7) {
98 case 0:
99 return x + y;
100 case 1:
101 return -x + y;
102 case 2:
103 return x - y;
104 case 3:
105 return -x - y;
106 case 4:
107 return x;
108 case 5:
109 return -x;
110 case 6:
111 return y;
112 default:
113 return -y;
114 }
115}
116
117// ---------------------------------------------------------------------------
118
120 FL_NO_EXCEPT : FlowField(xyMap, params) {
121 int w = (int)getWidth();
122 int h = (int)getHeight();
123 int n = w * h;
124
125 mR.resize(n, 0.0f);
126 mG.resize(n, 0.0f);
127 mB.resize(n, 0.0f);
128 mTR.resize(n, 0.0f);
129 mTG.resize(n, 0.0f);
130 mTB.resize(n, 0.0f);
131 mXProf.resize(w, 0.0f);
132 mYProf.resize(h, 0.0f);
133
134 mNoiseGenX.init(42);
135 mNoiseGenY.init(1337);
136}
137
139 float r = fmodf(x, m);
140 return r < 0.0f ? r + m : r;
141}
142
143float FlowFieldFloat::clampf(float v, float lo, float hi) FL_NO_EXCEPT {
144 return (v < lo) ? lo : (v > hi) ? hi : v;
145}
146
148 int i = (int)v;
149 if (i < 0)
150 return 0;
151 if (i > 255)
152 return 255;
153 return (u8)i;
154}
155
156CRGB FlowFieldFloat::rainbow(float t, float speed, float phase) FL_NO_EXCEPT {
157 float hue = fmodPos(t * speed + phase, 1.0f);
158 CHSV hsv((u8)(hue * 255.0f), 255, 255);
159 CRGB rgb;
160 hsv2rgb_rainbow(hsv, rgb);
161 return rgb;
162}
163
164void FlowFieldFloat::drawDot(float cx, float cy, float diam,
165 u8 cr, u8 cg, u8 cb) FL_NO_EXCEPT {
166 int w = (int)getWidth();
167 int h = (int)getHeight();
168 float rad = diam * 0.5f;
169 int x0 = fl::max(0, (int)floorf(cx - rad - 1.0f));
170 int x1 = fl::min(w - 1, (int)ceilf(cx + rad + 1.0f));
171 int y0 = fl::max(0, (int)floorf(cy - rad - 1.0f));
172 int y1 = fl::min(h - 1, (int)ceilf(cy + rad + 1.0f));
173 for (int y = y0; y <= y1; y++) {
174 for (int x = x0; x <= x1; x++) {
175 float dx = (x + 0.5f) - cx;
176 float dy = (y + 0.5f) - cy;
177 float dist = sqrtf(dx * dx + dy * dy);
178 float cov = clampf(rad + 0.5f - dist, 0.0f, 1.0f);
179 if (cov <= 0.0f)
180 continue;
181 float inv = 1.0f - cov;
182 int i = idx(y, x);
183 mR[i] = mR[i] * inv + cr * cov;
184 mG[i] = mG[i] * inv + cg * cov;
185 mB[i] = mB[i] * inv + cb * cov;
186 }
187 }
188}
189
190void FlowFieldFloat::drawAALine(float x0, float y0, float x1, float y1,
191 float t, float colorShift) FL_NO_EXCEPT {
192 int w = (int)getWidth();
193 int h = (int)getHeight();
194 float dx = x1 - x0;
195 float dy = y1 - y0;
196 int steps = fl::max(1, (int)(fl::max(fabsf(dx), fabsf(dy)) * 3.0f));
197 float invSteps = 1.0f / (float)steps;
198
199 for (int i = 0; i <= steps; i++) {
200 float u = i * invSteps;
201 float px = x0 + dx * u;
202 float py = y0 + dy * u;
203 CRGB c = rainbow(t, colorShift, u);
204
205 int ix = (int)floorf(px);
206 int iy = (int)floorf(py);
207 float fx = px - ix;
208 float fy = py - iy;
209
210 float weights[4] = {
211 (1.0f - fx) * (1.0f - fy),
212 fx * (1.0f - fy),
213 (1.0f - fx) * fy,
214 fx * fy,
215 };
216 int offX[4] = {0, 1, 0, 1};
217 int offY[4] = {0, 0, 1, 1};
218
219 for (int j = 0; j < 4; j++) {
220 int cx = ix + offX[j];
221 int cy = iy + offY[j];
222 if (cx < 0 || cx >= w || cy < 0 || cy >= h)
223 continue;
224 float wt = weights[j];
225 if (wt <= 0.0f)
226 continue;
227 int gi = idx(cy, cx);
228 float inv = 1.0f - wt;
229 mR[gi] = mR[gi] * inv + c.r * wt;
230 mG[gi] = mG[gi] * inv + c.g * wt;
231 mB[gi] = mB[gi] * inv + c.b * wt;
232 }
233 }
234}
235
237 int w = (int)getWidth();
238 int h = (int)getHeight();
239 float cx = (w - 1) * 0.5f;
240 float cy = (h - 1) * 0.5f;
241 float s = mParams.endpoint_speed;
242
243 // Radii scaled from original 32x32 prototype ratios.
244 float x1 = cx + cx * 0.742f * sinf(t * s * 1.13f + 0.20f);
245 float y1 = cy + cy * 0.677f * sinf(t * s * 1.71f + 1.30f);
246 float x2 = cx + cx * 0.774f * sinf(t * s * 1.89f + 2.20f);
247 float y2 = cy + cy * 0.710f * sinf(t * s * 1.37f + 0.70f);
248
249 drawAALine(x1, y1, x2, y2, t, mParams.color_shift);
250
251 CRGB endA = rainbow(t, mParams.color_shift, 0.0f);
252 CRGB endB = rainbow(t, mParams.color_shift, 0.5f);
253 float discDiam = 1.7f;
254 drawDot(x1, y1, discDiam, endA.r, endA.g, endA.b);
255 drawDot(x2, y2, discDiam, endB.r, endB.g, endB.b);
256}
257
259 int w = (int)getWidth();
260 int h = (int)getHeight();
261 int minDim = fl::min(w, h);
262 int n = mParams.dot_count;
263 float fn = (float)n;
264 float ocx = w * 0.5f - 0.5f;
265 float ocy = h * 0.5f - 0.5f;
266 float orad = minDim * 0.35f;
267 float base = t * 3.0f;
268 float dotDiam = 1.5f;
269 for (int i = 0; i < n; i++) {
270 float a = base + i * (2.0f * FL_PI / fn);
271 float cx = ocx + cosf(a) * orad;
272 float cy = ocy + sinf(a) * orad;
273 CRGB c = rainbow(t, mParams.color_shift, (float)i / fn);
274 drawDot(cx, cy, dotDiam, c.r, c.g, c.b);
275 }
276}
277
279 int w = (int)getWidth();
280 int h = (int)getHeight();
281 const float kBaseFreq = 0.23f;
282
283 for (int i = 0; i < w; i++) {
284 float v = mNoiseGenX.noise(
285 i * kBaseFreq * mParams.noise_freq_x + t * mParams.flow_speed_x,
286 0.0f);
287 mXProf[i] = clampf(v * mParams.flow_amp_x, -1.0f, 1.0f);
288 }
289
290 if (mParams.reverse_x_profile) {
291 for (int i = 0; i < w / 2; i++) {
292 float tmp = mXProf[i];
293 mXProf[i] = mXProf[w - 1 - i];
294 mXProf[w - 1 - i] = tmp;
295 }
296 }
297
298 for (int i = 0; i < h; i++) {
299 float v = mNoiseGenY.noise(
300 i * kBaseFreq * mParams.noise_freq_y + t * mParams.flow_speed_y,
301 0.0f);
302 mYProf[i] = clampf(v * mParams.flow_amp_y, -1.0f, 1.0f);
303 }
304
305 // Apply noise bias (attack/decay bumps from noisePunch triggers).
306 for (int i = 0; i < w; i++) {
307 mXProf[i] = clampf(mXProf[i] + mNoiseBias.getX(i), -1.0f, 1.0f);
308 }
309 for (int i = 0; i < h; i++) {
310 mYProf[i] = clampf(mYProf[i] + mNoiseBias.getY(i), -1.0f, 1.0f);
311 }
312}
313
315 int w = (int)getWidth();
316 int h = (int)getHeight();
317 float halfLife = fl::max(mParams.persistence, 0.001f);
318 float fade = powf(0.5f, dt / halfLife);
319 float shift = mParams.flow_shift;
320
321 // Pass 1: horizontal row shift.
322 for (int y = 0; y < h; y++) {
323 float sh = mYProf[y] * shift;
324 for (int x = 0; x < w; x++) {
325 float sx = fmodPos((float)x - sh, (float)w);
326 int ix0 = (int)floorf(sx) % w;
327 int ix1 = (ix0 + 1) % w;
328 float f = sx - floorf(sx);
329 float inv = 1.0f - f;
330 int src0 = idx(y, ix0);
331 int src1 = idx(y, ix1);
332 int dst = idx(y, x);
333 mTR[dst] = mR[src0] * inv + mR[src1] * f;
334 mTG[dst] = mG[src0] * inv + mG[src1] * f;
335 mTB[dst] = mB[src0] * inv + mB[src1] * f;
336 }
337 }
338
339 // Pass 2: vertical column shift + fade.
340 for (int x = 0; x < w; x++) {
341 float sh = mXProf[x] * shift;
342 for (int y = 0; y < h; y++) {
343 float sy = fmodPos((float)y - sh, (float)h);
344 int iy0 = (int)floorf(sy) % h;
345 int iy1 = (iy0 + 1) % h;
346 float f = sy - floorf(sy);
347 float inv = 1.0f - f;
348 int src0 = idx(iy0, x);
349 int src1 = idx(iy1, x);
350 int dst = idx(y, x);
351 mR[dst] = (mTR[src0] * inv + mTR[src1] * f) * fade;
352 mG[dst] = (mTG[src0] * inv + mTG[src1] * f) * fade;
353 mB[dst] = (mTB[src0] * inv + mTB[src1] * f) * fade;
354 }
355 }
356}
357
359 int w = (int)getWidth();
360 int h = (int)getHeight();
361
362 // Helper: plot a single subpixel point via Tile2x2 through the XYMap.
363 auto plotTile = [&](const CRGB &color, float px, float py,
364 bool horiz) {
365 int ix = (int)floorf(px);
366 int iy = (int)floorf(py);
367 float fx = px - (float)ix;
368 float fy = py - (float)iy;
369 Tile2x2_u8 tile;
370 tile.setOrigin((u16)ix, (u16)iy);
371 if (horiz) {
372 // X profile: subpixel along y only (one column wide)
373 tile.at(0, 0) = (u8)((1.0f - fy) * 255.0f);
374 tile.at(1, 0) = 0;
375 tile.at(0, 1) = (u8)(fy * 255.0f);
376 tile.at(1, 1) = 0;
377 } else {
378 // Y profile: subpixel along x only (one row tall)
379 tile.at(0, 0) = (u8)((1.0f - fx) * 255.0f);
380 tile.at(1, 0) = (u8)(fx * 255.0f);
381 tile.at(0, 1) = 0;
382 tile.at(1, 1) = 0;
383 }
384 tile.draw(color, mXyMap, leds);
385 };
386
387 // Display amplitude: 0.3 = profile uses ~30% of the display around center.
388 constexpr float amp = 0.3f;
389
390 // X profile (per-column): plot value as y-position — cyan.
391 // Interpolate between consecutive columns to fill gaps.
392 CRGB xColor(0, 255, 255);
393 float centerY = (float)(h - 1) * 0.5f;
394 float prevPy = 0.0f;
395 for (int x = 0; x < w; x++) {
396 float val = mXProf[x]; // [-1, 1]
397 float py = centerY - val * amp * centerY;
398 plotTile(xColor, (float)x, py, true);
399 if (x > 0) {
400 float dy = py - prevPy;
401 int gap = (int)fabsf(dy);
402 for (int s = 1; s < gap; s++) {
403 float t = (float)s / (float)gap;
404 float midY = prevPy + dy * t;
405 float midX = (float)(x - 1) + t;
406 plotTile(xColor, midX, midY, true);
407 }
408 }
409 prevPy = py;
410 }
411
412 // Y profile (per-row): plot value as x-position — yellow.
413 // Interpolate between consecutive rows to fill gaps.
414 CRGB yColor(255, 255, 0);
415 float centerX = (float)(w - 1) * 0.5f;
416 float prevPx = 0.0f;
417 for (int y = 0; y < h; y++) {
418 float val = mYProf[y]; // [-1, 1]
419 float px = centerX + val * amp * centerX;
420 plotTile(yColor, px, (float)y, false);
421 if (y > 0) {
422 float dx = px - prevPx;
423 int gap = (int)fabsf(dx);
424 for (int s = 1; s < gap; s++) {
425 float t = (float)s / (float)gap;
426 float midX = prevPx + dx * t;
427 float midY = (float)(y - 1) + t;
428 plotTile(yColor, midX, midY, false);
429 }
430 }
431 prevPx = px;
432 }
433}
434
435void FlowFieldFloat::drawImpl(DrawContext context, u32 dt_ms, u32 t_ms) FL_NO_EXCEPT {
436 float dt = dt_ms * 0.001f;
437 float t = t_ms * 0.001f;
438
439 flowPrepare(t);
440 switch (mParams.emitter_mode) {
441 case 0:
443 break;
444 case 1:
446 break;
447 case 2:
450 break;
451 default:
453 break;
454 }
455 flowAdvect(dt);
456
457 int w = (int)getWidth();
458 int h = (int)getHeight();
459 for (int y = 0; y < h; y++) {
460 for (int x = 0; x < w; x++) {
461 int ledIdx = mXyMap.mapToIndex(x, y);
462 int i = idx(y, x);
463 context.leds.data()[ledIdx].r = f2u8(mR[i]);
464 context.leds.data()[ledIdx].g = f2u8(mG[i]);
465 context.leds.data()[ledIdx].b = f2u8(mB[i]);
466 }
467 }
468
469 if (mParams.show_flow_vectors) {
470 drawFlowVectors(context.leds);
471 }
472}
473
474} // namespace fl
475
476// ===========================================================================
477// FlowFieldFP — pure fixed-point (s16x16) implementation
478// ===========================================================================
479
481
482namespace fl {
483
484// s16x16 constants
485static constexpr i32 FP_ONE = s16x16::SCALE; // 1.0 in Q16.16 = 65536
486static constexpr i32 FP_255 = 255 * FP_ONE; // 255.0
487
488// ---------------------------------------------------------------------------
489// Perm table init — Fisher-Yates shuffle (replaces Perlin2D::init)
490// ---------------------------------------------------------------------------
491
493 for (int i = 0; i < 256; i++)
494 perm[i] = (u8)i;
495 u32 s = seed;
496 for (int i = 255; i > 0; i--) {
497 s = s * 1664525u + 1013904223u;
498 int j = (int)((s >> 16) % (u32)(i + 1));
499 u8 tmp = perm[i];
500 perm[i] = perm[j];
501 perm[j] = tmp;
502 }
503}
504
505// ---------------------------------------------------------------------------
506// FlowFieldFP
507// ---------------------------------------------------------------------------
508
510 FL_NO_EXCEPT : FlowField(xyMap, params) {
511 int w = (int)getWidth();
512 int h = (int)getHeight();
513
514 mState.init(w, h);
515 initPerm256(mPermX, 42);
516 initPerm256(mPermY, 1337);
517 syncParams();
518 if (!mState.fade_lut_initialized) {
520 mState.fade_lut_initialized = true;
521 }
522
523}
524
526 mColorShift_fp = s16x16(mParams.color_shift);
527 mFlowShift_fp = s16x16(mParams.flow_shift);
528 mEndpointSpeed_fp = s16x16(mParams.endpoint_speed);
529 mPersistence_fp = s16x16(mParams.persistence);
530 mNoiseFreqX_fp = s16x16(mParams.noise_freq_x);
531 mNoiseFreqY_fp = s16x16(mParams.noise_freq_y);
532 mFlowSpeedX_fp = s16x16(mParams.flow_speed_x);
533 mFlowSpeedY_fp = s16x16(mParams.flow_speed_y);
534 mFlowAmpX_fp = s16x16(mParams.flow_amp_x);
535 mFlowAmpY_fp = s16x16(mParams.flow_amp_y);
536}
537
538i32 FlowFieldFP::clamp_q16(i32 v, i32 lo, i32 hi) FL_NO_EXCEPT {
539 return (v < lo) ? lo : (v > hi) ? hi : v;
540}
541
543 i32 integer = v >> 16;
544 if (integer < 0) return 0;
545 if (integer > 255) return 255;
546 return (u8)integer;
547}
548
550 constexpr s16x16 one(1.0f);
551 constexpr s16x16 fp_255(255.0f);
552 s16x16 hue_raw = t * speed + phase;
553 // Positive modulo: hue in [0, 1)
554 s16x16 hue = s16x16::mod(hue_raw, one);
555 if (hue.raw() < 0)
556 hue = hue + one;
557 i32 hue_int = s16x16::clamp(hue * fp_255, s16x16(), fp_255).to_int();
558 u8 hue_u8 = (u8)hue_int;
559 CHSV hsv(hue_u8, 255, 255);
560 CRGB rgb;
561 hsv2rgb_rainbow(hsv, rgb);
562 return rgb;
563}
564
565// ---------------------------------------------------------------------------
566// Emitters — pure s16x16 fixed-point
567// ---------------------------------------------------------------------------
568
570 u8 cr, u8 cg, u8 cb) FL_NO_EXCEPT {
571 int w = mState.width;
572 int h = mState.height;
573 constexpr s16x16 half(0.5f);
574 constexpr s16x16 one(1.0f);
575 s16x16 rad = diam * half;
576
577 int x0 = fl::max(0, s16x16::floor(cx - rad - one).to_int());
578 int x1 = fl::min(w - 1, s16x16::ceil(cx + rad + one).to_int());
579 int y0 = fl::max(0, s16x16::floor(cy - rad - one).to_int());
580 int y1 = fl::min(h - 1, s16x16::ceil(cy + rad + one).to_int());
581
582 s16x16 rad_plus_half = rad + half;
583 // Hoist loop-invariant computations
584 i32 threshold_sq_raw = (rad_plus_half * rad_plus_half).raw();
585 i32 cr_raw = static_cast<i32>(cr) << 16;
586 i32 cg_raw = static_cast<i32>(cg) << 16;
587 i32 cb_raw = static_cast<i32>(cb) << 16;
588 i32 *__restrict__ rp = mState.r.data();
589 i32 *__restrict__ gp = mState.g.data();
590 i32 *__restrict__ bp = mState.b.data();
591
592 for (int y = y0; y <= y1; y++) {
593 s16x16 dy = s16x16(y) + half - cy;
594 i32 dy_sq_raw = (dy * dy).raw();
595 // Early-out: if dy^2 alone exceeds threshold, skip entire row
596 if (dy_sq_raw >= threshold_sq_raw) continue;
597 int row_base = y * w;
598 for (int x = x0; x <= x1; x++) {
599 s16x16 dx = s16x16(x) + half - cx;
600 i32 dist_sq_raw = dy_sq_raw + (dx * dx).raw();
601 if (dist_sq_raw >= threshold_sq_raw)
602 continue;
603 s16x16 dist = s16x16::sqrt(s16x16::from_raw(dist_sq_raw));
604 s16x16 cov = s16x16::clamp(rad_plus_half - dist, s16x16(), one);
605 if (cov.raw() <= 0) continue;
606 int i = row_base + x;
607 // Lerp: old + (new - old) * cov — 1 i64 mul per channel instead of 2
608 i32 cov_q16 = cov.raw();
609 rp[i] += static_cast<i32>((static_cast<i64>(cr_raw - rp[i]) * cov_q16) >> 16);
610 gp[i] += static_cast<i32>((static_cast<i64>(cg_raw - gp[i]) * cov_q16) >> 16);
611 bp[i] += static_cast<i32>((static_cast<i64>(cb_raw - bp[i]) * cov_q16) >> 16);
612 }
613 }
614}
615
618 int w = mState.width;
619 int h = mState.height;
620 s16x16 dx = x1 - x0;
621 s16x16 dy = y1 - y0;
622 s16x16 max_delta = fl::max(s16x16::abs(dx), s16x16::abs(dy));
623 int steps = fl::max(1, (max_delta * s16x16(3)).to_int());
624 s16x16 invSteps = s16x16(1) / s16x16(steps);
625
626 i32 *__restrict__ rp = mState.r.data();
627 i32 *__restrict__ gp = mState.g.data();
628 i32 *__restrict__ bp = mState.b.data();
629
630 // Precompute step increments (was recomputing from scratch each iteration)
631 s16x16 dx_step = dx * invSteps;
632 s16x16 dy_step = dy * invSteps;
633 s16x16 u = s16x16();
634 s16x16 px = x0;
635 s16x16 py = y0;
636
637 for (int i = 0; i <= steps; i++, u = u + invSteps, px = px + dx_step, py = py + dy_step) {
638 CRGB c = rainbow(t, colorShift, u);
639
640 int ix = s16x16::floor(px).to_int();
641 int iy = s16x16::floor(py).to_int();
642 s16x16 fx = s16x16::fract(px);
643 s16x16 fy = s16x16::fract(py);
644 constexpr s16x16 one(1.0f);
645 s16x16 inv_fx = one - fx;
646 s16x16 inv_fy = one - fy;
647
648 i32 weights[4] = {
649 (inv_fx * inv_fy).raw(),
650 (fx * inv_fy).raw(),
651 (inv_fx * fy).raw(),
652 (fx * fy).raw(),
653 };
654
655 // Pre-shift color values once per step
656 i32 cr_raw = static_cast<i32>(c.r) << 16;
657 i32 cg_raw = static_cast<i32>(c.g) << 16;
658 i32 cb_raw = static_cast<i32>(c.b) << 16;
659
660 int offX[4] = {0, 1, 0, 1};
661 int offY[4] = {0, 0, 1, 1};
662
663 for (int j = 0; j < 4; j++) {
664 int cx = ix + offX[j];
665 int cy = iy + offY[j];
666 if (cx < 0 || cx >= w || cy < 0 || cy >= h)
667 continue;
668 i32 wt_q16 = weights[j];
669 if (wt_q16 <= 0) continue;
670 int gi = cy * w + cx;
671 // Lerp: old + (new - old) * wt — 1 i64 mul per channel instead of 2
672 rp[gi] += static_cast<i32>((static_cast<i64>(cr_raw - rp[gi]) * wt_q16) >> 16);
673 gp[gi] += static_cast<i32>((static_cast<i64>(cg_raw - gp[gi]) * wt_q16) >> 16);
674 bp[gi] += static_cast<i32>((static_cast<i64>(cb_raw - bp[gi]) * wt_q16) >> 16);
675 }
676 }
677}
678
680 int w = mState.width;
681 int h = mState.height;
682 constexpr s16x16 half(0.5f);
683 s16x16 cx = s16x16(w - 1) * half;
684 s16x16 cy = s16x16(h - 1) * half;
685
686 // Pre-multiply t * s once (was computed 4 times)
688
689 // Pre-multiply center * radius constants (was computing cx*k and cy*k each time)
690 constexpr s16x16 k_0742(0.742f);
691 constexpr s16x16 k_0677(0.677f);
692 constexpr s16x16 k_0774(0.774f);
693 constexpr s16x16 k_0710(0.710f);
694 s16x16 cx_r1 = cx * k_0742;
695 s16x16 cy_r1 = cy * k_0677;
696 s16x16 cx_r2 = cx * k_0774;
697 s16x16 cy_r2 = cy * k_0710;
698
699 // Pre-multiply ts * frequency constants (was computing ts*k each time)
700 constexpr s16x16 k_113(1.13f);
701 constexpr s16x16 k_171(1.71f);
702 constexpr s16x16 k_189(1.89f);
703 constexpr s16x16 k_137(1.37f);
704 constexpr s16x16 k_020(0.20f);
705 constexpr s16x16 k_130(1.30f);
706 constexpr s16x16 k_220(2.20f);
707 constexpr s16x16 k_070(0.70f);
708
709 s16x16 x1 = cx + cx_r1 * s16x16::sin(ts * k_113 + k_020);
710 s16x16 y1 = cy + cy_r1 * s16x16::sin(ts * k_171 + k_130);
711 s16x16 x2 = cx + cx_r2 * s16x16::sin(ts * k_189 + k_220);
712 s16x16 y2 = cy + cy_r2 * s16x16::sin(ts * k_137 + k_070);
713
714 drawAALine(x1, y1, x2, y2, t, mColorShift_fp);
715
716 CRGB endA = rainbow(t, mColorShift_fp, s16x16());
717 CRGB endB = rainbow(t, mColorShift_fp, half);
718 constexpr s16x16 discDiam(1.7f);
719 drawDot(x1, y1, discDiam, endA.r, endA.g, endA.b);
720 drawDot(x2, y2, discDiam, endB.r, endB.g, endB.b);
721}
722
724 int w = mState.width;
725 int h = mState.height;
726 int minDim = fl::min(w, h);
727 int n = mParams.dot_count;
728 constexpr s16x16 half(0.5f);
729 constexpr s16x16 two_pi(6.2831853f);
730 s16x16 fn = s16x16(n);
731 s16x16 inv_fn = s16x16(1) / fn; // Precompute reciprocal (was dividing per dot)
732 s16x16 ocx = s16x16(w) * half - half;
733 s16x16 ocy = s16x16(h) * half - half;
734 s16x16 orad = s16x16(minDim) * s16x16(0.35f);
735 s16x16 base = t * s16x16(3);
736 constexpr s16x16 dotDiam(1.5f);
737 s16x16 step = two_pi * inv_fn; // Use precomputed reciprocal
738
739 for (int i = 0; i < n; i++) {
740 s16x16 a = base + s16x16(i) * step;
741 s16x16 sin_a, cos_a;
742 s16x16::sincos(a, sin_a, cos_a);
743 s16x16 cx = ocx + cos_a * orad;
744 s16x16 cy = ocy + sin_a * orad;
745 CRGB c = rainbow(t, mColorShift_fp, s16x16(i) * inv_fn);
746 drawDot(cx, cy, dotDiam, c.r, c.g, c.b);
747 }
748}
749
750// ---------------------------------------------------------------------------
751// flowPrepare — fixed-point Perlin noise via perlin_s16x16::pnoise2d_raw()
752// ---------------------------------------------------------------------------
753
755 int w = mState.width;
756 int h = mState.height;
757 constexpr s16x16 kBaseFreq(0.23f);
758 constexpr s16x16 neg_one(-1.0f);
759 constexpr s16x16 one(1.0f);
760
761 const i32 *fade_lut = mState.fade_lut;
762
763 // Hoist loop-invariant products
764 s16x16 freqX = kBaseFreq * mNoiseFreqX_fp;
765 s16x16 scrollX = t * mFlowSpeedX_fp; // time offset added to spatial coord
766
767 for (int i = 0; i < w; i++) {
768 s16x16 fx = s16x16(i) * freqX + scrollX;
769 i32 noise_raw = perlin_s16x16::pnoise2d_raw(
770 fx.raw(), 0, fade_lut, mPermX);
771 s16x16 v = s16x16::from_raw(noise_raw);
772 s16x16 clamped = s16x16::clamp(v * mFlowAmpX_fp, neg_one, one);
773 mState.x_prof[i] = clamped.raw();
774 }
775
776 if (mParams.reverse_x_profile) {
777 for (int i = 0; i < w / 2; i++) {
778 i32 tmp = mState.x_prof[i];
779 mState.x_prof[i] = mState.x_prof[w - 1 - i];
780 mState.x_prof[w - 1 - i] = tmp;
781 }
782 }
783
784 // Hoist loop-invariant products
785 s16x16 freqY = kBaseFreq * mNoiseFreqY_fp;
786 s16x16 scrollY = t * mFlowSpeedY_fp; // time offset added to spatial coord
787
788 for (int i = 0; i < h; i++) {
789 s16x16 fx = s16x16(i) * freqY + scrollY;
790 i32 noise_raw = perlin_s16x16::pnoise2d_raw(
791 fx.raw(), 0, fade_lut, mPermY);
792 s16x16 v = s16x16::from_raw(noise_raw);
793 s16x16 clamped = s16x16::clamp(v * mFlowAmpY_fp, neg_one, one);
794 mState.y_prof[i] = clamped.raw();
795 }
796
797 // Apply noise bias (attack/decay bumps from noisePunch triggers).
798 for (int i = 0; i < w; i++) {
799 i32 bias_raw = s16x16(mNoiseBias.getX(i)).raw();
800 mState.x_prof[i] = clamp_q16(mState.x_prof[i] + bias_raw,
801 neg_one.raw(), one.raw());
802 }
803 for (int i = 0; i < h; i++) {
804 i32 bias_raw = s16x16(mNoiseBias.getY(i)).raw();
805 mState.y_prof[i] = clamp_q16(mState.y_prof[i] + bias_raw,
806 neg_one.raw(), one.raw());
807 }
808}
809
810// ---------------------------------------------------------------------------
811// flowAdvect — Q16.16 hot path (~80% of frame time)
812// ---------------------------------------------------------------------------
813
815 int w = mState.width;
816 int h = mState.height;
817
818 // fade = pow(0.5, dt / halfLife) — pure fixed-point
819 constexpr s16x16 half_fp(0.5f);
820 constexpr s16x16 min_persistence(0.001f);
821 s16x16 dt_fp = s16x16::from_raw(dt_raw);
822 s16x16 halfLife_fp = mPersistence_fp.raw() > min_persistence.raw()
824 : min_persistence;
825 i32 fade_raw = s16x16::pow(half_fp, dt_fp / halfLife_fp).raw();
826
827 i32 shift_raw = mFlowShift_fp.raw();
828
829 i32 w_q16 = static_cast<i32>(w) << 16;
830 i32 h_q16 = static_cast<i32>(h) << 16;
831
832 // Grab raw array pointers — avoids vector operator[] overhead in hot loops.
833 i32 *__restrict__ r = mState.r.data();
834 i32 *__restrict__ g = mState.g.data();
835 i32 *__restrict__ b = mState.b.data();
836 i32 *__restrict__ tr = mState.tr.data();
837 i32 *__restrict__ tg = mState.tg.data();
838 i32 *__restrict__ tb = mState.tb.data();
839
840 // Pass 1: horizontal row shift
841 // Lerp: a + ((b-a)*f >> 16) uses 1 i64 mul instead of 2.
842 // Note: f = (-sh) & 0xFFFF is constant per row; the compiler hoists it at -O3.
843 for (int y = 0; y < h; y++) {
844 i32 sh = static_cast<i32>((static_cast<i64>(mState.y_prof[y]) * shift_raw) >> 16);
845 int row_base = y * w;
846 for (int x = 0; x < w; x++) {
847 i32 sx_raw = (static_cast<i32>(x) << 16) - sh;
848 // Modular wrap (single iteration sufficient for typical shift values)
849 if (sx_raw < 0) sx_raw += w_q16;
850 else if (sx_raw >= w_q16) sx_raw -= w_q16;
851 // Safety for extreme shift values
852 if (sx_raw < 0) sx_raw += w_q16;
853 if (sx_raw >= w_q16) sx_raw -= w_q16;
854
855 int ix0 = sx_raw >> 16; // Already in [0, w-1] after wrap
856 int ix1 = (ix0 + 1 < w) ? ix0 + 1 : 0;
857 i32 f = sx_raw & 0xFFFF;
858
859 int src0 = row_base + ix0;
860 int src1 = row_base + ix1;
861 int dst = row_base + x;
862
863 tr[dst] = r[src0] + static_cast<i32>((static_cast<i64>(r[src1] - r[src0]) * f) >> 16);
864 tg[dst] = g[src0] + static_cast<i32>((static_cast<i64>(g[src1] - g[src0]) * f) >> 16);
865 tb[dst] = b[src0] + static_cast<i32>((static_cast<i64>(b[src1] - b[src0]) * f) >> 16);
866 }
867 }
868
869 // Pass 2: vertical column shift + fade
870 // Note: f = (-sh) & 0xFFFF is constant per column; the compiler hoists it at -O3.
871 for (int x = 0; x < w; x++) {
872 i32 sh = static_cast<i32>((static_cast<i64>(mState.x_prof[x]) * shift_raw) >> 16);
873 for (int y = 0; y < h; y++) {
874 i32 sy_raw = (static_cast<i32>(y) << 16) - sh;
875 if (sy_raw < 0) sy_raw += h_q16;
876 else if (sy_raw >= h_q16) sy_raw -= h_q16;
877 if (sy_raw < 0) sy_raw += h_q16;
878 if (sy_raw >= h_q16) sy_raw -= h_q16;
879
880 int iy0 = sy_raw >> 16;
881 int iy1 = (iy0 + 1 < h) ? iy0 + 1 : 0;
882 i32 f = sy_raw & 0xFFFF;
883
884 int src0 = iy0 * w + x;
885 int src1 = iy1 * w + x;
886 int dst = y * w + x;
887
888 // Lerp + fade in two steps (2 i64 muls per channel instead of 3)
889 i32 interp_r = tr[src0] + static_cast<i32>((static_cast<i64>(tr[src1] - tr[src0]) * f) >> 16);
890 i32 interp_g = tg[src0] + static_cast<i32>((static_cast<i64>(tg[src1] - tg[src0]) * f) >> 16);
891 i32 interp_b = tb[src0] + static_cast<i32>((static_cast<i64>(tb[src1] - tb[src0]) * f) >> 16);
892
893 r[dst] = static_cast<i32>((static_cast<i64>(interp_r) * fade_raw) >> 16);
894 g[dst] = static_cast<i32>((static_cast<i64>(interp_g) * fade_raw) >> 16);
895 b[dst] = static_cast<i32>((static_cast<i64>(interp_b) * fade_raw) >> 16);
896 }
897 }
898}
899
900// ---------------------------------------------------------------------------
901// Flow vector overlay — fixed-point version
902// ---------------------------------------------------------------------------
903
905 int w = mState.width;
906 int h = mState.height;
907
908 // Helper: plot a single subpixel point via Tile2x2 through the XYMap.
909 // Uses float for the interpolation math (unoptimized debug overlay).
910 auto plotTile = [&](const CRGB &color, float px, float py,
911 bool horiz) {
912 int ix = (int)floorf(px);
913 int iy = (int)floorf(py);
914 float fx = px - (float)ix;
915 float fy = py - (float)iy;
916 Tile2x2_u8 tile;
917 tile.setOrigin((u16)ix, (u16)iy);
918 if (horiz) {
919 tile.at(0, 0) = (u8)((1.0f - fy) * 255.0f);
920 tile.at(1, 0) = 0;
921 tile.at(0, 1) = (u8)(fy * 255.0f);
922 tile.at(1, 1) = 0;
923 } else {
924 tile.at(0, 0) = (u8)((1.0f - fx) * 255.0f);
925 tile.at(1, 0) = (u8)(fx * 255.0f);
926 tile.at(0, 1) = 0;
927 tile.at(1, 1) = 0;
928 }
929 tile.draw(color, mXyMap, leds);
930 };
931
932 // Display amplitude: 0.3 = profile uses ~30% of the display around center.
933 constexpr float amp = 0.3f;
934
935 // X profile (per-column): plot value as y-position — cyan.
936 // Interpolate between consecutive columns to fill gaps.
937 CRGB xColor(0, 255, 255);
938 float centerY = (float)(h - 1) * 0.5f;
939 float prevPy = 0.0f;
940 for (int x = 0; x < w; x++) {
941 s16x16 val = s16x16::from_raw(mState.x_prof[x]); // [-1, 1]
942 float py = centerY - val.to_float() * amp * centerY;
943 plotTile(xColor, (float)x, py, true);
944 if (x > 0) {
945 float dy = py - prevPy;
946 int gap = (int)fabsf(dy);
947 for (int s = 1; s < gap; s++) {
948 float t = (float)s / (float)gap;
949 float midY = prevPy + dy * t;
950 float midX = (float)(x - 1) + t;
951 plotTile(xColor, midX, midY, true);
952 }
953 }
954 prevPy = py;
955 }
956
957 // Y profile (per-row): plot value as x-position — yellow.
958 // Interpolate between consecutive rows to fill gaps.
959 CRGB yColor(255, 255, 0);
960 float centerX = (float)(w - 1) * 0.5f;
961 float prevPx = 0.0f;
962 for (int y = 0; y < h; y++) {
963 s16x16 val = s16x16::from_raw(mState.y_prof[y]); // [-1, 1]
964 float px = centerX + val.to_float() * amp * centerX;
965 plotTile(yColor, px, (float)y, false);
966 if (y > 0) {
967 float dx = px - prevPx;
968 int gap = (int)fabsf(dx);
969 for (int s = 1; s < gap; s++) {
970 float t = (float)s / (float)gap;
971 float midX = prevPx + dx * t;
972 float midY = (float)(y - 1) + t;
973 plotTile(yColor, midX, midY, false);
974 }
975 }
976 prevPx = px;
977 }
978}
979
980// ---------------------------------------------------------------------------
981// drawImpl — main entry point
982// ---------------------------------------------------------------------------
983
984void FlowFieldFP::drawImpl(DrawContext context, u32 dt_ms, u32 t_ms) FL_NO_EXCEPT {
985 syncParams();
986
987 s16x16 dt = s16x16(dt_ms * 0.001f);
988 s16x16 t = s16x16(t_ms * 0.001f);
989
990 i32 dt_raw = dt.raw();
991
992 flowPrepare(t);
993 switch (mParams.emitter_mode) {
994 case 0:
996 break;
997 case 1:
999 break;
1000 case 2:
1003 break;
1004 default:
1006 break;
1007 }
1008 flowAdvect(dt_raw);
1009
1010 // Output: convert Q16.16 grids to LED pixels
1011 int w = mState.width;
1012 int h = mState.height;
1013 const i32 *rp = mState.r.data();
1014 const i32 *gp = mState.g.data();
1015 const i32 *bp = mState.b.data();
1016 fl::span<CRGB> out = context.leds;
1017 for (int y = 0; y < h; y++) {
1018 int row_base = y * w;
1019 for (int x = 0; x < w; x++) {
1020 int i = row_base + x;
1021 u16 ledIdx = mXyMap.mapToIndex(static_cast<u16>(x), static_cast<u16>(y));
1022 out[ledIdx].r = q16_to_u8(rp[i]);
1023 out[ledIdx].g = q16_to_u8(gp[i]);
1024 out[ledIdx].b = q16_to_u8(bp[i]);
1025 }
1026 }
1027
1028 if (mParams.show_flow_vectors) {
1029 drawFlowVectors(context.leds);
1030 }
1031}
1032
1033} // namespace fl
1034
CRGB leds[1]
void rainbow()
fl::UISlider colorShift("Color Shift", 0.04f, 0.0f, 0.5f, 0.01f)
uint8_t hue
Definition advanced.h:94
virtual void drawImpl(DrawContext context, u32 dt_ms, u32 t_ms) FL_NO_EXCEPT=0
Subclasses implement rendering given the time delta and total time.
NoiseBias2D mNoiseBias
Definition flowfield.h:287
FlowField(const XYMap &xyMap, const Params &params=Params()) FL_NO_EXCEPT
Params mParams
Definition flowfield.h:286
FlowFieldParams Params
Definition flowfield.h:228
void draw(DrawContext context) FL_NO_EXCEPT override
Handles timing, then delegates to drawImpl().
void noisePunchY(float center, float width, float amplitude=1.0f, BumpShape shape=BumpShape::HalfSine) FL_NO_EXCEPT
Trigger a noise punch on the Y axis (rows).
void noisePunchX(float center, float width, float amplitude=1.0f, BumpShape shape=BumpShape::HalfSine) FL_NO_EXCEPT
Trigger a noise punch on the X axis (columns).
float speed() const FL_NO_EXCEPT
Definition flowfield.h:258
void noisePunch(float amplitude=1.0f, BumpShape shape=BumpShape::HalfSine) FL_NO_EXCEPT
Trigger a noise punch on both axes at center with proportional width.
TimeWarp mTimeWarp
Definition flowfield.h:290
FlowFieldFPState mState
Definition flowfield.h:400
FlowFieldFP(const XYMap &xyMap, const Params &params=Params()) FL_NO_EXCEPT
void emitLissajousLine(s16x16 t) FL_NO_EXCEPT
s16x16 mFlowAmpY_fp
Definition flowfield.h:411
void drawAALine(s16x16 x0, s16x16 y0, s16x16 x1, s16x16 y1, s16x16 t, s16x16 colorShift) FL_NO_EXCEPT
s16x16 mNoiseFreqY_fp
Definition flowfield.h:409
void drawDot(s16x16 cx, s16x16 cy, s16x16 diam, u8 cr, u8 cg, u8 cb) FL_NO_EXCEPT
s16x16 mColorShift_fp
Definition flowfield.h:405
s16x16 mFlowShift_fp
Definition flowfield.h:406
void syncParams() FL_NO_EXCEPT
s16x16 mFlowSpeedX_fp
Definition flowfield.h:410
static u8 q16_to_u8(i32 v) FL_NO_EXCEPT
void drawImpl(DrawContext context, u32 dt_ms, u32 t_ms) FL_NO_EXCEPT override
Subclasses implement rendering given the time delta and total time.
s16x16 mNoiseFreqX_fp
Definition flowfield.h:409
s16x16 mPersistence_fp
Definition flowfield.h:408
s16x16 mEndpointSpeed_fp
Definition flowfield.h:407
static CRGB rainbow(s16x16 t, s16x16 speed, s16x16 phase) FL_NO_EXCEPT
void flowPrepare(s16x16 t) FL_NO_EXCEPT
static void initPerm256(u8 *perm, u32 seed) FL_NO_EXCEPT
s16x16 mFlowSpeedY_fp
Definition flowfield.h:410
static i32 clamp_q16(i32 v, i32 lo, i32 hi) FL_NO_EXCEPT
void emitOrbitalDots(s16x16 t) FL_NO_EXCEPT
void drawFlowVectors(fl::span< CRGB > leds) FL_NO_EXCEPT
void flowAdvect(i32 dt_raw) FL_NO_EXCEPT
s16x16 mFlowAmpX_fp
Definition flowfield.h:411
void init(u32 seed) FL_NO_EXCEPT
float noise(float x, float y) const FL_NO_EXCEPT
static float grad(int h, float x, float y) FL_NO_EXCEPT
void drawAALine(float x0, float y0, float x1, float y1, float t, float colorShift) FL_NO_EXCEPT
fl::vector< float > mB
Definition flowfield.h:346
fl::vector< float > mYProf
Per-row values; drives horizontal shift.
Definition flowfield.h:352
fl::vector< float > mG
Definition flowfield.h:346
void flowPrepare(float t) FL_NO_EXCEPT
void drawDot(float cx, float cy, float diam, u8 cr, u8 cg, u8 cb) FL_NO_EXCEPT
Perlin2D mNoiseGenY
Definition flowfield.h:350
void emitLissajousLine(float t) FL_NO_EXCEPT
void flowAdvect(float dt) FL_NO_EXCEPT
static float fmodPos(float x, float m) FL_NO_EXCEPT
Perlin2D mNoiseGenX
Definition flowfield.h:350
fl::vector< float > mXProf
Per-column values; drives vertical shift.
Definition flowfield.h:351
void drawFlowVectors(fl::span< CRGB > leds) FL_NO_EXCEPT
static float clampf(float v, float lo, float hi) FL_NO_EXCEPT
FlowFieldFloat(const XYMap &xyMap, const Params &params=Params()) FL_NO_EXCEPT
static CRGB rainbow(float t, float speed, float phase) FL_NO_EXCEPT
void drawImpl(DrawContext context, u32 dt_ms, u32 t_ms) FL_NO_EXCEPT override
Subclasses implement rendering given the time delta and total time.
int idx(int y, int x) const FL_NO_EXCEPT
Definition flowfield.h:323
fl::vector< float > mTG
Definition flowfield.h:347
fl::vector< float > mTR
Definition flowfield.h:347
static u8 f2u8(float v) FL_NO_EXCEPT
fl::vector< float > mTB
Definition flowfield.h:347
void emitOrbitalDots(float t) FL_NO_EXCEPT
fl::vector< float > mR
Definition flowfield.h:346
XYMap mXyMap
Definition fx2d.h:31
u16 xyMap(u16 x, u16 y) const FL_NO_EXCEPT
Definition fx2d.h:21
u16 getWidth() const FL_NO_EXCEPT
Definition fx2d.h:25
Fx2d(const XYMap &xyMap) FL_NO_EXCEPT
Definition fx2d.h:20
u16 getHeight() const FL_NO_EXCEPT
Definition fx2d.h:24
void setOrigin(u16 x, u16 y) FL_NO_EXCEPT
Definition tile2x2.h:31
constexpr float to_float() const FL_NO_EXCEPT
Definition s16x16.h:64
static constexpr i32 SCALE
Definition s16x16.h:25
constexpr FASTLED_FORCE_INLINE s16x16 fract() const FL_NO_EXCEPT
Definition s16x16.h:303
constexpr FASTLED_FORCE_INLINE s16x16 floor() const FL_NO_EXCEPT
Definition s16x16.h:295
constexpr i32 raw() const FL_NO_EXCEPT
Definition s16x16.h:62
FASTLED_FORCE_INLINE s16x16 sin() const FL_NO_EXCEPT
Definition s16x16.h:315
static constexpr FASTLED_FORCE_INLINE s16x16 clamp(s16x16 x, s16x16 lo, s16x16 hi) FL_NO_EXCEPT
Definition s16x16.h:188
static constexpr FASTLED_FORCE_INLINE s16x16 mod(s16x16 a, s16x16 b) FL_NO_EXCEPT
Definition s16x16.h:155
static constexpr FASTLED_FORCE_INLINE s16x16 from_raw(i32 raw) FL_NO_EXCEPT
Definition s16x16.h:56
static FASTLED_FORCE_INLINE s16x16 pow(s16x16 base, s16x16 exp) FL_NO_EXCEPT
Definition s16x16.h:279
static constexpr FASTLED_FORCE_INLINE s16x16 ceil(s16x16 x) FL_NO_EXCEPT
Definition s16x16.h:163
constexpr FASTLED_FORCE_INLINE s16x16 sqrt() const FL_NO_EXCEPT
Definition s16x16.h:335
static FASTLED_FORCE_INLINE void sincos(s16x16 angle, s16x16 &out_sin, s16x16 &out_cos) FL_NO_EXCEPT
Definition s16x16.h:355
constexpr i32 to_int() const FL_NO_EXCEPT
Definition s16x16.h:63
static constexpr FASTLED_FORCE_INLINE s16x16 floor(s16x16 x) FL_NO_EXCEPT
Definition s16x16.h:159
static constexpr FASTLED_FORCE_INLINE s16x16 abs(s16x16 x) FL_NO_EXCEPT
Definition s16x16.h:172
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN
#define FL_OPTIMIZATION_LEVEL_O3_END
fl::UISlider steps("Steps", 100.0f, 1.0f, 200.0f, 1.0f)
2D flow field visualization: emitters paint color, noise advects it
fl::hsv8 CHSV
Definition chsv.h:11
CRGB hsv2rgb_rainbow(const CHSV &hsv) FL_NO_EXCEPT
#define FL_PI
Definition math.h:26
unsigned char u8
Definition stdint.h:131
constexpr common_type_t< T, U > max(T a, U b) FL_NO_EXCEPT
Definition math.h:75
FASTLED_FORCE_INLINE float lerp(float t, float a, float b) FL_NO_EXCEPT
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NO_EXCEPT
Definition math.h:71
float sinf(float value) FL_NO_EXCEPT
Definition math.h:360
FASTLED_FORCE_INLINE float grad(int hash, float x, float y, float z) FL_NO_EXCEPT
void fade_raw(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) FL_NO_EXCEPT
static constexpr i32 FP_ONE
u8 width
Definition blur.h:186
static constexpr i32 FP_255
constexpr enable_if< is_fixed_point< T >::value, T >::type step(T edge, T x) FL_NO_EXCEPT
float sqrtf(float value) FL_NO_EXCEPT
Definition math.h:461
BumpShape
Shape function for NoiseBias triggers.
Definition flowfield.h:23
InputGamut g
Definition rgbw.h:125
float fmodf(float x, float y) FL_NO_EXCEPT
Definition math.h:344
float cosf(float value) FL_NO_EXCEPT
Definition math.h:366
float fabsf(float value) FL_NO_EXCEPT
Definition math.h:516
FASTLED_FORCE_INLINE float fade(float t) FL_NO_EXCEPT
float powf(float base, float exponent) FL_NO_EXCEPT
Definition math.h:444
float floorf(float value) FL_NO_EXCEPT
Definition math.h:312
float ceilf(float value) FL_NO_EXCEPT
Definition math.h:318
Base definition for an LED controller.
Definition crgb.hpp:179
fl::i64 i64
Definition stdint.h:221
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38
static fl::i32 pnoise2d_raw(fl::i32 fx_raw, fl::i32 fy_raw, const fl::i32 *fade_lut, const fl::u8 *perm) FL_NO_EXCEPT
static void init_fade_lut(fl::i32 *table) FL_NO_EXCEPT