#include <FastLED.h>
// ========== CONFIGURATION ==========
#define NUM_LEDS 60
#define DATA_PIN 2
#define COLOR_ORDER GRB
#define LED_TYPE WS2812B
// ===================================
// ========== MEMORY & EFFECTS TUNING ==========
#define MAX_SPARKS (NUM_LEDS * 3 / 2) // Results in 90
// *** CHANGED: Reduced spark count for a less dense, more "particle-like" look ***
#define NUM_SPARKS_PER_BURST 20
// Controls the distribution of spark speeds.
const float VELOCITY_EXPONENT = 2.0;
float drag = 0.97;
const float BURST_FORCE = 0.0019f;
// *** CHANGED: Increased for faster fading and shorter trails ***
#define FADE_RATE 60
// ======================================
CRGB leds[NUM_LEDS];
// Optimized Spark struct
struct Spark {
bool active;
float pos;
float vel;
int8_t dir;
uint8_t hue;
uint8_t brightness;
uint8_t type;
};
Spark sparks[MAX_SPARKS];
// --- Timekeeping for new bursts ---
unsigned long lastBurstTime = 0;
int nextBurstInterval = 1000;
void setup() {
FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(255);
randomSeed(analogRead(A0));
for (int i = 0; i < MAX_SPARKS; i++) {
sparks[i].active = false;
}
fill_solid( leds, NUM_LEDS, CRGB::Black);
FastLED.show();
}
void createNewExplosion() {
float center = random(10, 51);
uint8_t explosionType = random(0, 6);
int sparksToCreate = NUM_SPARKS_PER_BURST;
for (int i = 0; i < MAX_SPARKS && sparksToCreate > 0; i++) {
if (!sparks[i].active) {
sparks[i].active = true;
sparks[i].pos = center;
// New non-linear velocity calculation
float baseVel = float(random16()) / 65535.0;
float finalVel = pow(baseVel, VELOCITY_EXPONENT);
if (random(2) == 0) {
finalVel *= -1.0;
}
sparks[i].vel = finalVel;
sparks[i].dir = (sparks[i].vel > 0) ? 1 : -1;
sparks[i].hue = abs(sparks[i].vel) * 255;
sparks[i].brightness = 255;
sparks[i].type = explosionType;
sparksToCreate--;
}
}
}
void updateAndDrawSparks() {
fadeToBlackBy(leds, NUM_LEDS, FADE_RATE);
for (int i = 0; i < MAX_SPARKS; i++) {
if (sparks[i].active) {
sparks[i].pos += sparks[i].vel;
sparks[i].vel += BURST_FORCE * sparks[i].dir;
sparks[i].vel *= drag;
sparks[i].brightness = scale8(sparks[i].brightness, 253);
if (sparks[i].pos < 0 || sparks[i].pos >= NUM_LEDS || sparks[i].brightness < 5) {
sparks[i].active = false;
continue;
}
int currentPos = (int)sparks[i].pos;
uint8_t c1 = 127, c2 = 50;
CRGB color;
switch (sparks[i].type) {
case 0: // Green/White
if (sparks[i].hue > c1) color = CRGB((255 * (sparks[i].hue - c1)) / (255 - c1), 255, 255);
else if (sparks[i].hue < c2) color = CRGB(0, (255 * sparks[i].hue) / c2, 0);
else color = CRGB(0, 255, (255 * (sparks[i].hue - c2)) / (c1 - c2));
break;
case 1: // Random-ish
if (sparks[i].hue > c1) color = CRGB(random8(180), random8(180), (255 * (sparks[i].hue - c1)) / (255 - c1));
else if (sparks[i].hue < c2) color = CRGB((255 * sparks[i].hue) / c2, random8(8), random8(8));
else color = CRGB(random8(80), (255 * (sparks[i].hue - c2)) / (c1 - c2), random8(18));
break;
case 2: // Red/Yellow/White
if (sparks[i].hue > c1) color = CRGB(255, 255, (255 * (sparks[i].hue - c1)) / (255 - c1));
else if (sparks[i].hue < c2) color = CRGB((255 * sparks[i].hue) / c2, 0, 0);
else color = CRGB(255, (255 * (sparks[i].hue - c2)) / (c1 - c2), 0);
break;
case 3: // Blue/Purple/White
if (sparks[i].hue > c1) color = CRGB(255, (255 * (sparks[i].hue - c1)) / (255 - c1), 255);
else if (sparks[i].hue < c2) color = CRGB(0, 0, (255 * sparks[i].hue) / c2);
else color = CRGB((255 * (sparks[i].hue - c2)) / (c1 - c2), 0, 255);
break;
case 4: // Random-ish 2
if (sparks[i].hue > c1) color = CRGB((255 * (sparks[i].hue - c1)) / (255 - c1), random8(sparks[i].hue + c2), random8(150));
else if (sparks[i].hue < c2) color = CRGB(random16(5, 10), (255 * sparks[i].hue) / c2, random8(2) > 1);
else color = CRGB(random8(c2), random8(90), (255 * (sparks[i].hue - c2)) / (c1 - c2));
break;
case 5: // Classic Red/Yellow with Glitter
if (sparks[i].hue > c1) color = CRGB(255, 255, (255 * (sparks[i].hue - c1)) / (255 - c1));
else if (sparks[i].hue < c2) color = CRGB((255 * sparks[i].hue) / c2, 0, 0);
else color = CRGB(255, (255 * (sparks[i].hue - c2)) / (c1 - c2), 0);
if(random8() < 10) color += CRGB::White; // Glitter
break;
}
leds[currentPos] += color.nscale8_video(sparks[i].brightness);
}
}
}
void loop() {
if (millis() - lastBurstTime > nextBurstInterval) {
createNewExplosion();
lastBurstTime = millis();
nextBurstInterval = random(1000, 3001);
}
updateAndDrawSparks();
FastLED.show();
FastLED.delay(16); // ~60 frames per second
}