2D Fireworks (enhanced 1D version).

Transposed Fire 2012 on matrix.
This commit is contained in:
Blaz Kristan 2022-05-22 17:20:06 +02:00
parent 8c31904838
commit e7c9b5a4f0

View File

@ -1927,46 +1927,47 @@ static const char *_data_FX_MODE_PALETTE PROGMEM = "Palette@!,;1,2,3;!";
uint16_t WS2812FX::mode_fire_2012() uint16_t WS2812FX::mode_fire_2012()
{ {
uint16_t height = SEGMENT.virtualWidth(); // same as SEGLEN on 1D
uint16_t width = SEGMENT.virtualHeight(); // they are actually transposed for the effect purpose to support 1D as well as 2D
uint32_t it = now >> 5; //div 32 uint32_t it = now >> 5; //div 32
uint16_t nFlames = SEGMENT.virtualHeight(); uint16_t q = width>>2; // a quarter of segment height
uint16_t q = nFlames>>2; // a quarter of segment height
if (!SEGENV.allocateData(SEGLEN*nFlames)) return mode_static(); //allocation failed if (!SEGENV.allocateData(height*width)) return mode_static(); //allocation failed
byte* heat = SEGENV.data; byte* heat = SEGENV.data;
if (it != SEGENV.step) { if (it != SEGENV.step) {
SEGENV.step = it; SEGENV.step = it;
uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels uint8_t ignition = max(3,height/10); // ignition area: 10% of segment length or minimum 3 pixels
for (uint16_t f = 0; f < nFlames; f++) { for (uint16_t f = 0; f < width; f++) {
// Step 1. Cool down every cell a little // Step 1. Cool down every cell a little
for (uint16_t i = 0; i < SEGLEN; i++) { for (uint16_t i = 0; i < height; i++) {
uint8_t cool = (((20 + SEGMENT.speed /3) * 10) / SEGLEN); uint8_t cool = (((20 + SEGMENT.speed /3) * 10) / height);
// 2D enhancement: cool sides of the flame a bit more // 2D enhancement: cool sides of the flame a bit more
if (nFlames>5) { if (width>5) {
if (f < q) qadd8(cool, (uint16_t)((cool * (q-f))/nFlames)); // cool segment sides a bit more if (f < q) cool = qadd8(cool, (uint16_t)((cool * (q-f))/width)); // cool segment sides a bit more
if (f > 3*q) qadd8(cool, (uint16_t)((cool * (nFlames-f))/nFlames)); // cool segment sides a bit more if (f > 3*q) cool = qadd8(cool, (uint16_t)((cool * (width-f))/width)); // cool segment sides a bit more
} }
uint8_t temp = qsub8(heat[i+SEGLEN*f], random8(0, cool + 2)); uint8_t temp = qsub8(heat[i+height*f], random8(0, cool + 2));
heat[i+SEGLEN*f] = (temp==0 && i<ignition) ? 16 : temp; // prevent ignition area from becoming black heat[i+height*f] = (temp==0 && i<ignition) ? 16 : temp; // prevent ignition area from becoming black
} }
// Step 2. Heat from each cell drifts 'up' and diffuses a little // Step 2. Heat from each cell drifts 'up' and diffuses a little
for (uint16_t k= SEGLEN -1; k > 1; k--) { for (uint16_t k= height -1; k > 1; k--) {
heat[k+SEGLEN*f] = (heat[k+SEGLEN*f - 1] + (heat[k+SEGLEN*f - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 heat[k+height*f] = (heat[k+height*f - 1] + (heat[k+height*f - 2]<<1) ) / 3; // heat[k-2] multiplied by 2
} }
// Step 3. Randomly ignite new 'sparks' of heat near the bottom // Step 3. Randomly ignite new 'sparks' of heat near the bottom
if (random8() <= SEGMENT.intensity) { if (random8() <= SEGMENT.intensity) {
uint8_t y = random8(ignition); uint8_t y = random8(ignition);
if (y < SEGLEN) heat[y+SEGLEN*f] = qadd8(heat[y+SEGLEN*f], random8(160,255)); if (y < height) heat[y+height*f] = qadd8(heat[y+height*f], random8(160,255));
} }
// Step 4. Map from heat cells to LED colors // Step 4. Map from heat cells to LED colors
for (uint16_t j = 0; j < SEGLEN; j++) { for (uint16_t j = 0; j < height; j++) {
CRGB color = ColorFromPalette(currentPalette, MIN(heat[j+SEGLEN*f],240), 255, LINEARBLEND); CRGB color = ColorFromPalette(currentPalette, MIN(heat[j+height*f],240), 255, LINEARBLEND);
if (isMatrix) setPixelColorXY(j, f, color); if (isMatrix) setPixelColorXY(f, j, color);
else setPixelColor(j, color); else setPixelColor(j, color);
} }
} }
@ -2881,8 +2882,8 @@ static const char *_data_FX_MODE_GLITTER PROGMEM = "Glitter";
//each needs 11 bytes //each needs 11 bytes
//Spark type is used for popcorn, 1D fireworks, and drip //Spark type is used for popcorn, 1D fireworks, and drip
typedef struct Spark { typedef struct Spark {
float pos; float pos, posX;
float vel; float vel, velX;
uint16_t col; uint16_t col;
uint8_t colIndex; uint8_t colIndex;
} spark; } spark;
@ -3174,9 +3175,13 @@ static const char *_data_FX_MODE_STARBURST PROGMEM = "Fireworks Starburst";
/* /*
* Exploding fireworks effect * Exploding fireworks effect
* adapted from: http://www.anirama.com/1000leds/1d-fireworks/ * adapted from: http://www.anirama.com/1000leds/1d-fireworks/
* adapted for 2D WLED by blazoncek (Blaz Kristan)
*/ */
uint16_t WS2812FX::mode_exploding_fireworks(void) uint16_t WS2812FX::mode_exploding_fireworks(void)
{ {
uint16_t height = SEGMENT.virtualWidth(); // same as SEGLEN on 1D
uint16_t width = SEGMENT.virtualHeight(); // they are actually transposed for the effect purpose to support 1D as well as 2D
//allocate segment data //allocate segment data
uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640
uint8_t segs = getActiveSegmentsNum(); uint8_t segs = getActiveSegmentsNum();
@ -3184,7 +3189,7 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
if (segs <= (MAX_NUM_SEGMENTS /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs if (segs <= (MAX_NUM_SEGMENTS /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs
int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg
uint16_t numSparks = min(2 + (SEGLEN >> 1), maxSparks); uint16_t numSparks = min(2 + (height >> 1), maxSparks);
uint16_t dataSize = sizeof(spark) * numSparks; uint16_t dataSize = sizeof(spark) * numSparks;
if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed
float *dying_gravity = reinterpret_cast<float*>(SEGENV.data + dataSize); float *dying_gravity = reinterpret_cast<float*>(SEGENV.data + dataSize);
@ -3195,36 +3200,36 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
SEGENV.aux1 = dataSize; SEGENV.aux1 = dataSize;
} }
fill(BLACK); //fill(BLACK);
fade_out(252);
bool actuallyReverse = SEGMENT.getOption(SEG_OPTION_REVERSED);
//have fireworks start in either direction based on intensity
SEGMENT.setOption(SEG_OPTION_REVERSED, SEGENV.step);
Spark* sparks = reinterpret_cast<Spark*>(SEGENV.data); Spark* sparks = reinterpret_cast<Spark*>(SEGENV.data);
Spark* flare = sparks; //first spark is flare data Spark* flare = sparks; //first spark is flare data
float gravity = -0.0004 - (SEGMENT.speed/800000.0); // m/s/s float gravity = -0.0004 - (SEGMENT.speed/800000.0); // m/s/s
gravity *= SEGLEN; gravity *= height;
if (SEGENV.aux0 < 2) { //FLARE if (SEGENV.aux0 < 2) { //FLARE
if (SEGENV.aux0 == 0) { //init flare if (SEGENV.aux0 == 0) { //init flare
flare->pos = 0; flare->pos = 0;
flare->posX = isMatrix ? random16(width) : (SEGMENT.intensity > random8()); // will enable random firing side on 1D
uint16_t peakHeight = 75 + random8(180); //0-255 uint16_t peakHeight = 75 + random8(180); //0-255
peakHeight = (peakHeight * (SEGLEN -1)) >> 8; peakHeight = (peakHeight * (height -1)) >> 8;
flare->vel = sqrt(-2.0 * gravity * peakHeight); flare->vel = sqrt(-2.0 * gravity * peakHeight);
flare->velX = isMatrix ? (random8(6)-3)/23 : 0; // no X velocity on 1D
flare->col = 255; //brightness flare->col = 255; //brightness
SEGENV.aux0 = 1; SEGENV.aux0 = 1;
} }
// launch // launch
if (flare->vel > 12 * gravity) { if (flare->vel > 12 * gravity) {
// flare // flare
setPixelColor(int(flare->pos),flare->col,flare->col,flare->col); if (isMatrix) setPixelColorXY(int(flare->posX), int(flare->pos), flare->col, flare->col, flare->col);
else setPixelColor(int(flare->posX) ? height-int(flare->pos)-1 : int(flare->pos), flare->col, flare->col, flare->col);
flare->pos += flare->vel; flare->pos += flare->vel;
flare->pos = constrain(flare->pos, 0, SEGLEN-1); flare->posX += flare->velX;
flare->pos = constrain(flare->pos, 0, height-1);
flare->posX = constrain(flare->posX, 0, width-isMatrix);
flare->vel += gravity; flare->vel += gravity;
flare->col -= 2; flare->col -= 2;
} else { } else {
@ -3244,11 +3249,14 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
if (SEGENV.aux0 == 2) { if (SEGENV.aux0 == 2) {
for (int i = 1; i < nSparks; i++) { for (int i = 1; i < nSparks; i++) {
sparks[i].pos = flare->pos; sparks[i].pos = flare->pos;
sparks[i].posX = flare->posX;
sparks[i].vel = (float(random16(0, 20000)) / 10000.0) - 0.9; // from -0.9 to 1.1 sparks[i].vel = (float(random16(0, 20000)) / 10000.0) - 0.9; // from -0.9 to 1.1
sparks[i].velX = isMatrix ? (float(random16(0, 20000)) / 10000.0) - 0.9 : 0; // from -0.9 to 1.1
sparks[i].col = 345;//abs(sparks[i].vel * 750.0); // set colors before scaling velocity to keep them bright sparks[i].col = 345;//abs(sparks[i].vel * 750.0); // set colors before scaling velocity to keep them bright
//sparks[i].col = constrain(sparks[i].col, 0, 345); //sparks[i].col = constrain(sparks[i].col, 0, 345);
sparks[i].colIndex = random8(); sparks[i].colIndex = random8();
sparks[i].vel *= flare->pos/SEGLEN; // proportional to height sparks[i].vel *= flare->pos/height; // proportional to height
sparks[i].velX *= isMatrix ? flare->posX/width : 0; // proportional to width
sparks[i].vel *= -gravity *50; sparks[i].vel *= -gravity *50;
} }
//sparks[1].col = 345; // this will be our known spark //sparks[1].col = 345; // this will be our known spark
@ -3259,10 +3267,13 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
if (sparks[1].col > 4) {//&& sparks[1].pos > 0) { // as long as our known spark is lit, work with all the sparks if (sparks[1].col > 4) {//&& sparks[1].pos > 0) { // as long as our known spark is lit, work with all the sparks
for (int i = 1; i < nSparks; i++) { for (int i = 1; i < nSparks; i++) {
sparks[i].pos += sparks[i].vel; sparks[i].pos += sparks[i].vel;
sparks[i].posX += sparks[i].velX;
sparks[i].vel += *dying_gravity; sparks[i].vel += *dying_gravity;
sparks[i].velX += isMatrix ? *dying_gravity : 0;
if (sparks[i].col > 3) sparks[i].col -= 4; if (sparks[i].col > 3) sparks[i].col -= 4;
if (sparks[i].pos > 0 && sparks[i].pos < SEGLEN) { if (sparks[i].pos > 0 && sparks[i].pos < height) {
if (isMatrix && !(sparks[i].posX > 0 && sparks[i].posX < width)) continue;
uint16_t prog = sparks[i].col; uint16_t prog = sparks[i].col;
uint32_t spColor = (SEGMENT.palette) ? color_wheel(sparks[i].colIndex) : SEGCOLOR(0); uint32_t spColor = (SEGMENT.palette) ? color_wheel(sparks[i].colIndex) : SEGCOLOR(0);
CRGB c = CRGB::Black; //HeatColor(sparks[i].col); CRGB c = CRGB::Black; //HeatColor(sparks[i].col);
@ -3274,10 +3285,11 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
c.g = qsub8(c.g, cooling); c.g = qsub8(c.g, cooling);
c.b = qsub8(c.b, cooling * 2); c.b = qsub8(c.b, cooling * 2);
} }
setPixelColor(int(sparks[i].pos), c.red, c.green, c.blue); if (isMatrix) setPixelColorXY(int(sparks[i].posX), int(sparks[i].pos), c.red, c.green, c.blue);
else setPixelColor(int(sparks[i].posX) ? height-int(sparks[i].pos)-1 : int(sparks[i].pos), c.red, c.green, c.blue);
} }
} }
*dying_gravity *= .99; // as sparks burn out they fall slower *dying_gravity *= .8; // as sparks burn out they fall slower
} else { } else {
SEGENV.aux0 = 6 + random8(10); //wait for this many frames SEGENV.aux0 = 6 + random8(10); //wait for this many frames
} }
@ -3285,12 +3297,9 @@ uint16_t WS2812FX::mode_exploding_fireworks(void)
SEGENV.aux0--; SEGENV.aux0--;
if (SEGENV.aux0 < 4) { if (SEGENV.aux0 < 4) {
SEGENV.aux0 = 0; //back to flare SEGENV.aux0 = 0; //back to flare
SEGENV.step = actuallyReverse ^ (SEGMENT.intensity > random8()); //decide firing side
} }
} }
SEGMENT.setOption(SEG_OPTION_REVERSED, actuallyReverse);
return FRAMETIME; return FRAMETIME;
} }
#undef MAX_SPARKS #undef MAX_SPARKS