FastLED 3.10.8
Loading...
Searching...
No Matches
wave_simulation.cpp.hpp
Go to the documentation of this file.
1// Based on works and code by Shawn Silverman.
2
3#include "fl/stl/stdint.h"
4
5#include "fl/math/math.h"
7#include "fl/stl/int.h"
8#include "fl/stl/noexcept.h"
9
10namespace {
11
13 x = fl::min(x, 32767); // Q15
14 const int Q = 15;
15 fl::u32 X = (fl::u32)x << Q; // promote to Q30
16 fl::u32 y = (1u << Q); // start at “1.0” in Q15
17
18 // 3–4 iterations is plenty for 15‑bit precision:
19 for (int i = 0; i < 4; i++) {
20 y = (y + (X / y)) >> 1;
21 }
22 return static_cast<fl::i16>(y) >> 8;
23}
24
26 x = fl::min(x, 32767); // Q15
27 x *= 2;
28 return x >> 8;
29}
30
31} // namespace
32
33namespace fl {
34
36
38 float speed, float dampening) FL_NO_EXCEPT {
39 init(W, H, factor, speed, dampening);
40}
41
43 float speed, int dampening) FL_NO_EXCEPT {
46 mMultiplier = static_cast<u32>(factor);
47 mSim.reset(); // clear out memory first.
48 u32 w = width * mMultiplier;
49 u32 h = height * mMultiplier;
51 // Auto-select the 9-point isotropic Laplacian when super-sampling is
52 // active: the 5-point stencil's anisotropy (square-ish ripples) is
53 // invisible at the native LED-grid resolution but becomes obvious
54 // at 2x and higher super-sample factors. The 9-point form costs ~2x
55 // reads + ALU per cell but the user has already opted into more CPU
56 // by requesting super-sampling.
57 if (mMultiplier >= 2) {
59 }
60 // Only allocate change grid if it's enabled (saves memory when disabled)
61 if (mUseChangeGrid) {
62 mChangeGrid.reset(w, h);
63 }
64 // Extra frames are needed because the simulation slows down in
65 // proportion to the supersampling factor.
66 mExtraFrames = u8(factor) - 1;
67}
68
69void WaveSimulation2D::setDampening(int damp) FL_NO_EXCEPT { mSim->setDampening(damp); }
70
71int WaveSimulation2D::getDampenening() const FL_NO_EXCEPT { return mSim->getDampenening(); }
72
73float WaveSimulation2D::getSpeed() const FL_NO_EXCEPT { return mSim->getSpeed(); }
74
75float WaveSimulation2D::getf(fl::size x, fl::size y) const FL_NO_EXCEPT {
76 if (!has(x, y))
77 return 0.0f;
78 float sum = 0.0f;
79 for (u32 j = 0; j < mMultiplier; ++j) {
80 for (u32 i = 0; i < mMultiplier; ++i) {
81 sum += mSim->getf(x * mMultiplier + i, y * mMultiplier + j);
82 }
83 }
84 return sum / static_cast<float>(mMultiplier * mMultiplier);
85}
86
87i16 WaveSimulation2D::geti16(fl::size x, fl::size y) const FL_NO_EXCEPT {
88 if (!has(x, y))
89 return 0;
90 i32 sum = 0;
91 u8 mult = fl::max(1, mMultiplier);
92 for (u32 j = 0; j < mult; ++j) {
93 for (u32 i = 0; i < mult; ++i) {
94 u32 xx = x * mult + i;
95 u32 yy = y * mult + j;
96 i32 pt = mSim->geti16(xx, yy);
97 if (mUseChangeGrid) {
98 // i32 ch_pt = mChangeGrid[(yy * mMultiplier) + xx];
99 i32 ch_pt = mChangeGrid(xx, yy);
100 if (ch_pt != 0) { // we got a hit.
101 sum += ch_pt;
102 } else {
103 sum += pt;
104 }
105 } else {
106 sum += pt;
107 }
108 }
109 }
110 i16 out = static_cast<i16>(sum / (mult * mult));
111 return out;
112}
113
114i16 WaveSimulation2D::geti16Previous(fl::size x, fl::size y) const FL_NO_EXCEPT {
115 if (!has(x, y))
116 return 0;
117 i32 sum = 0;
118 u8 mult = fl::max(1, mMultiplier);
119 for (u32 j = 0; j < mult; ++j) {
120 for (u32 i = 0; i < mult; ++i) {
121 sum +=
122 mSim->geti16Previous(x * mult + i, y * mult + j);
123 }
124 }
125 i16 out = static_cast<i16>(sum / (mult * mult));
126 return out;
127}
128
129bool WaveSimulation2D::geti16All(fl::size x, fl::size y, i16 *curr,
130 i16 *prev, i16 *diff) const FL_NO_EXCEPT {
131 if (!has(x, y))
132 return false;
133 *curr = geti16(x, y);
134 *prev = geti16Previous(x, y);
135 *diff = *curr - *prev;
136 return true;
137}
138
139i8 WaveSimulation2D::geti8(fl::size x, fl::size y) const FL_NO_EXCEPT {
140 return static_cast<i8>(geti16(x, y) >> 8);
141}
142
143u8 WaveSimulation2D::getu8(fl::size x, fl::size y) const FL_NO_EXCEPT {
144 i16 value = geti16(x, y);
145 if (mSim->getHalfDuplex()) {
146 u16 v2 = static_cast<u16>(value);
147 switch (mU8Mode) {
149 return half_duplex_blend_linear(v2);
151 return half_duplex_blend_sqrt_q15(v2);
152 }
153 }
154 return static_cast<u8>(((static_cast<u16>(value) + 32768)) >> 8);
155}
156
157bool WaveSimulation2D::has(fl::size x, fl::size y) const FL_NO_EXCEPT {
158 return (x < mOuterWidth) && (y < mOuterHeight);
159}
160
161void WaveSimulation2D::seti16(fl::size x, fl::size y, i16 v16) FL_NO_EXCEPT {
162 if (!has(x, y))
163 return;
164
165 u8 mult = fl::max(1, mMultiplier);
166
167 // radius in pixels of your diamond
168 int rad = static_cast<int>(mult) / 2;
169
170 for (fl::size j = 0; j < mult; ++j) {
171 for (fl::size i = 0; i < mult; ++i) {
172 // compute offset from the center of this block
173 int dx = static_cast<int>(i) - rad;
174 int dy = static_cast<int>(j) - rad;
175 // keep only those points whose Manhattan distance ≤ rad
176 if (fl::abs(dx) + fl::abs(dy) > rad) {
177 continue;
178 }
179 fl::size xx = x * mult + i;
180 fl::size yy = y * mult + j;
181 if (mSim->has(xx, yy)) {
182 if (mUseChangeGrid) {
183 i16 &pt = mChangeGrid.at(xx, yy);
184 if (pt == 0) {
185 // not set yet so set unconditionally.
186 pt = v16;
187 } else {
188 const bool sign_matches = (pt >= 0) == (v16 >= 0);
189 if (!sign_matches) {
190 // opposite signs, so overwrite
191 pt = v16;
192 } else {
193 // if the magnitude of the new pt is greater than what
194 // was already there, then overwrite.
195 u16 abs_pt = static_cast<u16>(fl::abs(pt));
196 u16 abs_v16 = static_cast<u16>(fl::abs(v16));
197 if (abs_v16 > abs_pt) {
198 pt = v16;
199 }
200 }
201 }
202 } else {
203 // Directly set the value in the simulation when change grid is disabled
204 mSim->seti16(xx, yy, v16);
205 }
206 }
207 }
208 }
209}
210
211void WaveSimulation2D::setf(fl::size x, fl::size y, float value) FL_NO_EXCEPT {
212 if (!has(x, y))
213 return;
214
215 value = fl::clamp(value, 0.0f, 1.0f);
217 seti16(x, y, v16);
218}
219
221 if (mUseChangeGrid) {
222 const vec2<i16> min_max = mChangeGrid.minMax();
223 const bool has_updates = min_max != vec2<i16>(0, 0);
224 for (u8 i = 0; i < mExtraFrames + 1; ++i) {
225 if (has_updates) {
226 // apply them
227 const u32 w = mChangeGrid.width();
228 const u32 h = mChangeGrid.height();
229 for (u32 x = 0; x < w; ++x) {
230 for (u32 y = 0; y < h; ++y) {
231 i16 v16 = mChangeGrid(x, y);
232 if (v16 != 0) {
233 mSim->seti16(x, y, v16);
234 }
235 }
236 }
237 }
238 mSim->update();
239 }
240 // zero out mChangeGrid
241 mChangeGrid.clear();
242 } else {
243 // When change grid is disabled, just run the simulation updates
244 for (u8 i = 0; i < mExtraFrames + 1; ++i) {
245 mSim->update();
246 }
247 }
248}
249
252
254
256 if (mUseChangeGrid == enabled) {
257 return; // No change needed
258 }
259
260 mUseChangeGrid = enabled;
261
262 if (mUseChangeGrid) {
263 // Allocate change grid if enabling
264 u32 w = mOuterWidth * mMultiplier;
265 u32 h = mOuterHeight * mMultiplier;
266 mChangeGrid.reset(w, h);
267 } else {
268 // Deallocate change grid if disabling (saves memory)
269 mChangeGrid.reset(0, 0);
270 }
271}
272
277
281 mMultiplier = static_cast<u32>(factor);
282 mSim.reset(); // clear out memory first.
284 // Extra updates (frames) are applied because the simulation slows down in
285 // proportion to the supersampling factor.
286 mExtraFrames = static_cast<u8>(factor) - 1;
287}
288
290
291void WaveSimulation1D::setDampening(int damp) FL_NO_EXCEPT { mSim->setDampening(damp); }
292
293int WaveSimulation1D::getDampenening() const FL_NO_EXCEPT { return mSim->getDampenening(); }
294
296
297float WaveSimulation1D::getSpeed() const FL_NO_EXCEPT { return mSim->getSpeed(); }
298
299float WaveSimulation1D::getf(fl::size x) const FL_NO_EXCEPT {
300 if (!has(x))
301 return 0.0f;
302 float sum = 0.0f;
303 u8 mult = fl::max(1, mMultiplier);
304 for (u32 i = 0; i < mult; ++i) {
305 sum += mSim->getf(x * mult + i);
306 }
307 return sum / static_cast<float>(mult);
308}
309
311 if (!has(x))
312 return 0;
313 u8 mult = fl::max(1, mMultiplier);
314 i32 sum = 0;
315 for (u32 i = 0; i < mult; ++i) {
316 sum += mSim->geti16(x * mult + i);
317 }
318 return static_cast<i16>(sum / mult);
319}
320
322 if (!has(x))
323 return 0;
324 u8 mult = fl::max(1, mMultiplier);
325 i32 sum = 0;
326 for (u32 i = 0; i < mult; ++i) {
327 sum += mSim->geti16Previous(x * mult + i);
328 }
329 return static_cast<i16>(sum / mult);
330}
331
332bool WaveSimulation1D::geti16All(fl::size x, i16 *curr, i16 *prev,
333 i16 *diff) const FL_NO_EXCEPT {
334 if (!has(x))
335 return false;
336 *curr = geti16(x);
337 *prev = geti16Previous(x);
338 *diff = *curr - *prev;
339 return true;
340}
341
343 return static_cast<i8>(geti16(x) >> 8);
344}
345
346// u8 WaveSimulation2D::getu8(fl::size x, fl::size y) const {
347// i16 value = geti16(x, y);
348// if (mSim->getHalfDuplex()) {
349// u16 v2 = static_cast<u16>(value);
350// switch (mU8Mode) {
351// case U8EasingFunction::WAVE_U8_MODE_LINEAR:
352// return half_duplex_blend_linear(v2);
353// case U8EasingFunction::WAVE_U8_MODE_SQRT:
354// return half_duplex_blend_sqrt_q15(v2);
355// }
356// }
357// return static_cast<u8>(((static_cast<u16>(value) + 32768)) >>
358// 8);
359// }
360
362 i16 value = geti16(x);
363 if (mSim->getHalfDuplex()) {
364 u16 v2 = static_cast<u16>(value);
365 switch (mU8Mode) {
367 return half_duplex_blend_linear(v2);
369 return half_duplex_blend_sqrt_q15(v2);
370 }
371 }
372 return static_cast<u8>(((static_cast<u16>(value) + 32768)) >> 8);
373}
374
375bool WaveSimulation1D::has(fl::size x) const FL_NO_EXCEPT { return (x < mOuterLength); }
376
378 if (!has(x))
379 return;
380 value = fl::clamp(value, -1.0f, 1.0f);
381 u8 mult = fl::max(1, mMultiplier);
382 for (u32 i = 0; i < mult; ++i) {
383 mSim->set(x * mult + i, value);
384 }
385}
386
388 mSim->update();
389 for (u8 i = 0; i < mExtraFrames; ++i) {
390 mSim->update();
391 }
392}
393
395
396} // namespace fl
int y
Definition simple.h:93
int x
Definition simple.h:92
uint16_t speed
Definition Noise.ino:72
static const int H
Definition PerfDisc.ino:21
static const int W
Definition PerfDisc.ino:20
u32 getLength() const FL_NO_EXCEPT
bool geti16All(fl::size x, i16 *curr, i16 *prev, i16 *diff) const FL_NO_EXCEPT
i16 geti16(fl::size x) const FL_NO_EXCEPT
fl::unique_ptr< WaveSimulation1D_Real > mSim
void setSpeed(float speed) FL_NO_EXCEPT
float getf(fl::size x) const FL_NO_EXCEPT
int getDampenening() const FL_NO_EXCEPT
WaveSimulation1D(u32 length, SuperSample factor=SuperSample::SUPER_SAMPLE_NONE, float speed=0.16f, int dampening=6) FL_NO_EXCEPT
void setDampening(int damp) FL_NO_EXCEPT
U8EasingFunction mU8Mode
float getSpeed() const FL_NO_EXCEPT
i16 geti16Previous(fl::size x) const FL_NO_EXCEPT
void setf(fl::size x, float value) FL_NO_EXCEPT
i8 geti8(fl::size x) const FL_NO_EXCEPT
void setExtraFrames(u8 extra) FL_NO_EXCEPT
u8 getu8(fl::size x) const FL_NO_EXCEPT
void update() FL_NO_EXCEPT
bool has(fl::size x) const FL_NO_EXCEPT
void init(u32 length, SuperSample factor, float speed, int dampening) FL_NO_EXCEPT
bool has(fl::size x, fl::size y) const FL_NO_EXCEPT
u32 getWidth() const FL_NO_EXCEPT
i8 geti8(fl::size x, fl::size y) const FL_NO_EXCEPT
void setExtraFrames(u8 extra) FL_NO_EXCEPT
fl::Grid< i16 > mChangeGrid
void init(u32 width, u32 height, SuperSample factor, float speed, int dampening) FL_NO_EXCEPT
WaveSimulation2D(u32 W, u32 H, SuperSample factor=SuperSample::SUPER_SAMPLE_NONE, float speed=0.16f, float dampening=6.0f) FL_NO_EXCEPT
float getSpeed() const FL_NO_EXCEPT
void setUseChangeGrid(bool enabled) FL_NO_EXCEPT
int getDampenening() const FL_NO_EXCEPT
void seti16(fl::size x, fl::size y, i16 value) FL_NO_EXCEPT
i16 geti16(fl::size x, fl::size y) const FL_NO_EXCEPT
void setSpeed(float speed) FL_NO_EXCEPT
u8 getu8(fl::size x, fl::size y) const FL_NO_EXCEPT
i16 geti16Previous(fl::size x, fl::size y) const FL_NO_EXCEPT
void setf(fl::size x, fl::size y, float value) FL_NO_EXCEPT
U8EasingFunction mU8Mode
bool geti16All(fl::size x, fl::size y, i16 *curr, i16 *prev, i16 *diff) const FL_NO_EXCEPT
u32 getHeight() const FL_NO_EXCEPT
float getf(fl::size x, fl::size y) const FL_NO_EXCEPT
void update() FL_NO_EXCEPT
fl::unique_ptr< WaveSimulation2D_Real > mSim
void setDampening(int damp) FL_NO_EXCEPT
fl::UISlider length("Length", 1.0f, 0.0f, 1.0f, 0.01f)
fl::UISlider dampening("Dampening", 6.0f, 0.0f, 10.0f, 0.1f)
fl::u8 half_duplex_blend_sqrt_q15(fl::u16 x) FL_NO_EXCEPT
fl::u8 half_duplex_blend_linear(fl::u16 x) FL_NO_EXCEPT
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
Definition unique_ptr.h:263
i16 float_to_fixed(float f) FL_NO_EXCEPT
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
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
constexpr int type_rank< T >::value
u8 u8 height
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
constexpr enable_if< is_fixed_point< T >::value, T >::type clamp(T x, T lo, T hi) FL_NO_EXCEPT
void init(Context &ctx, int w, int h) FL_NO_EXCEPT
Definition engine.h:133
u8 width
Definition blur.h:186
signed char i8
Definition stdint.h:130
SuperSample
Definition supersample.h:4
constexpr enable_if< is_fixed_point< T >::value, T >::type abs(T x) FL_NO_EXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NO_EXCEPT