FastLED 3.9.15
Loading...
Searching...
No Matches
wavefx.cpp
Go to the documentation of this file.
1
2/*
3This demo is best viewed using the FastLED compiler.
4
5Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
6
7Python
8
9Install: pip install fastled
10Run: fastled <this sketch directory>
11This will compile and preview the sketch in the browser, and enable
12all the UI elements you see below.
13
14OVERVIEW:
15This sketch demonstrates a 2D wave simulation with multiple layers and blending effects.
16It creates ripple effects that propagate across the LED matrix, similar to water waves.
17The demo includes two wave layers (upper and lower) with different colors and properties,
18which are blended together to create complex visual effects.
19*/
20
21#include <Arduino.h> // Core Arduino functionality
22#include <FastLED.h> // Main FastLED library for controlling LEDs
23
24#include "fl/math_macros.h" // Math helper functions and macros
25#include "fl/time_alpha.h" // Time-based alpha/transition effects
26#include "fl/ui.h" // UI components for the FastLED web compiler
27#include "fx/2d/blend.h" // 2D blending effects between layers
28#include "fx/2d/wave.h" // 2D wave simulation
29
30#include "wavefx.h" // Header file for this sketch
31
32using namespace fl; // Use the FastLED namespace for convenience
33
34
35
36// Array to hold all LED color values - one CRGB struct per LED
38
39// UI elements that appear in the FastLED web compiler interface:
40UITitle title("FxWave2D Demo");
41UIDescription description("Advanced layered and blended wave effects.");
42
43UICheckbox xCyclical("X Is Cyclical", false); // If true, waves wrap around the x-axis (like a loop)
44// Main control UI elements:
45UIButton button("Trigger"); // Button to trigger a single ripple
46UIButton buttonFancy("Trigger Fancy"); // Button to trigger a fancy cross-shaped effect
47UICheckbox autoTrigger("Auto Trigger", true); // Enable/disable automatic ripple triggering
48UISlider triggerSpeed("Trigger Speed", .5f, 0.0f, 1.0f, 0.01f); // Controls how frequently auto-triggers happen (lower = faster)
49UICheckbox easeModeSqrt("Ease Mode Sqrt", false); // Changes how wave heights are calculated (sqrt gives more natural waves)
50UISlider blurAmount("Global Blur Amount", 0, 0, 172, 1); // Controls overall blur amount for all layers
51UISlider blurPasses("Global Blur Passes", 1, 1, 10, 1); // Controls how many times blur is applied (more = smoother but slower)
52UISlider superSample("SuperSampleExponent", 1.f, 0.f, 3.f, 1.f); // Controls anti-aliasing quality (higher = better quality but more CPU)
53
54
55
56// Upper wave layer controls:
57UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f); // How fast the upper wave propagates
58UISlider dampeningUpper("Wave Upper: Dampening", 8.9f, 0.0f, 20.0f, 0.1f); // How quickly the upper wave loses energy
59UICheckbox halfDuplexUpper("Wave Upper: Half Duplex", true); // If true, waves only go positive (not negative)
60UISlider blurAmountUpper("Wave Upper: Blur Amount", 95, 0, 172, 1); // Blur amount for upper wave layer
61UISlider blurPassesUpper("Wave Upper: Blur Passes", 1, 1, 10, 1); // Blur passes for upper wave layer
62
63// Lower wave layer controls:
64UISlider speedLower("Wave Lower: Speed", 0.26f, 0.0f, 1.0f); // How fast the lower wave propagates
65UISlider dampeningLower("Wave Lower: Dampening", 9.0f, 0.0f, 20.0f, 0.1f); // How quickly the lower wave loses energy
66UICheckbox halfDuplexLower("Wave Lower: Half Duplex", true); // If true, waves only go positive (not negative)
67UISlider blurAmountLower("Wave Lower: Blur Amount", 0, 0, 172, 1); // Blur amount for lower wave layer
68UISlider blurPassesLower("Wave Lower: Blur Passes", 1, 1, 10, 1); // Blur passes for lower wave layer
69
70// Fancy effect controls (for the cross-shaped effect):
71UISlider fancySpeed("Fancy Speed", 796, 0, 1000, 1); // Speed of the fancy effect animation
72UISlider fancyIntensity("Fancy Intensity", 32, 1, 255, 1); // Intensity/height of the fancy effect waves
73UISlider fancyParticleSpan("Fancy Particle Span", 0.06f, 0.01f, 0.2f, 0.01f); // Width of the fancy effect lines
74
75// Color palettes define the gradient of colors used for the wave effects
76// Each entry has the format: position (0-255), R, G, B
77
78DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
79 0, 0, 0, 0, // Black (lowest wave height)
80 32, 0, 0, 70, // Dark blue (low wave height)
81 128, 20, 57, 255, // Electric blue (medium wave height)
82 255, 255, 255, 255 // White (maximum wave height)
83};
84
85DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
86 0, 0, 0, 0, // Black (lowest wave height)
87 8, 128, 64, 64, // Green with red tint (very low wave height)
88 16, 255, 222, 222, // Pinkish red (low wave height)
89 64, 255, 255, 255, // White (medium wave height)
90 255, 255, 255, 255 // White (maximum wave height)
91};
92
93// Create mappings between 1D array positions and 2D x,y coordinates
94XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); // For the actual LED output (may be serpentine)
95XYMap xyRect(WIDTH, HEIGHT, false); // For the wave simulation (always rectangular grid)
96
97// Create default configuration for the lower wave layer
99 WaveFx::Args out;
100 out.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves
101 out.half_duplex = true; // Only positive waves (no negative values)
102 out.auto_updates = true; // Automatically update the simulation each frame
103 out.speed = 0.18f; // Wave propagation speed
104 out.dampening = 9.0f; // How quickly waves lose energy
105 out.crgbMap = WaveCrgbGradientMapPtr::New(electricBlueFirePal); // Color palette for this wave
106 return out;
107}
108
109// Create default configuration for the upper wave layer
111 WaveFx::Args out;
112 out.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves
113 out.half_duplex = true; // Only positive waves (no negative values)
114 out.auto_updates = true; // Automatically update the simulation each frame
115 out.speed = 0.25f; // Wave propagation speed (faster than lower)
116 out.dampening = 3.0f; // How quickly waves lose energy (less than lower)
117 out.crgbMap = WaveCrgbGradientMapPtr::New(electricGreenFirePal); // Color palette for this wave
118 return out;
119}
120
121// Create the two wave simulation layers with their default configurations
122WaveFx waveFxLower(xyRect, CreateArgsLower()); // Lower/background wave layer (blue)
123WaveFx waveFxUpper(xyRect, CreateArgsUpper()); // Upper/foreground wave layer (green/red)
124
125// Create a blender that will combine the two wave layers
127
128
129// Convert the UI slider value to the appropriate SuperSample enum value
130// SuperSample controls the quality of the wave simulation (higher = better quality but more CPU)
132 switch (int(superSample)) {
133 case 0:
134 return SuperSample::SUPER_SAMPLE_NONE; // No supersampling (fastest, lowest quality)
135 case 1:
136 return SuperSample::SUPER_SAMPLE_2X; // 2x supersampling (2x2 grid = 4 samples per pixel)
137 case 2:
138 return SuperSample::SUPER_SAMPLE_4X; // 4x supersampling (4x4 grid = 16 samples per pixel)
139 case 3:
140 return SuperSample::SUPER_SAMPLE_8X; // 8x supersampling (8x8 grid = 64 samples per pixel, slowest)
141 default:
142 return SuperSample::SUPER_SAMPLE_NONE; // Default fallback
143 }
144}
145
146// Create a ripple effect at a random position within the central area of the display
148 // Define a margin percentage to keep ripples away from the edges
149 float perc = .15f;
150
151 // Calculate the boundaries for the ripple (15% from each edge)
152 uint8_t min_x = perc * WIDTH; // Left boundary
153 uint8_t max_x = (1 - perc) * WIDTH; // Right boundary
154 uint8_t min_y = perc * HEIGHT; // Top boundary
155 uint8_t max_y = (1 - perc) * HEIGHT; // Bottom boundary
156
157 // Generate a random position within these boundaries
158 int x = random(min_x, max_x);
159 int y = random(min_y, max_y);
160
161 // Set a wave peak at this position in both wave layers
162 // The value 1.0 represents the maximum height of the wave
163 waveFxLower.setf(x, y, 1); // Create ripple in lower layer
164 waveFxUpper.setf(x, y, 1); // Create ripple in upper layer
165}
166
167// Create a fancy cross-shaped effect that expands from the center
168void applyFancyEffect(uint32_t now, bool button_active) {
169 // Calculate the total animation duration based on the speed slider
170 // Higher fancySpeed value = shorter duration (faster animation)
171 uint32_t total =
172 map(fancySpeed.as<uint32_t>(), 0, fancySpeed.getMax(), 1000, 100);
173
174 // Create a static TimeRamp to manage the animation timing
175 // TimeRamp handles the transition from start to end over time
176 static TimeRamp pointTransition = TimeRamp(total, 0, 0);
177
178 // If the button is active, start/restart the animation
179 if (button_active) {
180 pointTransition.trigger(now, total, 0, 0);
181 }
182
183 // If the animation isn't currently active, exit early
184 if (!pointTransition.isActive(now)) {
185 // no need to draw
186 return;
187 }
188
189 // Find the center of the display
190 int mid_x = WIDTH / 2;
191 int mid_y = HEIGHT / 2;
192
193 // Calculate the maximum distance from center (half the width)
194 int amount = WIDTH / 2;
195
196 // Calculate the start and end coordinates for the cross
197 int start_x = mid_x - amount; // Leftmost point
198 int end_x = mid_x + amount; // Rightmost point
199 int start_y = mid_y - amount; // Topmost point
200 int end_y = mid_y + amount; // Bottommost point
201
202 // Get the current animation progress (0-255)
203 int curr_alpha = pointTransition.update8(now);
204
205 // Map the animation progress to the four points of the expanding cross
206 // As curr_alpha increases from 0 to 255, these points move from center to edges
207 int left_x = map(curr_alpha, 0, 255, mid_x, start_x); // Center to left
208 int down_y = map(curr_alpha, 0, 255, mid_y, start_y); // Center to top
209 int right_x = map(curr_alpha, 0, 255, mid_x, end_x); // Center to right
210 int up_y = map(curr_alpha, 0, 255, mid_y, end_y); // Center to bottom
211
212 // Convert the 0-255 alpha to 0.0-1.0 range
213 float curr_alpha_f = curr_alpha / 255.0f;
214
215 // Calculate the wave height value - starts high and decreases as animation progresses
216 // This makes the waves stronger at the beginning of the animation
217 float valuef = (1.0f - curr_alpha_f) * fancyIntensity.value() / 255.0f;
218
219 // Calculate the width of the cross lines
220 int span = fancyParticleSpan.value() * WIDTH;
221
222 // Add wave energy along the four expanding lines of the cross
223 // Each line is a horizontal or vertical span of pixels
224
225 // Left-moving horizontal line
226 for (int x = left_x - span; x < left_x + span; x++) {
227 waveFxLower.addf(x, mid_y, valuef); // Add to lower layer
228 waveFxUpper.addf(x, mid_y, valuef); // Add to upper layer
229 }
230
231 // Right-moving horizontal line
232 for (int x = right_x - span; x < right_x + span; x++) {
233 waveFxLower.addf(x, mid_y, valuef);
234 waveFxUpper.addf(x, mid_y, valuef);
235 }
236
237 // Downward-moving vertical line
238 for (int y = down_y - span; y < down_y + span; y++) {
239 waveFxLower.addf(mid_x, y, valuef);
240 waveFxUpper.addf(mid_x, y, valuef);
241 }
242
243 // Upward-moving vertical line
244 for (int y = up_y - span; y < up_y + span; y++) {
245 waveFxLower.addf(mid_x, y, valuef);
246 waveFxUpper.addf(mid_x, y, valuef);
247 }
248}
249
250// Structure to hold the state of UI buttons
251struct ui_state {
252 bool button = false; // Regular ripple button state
253 bool bigButton = false; // Fancy effect button state
254};
255
256// Apply all UI settings to the wave effects and return button states
258 // Set the easing function based on the checkbox
259 // Easing controls how wave heights are calculated:
260 // - LINEAR: Simple linear mapping (sharper waves)
261 // - SQRT: Square root mapping (more natural, rounded waves)
265
266 // Apply all settings from UI controls to the lower wave layer
267 waveFxLower.setSpeed(speedLower); // Wave propagation speed
268 waveFxLower.setDampening(dampeningLower); // How quickly waves lose energy
269 waveFxLower.setHalfDuplex(halfDuplexLower); // Whether waves can go negative
270 waveFxLower.setSuperSample(getSuperSample()); // Anti-aliasing quality
271 waveFxLower.setEasingMode(easeMode); // Wave height calculation method
272
273 // Apply all settings from UI controls to the upper wave layer
274 waveFxUpper.setSpeed(speedUpper); // Wave propagation speed
275 waveFxUpper.setDampening(dampeningUpper); // How quickly waves lose energy
276 waveFxUpper.setHalfDuplex(halfDuplexUpper); // Whether waves can go negative
277 waveFxUpper.setSuperSample(getSuperSample()); // Anti-aliasing quality
278 waveFxUpper.setEasingMode(easeMode); // Wave height calculation method
279
280 // Apply global blur settings to the blender
281 fxBlend.setGlobalBlurAmount(blurAmount); // Overall blur strength
282 fxBlend.setGlobalBlurPasses(blurPasses); // Number of blur passes
283
284 // Create parameter structures for each wave layer's blur settings
285 Blend2dParams lower_params = {
286 .blur_amount = blurAmountLower, // Blur amount for lower layer
287 .blur_passes = blurPassesLower, // Blur passes for lower layer
288 };
289
290 Blend2dParams upper_params = {
291 .blur_amount = blurAmountUpper, // Blur amount for upper layer
292 .blur_passes = blurPassesUpper, // Blur passes for upper layer
293 };
294
295 // Apply the layer-specific blur parameters
296 fxBlend.setParams(waveFxLower, lower_params);
297 fxBlend.setParams(waveFxUpper, upper_params);
298
299 // Return the current state of the UI buttons
300 ui_state state{
301 .button = button, // Regular ripple button
302 .bigButton = buttonFancy, // Fancy effect button
303 };
304 return state;
305}
306
307// Handle automatic triggering of ripples at random intervals
308void processAutoTrigger(uint32_t now) {
309 // Static variable to remember when the next auto-trigger should happen
310 static uint32_t nextTrigger = 0;
311
312 // Calculate time until next trigger
313 uint32_t trigger_delta = nextTrigger - now;
314
315 // Handle timer overflow (happens after ~49 days of continuous running)
316 if (trigger_delta > 10000) {
317 // If the delta is suspiciously large, we probably rolled over
318 trigger_delta = 0;
319 }
320
321 // Only proceed if auto-trigger is enabled
322 if (autoTrigger) {
323 // Check if it's time for the next trigger
324 if (now >= nextTrigger) {
325 // Create a ripple
327
328 // Calculate the next trigger time based on the speed slider
329 // Invert the speed value so higher slider = faster triggers
330 float speed = 1.0f - triggerSpeed.value();
331
332 // Calculate min and max random intervals
333 // Higher speed = shorter intervals between triggers
334 uint32_t min_rand = 400 * speed; // Minimum interval (milliseconds)
335 uint32_t max_rand = 2000 * speed; // Maximum interval (milliseconds)
336
337 // Ensure min is actually less than max (handles edge cases)
338 uint32_t min = MIN(min_rand, max_rand);
339 uint32_t max = MAX(min_rand, max_rand);
340
341 // Ensure min and max aren't equal (would cause random() to crash)
342 if (min == max) {
343 max += 1;
344 }
345
346 // Schedule the next trigger at a random time in the future
347 nextTrigger = now + random(min, max);
348 }
349 }
350}
351
353 // Create a screen map for visualization in the FastLED web compiler
354 auto screenmap = xyMap.toScreenMap();
355 screenmap.setDiameter(.2); // Set the size of the LEDs in the visualization
356
357 // Initialize the LED strip:
358 // - NEOPIXEL is the LED type
359 // - 2 is the data pin number (for real hardware)
360 // - setScreenMap connects our 2D coordinate system to the 1D LED array
361 FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS).setScreenMap(screenmap);
362
363 // Add both wave layers to the blender
364 // The order matters - lower layer is added first (background)
365 fxBlend.add(waveFxLower);
366 fxBlend.add(waveFxUpper);
367}
368
369
371 // The main program loop that runs continuously
372
373 // Get the current time in milliseconds
374 uint32_t now = millis();
375
376 // set the x cyclical
377 waveFxLower.setXCylindrical(xCyclical.value()); // Set whether lower wave wraps around x-axis
378
379 // Apply all UI settings and get button states
380 ui_state state = ui();
381
382 // Check if the regular ripple button was pressed
383 if (state.button) {
384 triggerRipple(); // Create a single ripple
385 }
386
387 // Apply the fancy cross effect if its button is pressed
388 applyFancyEffect(now, state.bigButton);
389
390 // Handle automatic triggering of ripples
392
393 // Create a drawing context with the current time and LED array
394 Fx::DrawContext ctx(now, leds);
395
396 // Draw the blended result of both wave layers to the LED array
397 fxBlend.draw(ctx);
398
399 // Send the color data to the actual LEDs
400 FastLED.show();
401}
CRGB leds[NUM_LEDS]
Definition Apa102.ino:11
#define NUM_LEDS
Definition Apa102.ino:6
#define WIDTH
Definition Blur2d.ino:9
#define HEIGHT
Definition Blur2d.ino:10
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
Definition FastLED.cpp:62
central include file for FastLED, defines the CFastLED class/object
uint32_t x[NUM_LAYERS]
Definition Fire2023.ino:82
uint32_t y[NUM_LAYERS]
Definition Fire2023.ino:83
#define IS_SERPINTINE
Definition FxEngine.ino:35
LED controller for WS2812 LEDs with GRB color order.
Definition FastLED.h:155
_DrawContext DrawContext
Definition fx.h:21
uint8_t update8(uint32_t now) override
Compute current 0–255 output based on how much time has elapsed since trigger().
bool isActive(uint32_t now) const override
void trigger(uint32_t now) override
Call this when you want to (re)start the ramp cycle.
WaveFxArgs Args
Definition wave.h:80
#define DEFINE_GRADIENT_PALETTE(X)
Defines a static RGB palette very compactly using a series of connected color gradients.
Definition colorutils.h:112
uint16_t speed
Definition funky.cpp:82
XYMap xyMap
Definition gfx.cpp:8
#define MIN(a, b)
Definition math_macros.h:15
#define MAX(a, b)
Definition math_macros.h:11
U8EasingFunction
@ WAVE_U8_MODE_LINEAR
@ WAVE_U8_MODE_SQRT
SuperSample
Definition supersample.h:4
@ SUPER_SAMPLE_8X
Definition supersample.h:8
@ SUPER_SAMPLE_2X
Definition supersample.h:6
@ SUPER_SAMPLE_4X
Definition supersample.h:7
@ SUPER_SAMPLE_NONE
Definition supersample.h:5
Implements a simple red square effect for 2D LED grids.
Definition crgb.h:16
Representation of an RGB pixel (Red, Green, Blue)
Definition crgb.h:55
float speed
Definition wave.h:71
WaveCrgbMapPtr crgbMap
Definition wave.h:74
bool auto_updates
Definition wave.h:70
SuperSample factor
Definition wave.h:68
float dampening
Definition wave.h:72
bool half_duplex
Definition wave.h:69
bool bigButton
Definition wavefx.cpp:253
bool button
Definition wavefx.cpp:252
ui_state()
Definition ui_state.h:12
UISlider blurAmount("Global Blur Amount", 0, 0, 172, 1)
XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE)
UISlider blurPassesLower("Wave Lower: Blur Passes", 1, 1, 10, 1)
UISlider blurPasses("Global Blur Passes", 1, 1, 10, 1)
Blend2d fxBlend(xyMap)
ui_state ui()
Definition wavefx.cpp:257
UISlider blurAmountLower("Wave Lower: Blur Amount", 0, 0, 172, 1)
UISlider speedLower("Wave Lower: Speed", 0.26f, 0.0f, 1.0f)
UISlider blurPassesUpper("Wave Upper: Blur Passes", 1, 1, 10, 1)
UISlider fancyParticleSpan("Fancy Particle Span", 0.06f, 0.01f, 0.2f, 0.01f)
UICheckbox autoTrigger("Auto Trigger", true)
WaveFx waveFxLower(xyRect, CreateArgsLower())
UICheckbox halfDuplexUpper("Wave Upper: Half Duplex", true)
UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f)
UITitle title("FxWave2D Demo")
void processAutoTrigger(uint32_t now)
Definition wavefx.cpp:308
UIButton button("Trigger")
void wavefx_loop()
Definition wavefx.cpp:370
UISlider fancySpeed("Fancy Speed", 796, 0, 1000, 1)
WaveFx::Args CreateArgsLower()
Definition wavefx.cpp:98
XYMap xyRect(WIDTH, HEIGHT, false)
void wavefx_setup()
Definition wavefx.cpp:352
UISlider dampeningUpper("Wave Upper: Dampening", 8.9f, 0.0f, 20.0f, 0.1f)
UICheckbox easeModeSqrt("Ease Mode Sqrt", false)
UICheckbox xCyclical("X Is Cyclical", false)
UIButton buttonFancy("Trigger Fancy")
UISlider fancyIntensity("Fancy Intensity", 32, 1, 255, 1)
UISlider triggerSpeed("Trigger Speed",.5f, 0.0f, 1.0f, 0.01f)
UIDescription description("Advanced layered and blended wave effects.")
UISlider blurAmountUpper("Wave Upper: Blur Amount", 95, 0, 172, 1)
SuperSample getSuperSample()
Definition wavefx.cpp:131
void triggerRipple()
Definition wavefx.cpp:147
UISlider superSample("SuperSampleExponent", 1.f, 0.f, 3.f, 1.f)
void applyFancyEffect(uint32_t now, bool button_active)
Definition wavefx.cpp:168
UISlider dampeningLower("Wave Lower: Dampening", 9.0f, 0.0f, 20.0f, 0.1f)
UICheckbox halfDuplexLower("Wave Lower: Half Duplex", true)
WaveFx::Args CreateArgsUpper()
Definition wavefx.cpp:110
WaveFx waveFxUpper(xyRect, CreateArgsUpper())