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 <stdint.h>
4
5#include "fl/clamp.h"
6#include "fl/namespace.h"
8
9namespace {
10
11uint8_t half_duplex_blend_sqrt_q15(uint16_t x) {
12 x = MIN(x, 32767); // Q15
13 const int Q = 15;
14 uint32_t X = (uint32_t)x << Q; // promote to Q30
15 uint32_t 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<int16_t>(y) >> 8;
22}
23
24uint8_t half_duplex_blend_linear(uint16_t x) {
25 x = 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
36WaveSimulation2D::WaveSimulation2D(uint32_t W, uint32_t H, SuperSample factor,
37 float speed, float dampening) {
38 init(W, H, factor, speed, dampening);
39}
40
41void WaveSimulation2D::init(uint32_t width, uint32_t height, SuperSample factor,
42 float speed, int dampening) {
43 mOuterWidth = width;
44 mOuterHeight = height;
45 mMultiplier = static_cast<uint32_t>(factor);
46 mSim.reset(); // clear out memory first.
47 uint32_t w = width * mMultiplier;
48 uint32_t h = height * mMultiplier;
49 mSim.reset(new WaveSimulation2D_Real(w, h, speed, dampening));
50 mChangeGrid.reset(w, h);
51 // Extra frames are needed because the simulation slows down in
52 // proportion to the supersampling factor.
53 mExtraFrames = uint8_t(factor) - 1;
54}
55
56void WaveSimulation2D::setDampening(int damp) { mSim->setDampening(damp); }
57
58int WaveSimulation2D::getDampenening() const { return mSim->getDampenening(); }
59
60float WaveSimulation2D::getSpeed() const { return mSim->getSpeed(); }
61
62float WaveSimulation2D::getf(size_t x, size_t y) const {
63 if (!has(x, y))
64 return 0.0f;
65 float sum = 0.0f;
66 for (uint32_t j = 0; j < mMultiplier; ++j) {
67 for (uint32_t i = 0; i < mMultiplier; ++i) {
68 sum += mSim->getf(x * mMultiplier + i, y * mMultiplier + j);
69 }
70 }
71 return sum / static_cast<float>(mMultiplier * mMultiplier);
72}
73
74int16_t WaveSimulation2D::geti16(size_t x, size_t y) const {
75 if (!has(x, y))
76 return 0;
77 int32_t sum = 0;
78 uint8_t mult = MAX(1, mMultiplier);
79 for (uint32_t j = 0; j < mult; ++j) {
80 for (uint32_t i = 0; i < mult; ++i) {
81 uint32_t xx = x * mult + i;
82 uint32_t yy = y * mult + j;
83 int32_t pt = mSim->geti16(xx, yy);
84 // int32_t ch_pt = mChangeGrid[(yy * mMultiplier) + xx];
85 int32_t ch_pt = mChangeGrid(xx, yy);
86 if (ch_pt != 0) { // we got a hit.
87 sum += ch_pt;
88 } else {
89 sum += pt;
90 }
91 }
92 }
93 int16_t out = static_cast<int16_t>(sum / (mult * mult));
94 return out;
95}
96
97int16_t WaveSimulation2D::geti16Previous(size_t x, size_t y) const {
98 if (!has(x, y))
99 return 0;
100 int32_t sum = 0;
101 uint8_t mult = MAX(1, mMultiplier);
102 for (uint32_t j = 0; j < mult; ++j) {
103 for (uint32_t i = 0; i < mult; ++i) {
104 sum +=
105 mSim->geti16Previous(x * mult + i, y * mult + j);
106 }
107 }
108 int16_t out = static_cast<int16_t>(sum / (mult * mult));
109 return out;
110}
111
112bool WaveSimulation2D::geti16All(size_t x, size_t y, int16_t *curr,
113 int16_t *prev, int16_t *diff) const {
114 if (!has(x, y))
115 return false;
116 *curr = geti16(x, y);
117 *prev = geti16Previous(x, y);
118 *diff = *curr - *prev;
119 return true;
120}
121
122int8_t WaveSimulation2D::geti8(size_t x, size_t y) const {
123 return static_cast<int8_t>(geti16(x, y) >> 8);
124}
125
126uint8_t WaveSimulation2D::getu8(size_t x, size_t y) const {
127 int16_t value = geti16(x, y);
128 if (mSim->getHalfDuplex()) {
129 uint16_t v2 = static_cast<uint16_t>(value);
130 switch (mU8Mode) {
132 return half_duplex_blend_linear(v2);
134 return half_duplex_blend_sqrt_q15(v2);
135 }
136 }
137 return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768)) >> 8);
138}
139
140bool WaveSimulation2D::has(size_t x, size_t y) const {
141 return (x < mOuterWidth) && (y < mOuterHeight);
142}
143
144void WaveSimulation2D::seti16(size_t x, size_t y, int16_t v16) {
145 if (!has(x, y))
146 return;
147
148 uint8_t mult = MAX(1, mMultiplier);
149
150 // radius in pixels of your diamond
151 int rad = static_cast<int>(mult) / 2;
152
153 for (size_t j = 0; j < mult; ++j) {
154 for (size_t i = 0; i < mult; ++i) {
155 // compute offset from the center of this block
156 int dx = static_cast<int>(i) - rad;
157 int dy = static_cast<int>(j) - rad;
158 // keep only those points whose Manhattan distance ≤ rad
159 if (ABS(dx) + ABS(dy) > rad) {
160 continue;
161 }
162 size_t xx = x * mult + i;
163 size_t yy = y * mult + j;
164 if (mSim->has(xx, yy)) {
165 int16_t &pt = mChangeGrid.at(xx, yy);
166 if (pt == 0) {
167 // not set yet so set unconditionally.
168 pt = v16;
169 } else {
170 const bool sign_matches = (pt >= 0) == (v16 >= 0);
171 if (!sign_matches) {
172 // opposite signs, so overwrite
173 pt = v16;
174 } else {
175 // if the magnitude of the new pt is greater than what
176 // was already there, then overwrite.
177 uint16_t abs_pt = static_cast<uint16_t>(ABS(pt));
178 uint16_t abs_v16 = static_cast<uint16_t>(ABS(v16));
179 if (abs_v16 > abs_pt) {
180 pt = v16;
181 }
182 }
183 }
184 }
185 }
186 }
187}
188
189void WaveSimulation2D::setf(size_t x, size_t y, float value) {
190 if (!has(x, y))
191 return;
192
193 value = fl::clamp(value, 0.0f, 1.0f);
194 int16_t v16 = wave_detail::float_to_fixed(value);
195 seti16(x, y, v16);
196}
197
199 const vec2<int16_t> min_max = mChangeGrid.minMax();
200 const bool has_updates = min_max != vec2<int16_t>(0, 0);
201 for (uint8_t i = 0; i < mExtraFrames + 1; ++i) {
202 if (has_updates) {
203 // apply them
204 const uint32_t w = mChangeGrid.width();
205 const uint32_t h = mChangeGrid.height();
206 for (uint32_t x = 0; x < w; ++x) {
207 for (uint32_t y = 0; y < h; ++y) {
208 int16_t v16 = mChangeGrid(x, y);
209 if (v16 != 0) {
210 mSim->seti16(x, y, v16);
211 }
212 }
213 }
214 }
215 mSim->update();
216 }
217 // zero out mChangeGrid
218 mChangeGrid.clear();
219}
220
221uint32_t WaveSimulation2D::getWidth() const { return mOuterWidth; }
222uint32_t WaveSimulation2D::getHeight() const { return mOuterHeight; }
223
224void WaveSimulation2D::setExtraFrames(uint8_t extra) { mExtraFrames = extra; }
225
227 float speed, int dampening) {
228 init(length, factor, speed, dampening);
229}
230
231void WaveSimulation1D::init(uint32_t length, SuperSample factor, float speed,
232 int dampening) {
234 mMultiplier = static_cast<uint32_t>(factor);
235 mSim.reset(); // clear out memory first.
236 mSim.reset(
238 // Extra updates (frames) are applied because the simulation slows down in
239 // proportion to the supersampling factor.
240 mExtraFrames = static_cast<uint8_t>(factor) - 1;
241}
242
243void WaveSimulation1D::setSpeed(float speed) { mSim->setSpeed(speed); }
244
245void WaveSimulation1D::setDampening(int damp) { mSim->setDampening(damp); }
246
247int WaveSimulation1D::getDampenening() const { return mSim->getDampenening(); }
248
249void WaveSimulation1D::setExtraFrames(uint8_t extra) { mExtraFrames = extra; }
250
251float WaveSimulation1D::getSpeed() const { return mSim->getSpeed(); }
252
253float WaveSimulation1D::getf(size_t x) const {
254 if (!has(x))
255 return 0.0f;
256 float sum = 0.0f;
257 uint8_t mult = MAX(1, mMultiplier);
258 for (uint32_t i = 0; i < mult; ++i) {
259 sum += mSim->getf(x * mult + i);
260 }
261 return sum / static_cast<float>(mult);
262}
263
264int16_t WaveSimulation1D::geti16(size_t x) const {
265 if (!has(x))
266 return 0;
267 uint8_t mult = MAX(1, mMultiplier);
268 int32_t sum = 0;
269 for (uint32_t i = 0; i < mult; ++i) {
270 sum += mSim->geti16(x * mult + i);
271 }
272 return static_cast<int16_t>(sum / mult);
273}
274
275int16_t WaveSimulation1D::geti16Previous(size_t x) const {
276 if (!has(x))
277 return 0;
278 uint8_t mult = MAX(1, mMultiplier);
279 int32_t sum = 0;
280 for (uint32_t i = 0; i < mult; ++i) {
281 sum += mSim->geti16Previous(x * mult + i);
282 }
283 return static_cast<int16_t>(sum / mult);
284}
285
286bool WaveSimulation1D::geti16All(size_t x, int16_t *curr, int16_t *prev,
287 int16_t *diff) const {
288 if (!has(x))
289 return false;
290 *curr = geti16(x);
291 *prev = geti16Previous(x);
292 *diff = *curr - *prev;
293 return true;
294}
295
296int8_t WaveSimulation1D::geti8(size_t x) const {
297 return static_cast<int8_t>(geti16(x) >> 8);
298}
299
300// uint8_t WaveSimulation2D::getu8(size_t x, size_t y) const {
301// int16_t value = geti16(x, y);
302// if (mSim->getHalfDuplex()) {
303// uint16_t v2 = static_cast<uint16_t>(value);
304// switch (mU8Mode) {
305// case WAVE_U8_MODE_LINEAR:
306// return half_duplex_blend_linear(v2);
307// case WAVE_U8_MODE_SQRT:
308// return half_duplex_blend_sqrt_q15(v2);
309// }
310// }
311// return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768)) >>
312// 8);
313// }
314
315uint8_t WaveSimulation1D::getu8(size_t x) const {
316 int16_t value = geti16(x);
317 if (mSim->getHalfDuplex()) {
318 uint16_t v2 = static_cast<uint16_t>(value);
319 switch (mU8Mode) {
321 return half_duplex_blend_linear(v2);
323 return half_duplex_blend_sqrt_q15(v2);
324 }
325 }
326 return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768)) >> 8);
327}
328
329bool WaveSimulation1D::has(size_t x) const { return (x < mOuterLength); }
330
331void WaveSimulation1D::setf(size_t x, float value) {
332 if (!has(x))
333 return;
334 value = fl::clamp(value, -1.0f, 1.0f);
335 uint8_t mult = MAX(1, mMultiplier);
336 for (uint32_t i = 0; i < mult; ++i) {
337 mSim->set(x * mult + i, value);
338 }
339}
340
342 mSim->update();
343 for (uint8_t i = 0; i < mExtraFrames; ++i) {
344 mSim->update();
345 }
346}
347
348uint32_t WaveSimulation1D::getLength() const { return mOuterLength; }
349
350} // namespace fl
uint32_t x[NUM_LAYERS]
Definition Fire2023.ino:82
uint32_t y[NUM_LAYERS]
Definition Fire2023.ino:83
UISlider dampening("Dampening", 6.0f, 0.0f, 10.0f, 0.1f)
void setf(size_t x, float value)
fl::scoped_ptr< WaveSimulation1D_Real > mSim
void init(uint32_t length, SuperSample factor, float speed, int dampening)
WaveSimulation1D(uint32_t length, SuperSample factor=SuperSample::SUPER_SAMPLE_NONE, float speed=0.16f, int dampening=6)
int8_t geti8(size_t x) const
int16_t geti16(size_t x) const
bool has(size_t x) const
float getf(size_t x) const
void setSpeed(float speed)
U8EasingFunction mU8Mode
void setDampening(int damp)
void setExtraFrames(uint8_t extra)
uint8_t getu8(size_t x) const
int16_t geti16Previous(size_t x) const
bool geti16All(size_t x, int16_t *curr, int16_t *prev, int16_t *diff) const
uint32_t getLength() const
int16_t geti16(size_t x, size_t y) const
uint32_t getWidth() const
void init(uint32_t width, uint32_t height, SuperSample factor, float speed, int dampening)
void setExtraFrames(uint8_t extra)
uint32_t getHeight() const
int8_t geti8(size_t x, size_t y) const
fl::Grid< int16_t > mChangeGrid
fl::scoped_ptr< WaveSimulation2D_Real > mSim
void setSpeed(float speed)
bool geti16All(size_t x, size_t y, int16_t *curr, int16_t *prev, int16_t *diff) const
float getf(size_t x, size_t y) const
uint8_t getu8(size_t x, size_t y) const
void seti16(size_t x, size_t y, int16_t value)
void setf(size_t x, size_t y, float value)
U8EasingFunction mU8Mode
bool has(size_t x, size_t y) const
void setDampening(int damp)
WaveSimulation2D(uint32_t W, uint32_t H, SuperSample factor=SuperSample::SUPER_SAMPLE_NONE, float speed=0.16f, float dampening=6.0f)
int16_t geti16Previous(size_t x, size_t y) const
UISlider length("Length", 1.0f, 0.0f, 1.0f, 0.01f)
uint16_t speed
Definition funky.cpp:82
#define MIN(a, b)
Definition math_macros.h:15
#define ABS(x)
Definition math_macros.h:19
#define MAX(a, b)
Definition math_macros.h:11
Implements the FastLED namespace macros.
int16_t float_to_fixed(float f)
@ WAVE_U8_MODE_LINEAR
@ WAVE_U8_MODE_SQRT
FASTLED_FORCE_INLINE T clamp(T value, T min, T max)
Definition clamp.h:10
SuperSample
Definition supersample.h:4
Implements a simple red square effect for 2D LED grids.
Definition crgb.h:16