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