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