FastLED 3.9.15
Loading...
Searching...
No Matches
wave_simulation.cpp
Go to the documentation of this file.
1// Based on works and code by Shawn Silverman.
2
3#include "fl/stdint.h"
4
5#include "fl/clamp.h"
6#include "fl/namespace.h"
8#include "fl/int.h"
9
10namespace {
11
13 x = 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 = MIN(x, 32767); // Q15
27 x *= 2;
28 return x >> 8;
29}
30
31} // namespace
32
33namespace fl {
34
35void WaveSimulation2D::setSpeed(float speed) { mSim->setSpeed(speed); }
36
38 float speed, float dampening) {
39 init(W, H, factor, speed, dampening);
40}
41
42void WaveSimulation2D::init(u32 width, u32 height, SuperSample factor,
43 float speed, int dampening) {
44 mOuterWidth = width;
45 mOuterHeight = height;
46 mMultiplier = static_cast<u32>(factor);
47 mSim.reset(); // clear out memory first.
48 u32 w = width * mMultiplier;
49 u32 h = height * mMultiplier;
50 mSim.reset(new WaveSimulation2D_Real(w, h, speed, dampening));
51 // Only allocate change grid if it's enabled (saves memory when disabled)
52 if (mUseChangeGrid) {
53 mChangeGrid.reset(w, h);
54 }
55 // Extra frames are needed because the simulation slows down in
56 // proportion to the supersampling factor.
57 mExtraFrames = u8(factor) - 1;
58}
59
60void WaveSimulation2D::setDampening(int damp) { mSim->setDampening(damp); }
61
62int WaveSimulation2D::getDampenening() const { return mSim->getDampenening(); }
63
64float WaveSimulation2D::getSpeed() const { return mSim->getSpeed(); }
65
66float WaveSimulation2D::getf(fl::size x, fl::size y) const {
67 if (!has(x, y))
68 return 0.0f;
69 float sum = 0.0f;
70 for (u32 j = 0; j < mMultiplier; ++j) {
71 for (u32 i = 0; i < mMultiplier; ++i) {
72 sum += mSim->getf(x * mMultiplier + i, y * mMultiplier + j);
73 }
74 }
75 return sum / static_cast<float>(mMultiplier * mMultiplier);
76}
77
78i16 WaveSimulation2D::geti16(fl::size x, fl::size y) const {
79 if (!has(x, y))
80 return 0;
81 i32 sum = 0;
82 u8 mult = MAX(1, mMultiplier);
83 for (u32 j = 0; j < mult; ++j) {
84 for (u32 i = 0; i < mult; ++i) {
85 u32 xx = x * mult + i;
86 u32 yy = y * mult + j;
87 i32 pt = mSim->geti16(xx, yy);
88 if (mUseChangeGrid) {
89 // i32 ch_pt = mChangeGrid[(yy * mMultiplier) + xx];
90 i32 ch_pt = mChangeGrid(xx, yy);
91 if (ch_pt != 0) { // we got a hit.
92 sum += ch_pt;
93 } else {
94 sum += pt;
95 }
96 } else {
97 sum += pt;
98 }
99 }
100 }
101 i16 out = static_cast<i16>(sum / (mult * mult));
102 return out;
103}
104
105i16 WaveSimulation2D::geti16Previous(fl::size x, fl::size y) const {
106 if (!has(x, y))
107 return 0;
108 i32 sum = 0;
109 u8 mult = MAX(1, mMultiplier);
110 for (u32 j = 0; j < mult; ++j) {
111 for (u32 i = 0; i < mult; ++i) {
112 sum +=
113 mSim->geti16Previous(x * mult + i, y * mult + j);
114 }
115 }
116 i16 out = static_cast<i16>(sum / (mult * mult));
117 return out;
118}
119
120bool WaveSimulation2D::geti16All(fl::size x, fl::size y, i16 *curr,
121 i16 *prev, i16 *diff) const {
122 if (!has(x, y))
123 return false;
124 *curr = geti16(x, y);
125 *prev = geti16Previous(x, y);
126 *diff = *curr - *prev;
127 return true;
128}
129
130i8 WaveSimulation2D::geti8(fl::size x, fl::size y) const {
131 return static_cast<i8>(geti16(x, y) >> 8);
132}
133
134u8 WaveSimulation2D::getu8(fl::size x, fl::size y) const {
135 i16 value = geti16(x, y);
136 if (mSim->getHalfDuplex()) {
137 u16 v2 = static_cast<u16>(value);
138 switch (mU8Mode) {
140 return half_duplex_blend_linear(v2);
142 return half_duplex_blend_sqrt_q15(v2);
143 }
144 }
145 return static_cast<u8>(((static_cast<u16>(value) + 32768)) >> 8);
146}
147
148bool WaveSimulation2D::has(fl::size x, fl::size y) const {
149 return (x < mOuterWidth) && (y < mOuterHeight);
150}
151
152void WaveSimulation2D::seti16(fl::size x, fl::size y, i16 v16) {
153 if (!has(x, y))
154 return;
155
156 u8 mult = MAX(1, mMultiplier);
157
158 // radius in pixels of your diamond
159 int rad = static_cast<int>(mult) / 2;
160
161 for (fl::size j = 0; j < mult; ++j) {
162 for (fl::size i = 0; i < mult; ++i) {
163 // compute offset from the center of this block
164 int dx = static_cast<int>(i) - rad;
165 int dy = static_cast<int>(j) - rad;
166 // keep only those points whose Manhattan distance ≤ rad
167 if (ABS(dx) + ABS(dy) > rad) {
168 continue;
169 }
170 fl::size xx = x * mult + i;
171 fl::size yy = y * mult + j;
172 if (mSim->has(xx, yy)) {
173 if (mUseChangeGrid) {
174 i16 &pt = mChangeGrid.at(xx, yy);
175 if (pt == 0) {
176 // not set yet so set unconditionally.
177 pt = v16;
178 } else {
179 const bool sign_matches = (pt >= 0) == (v16 >= 0);
180 if (!sign_matches) {
181 // opposite signs, so overwrite
182 pt = v16;
183 } else {
184 // if the magnitude of the new pt is greater than what
185 // was already there, then overwrite.
186 u16 abs_pt = static_cast<u16>(ABS(pt));
187 u16 abs_v16 = static_cast<u16>(ABS(v16));
188 if (abs_v16 > abs_pt) {
189 pt = v16;
190 }
191 }
192 }
193 } else {
194 // Directly set the value in the simulation when change grid is disabled
195 mSim->seti16(xx, yy, v16);
196 }
197 }
198 }
199 }
200}
201
202void WaveSimulation2D::setf(fl::size x, fl::size y, float value) {
203 if (!has(x, y))
204 return;
205
206 value = fl::clamp(value, 0.0f, 1.0f);
207 i16 v16 = wave_detail::float_to_fixed(value);
208 seti16(x, y, v16);
209}
210
212 if (mUseChangeGrid) {
213 const vec2<i16> min_max = mChangeGrid.minMax();
214 const bool has_updates = min_max != vec2<i16>(0, 0);
215 for (u8 i = 0; i < mExtraFrames + 1; ++i) {
216 if (has_updates) {
217 // apply them
218 const u32 w = mChangeGrid.width();
219 const u32 h = mChangeGrid.height();
220 for (u32 x = 0; x < w; ++x) {
221 for (u32 y = 0; y < h; ++y) {
222 i16 v16 = mChangeGrid(x, y);
223 if (v16 != 0) {
224 mSim->seti16(x, y, v16);
225 }
226 }
227 }
228 }
229 mSim->update();
230 }
231 // zero out mChangeGrid
232 mChangeGrid.clear();
233 } else {
234 // When change grid is disabled, just run the simulation updates
235 for (u8 i = 0; i < mExtraFrames + 1; ++i) {
236 mSim->update();
237 }
238 }
239}
240
243
245
247 if (mUseChangeGrid == enabled) {
248 return; // No change needed
249 }
250
251 mUseChangeGrid = enabled;
252
253 if (mUseChangeGrid) {
254 // Allocate change grid if enabling
255 u32 w = mOuterWidth * mMultiplier;
256 u32 h = mOuterHeight * mMultiplier;
257 mChangeGrid.reset(w, h);
258 } else {
259 // Deallocate change grid if disabling (saves memory)
260 mChangeGrid.reset(0, 0);
261 }
262}
263
268
270 int dampening) {
272 mMultiplier = static_cast<u32>(factor);
273 mSim.reset(); // clear out memory first.
274 mSim.reset(
276 // Extra updates (frames) are applied because the simulation slows down in
277 // proportion to the supersampling factor.
278 mExtraFrames = static_cast<u8>(factor) - 1;
279}
280
281void WaveSimulation1D::setSpeed(float speed) { mSim->setSpeed(speed); }
282
283void WaveSimulation1D::setDampening(int damp) { mSim->setDampening(damp); }
284
285int WaveSimulation1D::getDampenening() const { return mSim->getDampenening(); }
286
288
289float WaveSimulation1D::getSpeed() const { return mSim->getSpeed(); }
290
291float WaveSimulation1D::getf(fl::size x) const {
292 if (!has(x))
293 return 0.0f;
294 float sum = 0.0f;
295 u8 mult = MAX(1, mMultiplier);
296 for (u32 i = 0; i < mult; ++i) {
297 sum += mSim->getf(x * mult + i);
298 }
299 return sum / static_cast<float>(mult);
300}
301
302i16 WaveSimulation1D::geti16(fl::size x) const {
303 if (!has(x))
304 return 0;
305 u8 mult = MAX(1, mMultiplier);
306 i32 sum = 0;
307 for (u32 i = 0; i < mult; ++i) {
308 sum += mSim->geti16(x * mult + i);
309 }
310 return static_cast<i16>(sum / mult);
311}
312
314 if (!has(x))
315 return 0;
316 u8 mult = MAX(1, mMultiplier);
317 i32 sum = 0;
318 for (u32 i = 0; i < mult; ++i) {
319 sum += mSim->geti16Previous(x * mult + i);
320 }
321 return static_cast<i16>(sum / mult);
322}
323
324bool WaveSimulation1D::geti16All(fl::size x, i16 *curr, i16 *prev,
325 i16 *diff) const {
326 if (!has(x))
327 return false;
328 *curr = geti16(x);
329 *prev = geti16Previous(x);
330 *diff = *curr - *prev;
331 return true;
332}
333
334i8 WaveSimulation1D::geti8(fl::size x) const {
335 return static_cast<i8>(geti16(x) >> 8);
336}
337
338// u8 WaveSimulation2D::getu8(fl::size x, fl::size y) const {
339// i16 value = geti16(x, y);
340// if (mSim->getHalfDuplex()) {
341// u16 v2 = static_cast<u16>(value);
342// switch (mU8Mode) {
343// case WAVE_U8_MODE_LINEAR:
344// return half_duplex_blend_linear(v2);
345// case WAVE_U8_MODE_SQRT:
346// return half_duplex_blend_sqrt_q15(v2);
347// }
348// }
349// return static_cast<u8>(((static_cast<u16>(value) + 32768)) >>
350// 8);
351// }
352
353u8 WaveSimulation1D::getu8(fl::size x) const {
354 i16 value = geti16(x);
355 if (mSim->getHalfDuplex()) {
356 u16 v2 = static_cast<u16>(value);
357 switch (mU8Mode) {
359 return half_duplex_blend_linear(v2);
361 return half_duplex_blend_sqrt_q15(v2);
362 }
363 }
364 return static_cast<u8>(((static_cast<u16>(value) + 32768)) >> 8);
365}
366
367bool WaveSimulation1D::has(fl::size x) const { return (x < mOuterLength); }
368
369void WaveSimulation1D::setf(fl::size x, float value) {
370 if (!has(x))
371 return;
372 value = fl::clamp(value, -1.0f, 1.0f);
373 u8 mult = MAX(1, mMultiplier);
374 for (u32 i = 0; i < mult; ++i) {
375 mSim->set(x * mult + i, value);
376 }
377}
378
380 mSim->update();
381 for (u8 i = 0; i < mExtraFrames; ++i) {
382 mSim->update();
383 }
384}
385
387
388} // namespace fl
int y
Definition simple.h:93
int x
Definition simple.h:92
uint16_t speed
Definition Noise.ino:63
float getf(fl::size x) const
fl::unique_ptr< WaveSimulation1D_Real > mSim
void setExtraFrames(u8 extra)
i16 geti16Previous(fl::size x) const
void setSpeed(float speed)
U8EasingFunction mU8Mode
void setDampening(int damp)
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
void setExtraFrames(u8 extra)
float getf(fl::size x, fl::size y) const
i16 geti16Previous(fl::size x, fl::size y) const
void setSpeed(float speed)
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
void setDampening(int damp)
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)
UISlider length("Length", 1.0f, 0.0f, 1.0f, 0.01f)
UISlider dampening("Dampening", 6.0f, 0.0f, 10.0f, 0.1f)
#define MIN(a, b)
Definition math_macros.h:49
#define ABS(x)
Definition math_macros.h:53
#define MAX(a, b)
Definition math_macros.h:45
Implements the FastLED namespace macros.
unsigned char u8
Definition int.h:17
@ WAVE_U8_MODE_LINEAR
@ WAVE_U8_MODE_SQRT
FASTLED_FORCE_INLINE T clamp(T value, T min, T max)
Definition clamp.h:10
signed char i8
Definition int.h:16
SuperSample
Definition supersample.h:4
IMPORTANT!
Definition crgb.h:20