#include <FastLED.h>
#define LED_PIN 2
#define NUM_LEDS 60
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define BRIGHTNESS 200
CRGB leds[NUM_LEDS];
// ------------------- Particle Class -------------------
struct Particle {
CRGB color;
float position;
float velocity;
unsigned long birthTime;
unsigned long lastUpdate;
bool active;
Particle() {
active = false;
}
void init(CRGB c, float pos, float maxSpeed) {
color = c;
position = pos;
velocity = (random(1000, 10000) / 10000.0f) * maxSpeed * 2 - maxSpeed;
birthTime = millis();
lastUpdate = millis();
active = true;
}
float age() {
return (millis() - birthTime) / 1000.0f; // seconds
}
void update() {
float deltaTime = (millis() - lastUpdate) / 1000.0f;
position += velocity * deltaTime;
lastUpdate = millis();
velocity -= (2 * velocity * deltaTime);
color.fadeToBlackBy(random(10, 25)); // random fading
}
};
// ------------------- Settings -------------------
float MaxSpeed = 30.0f; // max pixels per sec velocity
float NewParticleProb = 0.05f; // chance per frame of new burst
float ParticlePreignition = 0.0f;
float ParticleIgnition = 0.2f;
float ParticleHold = 0.0f;
float ParticleFade = 2.0f;
#define MAX_PARTICLES 60
Particle particles[MAX_PARTICLES];
// ------------------- Setup -------------------
void setup() {
FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
randomSeed(analogRead(0));
}
// ------------------- Loop -------------------
void loop() {
// Maybe spawn a new burst
if (random(0, 1000) < (NewParticleProb * 1000)) {
int startPos = random(0, NUM_LEDS);
CRGB c = CHSV(random8(), 255, 255);
int count = random(10, 50);
float multiplier = (random(0, 1000) / 1000.0f) * 3;
for (int i = 0; i < count; i++) {
// find a free slot
for (int j = 0; j < MAX_PARTICLES; j++) {
if (!particles[j].active) {
particles[j].init(c, startPos, MaxSpeed * multiplier);
break;
}
}
}
}
// Clear background
fill_solid(leds, NUM_LEDS, CRGB::Black);
// Update & draw particles
for (int i = 0; i < MAX_PARTICLES; i++) {
if (!particles[i].active) continue;
particles[i].update();
float age = particles[i].age();
CRGB c = particles[i].color;
float fade = 0;
if (age > ParticlePreignition && age < ParticleIgnition + ParticlePreignition) {
c = CRGB::White; // flash white
} else {
if (age < ParticlePreignition) {
fade = 1.0 - (age / ParticlePreignition);
} else {
age -= ParticlePreignition;
if (age < ParticleHold + ParticleIgnition) {
fade = 0.0;
} else if (age > ParticleHold + ParticleIgnition + ParticleFade) {
fade = 1.0;
} else {
age -= (ParticleHold + ParticleIgnition);
fade = (age / ParticleFade);
}
}
c.fadeToBlackBy((uint8_t)(fade * 255));
}
int pos = constrain((int)round(particles[i].position), 0, NUM_LEDS - 1);
leds[pos] += c;
// Kill particle if too old
if (age > (ParticleHold + ParticleIgnition + ParticleFade)) {
particles[i].active = false;
}
}
FastLED.show();
delay(16); // ~60fps
}