/* This demo is best viewed using the FastLED compiler. Install: pip install fastled Run: fastled This will compile and preview the sketch in the browser, and enable all the UI elements you see below. */ #include #include #include "fl/math_macros.h" #include "fl/time_alpha.h" #include "fl/ui.h" #include "fx/2d/blend.h" #include "fx/2d/wave.h" using namespace fl; #define HEIGHT 64 #define WIDTH 64 #define NUM_LEDS ((WIDTH) * (HEIGHT)) #define IS_SERPINTINE true CRGB leds[NUM_LEDS]; UITitle title("FxWave2D Demo"); UIDescription description("Advanced layered and blended wave effects."); UIButton button("Trigger"); UIButton buttonFancy("Trigger Fancy"); UICheckbox autoTrigger("Auto Trigger", true); UISlider triggerSpeed("Trigger Speed", .5f, 0.0f, 1.0f, 0.01f); UICheckbox easeModeSqrt("Ease Mode Sqrt", false); UISlider blurAmount("Global Blur Amount", 0, 0, 172, 1); UISlider blurPasses("Global Blur Passes", 1, 1, 10, 1); UISlider superSample("SuperSampleExponent", 1.f, 0.f, 3.f, 1.f); UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f); UISlider dampeningUpper("Wave Upper: Dampening", 8.9f, 0.0f, 20.0f, 0.1f); UICheckbox halfDuplexUpper("Wave Upper: Half Duplex", true); UISlider blurAmountUpper("Wave Upper: Blur Amount", 95, 0, 172, 1); UISlider blurPassesUpper("Wave Upper: Blur Passes", 1, 1, 10, 1); UISlider speedLower("Wave Lower: Speed", 0.26f, 0.0f, 1.0f); UISlider dampeningLower("Wave Lower: Dampening", 9.0f, 0.0f, 20.0f, 0.1f); UICheckbox halfDuplexLower("Wave Lower: Half Duplex", true); UISlider blurAmountLower("Wave Lower: Blur Amount", 0, 0, 172, 1); UISlider blurPassesLower("Wave Lower: Blur Passes", 1, 1, 10, 1); UISlider fancySpeed("Fancy Speed", 796, 0, 1000, 1); UISlider fancyIntensity("Fancy Intensity", 32, 1, 255, 1); UISlider fancyParticleSpan("Fancy Particle Span", 0.06f, 0.01f, 0.2f, 0.01f); DEFINE_GRADIENT_PALETTE(electricBlueFirePal){ 0, 0, 0, 0, // Black 32, 0, 0, 70, // Dark blue 128, 20, 57, 255, // Electric blue 255, 255, 255, 255 // White }; DEFINE_GRADIENT_PALETTE(electricGreenFirePal){ 0, 0, 0, 0, // black 8, 128, 64, 64, // green 16, 255, 222, 222, // red 64, 255, 255, 255, // white 255, 255, 255, 255 // white }; XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); XYMap xyRect(WIDTH, HEIGHT, false); WaveFx waveFxLower(xyRect, WaveFx::Args{ .factor = SUPER_SAMPLE_4X, .half_duplex = true, .speed = 0.18f, .dampening = 9.0f, .crgbMap = WaveCrgbGradientMapPtr::New(electricBlueFirePal), }); WaveFx waveFxUpper( xyRect, WaveFx::Args{ .factor = SUPER_SAMPLE_4X, .half_duplex = true, .speed = 0.25f, .dampening = 3.0f, .crgbMap = WaveCrgbGradientMapPtr::New(electricGreenFirePal), }); Blend2d fxBlend(xyMap); void setup() { Serial.begin(115200); auto screenmap = xyMap.toScreenMap(); screenmap.setDiameter(.2); FastLED.addLeds(leds, NUM_LEDS).setScreenMap(screenmap); fxBlend.add(waveFxLower); fxBlend.add(waveFxUpper); } SuperSample getSuperSample() { switch (int(superSample)) { case 0: return SuperSample::SUPER_SAMPLE_NONE; case 1: return SuperSample::SUPER_SAMPLE_2X; case 2: return SuperSample::SUPER_SAMPLE_4X; case 3: return SuperSample::SUPER_SAMPLE_8X; default: return SuperSample::SUPER_SAMPLE_NONE; } } void triggerRipple() { float perc = .15f; uint8_t min_x = perc * WIDTH; uint8_t max_x = (1 - perc) * WIDTH; uint8_t min_y = perc * HEIGHT; uint8_t max_y = (1 - perc) * HEIGHT; int x = random(min_x, max_x); int y = random(min_y, max_y); waveFxLower.setf(x, y, 1); waveFxUpper.setf(x, y, 1); } void applyFancyEffect(uint32_t now, bool button_active) { uint32_t total = map(fancySpeed.as(), 0, fancySpeed.max_value(), 1000, 100); static TimeRamp pointTransition = TimeRamp(total, 0, 0); if (button_active) { pointTransition.trigger(now, total, 0, 0); } if (!pointTransition.isActive(now)) { // no need to draw return; } int mid_x = WIDTH / 2; int mid_y = HEIGHT / 2; // now make a cross int amount = WIDTH / 2; int start_x = mid_x - amount; int end_x = mid_x + amount; int start_y = mid_y - amount; int end_y = mid_y + amount; int curr_alpha = pointTransition.update(now); int left_x = map(curr_alpha, 0, 255, mid_x, start_x); int down_y = map(curr_alpha, 0, 255, mid_y, start_y); int right_x = map(curr_alpha, 0, 255, mid_x, end_x); int up_y = map(curr_alpha, 0, 255, mid_y, end_y); float curr_alpha_f = curr_alpha / 255.0f; float valuef = (1.0f - curr_alpha_f) * fancyIntensity.value() / 255.0f; int span = fancyParticleSpan.value() * WIDTH; for (int x = left_x - span; x < left_x + span; x++) { waveFxLower.addf(x, mid_y, valuef); waveFxUpper.addf(x, mid_y, valuef); } for (int x = right_x - span; x < right_x + span; x++) { waveFxLower.addf(x, mid_y, valuef); waveFxUpper.addf(x, mid_y, valuef); } for (int y = down_y - span; y < down_y + span; y++) { waveFxLower.addf(mid_x, y, valuef); waveFxUpper.addf(mid_x, y, valuef); } for (int y = up_y - span; y < up_y + span; y++) { waveFxLower.addf(mid_x, y, valuef); waveFxUpper.addf(mid_x, y, valuef); } } struct ui_state { bool button = false; bool bigButton = false; }; ui_state ui() { U8EasingFunction easeMode = easeModeSqrt ? U8EasingFunction::WAVE_U8_MODE_SQRT : U8EasingFunction::WAVE_U8_MODE_LINEAR; waveFxLower.setSpeed(speedLower); waveFxLower.setDampening(dampeningLower); waveFxLower.setHalfDuplex(halfDuplexLower); waveFxLower.setSuperSample(getSuperSample()); waveFxLower.setEasingMode(easeMode); waveFxUpper.setSpeed(speedUpper); waveFxUpper.setDampening(dampeningUpper); waveFxUpper.setHalfDuplex(halfDuplexUpper); waveFxUpper.setSuperSample(getSuperSample()); waveFxUpper.setEasingMode(easeMode); fxBlend.setGlobalBlurAmount(blurAmount); fxBlend.setGlobalBlurPasses(blurPasses); Blend2dParams lower_params = { .blur_amount = blurAmountLower, .blur_passes = blurPassesLower, }; Blend2dParams upper_params = { .blur_amount = blurAmountUpper, .blur_passes = blurPassesUpper, }; fxBlend.setParams(waveFxLower, lower_params); fxBlend.setParams(waveFxUpper, upper_params); ui_state state{ .button = button, .bigButton = buttonFancy, }; return state; } void processAutoTrigger(uint32_t now) { static uint32_t nextTrigger = 0; uint32_t trigger_delta = nextTrigger - now; if (trigger_delta > 10000) { // rolled over! trigger_delta = 0; } if (autoTrigger) { if (now >= nextTrigger) { triggerRipple(); float speed = 1.0f - triggerSpeed.value(); uint32_t min_rand = 400 * speed; uint32_t max_rand = 2000 * speed; uint32_t min = MIN(min_rand, max_rand); uint32_t max = MAX(min_rand, max_rand); if (min == max) { max += 1; } nextTrigger = now + random(min, max); } } } void loop() { // Your code here uint32_t now = millis(); ui_state state = ui(); if (state.button) { triggerRipple(); } applyFancyEffect(now, state.bigButton); processAutoTrigger(now); Fx::DrawContext ctx(now, leds); fxBlend.draw(ctx); FastLED.show(); }