diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c430104eb6..b6c4a04603 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7909,18 +7909,18 @@ uint16_t mode_particlevortex(void) if (SEGMENT.call == 0) // initialization { if (!initParticleSystem(PartSys, NUMBEROFSOURCES)) - return mode_static(); // allocation failed; //allocation failed + return mode_static(); // allocation failed - //SEGMENT.aux0 = 0; // starting angle + //SEGMENT.aux0 = 0; // starting angle SEGMENT.aux1 = 0x01; // check flags - #ifdef ESP8266 + #ifdef ESP8266 PartSys->setMotionBlur(150); #else PartSys->setMotionBlur(100); #endif uint8_t numSprays = min(PartSys->numSources, (uint8_t)NUMBEROFSOURCES); for (i = 0; i < numSprays; i++) - { + { PartSys->sources[i].source.x = (PartSys->maxX + 1) >> 1; // center PartSys->sources[i].source.y = (PartSys->maxY + 1) >> 1; // center PartSys->sources[i].maxLife = 900; @@ -7939,8 +7939,8 @@ uint16_t mode_particlevortex(void) PartSys->updateSystem(); // update system properties (dimensions and data pointers) uint8_t spraycount = min(PartSys->numSources, (uint8_t)(1 + (SEGMENT.custom1 >> 5))); // number of sprays to display, 1-8 - #ifdef ESP8266 - for (i = 1; i < 4; i++) //need static particles in the center to reduce blinking (would be black every other frame without this hack), just set them there fixed + #ifdef ESP8266 + for (i = 1; i < 4; i++) // need static particles in the center to reduce blinking (would be black every other frame without this hack), just set them there fixed { PartSys->particles[PartSys->numParticles - i].x = (PartSys->maxX + 1) >> 1; // center PartSys->particles[PartSys->numParticles - i].y = (PartSys->maxY + 1) >> 1; // center @@ -7948,7 +7948,7 @@ uint16_t mode_particlevortex(void) PartSys->particles[PartSys->numParticles - i].ttl = 255; //set alive } #endif - if (SEGMENT.check1 != (SEGMENT.aux1 & 0x01) || SEGMENT.call == 0) //state change + if (SEGMENT.check1 != (SEGMENT.aux1 & 0x01) || SEGMENT.call == 0) // state change { if (SEGMENT.check1) SEGMENT.aux1 |= 0x01; //set the flag @@ -7972,7 +7972,7 @@ uint16_t mode_particlevortex(void) // set rotation direction and speed // can use direction flag to determine current direction bool direction = SEGMENT.check2; //no automatic direction change, set it to flag - int32_t currentspeed = (int32_t)SEGMENT.step; // make a signed integer out of step + int32_t currentspeed = (int32_t)SEGMENT.step; // make a signed integer out of step if (SEGMENT.custom2 > 0) // automatic direction change enabled { @@ -7989,8 +7989,8 @@ uint16_t mode_particlevortex(void) SEGMENT.aux1 |= 0x04; // set the update flag (for random interval update) if (direction) SEGMENT.aux1 &= ~0x02; // clear the direction flag - else - SEGMENT.aux1 |= 0x02; // set the direction flag + else + SEGMENT.aux1 |= 0x02; // set the direction flag } } @@ -8006,7 +8006,7 @@ uint16_t mode_particlevortex(void) speedincrement = 1; } - currentspeed += speedincrement; + currentspeed += speedincrement; SEGMENT.aux0 += currentspeed; SEGMENT.step = (uint32_t)currentspeed; //save it back @@ -8055,14 +8055,14 @@ uint16_t mode_particlefireworks(void) { if (!initParticleSystem(PartSys, NUMBEROFSOURCES, true)) // init with advanced particle properties return mode_static(); // allocation failed - PartSys->setKillOutOfBounds(true); //out of bounds particles dont return (except on top, taken care of by gravity setting) - PartSys->setWallHardness(100); //ground bounce is fixed + PartSys->setKillOutOfBounds(true); //out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setWallHardness(100); //ground bounce is fixed numRockets = min(PartSys->numSources, (uint8_t)NUMBEROFSOURCES); for (j = 0; j < numRockets; j++) { PartSys->sources[j].source.ttl = 500 * j; // first rocket starts immediately, others follow soon PartSys->sources[j].source.vy = -1; // at negative speed, no particles are emitted and if rocket dies, it will be relaunched - } + } } else PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS @@ -8075,9 +8075,9 @@ uint16_t mode_particlefireworks(void) PartSys->updateSystem(); // update system properties (dimensions and data pointers) numRockets = min(PartSys->numSources, (uint8_t)NUMBEROFSOURCES); - PartSys->setWrapX(SEGMENT.check1); + PartSys->setWrapX(SEGMENT.check1); PartSys->setBounceY(SEGMENT.check2); - PartSys->setGravity(map(SEGMENT.custom3,0,31,0,10)); // todo: make it a slider to adjust + PartSys->setGravity(map(SEGMENT.custom3,0,31,0,10)); // check each rocket's state and emit particles according to its state: moving up = emit exhaust, at top = explode; falling down = standby time uint32_t emitparticles; // number of particles to emit for each rocket's state @@ -8105,7 +8105,7 @@ uint16_t mode_particlefireworks(void) else // speed is zero, explode! { #ifdef ESP8266 - emitparticles = random16(SEGMENT.intensity >> 3) + (SEGMENT.intensity >> 3) + 5; // defines the size of the explosion + emitparticles = random16(SEGMENT.intensity >> 3) + (SEGMENT.intensity >> 3) + 5; // defines the size of the explosion #else emitparticles = random16(SEGMENT.intensity >> 2) + (SEGMENT.intensity >> 2) + 5; // defines the size of the explosion #endif @@ -8116,7 +8116,7 @@ uint16_t mode_particlefireworks(void) speed = 2 + random16(3); currentspeed = speed; counter = 0; - angleincrement = 2730 + random16(5461); // minimum 15° (=2730), + random(30°) (=5461) + angleincrement = 2730 + random16(5461); // minimum 15° (=2730), + random(30°) (=5461) angle = random16(); // random start angle speedvariation = angle & 0x01; // 0 or 1, no need for a new random number // calculate the number of particles to make complete circles @@ -8149,7 +8149,7 @@ uint16_t mode_particlefireworks(void) else { /* - //TODO: this does not look good. adjust or remove completely + //TODO: this does not look good. adjust or remove completely if( PartSys->sources[j].source.vy < 0) //explosion is ongoing { if(i < (emitparticles>>2)) //set 1/4 of particles to larger size @@ -8167,7 +8167,7 @@ uint16_t mode_particlefireworks(void) } if(i == 0) //no particles emitted, this rocket is falling PartSys->sources[j].source.y = 1000; // set position up high so gravity wont pull it to the ground and bounce it (vy MUST stay negative until relaunch) - circularexplosion = false; //reset for next rocket + circularexplosion = false; // reset for next rocket } // update the rockets, set the speed state @@ -8192,8 +8192,8 @@ uint16_t mode_particlefireworks(void) // reinitialize rocket PartSys->sources[j].source.y = PS_P_RADIUS<<1; // start from bottom PartSys->sources[j].source.x = random(PartSys->maxX >> 2, PartSys->maxX >> 1); // centered half - PartSys->sources[j].source.vy = random16(SEGMENT.custom1 >> 3) + 5; // rocket speed depends also on rocket fuse - PartSys->sources[j].source.vx = random(-3,3); //i.e. not perfectly straight up + PartSys->sources[j].source.vy = random16(SEGMENT.custom1 >> 3) + 5; // rocket speed depends also on rocket fuse + PartSys->sources[j].source.vx = random(-3,3); // not perfectly straight up PartSys->sources[j].source.sat = 30; // low saturation -> exhaust is off-white PartSys->sources[j].source.ttl = random16(SEGMENT.custom1) + (SEGMENT.custom1 >> 1); // sets explosion height (rockets explode at the top if set too high as paticle update set speed to zero if moving out of matrix) PartSys->sources[j].maxLife = 40; // exhaust particle life @@ -8204,7 +8204,7 @@ uint16_t mode_particlefireworks(void) } } - PartSys->update(); // update and render + PartSys->update(); // update and render return FRAMETIME; } #undef NUMBEROFSOURCES @@ -8222,25 +8222,25 @@ uint16_t mode_particlevolcano(void) if (SEGLEN == 1) return mode_static(); ParticleSystem *PartSys = NULL; - uint8_t numSprays; //note: so far only one tested but more is possible + uint8_t numSprays; // note: so far only one tested but more is possible uint32_t i = 0; - if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + if (SEGMENT.call == 0) // initialization { if (!initParticleSystem(PartSys, NUMBEROFSOURCES)) // init, no additional data needed return mode_static(); // allocation failed PartSys->setBounceY(true); PartSys->setGravity(); // enable with default gforce PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) - PartSys->setMotionBlur(190); //anable motion blur + PartSys->setMotionBlur(190); // anable motion blur numSprays = min(PartSys->numSources, (uint8_t)NUMBEROFSOURCES); // number of sprays for (i = 0; i < numSprays; i++) { - PartSys->sources[i].source.hue = random16(); - PartSys->sources[i].source.x = PartSys->maxX / (numSprays + 1) * (i + 1); //distribute evenly + PartSys->sources[i].source.hue = random16(); + PartSys->sources[i].source.x = PartSys->maxX / (numSprays + 1) * (i + 1); // distribute evenly PartSys->sources[i].maxLife = 300; // lifetime in frames PartSys->sources[i].minLife = 250; - PartSys->sources[i].source.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[i].source.collide = true; // seeded particles will collide (if enabled) PartSys->sources[i].source.perpetual = true; // source never dies } } @@ -8259,7 +8259,7 @@ uint16_t mode_particlevolcano(void) PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setColorByAge(SEGMENT.check1); PartSys->setBounceX(SEGMENT.check2); - PartSys->setWallHardness(SEGMENT.custom2); + PartSys->setWallHardness(SEGMENT.custom2); if (SEGMENT.check3) // collisions enabled PartSys->enableParticleCollisions(true, SEGMENT.custom2); // enable collisions and set particle collision hardness @@ -8273,16 +8273,13 @@ uint16_t mode_particlevolcano(void) { PartSys->sources[i].source.y = PS_P_RADIUS + 5; // reset to just above the lower edge that is allowed for bouncing particles, if zero, particles already 'bounce' at start and loose speed. PartSys->sources[i].source.vy = 0; //reset speed (so no extra particlesettin is required to keep the source 'afloat') - PartSys->sources[i].source.hue++; // = random16(); //change hue of spray source - // percycle = 1+(SEGMENT.intensity>>4); //how many particles are sprayed per cycle and how fast ist the color changing - PartSys->sources[i].source.vx > 0 ? SEGMENT.custom1 >> 4 : -(SEGMENT.custom1 >> 4); // set moving speed but keep the direction + PartSys->sources[i].source.hue++; // = random16(); //change hue of spray source (note: random does not look good) + PartSys->sources[i].source.vx = PartSys->sources[i].source.vx > 0 ? SEGMENT.custom1 >> 4 : -(SEGMENT.custom1 >> 4); // set moving speed but keep the direction given by PS PartSys->sources[i].vy = SEGMENT.speed >> 2; // emitting speed PartSys->sources[i].vx = 0; - PartSys->sources[i].var = SEGMENT.custom3 >> 1; // emiting variation = nozzle size (custom 3 goes from 0-31) - // spray[j].source.hue = random16(); //set random color for each particle (using palette) -> does not look good + PartSys->sources[i].var = SEGMENT.custom3 >> 1; // emiting variation = nozzle size (custom 3 goes from 0-31) PartSys->sprayEmit(PartSys->sources[i]); PartSys->particleMoveUpdate(PartSys->sources[i].source); //move the source (also applies gravity, which is corrected for above, that is a hack but easier than creating more particlesettings) - //Serial.println("emit"); } } @@ -8304,13 +8301,13 @@ uint16_t mode_particlefire(void) ParticleSystem *PartSys = NULL; uint32_t i; // index variable - uint32_t numFlames; // number of flames: depends on fire width. for a fire width of 16 pixels, about 25-30 flames give good results + uint32_t numFlames; // number of flames: depends on fire width. for a fire width of 16 pixels, about 25-30 flames give good results if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. { if (!initParticleSystem(PartSys, 25, 4)) //maximum number of source (PS will determine the exact number based on segment size) and need 4 additional bytes for time keeping (uint32_t lastcall) return mode_static(); // allocation failed; //allocation failed - SEGMENT.aux0 = random(); // aux0 is wind position (index) in the perlin noise + SEGMENT.aux0 = random16(); // aux0 is wind position (index) in the perlin noise numFlames = PartSys->numSources; DEBUG_PRINTF_P(PSTR("segment data ptr in fireFX %p\n"), SEGMENT.data); } @@ -8331,7 +8328,7 @@ uint16_t mode_particlefire(void) if (SEGMENT.speed < 100) //slow, limit FPS { uint32_t *lastcall = reinterpret_cast(PartSys->PSdataEnd); - uint32_t period = strip.now - *lastcall; + uint32_t period = strip.now - *lastcall; if (period < (uint32_t)map(SEGMENT.speed, 0, 99, 50, 10)) // limit to 90FPS - 20FPS { SEGMENT.call--; //skipping a frame, decrement the counter (on call0, this is never executed as lastcall is 0, so its fine to not check if >0) @@ -8352,21 +8349,21 @@ uint16_t mode_particlefire(void) { if (PartSys->sources[i].source.ttl > 0) { - PartSys->sources[i].source.ttl--; + PartSys->sources[i].source.ttl--; } else // flame source is dead { - // initialize new flame: set properties of source + // initialize new flame: set properties of source if (random16(20) == 0 || SEGMENT.call == 0) // from time to time, change flame position - { - PartSys->sources[i].source.x = (PartSys->maxX >> 1) - (spread>>1) + random(spread); // distribute randomly on chosen width + { + PartSys->sources[i].source.x = (PartSys->maxX >> 1) - (spread>>1) + random(spread); // distribute randomly on chosen width } PartSys->sources[i].source.y = -PS_P_RADIUS; // set the source below the frame PartSys->sources[i].source.ttl = 5 + random16((SEGMENT.custom1 * SEGMENT.custom1) >> 7) / (2 + (firespeed >> 4)); //'hotness' of fire, faster flames reduce the effect or flame height will scale too much with speed -> new, this works! PartSys->sources[i].maxLife = random16(7) + 13; // defines flame height together with the vy speed, vy speed*maxlife/PS_P_RADIUS is the average flame height PartSys->sources[i].minLife = 4; - PartSys->sources[i].vx = (int8_t)random(-3, 3); // emitting speed (sideways) - PartSys->sources[i].vy = 5 + (firespeed >> 2); // emitting speed (upwards) -> this is good + PartSys->sources[i].vx = (int8_t)random(-3, 3); // emitting speed (sideways) + PartSys->sources[i].vy = 5 + (firespeed >> 2); // emitting speed (upwards) -> this is good PartSys->sources[i].var = (random16(1 + (firespeed >> 5)) + 2); // speed variation around vx,vy (+/- var) } @@ -8375,7 +8372,7 @@ uint16_t mode_particlefire(void) if (SEGMENT.call & 0x01) // update noise position every second frames, also add wind { SEGMENT.aux0++; // position in the perlin noise matrix for wind generation - if (SEGMENT.call & 0x02) //every third frame + if (SEGMENT.call & 0x02) // every third frame SEGMENT.aux1++; // move in noise y direction so noise does not repeat as often // add wind force to all particles int8_t windspeed = ((int16_t)(inoise8(SEGMENT.aux0, SEGMENT.aux1) - 127) * SEGMENT.custom2) >> 7; @@ -8403,7 +8400,7 @@ uint16_t mode_particlefire(void) for(i=0; i < percycle; i++) { j = (j + 1) % numFlames; - PartSys->flameEmit(PartSys->sources[j]); + PartSys->flameEmit(PartSys->sources[j]); } PartSys->updateFire(SEGMENT.intensity); // update and render the fire @@ -8427,11 +8424,11 @@ uint16_t mode_particlepit(void) if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. { - if (!initParticleSystem(PartSys, 1, 0, true)) //init, request one source (actually dont really need one TODO: test if using zero sources also works) + if (!initParticleSystem(PartSys, 1, 0, true)) // init, request one source (actually dont really need one TODO: test if using zero sources also works) return mode_static(); // allocation failed; //allocation failed PartSys->setKillOutOfBounds(true); - PartSys->setGravity(); //enable with default gravity - PartSys->setUsedParticles((PartSys->numParticles*3)/2); //use 2/3 of available particles + PartSys->setGravity(); // enable with default gravity + PartSys->setUsedParticles((PartSys->numParticles*3)/2); // use 2/3 of available particles } else PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS @@ -8444,8 +8441,8 @@ uint16_t mode_particlepit(void) PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setWrapX(SEGMENT.check1); PartSys->setBounceX(SEGMENT.check2); - PartSys->setBounceY(SEGMENT.check3); - PartSys->setWallHardness(min(SEGMENT.custom2, (uint8_t)150)); //limit to 100 min (if collisions are disabled, still want bouncy) + PartSys->setBounceY(SEGMENT.check3); + PartSys->setWallHardness(min(SEGMENT.custom2, (uint8_t)150)); // limit to 100 min (if collisions are disabled, still want bouncy) if (SEGMENT.custom2>0) { PartSys->enableParticleCollisions(true, SEGMENT.custom2); // enable collisions and set particle collision hardness @@ -8454,7 +8451,7 @@ uint16_t mode_particlepit(void) PartSys->enableParticleCollisions(false); } - uint32_t i; + uint32_t i; if (SEGMENT.call % (128 - (SEGMENT.intensity >> 1)) == 0 && SEGMENT.intensity > 0) // every nth frame emit particles, stop emitting if set to zero { for (i = 0; i < PartSys->usedParticles; i++) // emit particles @@ -8467,19 +8464,19 @@ uint16_t mode_particlepit(void) PartSys->particles[i].y = (PartSys->maxY<<1); // particles appear somewhere above the matrix, maximum is double the height PartSys->particles[i].vx = (int16_t)random(SEGMENT.speed >> 1) - (SEGMENT.speed >> 2); // side speed is +/- PartSys->particles[i].vy = map(SEGMENT.speed, 0, 255, -5, -100); // downward speed - PartSys->particles[i].hue = random16(); // set random color - PartSys->particles[i].collide = true; //enable collision for particle + PartSys->particles[i].hue = random16(); // set random color + PartSys->particles[i].collide = true; // enable collision for particle PartSys->particles[i].sat = ((SEGMENT.custom3) << 3) + 7; - //set particle size + // set particle size if(SEGMENT.custom1 == 255) - { - PartSys->setParticleSize(0); //set global size to zero - PartSys->advPartProps[i].size = random(SEGMENT.custom1); //set each particle to random size + { + PartSys->setParticleSize(0); // set global size to zero + PartSys->advPartProps[i].size = random(SEGMENT.custom1); // set each particle to random size } else { - PartSys->setParticleSize(SEGMENT.custom1); //set global size - PartSys->advPartProps[i].size = 0; //use global size + PartSys->setParticleSize(SEGMENT.custom1); // set global size + PartSys->advPartProps[i].size = 0; // use global size } break; // emit only one particle per round } @@ -8492,7 +8489,7 @@ uint16_t mode_particlepit(void) //if (SEGMENT.call % (3 + (SEGMENT.custom2 >> 2)) == 0) //if (SEGMENT.call % (3 + (SEGMENT.speed >> 2)) == 0) - if (SEGMENT.call % 6 == 0)// (3 + max(3, (SEGMENT.speed >> 2))) == 0) //note: if friction is too low, hard particles uncontrollably 'wander' left and right if wrapX is enabled + if (SEGMENT.call % 6 == 0)// (3 + max(3, (SEGMENT.speed >> 2))) == 0) // note: if friction is too low, hard particles uncontrollably 'wander' left and right if wrapX is enabled PartSys->applyFriction(frictioncoefficient); PartSys->update(); // update and render @@ -8555,16 +8552,16 @@ uint16_t mode_particlewaterfall(void) { if (!initParticleSystem(PartSys, 12)) // init, request 12 sources, no additional data needed return mode_static(); // allocation failed; //allocation failed - PartSys->setGravity(); // enable with default gforce + PartSys->setGravity(); // enable with default gforce PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) - PartSys->setMotionBlur(190); //anable motion blur + PartSys->setMotionBlur(190); // anable motion blur numSprays = min((uint32_t)PartSys->numSources, min(PartSys->maxXpixel/5, (uint32_t)2)); // number of sprays for (i = 0; i < numSprays; i++) { PartSys->sources[i].source.hue = random16(); PartSys->sources[i].source.collide = true; // seeded particles will collide #ifdef ESP8266 - PartSys->sources[i].maxLife = 250; // lifetime in frames (ESP8266 has less particles, make them short lived to keep the water flowing) + PartSys->sources[i].maxLife = 250; // lifetime in frames (ESP8266 has less particles, make them short lived to keep the water flowing) PartSys->sources[i].minLife = 100; #else PartSys->sources[i].maxLife = 400; // lifetime in frames @@ -8598,7 +8595,7 @@ uint16_t mode_particlewaterfall(void) for (i = 0; i < numSprays; i++) { - PartSys->sources[i].source.hue++; //change hue of spray source + PartSys->sources[i].source.hue++; // change hue of spray source } if (SEGMENT.call % (9 - (SEGMENT.intensity >> 5)) == 0 && SEGMENT.intensity > 0) // every nth frame, cycle color and emit particles, do not emit if intensity is zero @@ -8643,20 +8640,20 @@ uint16_t mode_particlebox(void) if (!initParticleSystem(PartSys, 1)) // init return mode_static(); // allocation failed PartSys->setBounceX(true); - PartSys->setBounceY(true); - //set max number of particles and save to aux1 for later + PartSys->setBounceY(true); + // set max number of particles and save to aux1 for later #ifdef ESP8266 SEGMENT.aux1 = min((uint16_t)((PartSys->maxXpixel * PartSys->maxYpixel) >> 1), PartSys->numParticles); #else - SEGMENT.aux1 = min((uint16_t)((PartSys->maxXpixel * PartSys->maxYpixel)), PartSys->numParticles); //max number of particles - #endif + SEGMENT.aux1 = min((uint16_t)((PartSys->maxXpixel * PartSys->maxYpixel)), PartSys->numParticles); // max number of particles + #endif for (i = 0; i < SEGMENT.aux1; i++) { - PartSys->particles[i].ttl = 500; //set all particles alive (not all are rendered though) - PartSys->particles[i].perpetual = true; //never die - PartSys->particles[i].hue = i * 3; // color range + PartSys->particles[i].ttl = 500; // set all particles alive (not all are rendered though) + PartSys->particles[i].perpetual = true; // never die + PartSys->particles[i].hue = i * 3; // color range PartSys->particles[i].x = map(i, 0, SEGMENT.aux1, 1, PartSys->maxX); // distribute along x according to color - PartSys->particles[i].y = random16(PartSys->maxY >> 2); //bottom quarter + PartSys->particles[i].y = random16(PartSys->maxY >> 2); // bottom quarter PartSys->particles[i].collide = true; // all particles collide } SEGMENT.aux0 = rand(); // position in perlin noise @@ -8674,7 +8671,7 @@ uint16_t mode_particlebox(void) PartSys->setWallHardness(min(SEGMENT.custom2, (uint8_t)200)); // wall hardness is 200 or more PartSys->enableParticleCollisions(true, max(2, (int)SEGMENT.custom2)); // enable collisions and set particle collision hardness - PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 10, SEGMENT.aux1)); //aux1 holds max number of particles to use + PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 10, SEGMENT.aux1)); // aux1 holds max number of particles to use if (SEGMENT.call % (((255 - SEGMENT.speed) >> 6) + 1) == 0 && SEGMENT.speed > 0) // how often the force is applied depends on speed setting @@ -8682,12 +8679,12 @@ uint16_t mode_particlebox(void) int32_t xgravity; int32_t ygravity; int32_t increment = (SEGMENT.speed >> 6) + 1; - if(SEGMENT.check2) //direction + if(SEGMENT.check2) // direction SEGMENT.aux0 += increment; // update counter else SEGMENT.aux0 -= increment; - if(SEGMENT.check1) //random, use perlin noise + if(SEGMENT.check1) // random, use perlin noise { xgravity = ((int16_t)inoise8(SEGMENT.aux0) - 127); ygravity = ((int16_t)inoise8(SEGMENT.aux0 + 10000) - 127); @@ -8695,12 +8692,12 @@ uint16_t mode_particlebox(void) xgravity = (xgravity * SEGMENT.custom1) / 128; ygravity = (ygravity * SEGMENT.custom1) / 128; } - else //go in a circle + else // go in a circle { xgravity = ((int32_t)(SEGMENT.custom1) * cos16(SEGMENT.aux0 << 8)) / 0xFFFF; ygravity = ((int32_t)(SEGMENT.custom1) * sin16(SEGMENT.aux0 << 8)) / 0xFFFF; } - if (SEGMENT.check3) //sloshing, y force is alwys downwards + if (SEGMENT.check3) // sloshing, y force is alwys downwards { if(ygravity > 0) ygravity = -ygravity; @@ -8732,8 +8729,8 @@ uint16_t mode_particleperlin(void) if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. { if (!initParticleSystem(PartSys, 1, 0, true)) // init with 1 source and advanced properties - return mode_static(); // allocation failed; //allocation failed - PartSys->setKillOutOfBounds(true); //should never happen, but lets make sure there are no stray particles + return mode_static(); // allocation failed; //allocation failed + PartSys->setKillOutOfBounds(true); // should never happen, but lets make sure there are no stray particles SEGMENT.aux0 = rand(); for (i = 0; i < PartSys->numParticles; i++) { @@ -8752,7 +8749,7 @@ uint16_t mode_particleperlin(void) PartSys->setWrapX(SEGMENT.check1); PartSys->setBounceX(!SEGMENT.check1); PartSys->setBounceY(true); - PartSys->setWallHardness(SEGMENT.custom1); //wall hardness + PartSys->setWallHardness(SEGMENT.custom1); // wall hardness PartSys->enableParticleCollisions(SEGMENT.check3, SEGMENT.custom1); // enable collisions and set particle collision hardness uint32_t displayparticles = map(SEGMENT.intensity, 0, 255, 10, PartSys->numParticles>>1); PartSys->setUsedParticles(displayparticles); @@ -8801,21 +8798,19 @@ uint16_t mode_particleimpact(void) ParticleSystem *PartSys = NULL; uint32_t i = 0; uint8_t MaxNumMeteors; - PSsettings meteorsettings;// = {0, 0, 0, 1, 0, 1, 0, 0}; // PS settings for meteors: bounceY and gravity enabled - meteorsettings.asByte = 0b00101000; - //uint8_t *settingsPtr = reinterpret_cast(&meteorsettings); // access settings as one byte (wmore efficient in code and speed) - //*settingsPtr = 0b00101000; // PS settings for meteors: bounceY and gravity enabled + PSsettings meteorsettings; + meteorsettings.asByte = 0b00101000; // PS settings for meteors: bounceY and gravity enabled if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. { if (!initParticleSystem(PartSys, NUMBEROFSOURCES)) // init, no additional data needed return mode_static(); // allocation failed; //allocation failed - PartSys->setKillOutOfBounds(false); //explosions out of frame ar allowed, set to true to save particles (TODO: better enable it in ESP8266?) - PartSys->setGravity(); //enable default gravity - PartSys->setBounceY(true); //always use ground bounce + PartSys->setKillOutOfBounds(false); // explosions out of frame ar allowed, set to true to save particles (TODO: better enable it in ESP8266?) + PartSys->setGravity(); // enable default gravity + PartSys->setBounceY(true); // always use ground bounce MaxNumMeteors = min(PartSys->numSources, (uint8_t)NUMBEROFSOURCES); for (i = 0; i < MaxNumMeteors; i++) - { + { PartSys->sources[i].source.y = 500; PartSys->sources[i].source.ttl = random16(20 * i); // set initial delay for meteors PartSys->sources[i].source.vy = 10; // at positive speeds, no particles are emitted and if particle dies, it will be relaunched @@ -8876,7 +8871,7 @@ uint16_t mode_particleimpact(void) { if (PartSys->sources[i].source.ttl) { - PartSys->sources[i].source.ttl--; //note: this saves an if statement, but moving down particles age twice + PartSys->sources[i].source.ttl--; // note: this saves an if statement, but moving down particles age twice if (PartSys->sources[i].source.vy < 0) //move down { PartSys->applyGravity(&PartSys->sources[i].source); @@ -8884,10 +8879,9 @@ uint16_t mode_particleimpact(void) // if source reaches the bottom, set speed to 0 so it will explode on next function call (handled above) if (PartSys->sources[i].source.y < PS_P_RADIUS<<1) // reached the bottom pixel on its way down - { + { PartSys->sources[i].source.vy = 0; // set speed zero so it will explode PartSys->sources[i].source.vx = 0; - //PartSys->sources[i].source.y = 5; // offset from ground so explosion happens not out of frame (!!!TODO: still needed? the class takes care of that) PartSys->sources[i].source.collide = true; #ifdef ESP8266 PartSys->sources[i].maxLife = 130; @@ -8901,7 +8895,7 @@ uint16_t mode_particleimpact(void) PartSys->sources[i].vy = (SEGMENT.custom1 >> 2); // emitting speed y PartSys->sources[i].var = (SEGMENT.custom1 >> 2); // speed variation around vx,vy (+/- var) } - } + } } else if (PartSys->sources[i].source.vy > 0) // meteor is exploded and time is up (ttl==0 and positive speed), relaunch it { @@ -8914,13 +8908,13 @@ uint16_t mode_particleimpact(void) PartSys->sources[i].source.ttl = 500; // long life, will explode at bottom PartSys->sources[i].source.collide = false; // trail particles will not collide PartSys->sources[i].maxLife = 60; // spark particle life - PartSys->sources[i].minLife = 20; + PartSys->sources[i].minLife = 20; PartSys->sources[i].vy = -9; // emitting speed (down) - PartSys->sources[i].var = 3; // speed variation around vx,vy (+/- var) + PartSys->sources[i].var = 3; // speed variation around vx,vy (+/- var) } } - PartSys->update(); // update and render + PartSys->update(); // update and render return FRAMETIME; } #undef NUMBEROFSOURCES @@ -8937,22 +8931,22 @@ uint16_t mode_particleattractor(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem *PartSys = NULL; uint32_t i = 0; - PSsettings sourcesettings;// = {0, 0, 1, 1, 0, 0, 0, 0}; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) - sourcesettings.asByte = 0b00001100; - PSparticle *attractor; //particle pointer to the attractor - if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. + PSsettings sourcesettings; + sourcesettings.asByte = 0b00001100; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) + PSparticle *attractor; // particle pointer to the attractor + if (SEGMENT.call == 0) // initialization { if (!initParticleSystem(PartSys, 1, 0, true)) // init using 1 source and advanced particle settings - return mode_static(); // allocation failed; //allocation failed + return mode_static(); // allocation failed //DEBUG_PRINTF_P(PSTR("sources in FX %p\n"), &PartSys->sources[0]); - PartSys->sources[0].source.hue = random16(); - PartSys->sources[0].source.vx = -7; //will collied with wall and get random bounce direction - PartSys->sources[0].source.collide = true; // seeded particles will collide + PartSys->sources[0].source.hue = random16(); + PartSys->sources[0].source.vx = -7; // will collied with wall and get random bounce direction + PartSys->sources[0].source.collide = true; // seeded particles will collide PartSys->sources[0].source.perpetual = true; //source does not age #ifdef ESP8266 - PartSys->sources[0].maxLife = 200; // lifetime in frames (ESP8266 has less particles) + PartSys->sources[0].maxLife = 200; // lifetime in frames (ESP8266 has less particles) PartSys->sources[0].minLife = 30; #else PartSys->sources[0].maxLife = 350; // lifetime in frames @@ -8968,7 +8962,7 @@ uint16_t mode_particleattractor(void) if (PartSys == NULL) { DEBUG_PRINT(F("ERROR: FX PartSys nullpointer")); - return mode_static(); // something went wrong, no data! (TODO: ask how to handle this so it always works) + return mode_static(); // something went wrong, no data! } // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) @@ -8980,11 +8974,10 @@ uint16_t mode_particleattractor(void) PartSys->enableParticleCollisions(true, map(SEGMENT.custom2, 1, 255, 120, 255)); // enable collisions and set particle collision hardness else PartSys->enableParticleCollisions(false); - uint16_t lastusedparticle = (PartSys->numParticles * 3) >> 2; //use 3/4 of particles + uint16_t lastusedparticle = (PartSys->numParticles * 3) >> 2; // use 3/4 of particles uint32_t displayparticles = map(SEGMENT.intensity, 0, 255, 10, lastusedparticle); PartSys->setUsedParticles(displayparticles); // set pointers - //attractor = reinterpret_cast(&PartSys->particles[lastusedparticle + 1]); attractor = &PartSys->particles[lastusedparticle + 1]; if(SEGMENT.call == 0) { @@ -8993,7 +8986,7 @@ uint16_t mode_particleattractor(void) attractor->ttl = 100; attractor->perpetual = true; } - //set attractor properties + // set attractor properties if (SEGMENT.check2) { if((SEGMENT.call % 3) == 0) // move slowly @@ -9007,17 +9000,15 @@ uint16_t mode_particleattractor(void) if (SEGMENT.call % 5 == 0) PartSys->sources[0].source.hue++; - SEGMENT.aux0 += 256; //emitting angle, one full turn in 255 frames (0xFFFF is 360°) + SEGMENT.aux0 += 256; // emitting angle, one full turn in 255 frames (0xFFFF is 360°) if (SEGMENT.call % 2 == 0) // alternate direction of emit - PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0, 12); - //PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0, SEGMENT.custom1 >> 4); + PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0, 12); else PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0 + 0x7FFF, 12); // emit at 180° as well - //PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0 + 0x7FFF, SEGMENT.custom1 >> 4); // apply force for(i = 0; i < displayparticles; i++) { - PartSys->pointAttractor(i, attractor, SEGMENT.speed, SEGMENT.check3); //TODO: there was a bug here, counters was always the same pointer, check if this works. + PartSys->pointAttractor(i, attractor, SEGMENT.speed, SEGMENT.check3); } if (SEGMENT.call % (33 - SEGMENT.custom3) == 0) PartSys->applyFriction(2); @@ -9029,8 +9020,7 @@ static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass /* -Particle Attractor, a particle attractor sits in the matrix center, a spray bounces around and seeds particles -uses inverse square law like in planetary motion +Particle Line Attractor, an idea that is not finished and not working Uses palette for particle color by DedeHai (Damian Schneider) */ @@ -9144,6 +9134,8 @@ uint16_t mode_particleattractor(void) } static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Emit Speed,Collisions,Friction,Color by Age,Move,Swallow;;!;2;pal=9,sx=100,ix=82,c1=190,c2=0,o1=0,o2=0,o3=0"; */ + + /* Particle Spray, just a particle spray with many parameters Uses palette for particle color @@ -9156,7 +9148,7 @@ uint16_t mode_particlespray(void) return mode_static(); ParticleSystem *PartSys = NULL; //uint8_t numSprays; - const uint8_t hardness = 200; //collision hardness is fixed + const uint8_t hardness = 200; // collision hardness is fixed if (SEGMENT.call == 0) // initialization { @@ -9186,7 +9178,7 @@ uint16_t mode_particlespray(void) PartSys->setBounceX(!SEGMENT.check2); PartSys->setWrapX(SEGMENT.check2); PartSys->setWallHardness(hardness); - PartSys->setGravity(8 * SEGMENT.check1); //enable gravity if checked (8 is default strength) + PartSys->setGravity(8 * SEGMENT.check1); // enable gravity if checked (8 is default strength) //numSprays = min(PartSys->numSources, (uint8_t)1); // number of sprays if (SEGMENT.check3) // collisions enabled @@ -9196,12 +9188,12 @@ uint16_t mode_particlespray(void) // change source properties if (SEGMENT.call % (11 - (SEGMENT.intensity / 25)) == 0) // every nth frame, cycle color and emit particles - { + { PartSys->sources[0].source.hue++; // = random16(); //change hue of spray source - // PartSys->sources[i].var = SEGMENT.custom3; // emiting variation = nozzle size (custom 3 goes from 0-32) + // PartSys->sources[i].var = SEGMENT.custom3; // emiting variation = nozzle size (custom 3 goes from 0-32) PartSys->sources[0].source.x = map(SEGMENT.custom1, 0, 255, 0, PartSys->maxX); - PartSys->sources[0].source.y = map(SEGMENT.custom2, 0, 255, 0, PartSys->maxY); - // spray[j].source.hue = random16(); //set random color for each particle (using palette) + PartSys->sources[0].source.y = map(SEGMENT.custom2, 0, 255, 0, PartSys->maxY); + // spray[j].source.hue = random16(); //set random color for each particle (using palette) PartSys->angleEmit(PartSys->sources[0], (256 - (((int32_t)SEGMENT.custom3 + 1) << 3)) << 8, SEGMENT.speed >> 2); } @@ -9227,8 +9219,8 @@ uint16_t mode_particleGEQ(void) if (SEGMENT.call == 0) // initialization { if (!initParticleSystem(PartSys, 1)) // init - return mode_static(); // allocation failed; //allocation failed - PartSys->setKillOutOfBounds(true); + return mode_static(); // allocation failed + PartSys->setKillOutOfBounds(true); PartSys->setUsedParticles((PartSys->numParticles * 3) / 2); // use 2/3 of available particles } else @@ -9241,7 +9233,7 @@ uint16_t mode_particleGEQ(void) } uint32_t i; - // set particle system properties + // set particle system properties PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setWrapX(SEGMENT.check1); PartSys->setBounceX(SEGMENT.check2); @@ -9260,9 +9252,6 @@ uint16_t mode_particleGEQ(void) uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255 //map the bands into 16 positions on x axis, emit some particles according to frequency loudness - //Idea: emit 20 particles at full loudness, can use a shift for that, for example shift by 4 or 5 - //in order to also emit particles for not so loud bands, get a bunch of particles based on frame counter and current loudness? - //implement it simply first, then add complexity... need to check what looks good i = 0; uint32_t bin; //current bin uint32_t binwidth = (PartSys->maxX + 1)>>4; //emit poisition variation for one bin (+/-) is equal to width/16 (for 16 bins) @@ -9292,20 +9281,20 @@ uint16_t mode_particleGEQ(void) { if (PartSys->particles[i].ttl == 0) // find a dead particle { - //set particle properties TODO: could also use the spray... - PartSys->particles[i].ttl = 20 + map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames - PartSys->particles[i].x = xposition + random16(binwidth) - (binwidth>>1); //position randomly, deviating half a bin width - PartSys->particles[i].y = PS_P_RADIUS; //tart at the bottom (PS_P_RADIUS is minimum position a particle is fully in frame) + //set particle properties TODO: could also use the spray... + PartSys->particles[i].ttl = 20 + map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames + PartSys->particles[i].x = xposition + random16(binwidth) - (binwidth>>1); // position randomly, deviating half a bin width + PartSys->particles[i].y = PS_P_RADIUS; // start at the bottom (PS_P_RADIUS is minimum position a particle is fully in frame) PartSys->particles[i].vx = random16(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation: +/- custom1/4 TODO: ok to use random16 here? PartSys->particles[i].vy = emitspeed; - PartSys->particles[i].hue = (bin<<4) + random16(17) - 8; // color from palette according to bin + PartSys->particles[i].hue = (bin<<4) + random16(17) - 8; // color from palette according to bin emitparticles--; } i++; } } - PartSys->update(); // update and render + PartSys->update(); // update and render return FRAMETIME; } static const char _data_FX_MODE_PARTICLEGEQ[] PROGMEM = "PS Equalizer@Speed,Intensity,Diverge,Bounce,Gravity,Cylinder,Walls,Floor;;!;2f;pal=0,sx=155,ix=200,c1=0,c2=128,o1=0,o2=0,o3=0"; @@ -9330,8 +9319,8 @@ uint16_t mode_particleghostrider(void) PartSys->sources[0].maxLife = 260; // lifetime in frames PartSys->sources[0].minLife = 250; PartSys->sources[0].source.x = random16(PartSys->maxX); - PartSys->sources[0].source.y = random16(PartSys->maxY); - SEGMENT.step = random(MAXANGLESTEP) - (MAXANGLESTEP>>1); //angle increment + PartSys->sources[0].source.y = random16(PartSys->maxY); + SEGMENT.step = random(MAXANGLESTEP) - (MAXANGLESTEP>>1); // angle increment } else PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS @@ -9342,7 +9331,7 @@ uint16_t mode_particleghostrider(void) return mode_static(); // something went wrong, no data! } - if(SEGMENT.intensity > 0) //spiraling + if(SEGMENT.intensity > 0) // spiraling { if(SEGMENT.aux1) { @@ -9362,7 +9351,7 @@ uint16_t mode_particleghostrider(void) PartSys->setMotionBlur(SEGMENT.custom1); PartSys->sources[0].var = SEGMENT.custom3 >> 1; - //color by age (PS color by age always starts with hue = 255 so cannot use that) + // color by age (PS 'color by age' always starts with hue = 255, don't want that here) if(SEGMENT.check1) { for(int i = 0; i < PartSys->usedParticles; i++) @@ -9371,25 +9360,25 @@ uint16_t mode_particleghostrider(void) } } - //enable/disable walls + // enable/disable walls ghostsettings.bounceX = SEGMENT.check2; ghostsettings.bounceY = SEGMENT.check2; - SEGMENT.aux0 += (int32_t)SEGMENT.step; //step is angle increment - uint16_t emitangle = SEGMENT.aux0 + 32767; //+180° + SEGMENT.aux0 += (int32_t)SEGMENT.step; // step is angle increment + uint16_t emitangle = SEGMENT.aux0 + 32767; // +180° int32_t speed = map(SEGMENT.speed, 0, 255, 12, 64); int8_t newvx = ((int32_t)cos16(SEGMENT.aux0) * speed) / (int32_t)32767; int8_t newvy = ((int32_t)sin16(SEGMENT.aux0) * speed) / (int32_t)32767; PartSys->sources[0].source.vx = ((int32_t)cos16(SEGMENT.aux0) * speed) / (int32_t)32767; - PartSys->sources[0].source.vy = ((int32_t)sin16(SEGMENT.aux0) * speed) / (int32_t)32767; - PartSys->sources[0].source.ttl = 500; //source never dies (note: setting 'perpetual' is not needed if replenished each frame) + PartSys->sources[0].source.vy = ((int32_t)sin16(SEGMENT.aux0) * speed) / (int32_t)32767; + PartSys->sources[0].source.ttl = 500; // source never dies (note: setting 'perpetual' is not needed if replenished each frame) PartSys->particleMoveUpdate(PartSys->sources[0].source, &ghostsettings); - //set head (steal one of the particles) + // set head (steal one of the particles) PartSys->particles[PartSys->usedParticles-1].x = PartSys->sources[0].source.x; PartSys->particles[PartSys->usedParticles-1].y = PartSys->sources[0].source.y; PartSys->particles[PartSys->usedParticles-1].ttl = 255; - PartSys->particles[PartSys->usedParticles-1].sat = 0; - //emit two particles + PartSys->particles[PartSys->usedParticles-1].sat = 0; //white + // emit two particles PartSys->angleEmit(PartSys->sources[0], emitangle, speed); PartSys->angleEmit(PartSys->sources[0], emitangle, speed); PartSys->sources[0].source.hue += SEGMENT.custom2 >> 3; @@ -9401,7 +9390,7 @@ static const char _data_FX_MODE_PARTICLEGHOSTRIDER[] PROGMEM = "PS Ghost Rider@S /* -PS Blobs: large particles bouncing around +PS Blobs: large particles bouncing around, changing size and form Uses palette for particle color by DedeHai (Damian Schneider) */ @@ -9414,10 +9403,9 @@ uint16_t mode_particleblobs(void) if (SEGMENT.call == 0) { if (!initParticleSystem(PartSys, 1, 0, true, true)) //init, request one source, no additional bytes, advanced size & size control (actually dont really need one TODO: test if using zero sources also works) - return mode_static(); // allocation failed; //allocation failed - //PartSys->setGravity(); //enable with default gravity + return mode_static(); // allocation failed PartSys->setBounceX(true); - PartSys->setBounceY(true); + PartSys->setBounceY(true); PartSys->setWallHardness(255); PartSys->setWallRoughness(255); PartSys->setCollisionHardness(255); @@ -9434,37 +9422,37 @@ uint16_t mode_particleblobs(void) PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setUsedParticles(min(PartSys->numParticles, (uint16_t)map(SEGMENT.intensity,0 ,255, 1, (PartSys->maxXpixel * PartSys->maxYpixel)>>4))); PartSys->enableParticleCollisions(SEGMENT.check2); - + for (uint32_t i = 0; i < PartSys->usedParticles; i++) // update particles { - if(SEGMENT.aux0 != SEGMENT.speed || PartSys->particles[i].ttl == 0) //speed changed or dead - { + if(SEGMENT.aux0 != SEGMENT.speed || PartSys->particles[i].ttl == 0) // speed changed or dead + { PartSys->particles[i].vx = (int16_t)random(-(SEGMENT.speed >> 2), SEGMENT.speed >> 2); PartSys->particles[i].vy = (int16_t)random(-(SEGMENT.speed >> 2), SEGMENT.speed >> 2); } - if(SEGMENT.aux1 != SEGMENT.custom1 || PartSys->particles[i].ttl == 0) //size changed or dead - PartSys->advPartSize[i].maxsize = 60 + (SEGMENT.custom1 >> 1) + random((SEGMENT.custom1 >> 2)); //set each particle to slightly randomized size + if(SEGMENT.aux1 != SEGMENT.custom1 || PartSys->particles[i].ttl == 0) // size changed or dead + PartSys->advPartSize[i].maxsize = 60 + (SEGMENT.custom1 >> 1) + random((SEGMENT.custom1 >> 2)); // set each particle to slightly randomized size - //PartSys->particles[i].perpetual = SEGMENT.check2; //infinite life if set + //PartSys->particles[i].perpetual = SEGMENT.check2; //infinite life if set if (PartSys->particles[i].ttl == 0) // find dead particle, renitialize - { + { PartSys->particles[i].ttl = 300 + random16(((uint16_t)SEGMENT.custom2 << 3) + 100); - PartSys->particles[i].x = random(PartSys->maxX); - PartSys->particles[i].y = random16(PartSys->maxY); - PartSys->particles[i].hue = random16(); // set random color - PartSys->particles[i].collide = true; //enable collision for particle - PartSys->advPartProps[i].size = 0; //start out small + PartSys->particles[i].x = random(PartSys->maxX); + PartSys->particles[i].y = random16(PartSys->maxY); + PartSys->particles[i].hue = random16(); // set random color + PartSys->particles[i].collide = true; // enable collision for particle + PartSys->advPartProps[i].size = 0; // start out small PartSys->advPartSize[i].asymmetry = random16(220); PartSys->advPartSize[i].asymdir = random16(255); - //set advanced size control properties + // set advanced size control properties PartSys->advPartSize[i].grow = true; PartSys->advPartSize[i].growspeed = 1 + random16(9); - PartSys->advPartSize[i].shrinkspeed = 1 + random16(9); - PartSys->advPartSize[i].wobblespeed = 1 + random(3); - } + PartSys->advPartSize[i].shrinkspeed = 1 + random16(9); + PartSys->advPartSize[i].wobblespeed = 1 + random(3); + } //PartSys->advPartSize[i].asymmetry++; - PartSys->advPartSize[i].pulsate = SEGMENT.check3; - PartSys->advPartSize[i].wobble = SEGMENT.check1; + PartSys->advPartSize[i].pulsate = SEGMENT.check3; + PartSys->advPartSize[i].wobble = SEGMENT.check1; } SEGMENT.aux0 = SEGMENT.speed; //write state back SEGMENT.aux1 = SEGMENT.custom1; @@ -9478,7 +9466,7 @@ static const char _data_FX_MODE_PARTICLEBLOBS[] PROGMEM = "PS Blobs@Speed,Blobs, /* - * Particle rotating GEQ + * Particle rotating GEQ (unfinished, basically works but needs more fine-tuning) * Particles sprayed from center with a rotating spray * Uses palette for particle color * by DedeHai (Damian Schneider) @@ -9528,8 +9516,8 @@ uint16_t mode_particlecenterGEQ(void) } for (i = 0; i < numSprays; i++) { - PartSys->sources[i].source.hue = i*16; //even color distribution - PartSys->sources[i].source.sat = 255; // set saturation + PartSys->sources[i].source.hue = i*16; // even color distribution + PartSys->sources[i].source.sat = 255; // set saturation PartSys->sources[i].source.x = (cols * PS_P_RADIUS) / 2; // center PartSys->sources[i].source.y = (rows * PS_P_RADIUS) / 2; // center PartSys->sources[i].source.vx = 0; diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index 3a8b4cf7ae..b7a132e139 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -6,7 +6,7 @@ LICENSE The MIT License (MIT) - Copyright (c) 2024 Damian Schneider + Copyright (c) 2024 Damian Schneider Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -22,13 +22,12 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ /* - Note on ESP32: using 32bit integer is faster than 16bit or 8bit, each operation takes on less instruction, can be testen on https://godbolt.org/ - it does not matter if using int, unsigned int, uint32_t or int32_t, the compiler will make int into 32bit - this should be used to optimize speed but not if memory is affected much + Note on ESP32: using 32bit integer is faster than 16bit or 8bit, each operation takes on less instruction, can be testen on https://godbolt.org/ + it does not matter if using int, unsigned int, uint32_t or int32_t, the compiler will make int into 32bit + this should be used to optimize speed but not if memory is affected much */ /* @@ -45,412 +44,412 @@ ParticleSystem::ParticleSystem(uint16_t width, uint16_t height, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced, bool sizecontrol) { - //Serial.println("PS Constructor"); - numSources = numberofsources; - numParticles = numberofparticles; // set number of particles in the array - usedParticles = numberofparticles; // use all particles by default - advPartProps = NULL; //make sure we start out with null pointers (just in case memory was not cleared) - advPartSize = NULL; - updatePSpointers(isadvanced, sizecontrol); // set the particle and sources pointer (call this before accessing sprays or particles) - setMatrixSize(width, height); - setWallHardness(255); // set default wall hardness to max - setWallRoughness(0); // smooth walls by default - setGravity(0); //gravity disabled by default - setParticleSize(0); // minimum size by default - motionBlur = 0; //no fading by default - emitIndex = 0; - - //initialize some default non-zero values most FX use - for (int i = 0; i < numSources; i++) - { - sources[i].source.sat = 255; //set saturation to max by default - sources[i].source.ttl = 1; //set source alive - } - for (int i = 0; i < numParticles; i++) - { - particles[i].sat = 255; // full saturation - } - //Serial.println("PS Constructor done"); + //Serial.println("PS Constructor"); + numSources = numberofsources; + numParticles = numberofparticles; // set number of particles in the array + usedParticles = numberofparticles; // use all particles by default + advPartProps = NULL; //make sure we start out with null pointers (just in case memory was not cleared) + advPartSize = NULL; + updatePSpointers(isadvanced, sizecontrol); // set the particle and sources pointer (call this before accessing sprays or particles) + setMatrixSize(width, height); + setWallHardness(255); // set default wall hardness to max + setWallRoughness(0); // smooth walls by default + setGravity(0); //gravity disabled by default + setParticleSize(0); // minimum size by default + motionBlur = 0; //no fading by default + emitIndex = 0; + + //initialize some default non-zero values most FX use + for (int i = 0; i < numSources; i++) + { + sources[i].source.sat = 255; //set saturation to max by default + sources[i].source.ttl = 1; //set source alive + } + for (int i = 0; i < numParticles; i++) + { + particles[i].sat = 255; // full saturation + } + //Serial.println("PS Constructor done"); } // update function applies gravity, moves the particles, handles collisions and renders the particles void ParticleSystem::update(void) { - PSadvancedParticle *advprop = NULL; - //apply gravity globally if enabled - if (particlesettings.useGravity) - applyGravity(); - - //update size settings before handling collisions - if(advPartSize) - { - for (int i = 0; i < usedParticles; i++) - { - updateSize(&advPartProps[i], &advPartSize[i]); - } - } - - // handle collisions (can push particles, must be done before updating particles or they can render out of bounds, causing a crash if using local buffer for speed) - if (particlesettings.useCollisions) - handleCollisions(); - - //move all particles - for (int i = 0; i < usedParticles; i++) - { - if(advPartProps) - { - advprop = &advPartProps[i]; - } - particleMoveUpdate(particles[i], &particlesettings, advprop); - } - - /*TODO remove this - Serial.print("alive particles: "); - uint32_t aliveparticles = 0; - for (int i = 0; i < numParticles; i++) - { - if(particles[i].ttl) - aliveparticles++; - } - Serial.println(aliveparticles); - */ - ParticleSys_render(); + PSadvancedParticle *advprop = NULL; + //apply gravity globally if enabled + if (particlesettings.useGravity) + applyGravity(); + + //update size settings before handling collisions + if (advPartSize) + { + for (int i = 0; i < usedParticles; i++) + { + updateSize(&advPartProps[i], &advPartSize[i]); + } + } + + // handle collisions (can push particles, must be done before updating particles or they can render out of bounds, causing a crash if using local buffer for speed) + if (particlesettings.useCollisions) + handleCollisions(); + + //move all particles + for (int i = 0; i < usedParticles; i++) + { + if (advPartProps) + { + advprop = &advPartProps[i]; + } + particleMoveUpdate(particles[i], &particlesettings, advprop); + } + + /*TODO remove this + Serial.print("alive particles: "); + uint32_t aliveparticles = 0; + for (int i = 0; i < numParticles; i++) + { + if (particles[i].ttl) + aliveparticles++; + } + Serial.println(aliveparticles); + */ + ParticleSys_render(); } // update function for fire animation void ParticleSystem::updateFire(uint32_t intensity, bool renderonly) { - if(!renderonly) - fireParticleupdate(); - ParticleSys_render(true, intensity); + if (!renderonly) + fireParticleupdate(); + ParticleSys_render(true, intensity); } void ParticleSystem::setUsedParticles(uint16_t num) { - usedParticles = min(num, numParticles); //limit to max particles + usedParticles = min(num, numParticles); //limit to max particles } void ParticleSystem::setWallHardness(uint8_t hardness) { - wallHardness = hardness; + wallHardness = hardness; } void ParticleSystem::setWallRoughness(uint8_t roughness) { - wallRoughness = roughness; + wallRoughness = roughness; } void ParticleSystem::setCollisionHardness(uint8_t hardness) -{ - collisionHardness = hardness; +{ + collisionHardness = hardness; } void ParticleSystem::setMatrixSize(uint16_t x, uint16_t y) { - maxXpixel = x - 1; // last physical pixel that can be drawn to - maxYpixel = y - 1; - maxX = x * PS_P_RADIUS - 1; // particle system boundary for movements - maxY = y * PS_P_RADIUS - 1; // this value is often needed (also by FX) to calculate positions + maxXpixel = x - 1; // last physical pixel that can be drawn to + maxYpixel = y - 1; + maxX = x * PS_P_RADIUS - 1; // particle system boundary for movements + maxY = y * PS_P_RADIUS - 1; // this value is often needed (also by FX) to calculate positions } void ParticleSystem::setWrapX(bool enable) { - particlesettings.wrapX = enable; + particlesettings.wrapX = enable; } void ParticleSystem::setWrapY(bool enable) { - particlesettings.wrapY = enable; + particlesettings.wrapY = enable; } void ParticleSystem::setBounceX(bool enable) { - particlesettings.bounceX = enable; + particlesettings.bounceX = enable; } void ParticleSystem::setBounceY(bool enable) { - particlesettings.bounceY = enable; + particlesettings.bounceY = enable; } void ParticleSystem::setKillOutOfBounds(bool enable) { - particlesettings.killoutofbounds = enable; + particlesettings.killoutofbounds = enable; } void ParticleSystem::setColorByAge(bool enable) { - particlesettings.colorByAge = enable; + particlesettings.colorByAge = enable; } void ParticleSystem::setMotionBlur(uint8_t bluramount) { - if(particlesize == 0) // only allwo motion blurring on default particle size or advanced size(cannot combine motion blur with normal blurring used for particlesize, would require another buffer) - motionBlur = bluramount; + if (particlesize == 0) // only allwo motion blurring on default particle size or advanced size(cannot combine motion blur with normal blurring used for particlesize, would require another buffer) + motionBlur = bluramount; } // render size using smearing (see blur function) void ParticleSystem::setParticleSize(uint8_t size) { - particlesize = size; - particleHardRadius = PS_P_MINHARDRADIUS + particlesize; // note: this sets size if not using advanced props - motionBlur = 0; // disable motion blur if particle size is set + particlesize = size; + particleHardRadius = PS_P_MINHARDRADIUS + particlesize; // note: this sets size if not using advanced props + motionBlur = 0; // disable motion blur if particle size is set } // enable/disable gravity, optionally, set the force (force=8 is default) can be 1-255, 0 is disable // if enabled, gravity is applied to all particles in ParticleSystemUpdate() // force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame (gives good results) void ParticleSystem::setGravity(int8_t force) -{ - if (force) - { - gforce = force; - particlesettings.useGravity = true; - } - else - particlesettings.useGravity = false; +{ + if (force) + { + gforce = force; + particlesettings.useGravity = true; + } + else + particlesettings.useGravity = false; } void ParticleSystem::enableParticleCollisions(bool enable, uint8_t hardness) // enable/disable gravity, optionally, set the force (force=8 is default) can be 1-255, 0 is also disable { - particlesettings.useCollisions = enable; - collisionHardness = hardness + 1; + particlesettings.useCollisions = enable; + collisionHardness = hardness + 1; } // emit one particle with variation, returns index of emitted particle (or -1 if no particle emitted) int32_t ParticleSystem::sprayEmit(PSsource &emitter) { - for (int32_t i = 0; i < usedParticles; i++) - { - emitIndex++; - if (emitIndex >= usedParticles) - emitIndex = 0; - if (particles[emitIndex].ttl == 0) // find a dead particle - { - particles[emitIndex].vx = emitter.vx + random(-emitter.var, emitter.var); - particles[emitIndex].vy = emitter.vy + random(-emitter.var, emitter.var); - particles[emitIndex].x = emitter.source.x; - particles[emitIndex].y = emitter.source.y; - particles[emitIndex].hue = emitter.source.hue; - particles[emitIndex].sat = emitter.source.sat; - particles[emitIndex].collide = emitter.source.collide; - particles[emitIndex].ttl = random(emitter.minLife, emitter.maxLife); - if (advPartProps) - advPartProps[emitIndex].size = emitter.size; - return i; - } - } - return -1; + for (int32_t i = 0; i < usedParticles; i++) + { + emitIndex++; + if (emitIndex >= usedParticles) + emitIndex = 0; + if (particles[emitIndex].ttl == 0) // find a dead particle + { + particles[emitIndex].vx = emitter.vx + random(-emitter.var, emitter.var); + particles[emitIndex].vy = emitter.vy + random(-emitter.var, emitter.var); + particles[emitIndex].x = emitter.source.x; + particles[emitIndex].y = emitter.source.y; + particles[emitIndex].hue = emitter.source.hue; + particles[emitIndex].sat = emitter.source.sat; + particles[emitIndex].collide = emitter.source.collide; + particles[emitIndex].ttl = random(emitter.minLife, emitter.maxLife); + if (advPartProps) + advPartProps[emitIndex].size = emitter.size; + return i; + } + } + return -1; } // Spray emitter for particles used for flames (particle TTL depends on source TTL) void ParticleSystem::flameEmit(PSsource &emitter) { - for (uint32_t i = 0; i < usedParticles; i++) - { - emitIndex++; - if (emitIndex >= usedParticles) - emitIndex = 0; - if (particles[emitIndex].ttl == 0) // find a dead particle - { - particles[emitIndex].x = emitter.source.x + random16(PS_P_RADIUS<<1) - PS_P_RADIUS; // jitter the flame by one pixel to make the flames wider at the base - particles[emitIndex].y = emitter.source.y; - particles[emitIndex].vx = emitter.vx + random16(emitter.var) - (emitter.var >> 1); // random16 is good enough for fire and much faster - particles[emitIndex].vy = emitter.vy + random16(emitter.var) - (emitter.var >> 1); - particles[emitIndex].ttl = random(emitter.minLife, emitter.maxLife) + emitter.source.ttl; - // fire uses ttl and not hue for heat, so no need to set the hue - break; // done - } - } - /* - // note: this attemt to save on code size turns out to be much slower as fire uses a lot of particle emits, this must be efficient. also emitter.var would need adjustment - uint32_t partidx = sprayEmit(emitter); //emit one particle - // adjust properties - particles[partidx].x += random16(PS_P_RADIUS<<1) - PS_P_RADIUS; // jitter the flame by one pixel to make the flames wider at the base - particles[partidx].ttl += emitter.source.ttl; // flame intensity dies down with emitter TTL - */ + for (uint32_t i = 0; i < usedParticles; i++) + { + emitIndex++; + if (emitIndex >= usedParticles) + emitIndex = 0; + if (particles[emitIndex].ttl == 0) // find a dead particle + { + particles[emitIndex].x = emitter.source.x + random16(PS_P_RADIUS<<1) - PS_P_RADIUS; // jitter the flame by one pixel to make the flames wider at the base + particles[emitIndex].y = emitter.source.y; + particles[emitIndex].vx = emitter.vx + random16(emitter.var) - (emitter.var >> 1); // random16 is good enough for fire and much faster + particles[emitIndex].vy = emitter.vy + random16(emitter.var) - (emitter.var >> 1); + particles[emitIndex].ttl = random(emitter.minLife, emitter.maxLife) + emitter.source.ttl; + // fire uses ttl and not hue for heat, so no need to set the hue + break; // done + } + } + /* + // note: this attemt to save on code size turns out to be much slower as fire uses a lot of particle emits, this must be efficient. also emitter.var would need adjustment + uint32_t partidx = sprayEmit(emitter); //emit one particle + // adjust properties + particles[partidx].x += random16(PS_P_RADIUS<<1) - PS_P_RADIUS; // jitter the flame by one pixel to make the flames wider at the base + particles[partidx].ttl += emitter.source.ttl; // flame intensity dies down with emitter TTL + */ } // Emits a particle at given angle and speed, angle is from 0-65535 (=0-360deg), speed is also affected by emitter->var // angle = 0 means in positive x-direction (i.e. to the right) void ParticleSystem::angleEmit(PSsource &emitter, uint16_t angle, int8_t speed) { - emitter.vx = ((int32_t)cos16(angle) * (int32_t)speed) / (int32_t)32600; // cos16() and sin16() return signed 16bit, division should be 32767 but 32600 gives slightly better rounding - emitter.vy = ((int32_t)sin16(angle) * (int32_t)speed) / (int32_t)32600; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! - sprayEmit(emitter); + emitter.vx = ((int32_t)cos16(angle) * (int32_t)speed) / (int32_t)32600; // cos16() and sin16() return signed 16bit, division should be 32767 but 32600 gives slightly better rounding + emitter.vy = ((int32_t)sin16(angle) * (int32_t)speed) / (int32_t)32600; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + sprayEmit(emitter); } // particle moves, decays and dies, if killoutofbounds is set, out of bounds particles are set to ttl=0 // uses passed settings to set bounce or wrap, if useGravity is set, it will never bounce at the top and killoutofbounds is not applied over the top void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings *options, PSadvancedParticle *advancedproperties) { - if(options == NULL) - options = &particlesettings; //use PS system settings by default - if (part.ttl > 0) - { - if(!part.perpetual) - part.ttl--; // age - if (particlesettings.colorByAge) - part.hue = part.ttl > 255 ? 255 : part.ttl; //set color to ttl - - bool usesize = false; // particle uses individual size rendering - int32_t newX = part.x + (int16_t)part.vx; - int32_t newY = part.y + (int16_t)part.vy; - part.outofbounds = 0; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) - - if(advancedproperties) //using individual particle size? - { - if(advancedproperties->size > 0) - usesize = true; // note: variable eases out of frame checking below - particleHardRadius = max(PS_P_MINHARDRADIUS, (int)particlesize + (advancedproperties->size)); - } - // if wall collisions are enabled, bounce them before they reach the edge, it looks much nicer if the particle is not half out of view - if (options->bounceX) - { - if ((newX < particleHardRadius) || (newX > maxX - particleHardRadius)) // reached a wall - bounce(part.vx, part.vy, newX, maxX); - } - - if ((newX < 0) || (newX > maxX)) // check if particle reached an edge (note: this also checks out of bounds and must not be skipped, even if bounce is enabled) - { - if (options->wrapX) - { - newX = (uint16_t)newX % (maxX + 1); - } - else if (((newX <= -PS_P_HALFRADIUS) || (newX > maxX + PS_P_HALFRADIUS))) // particle is leaving, set out of bounds if it has fully left - { - bool isleaving = true; - if(usesize) // using individual particle size - { - if (((newX > -particleHardRadius) && (newX < maxX + particleHardRadius))) // large particle is not yet leaving the view - note: this is not pixel perfect but good enough - isleaving = false; - } - - if(isleaving) - { - part.outofbounds = 1; - if (options->killoutofbounds) - part.ttl = 0; - } - } - } - - if (options->bounceY) - { - if ((newY < particleHardRadius) || (newY > maxY - particleHardRadius)) // reached floor / ceiling - { - if (newY < particleHardRadius) // bounce at bottom - bounce(part.vy, part.vx, newY, maxY); - else - { - if(!options->useGravity) - bounce(part.vy, part.vx, newY, maxY); - } - } - } - - if (((newY < 0) || (newY > maxY))) // check if particle reached an edge (makes sure particles are within frame for rendering) - { - if (options->wrapY) - { - newY = (uint16_t)newY % (maxY + 1); - } - else if (((newY <= -PS_P_HALFRADIUS) || (newY > maxY + PS_P_HALFRADIUS))) // particle is leaving, set out of bounds if it has fully left - { - bool isleaving = true; - if(usesize) // using individual particle size - { - if (((newY > -particleHardRadius) && (newY < maxY + particleHardRadius))) // still withing rendering reach - isleaving = false; - } - if(isleaving) - { - part.outofbounds = 1; - if (options->killoutofbounds) - { - if (newY < 0) // if gravity is enabled, only kill particles below ground - part.ttl = 0; - else if (!options->useGravity) - part.ttl = 0; - } - } - } - } - part.x = (int16_t)newX; // set new position - part.y = (int16_t)newY; // set new position - } + if (options == NULL) + options = &particlesettings; //use PS system settings by default + if (part.ttl > 0) + { + if (!part.perpetual) + part.ttl--; // age + if (particlesettings.colorByAge) + part.hue = part.ttl > 255 ? 255 : part.ttl; //set color to ttl + + bool usesize = false; // particle uses individual size rendering + int32_t newX = part.x + (int16_t)part.vx; + int32_t newY = part.y + (int16_t)part.vy; + part.outofbounds = 0; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) + + if (advancedproperties) //using individual particle size? + { + if (advancedproperties->size > 0) + usesize = true; // note: variable eases out of frame checking below + particleHardRadius = max(PS_P_MINHARDRADIUS, (int)particlesize + (advancedproperties->size)); + } + // if wall collisions are enabled, bounce them before they reach the edge, it looks much nicer if the particle is not half out of view + if (options->bounceX) + { + if ((newX < particleHardRadius) || (newX > maxX - particleHardRadius)) // reached a wall + bounce(part.vx, part.vy, newX, maxX); + } + + if ((newX < 0) || (newX > maxX)) // check if particle reached an edge (note: this also checks out of bounds and must not be skipped, even if bounce is enabled) + { + if (options->wrapX) + { + newX = (uint16_t)newX % (maxX + 1); + } + else if (((newX <= -PS_P_HALFRADIUS) || (newX > maxX + PS_P_HALFRADIUS))) // particle is leaving, set out of bounds if it has fully left + { + bool isleaving = true; + if (usesize) // using individual particle size + { + if (((newX > -particleHardRadius) && (newX < maxX + particleHardRadius))) // large particle is not yet leaving the view - note: this is not pixel perfect but good enough + isleaving = false; + } + + if (isleaving) + { + part.outofbounds = 1; + if (options->killoutofbounds) + part.ttl = 0; + } + } + } + + if (options->bounceY) + { + if ((newY < particleHardRadius) || (newY > maxY - particleHardRadius)) // reached floor / ceiling + { + if (newY < particleHardRadius) // bounce at bottom + bounce(part.vy, part.vx, newY, maxY); + else + { + if (!options->useGravity) + bounce(part.vy, part.vx, newY, maxY); + } + } + } + + if (((newY < 0) || (newY > maxY))) // check if particle reached an edge (makes sure particles are within frame for rendering) + { + if (options->wrapY) + { + newY = (uint16_t)newY % (maxY + 1); + } + else if (((newY <= -PS_P_HALFRADIUS) || (newY > maxY + PS_P_HALFRADIUS))) // particle is leaving, set out of bounds if it has fully left + { + bool isleaving = true; + if (usesize) // using individual particle size + { + if (((newY > -particleHardRadius) && (newY < maxY + particleHardRadius))) // still withing rendering reach + isleaving = false; + } + if (isleaving) + { + part.outofbounds = 1; + if (options->killoutofbounds) + { + if (newY < 0) // if gravity is enabled, only kill particles below ground + part.ttl = 0; + else if (!options->useGravity) + part.ttl = 0; + } + } + } + } + part.x = (int16_t)newX; // set new position + part.y = (int16_t)newY; // set new position + } } // update advanced particle size control void ParticleSystem::updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize) { - if(advsize == NULL) // just a safety check - return; - // grow/shrink particle - int32_t newsize = advprops->size; - uint32_t counter = advsize->sizecounter; - uint32_t increment; - // calculate grow speed using 0-8 for low speeds and 9-15 for higher speeds - if(advsize->grow) increment = advsize->growspeed; - else if(advsize->shrink) increment = advsize->shrinkspeed; - if(increment < 9) // 8 means +1 every frame - { - counter += increment; - if(counter > 7) - { - counter -= 8; - increment = 1; - } - else - increment = 0; - advsize->sizecounter = counter; - } - else{ - increment = (increment - 8) << 1; // 9 means +2, 10 means +4 etc. 15 means +14 - } - if(advsize->grow) - { - if(newsize < advsize->maxsize) - { - newsize += increment; - if(newsize >= advsize->maxsize) - { - advsize->grow = false; // stop growing, shrink from now on if enabled - newsize = advsize->maxsize; // limit - if(advsize->pulsate) advsize->shrink = true; - } - } - } - else if(advsize->shrink) - { - if(newsize > advsize->minsize) - { - newsize -= increment; - if(newsize <= advsize->minsize) - { - //if(advsize->minsize == 0) part.ttl = 0; //TODO: need to pass particle or return kill instruction - advsize->shrink = false; // disable shrinking - newsize = advsize->minsize; // limit - if(advsize->pulsate) advsize->grow = true; - } - } - } - advprops->size = newsize; - // handle wobbling - if(advsize->wobble) - { - advsize->asymdir += advsize->wobblespeed; // todo: need better wobblespeed control? counter is already in the struct... - } + if (advsize == NULL) // just a safety check + return; + // grow/shrink particle + int32_t newsize = advprops->size; + uint32_t counter = advsize->sizecounter; + uint32_t increment; + // calculate grow speed using 0-8 for low speeds and 9-15 for higher speeds + if (advsize->grow) increment = advsize->growspeed; + else if (advsize->shrink) increment = advsize->shrinkspeed; + if (increment < 9) // 8 means +1 every frame + { + counter += increment; + if (counter > 7) + { + counter -= 8; + increment = 1; + } + else + increment = 0; + advsize->sizecounter = counter; + } + else{ + increment = (increment - 8) << 1; // 9 means +2, 10 means +4 etc. 15 means +14 + } + if (advsize->grow) + { + if (newsize < advsize->maxsize) + { + newsize += increment; + if (newsize >= advsize->maxsize) + { + advsize->grow = false; // stop growing, shrink from now on if enabled + newsize = advsize->maxsize; // limit + if (advsize->pulsate) advsize->shrink = true; + } + } + } + else if (advsize->shrink) + { + if (newsize > advsize->minsize) + { + newsize -= increment; + if (newsize <= advsize->minsize) + { + //if (advsize->minsize == 0) part.ttl = 0; //TODO: need to pass particle or return kill instruction + advsize->shrink = false; // disable shrinking + newsize = advsize->minsize; // limit + if (advsize->pulsate) advsize->grow = true; + } + } + } + advprops->size = newsize; + // handle wobbling + if (advsize->wobble) + { + advsize->asymdir += advsize->wobblespeed; // todo: need better wobblespeed control? counter is already in the struct... + } } // calculate x and y size for asymmetrical particles (advanced size control) void ParticleSystem::getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize) { - if(advsize == NULL) // if advanced size is valid, also advced properties pointer is valid (handled by pointer assignment function) - return; + if (advsize == NULL) // if advanced size is valid, also advced properties pointer is valid (handled by pointer assignment function) + return; int32_t deviation = ((uint32_t)advprops->size * (uint32_t)advsize->asymmetry) / 255; // deviation from symmetrical size // Calculate x and y size based on deviation and direction (0 is symmetrical, 64 is x, 128 is symmetrical, 192 is y) - if (advsize->asymdir < 64) { - deviation = ((int32_t)advsize->asymdir * deviation) / 64; + if (advsize->asymdir < 64) { + deviation = ((int32_t)advsize->asymdir * deviation) / 64; } else if (advsize->asymdir < 192) { deviation = ((128 - (int32_t)advsize->asymdir) * deviation) / 64; } else { @@ -464,23 +463,23 @@ void ParticleSystem::getParticleXYsize(PSadvancedParticle *advprops, PSsizeContr // function to bounce a particle from a wall using set parameters (wallHardness and wallRoughness) void ParticleSystem::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition) { - incomingspeed = -incomingspeed; - incomingspeed = (incomingspeed * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface - if (position < particleHardRadius) - position = particleHardRadius; // fast particles will never reach the edge if position is inverted, this looks better - else - position = maxposition - particleHardRadius; - if(wallRoughness) - { - int32_t totalspeed = abs(incomingspeed) + abs(parallelspeed); - // transfer an amount of incomingspeed speed to parallel speed - int32_t donatespeed = abs(incomingspeed); - donatespeed = (random(-donatespeed, donatespeed) * wallRoughness) / 255; //take random portion of + or - perpendicular speed, scaled by roughness - parallelspeed = limitSpeed((int32_t)parallelspeed + donatespeed); - incomingspeed = limitSpeed((int32_t)incomingspeed - donatespeed); - donatespeed = totalspeed - abs(parallelspeed); // keep total speed the same - incomingspeed = incomingspeed > 0 ? donatespeed : -donatespeed; - } + incomingspeed = -incomingspeed; + incomingspeed = (incomingspeed * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface + if (position < particleHardRadius) + position = particleHardRadius; // fast particles will never reach the edge if position is inverted, this looks better + else + position = maxposition - particleHardRadius; + if (wallRoughness) + { + int32_t totalspeed = abs(incomingspeed) + abs(parallelspeed); + // transfer an amount of incomingspeed speed to parallel speed + int32_t donatespeed = abs(incomingspeed); + donatespeed = (random(-donatespeed, donatespeed) * wallRoughness) / 255; //take random portion of + or - perpendicular speed, scaled by roughness + parallelspeed = limitSpeed((int32_t)parallelspeed + donatespeed); + incomingspeed = limitSpeed((int32_t)incomingspeed - donatespeed); + donatespeed = totalspeed - abs(parallelspeed); // keep total speed the same + incomingspeed = incomingspeed > 0 ? donatespeed : -donatespeed; + } } // apply a force in x,y direction to individual particle @@ -488,44 +487,44 @@ void ParticleSystem::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_ // force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame (gives good results) void ParticleSystem::applyForce(PSparticle *part, int8_t xforce, int8_t yforce, uint8_t *counter) { - // for small forces, need to use a delay counter - uint8_t xcounter = (*counter) & 0x0F; // lower four bits - uint8_t ycounter = (*counter) >> 4; // upper four bits + // for small forces, need to use a delay counter + uint8_t xcounter = (*counter) & 0x0F; // lower four bits + uint8_t ycounter = (*counter) >> 4; // upper four bits - // velocity increase - int32_t dvx = calcForce_dv(xforce, &xcounter); - int32_t dvy = calcForce_dv(yforce, &ycounter); + // velocity increase + int32_t dvx = calcForce_dv(xforce, &xcounter); + int32_t dvy = calcForce_dv(yforce, &ycounter); - // save counter values back - *counter = xcounter & 0x0F; // write lower four bits, make sure not to write more than 4 bits - *counter |= (ycounter << 4) & 0xF0; // write upper four bits + // save counter values back + *counter = xcounter & 0x0F; // write lower four bits, make sure not to write more than 4 bits + *counter |= (ycounter << 4) & 0xF0; // write upper four bits - // apply the force to particle: - part->vx = limitSpeed((int32_t)part->vx + dvx); - part->vy = limitSpeed((int32_t)part->vy + dvy); + // apply the force to particle + part->vx = limitSpeed((int32_t)part->vx + dvx); + part->vy = limitSpeed((int32_t)part->vy + dvy); } // apply a force in x,y direction to individual particle using advanced particle properties void ParticleSystem::applyForce(uint16_t particleindex, int8_t xforce, int8_t yforce) { - if (advPartProps == NULL) - return; // no advanced properties available - applyForce(&particles[particleindex], xforce, yforce, &advPartProps[particleindex].forcecounter); + if (advPartProps == NULL) + return; // no advanced properties available + applyForce(&particles[particleindex], xforce, yforce, &advPartProps[particleindex].forcecounter); } // apply a force in x,y direction to all particles // force is in 3.4 fixed point notation (see above) void ParticleSystem::applyForce(int8_t xforce, int8_t yforce) { - // for small forces, need to use a delay counter - uint8_t tempcounter; - // note: this is not the most compuatationally efficient way to do this, but it saves on duplacte code and is fast enough - for (uint i = 0; i < usedParticles; i++) - { - tempcounter = forcecounter; - applyForce(&particles[i], xforce, yforce, &tempcounter); - } - forcecounter = tempcounter; //save value back + // for small forces, need to use a delay counter + uint8_t tempcounter; + // note: this is not the most compuatationally efficient way to do this, but it saves on duplacte code and is fast enough + for (uint i = 0; i < usedParticles; i++) + { + tempcounter = forcecounter; + applyForce(&particles[i], xforce, yforce, &tempcounter); + } + forcecounter = tempcounter; //save value back } // apply a force in angular direction to single particle @@ -534,26 +533,26 @@ void ParticleSystem::applyForce(int8_t xforce, int8_t yforce) // force is in 3.4 fixed point notation so force=16 means apply v+1 each frame (useful force range is +/- 127) void ParticleSystem::applyAngleForce(PSparticle *part, int8_t force, uint16_t angle, uint8_t *counter) { - int8_t xforce = ((int32_t)force * cos16(angle)) / 32767; // force is +/- 127 - int8_t yforce = ((int32_t)force * sin16(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! - // note: sin16 is 10% faster than sin8() on ESP32 but on ESP8266 it is 9% slower - applyForce(part, xforce, yforce, counter); + int8_t xforce = ((int32_t)force * cos16(angle)) / 32767; // force is +/- 127 + int8_t yforce = ((int32_t)force * sin16(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + // note: sin16 is 10% faster than sin8() on ESP32 but on ESP8266 it is 9% slower + applyForce(part, xforce, yforce, counter); } void ParticleSystem::applyAngleForce(uint16_t particleindex, int8_t force, uint16_t angle) { - if (advPartProps == NULL) - return; // no advanced properties available - applyAngleForce(&particles[particleindex], force, angle, &advPartProps[particleindex].forcecounter); + if (advPartProps == NULL) + return; // no advanced properties available + applyAngleForce(&particles[particleindex], force, angle, &advPartProps[particleindex].forcecounter); } // apply a force in angular direction to all particles // angle is from 0-65535 (=0-360deg) angle = 0 means in positive x-direction (i.e. to the right) void ParticleSystem::applyAngleForce(int8_t force, uint16_t angle) { - int8_t xforce = ((int32_t)force * cos16(angle)) / 32767; // force is +/- 127 - int8_t yforce = ((int32_t)force * sin16(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! - applyForce(xforce, yforce); + int8_t xforce = ((int32_t)force * cos16(angle)) / 32767; // force is +/- 127 + int8_t yforce = ((int32_t)force * sin16(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + applyForce(xforce, yforce); } @@ -562,139 +561,139 @@ void ParticleSystem::applyAngleForce(int8_t force, uint16_t angle) // note: faster than apply force since direction is always down and counter is fixed for all particles void ParticleSystem::applyGravity() { - int32_t dv = calcForce_dv(gforce, &gforcecounter); - for (uint32_t i = 0; i < usedParticles; i++) - { - // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways - particles[i].vy = limitSpeed((int32_t)particles[i].vy - dv); - } + int32_t dv = calcForce_dv(gforce, &gforcecounter); + for (uint32_t i = 0; i < usedParticles; i++) + { + // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways + particles[i].vy = limitSpeed((int32_t)particles[i].vy - dv); + } } // apply gravity to single particle using system settings (use this for sources) // function does not increment gravity counter, if gravity setting is disabled, this cannot be used void ParticleSystem::applyGravity(PSparticle *part) { - int32_t dv; // velocity increase - if (gforce > 15) - dv = (gforce >> 4); // apply the 4 MSBs - else - dv = 1; - - if (gforcecounter + gforce > 15) //counter is updated in global update when applying gravity - { - part->vy = limitSpeed((int32_t)part->vy - dv); - } + int32_t dv; // velocity increase + if (gforce > 15) + dv = (gforce >> 4); // apply the 4 MSBs + else + dv = 1; + + if (gforcecounter + gforce > 15) //counter is updated in global update when applying gravity + { + part->vy = limitSpeed((int32_t)part->vy - dv); + } } // slow down particle by friction, the higher the speed, the higher the friction. a high friction coefficient slows them more (255 means instant stop) // note: a coefficient smaller than 0 will speed them up (this is a feature, not a bug), coefficient larger than 255 inverts the speed, so don't do that void ParticleSystem::applyFriction(PSparticle *part, int32_t coefficient) { - int32_t friction = 255 - coefficient; - // note: not checking if particle is dead can be done by caller (or can be omitted) - // note2: cannot use right shifts as bit shifting in right direction is asymmetrical for positive and negative numbers and this needs to be accurate or things start to go to the left side. - part->vx = ((int16_t)part->vx * friction) / 255; - part->vy = ((int16_t)part->vy * friction) / 255; + int32_t friction = 255 - coefficient; + // note: not checking if particle is dead can be done by caller (or can be omitted) + // note2: cannot use right shifts as bit shifting in right direction is asymmetrical for positive and negative numbers and this needs to be accurate or things start to go to the left side. + part->vx = ((int16_t)part->vx * friction) / 255; + part->vy = ((int16_t)part->vy * friction) / 255; } // apply friction to all particles void ParticleSystem::applyFriction(int32_t coefficient) { - for (uint32_t i = 0; i < usedParticles; i++) - { - if(particles[i].ttl) - applyFriction(&particles[i], coefficient); - } + for (uint32_t i = 0; i < usedParticles; i++) + { + if (particles[i].ttl) + applyFriction(&particles[i], coefficient); + } } // attracts a particle to an attractor particle using the inverse square-law void ParticleSystem::pointAttractor(uint16_t particleindex, PSparticle *attractor, uint8_t strength, bool swallow) { - if (advPartProps == NULL) - return; // no advanced properties available - - // Calculate the distance between the particle and the attractor - int32_t dx = attractor->x - particles[particleindex].x; - int32_t dy = attractor->y - particles[particleindex].y; - - // Calculate the force based on inverse square law - int32_t distanceSquared = dx * dx + dy * dy; - if (distanceSquared < 8192) - { - if (swallow) // particle is close, age it fast so it fades out, do not attract further - { - if (particles[particleindex].ttl > 7) - particles[particleindex].ttl -= 8; - else - { - particles[particleindex].ttl = 0; - return; - } - } - distanceSquared = 2 * PS_P_RADIUS * PS_P_RADIUS; // limit the distance to avoid very high forces - } - - int32_t force = ((int32_t)strength << 16) / distanceSquared; - int8_t xforce = (force * dx) / 1024; // scale to a lower value, found by experimenting - int8_t yforce = (force * dy) / 1024; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! - - applyForce(particleindex, xforce, yforce); + if (advPartProps == NULL) + return; // no advanced properties available + + // Calculate the distance between the particle and the attractor + int32_t dx = attractor->x - particles[particleindex].x; + int32_t dy = attractor->y - particles[particleindex].y; + + // Calculate the force based on inverse square law + int32_t distanceSquared = dx * dx + dy * dy; + if (distanceSquared < 8192) + { + if (swallow) // particle is close, age it fast so it fades out, do not attract further + { + if (particles[particleindex].ttl > 7) + particles[particleindex].ttl -= 8; + else + { + particles[particleindex].ttl = 0; + return; + } + } + distanceSquared = 2 * PS_P_RADIUS * PS_P_RADIUS; // limit the distance to avoid very high forces + } + + int32_t force = ((int32_t)strength << 16) / distanceSquared; + int8_t xforce = (force * dx) / 1024; // scale to a lower value, found by experimenting + int8_t yforce = (force * dy) / 1024; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + + applyForce(particleindex, xforce, yforce); } /* //attract to a line (TODO: this is not yet working) void ParticleSystem::lineAttractor(uint16_t particleindex, PSparticle *attractorcenter, uint16_t attractorangle, uint8_t strength) { - // Calculate the distance between the particle and the attractor - if(advPartProps == NULL) - return; // no advanced properties available - - // calculate a second point on the line - int32_t x1 = attractorcenter->x + (cos16(attractorangle) >> 5); - int32_t y1 = attractorcenter->y + (sin16(attractorangle) >> 5); - // calculate squared distance from particle to the line: - int32_t dx = (x1 - attractorcenter->x) >> 4; - int32_t dy = (y1 - attractorcenter->y) >> 4; - int32_t d = ((dx * (particles[particleindex].y - attractorcenter->y)) - (dy * (particles[particleindex].x - attractorcenter->x))) >> 8; - int32_t distanceSquared = (d * d) / (dx * dx + dy * dy); - - - // Calculate the force based on inverse square law - if (distanceSquared < 2) - { - distanceSquared = 1; - // distanceSquared = 4 * PS_P_RADIUS * PS_P_RADIUS; // limit the distance to avoid very high forces - } - - int32_t force = (((int32_t)strength << 16) / distanceSquared)>>10; - //apply force in a 90° angle to the line - int8_t xforce = (d > 0 ? 1 : -1) * (force * dy) / 100; // scale to a lower value, found by experimenting - int8_t yforce = (d > 0 ? -1 : 1) * (force * dx) / 100; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! - - Serial.print(" partx: "); - Serial.print(particles[particleindex].x); - Serial.print(" party "); - Serial.print(particles[particleindex].y); - Serial.print(" x1 "); - Serial.print(x1); - Serial.print(" y1 "); - Serial.print(y1); - Serial.print(" dx "); - Serial.print(dx); - Serial.print(" dy "); - Serial.print(dy); - Serial.print(" d: "); - Serial.print(d); - Serial.print(" dsq: "); - Serial.print(distanceSquared); - Serial.print(" force: "); - Serial.print(force); - Serial.print(" fx: "); - Serial.print(xforce); - Serial.print(" fy: "); - Serial.println(yforce); - - applyForce(particleindex, xforce, yforce); + // Calculate the distance between the particle and the attractor + if (advPartProps == NULL) + return; // no advanced properties available + + // calculate a second point on the line + int32_t x1 = attractorcenter->x + (cos16(attractorangle) >> 5); + int32_t y1 = attractorcenter->y + (sin16(attractorangle) >> 5); + // calculate squared distance from particle to the line: + int32_t dx = (x1 - attractorcenter->x) >> 4; + int32_t dy = (y1 - attractorcenter->y) >> 4; + int32_t d = ((dx * (particles[particleindex].y - attractorcenter->y)) - (dy * (particles[particleindex].x - attractorcenter->x))) >> 8; + int32_t distanceSquared = (d * d) / (dx * dx + dy * dy); + + + // Calculate the force based on inverse square law + if (distanceSquared < 2) + { + distanceSquared = 1; + // distanceSquared = 4 * PS_P_RADIUS * PS_P_RADIUS; // limit the distance to avoid very high forces + } + + int32_t force = (((int32_t)strength << 16) / distanceSquared)>>10; + //apply force in a 90° angle to the line + int8_t xforce = (d > 0 ? 1 : -1) * (force * dy) / 100; // scale to a lower value, found by experimenting + int8_t yforce = (d > 0 ? -1 : 1) * (force * dx) / 100; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! + + Serial.print(" partx: "); + Serial.print(particles[particleindex].x); + Serial.print(" party "); + Serial.print(particles[particleindex].y); + Serial.print(" x1 "); + Serial.print(x1); + Serial.print(" y1 "); + Serial.print(y1); + Serial.print(" dx "); + Serial.print(dx); + Serial.print(" dy "); + Serial.print(dy); + Serial.print(" d: "); + Serial.print(d); + Serial.print(" dsq: "); + Serial.print(distanceSquared); + Serial.print(" force: "); + Serial.print(force); + Serial.print(" fx: "); + Serial.print(xforce); + Serial.print(" fy: "); + Serial.println(yforce); + + applyForce(particleindex, xforce, yforce); }*/ // render particles to the LED buffer (uses palette to render the 8bit particle color value) @@ -703,355 +702,355 @@ void ParticleSystem::lineAttractor(uint16_t particleindex, PSparticle *attractor // fireintensity and firemode are optional arguments (fireintensity is only used in firemode) void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) { - - CRGB baseRGB; - bool useLocalBuffer = true; //use local rendering buffer, gives huge speed boost (at least 30% more FPS) - CRGB **framebuffer = NULL; //local frame buffer - CRGB **renderbuffer = NULL; //local particle render buffer for advanced particles - uint32_t i; - uint32_t brightness; // particle brightness, fades if dying - - if (useLocalBuffer) - { - /* - //memory fragmentation check: - Serial.print("heap: "); - Serial.print(heap_caps_get_free_size(MALLOC_CAP_8BIT)); - Serial.print(" block: "); - Serial.println(heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)); - */ - - // allocate empty memory for the local renderbuffer - framebuffer = allocate2Dbuffer(maxXpixel + 1, maxYpixel + 1); - if (framebuffer == NULL) - { - Serial.println("Frame buffer alloc failed"); - useLocalBuffer = false; //render to segment pixels directly if not enough memory - } - else{ - if(advPartProps) - { - renderbuffer = allocate2Dbuffer(10, 10); //buffer to render individual particles to if size > 0 note: null checking is done when accessing it - } - if (motionBlur > 0) // using SEGMENT.fadeToBlackBy is much slower, this approximately doubles the speed of fade calculation - { - uint32_t yflipped; - for (uint32_t y = 0; y <= maxYpixel; y++) - { - yflipped = maxYpixel - y; - for (uint32_t x = 0; x <= maxXpixel; x++) - { - framebuffer[x][y] = SEGMENT.getPixelColorXY(x, yflipped); //copy to local buffer - fast_color_scale(framebuffer[x][y], motionBlur); - } - } - } - - } - - } - - if(!useLocalBuffer) //disabled or allocation above failed - { - Serial.println("NOT using local buffer!"); - if (motionBlur > 0) - SEGMENT.fadeToBlackBy(255 - motionBlur); - else - SEGMENT.fill(BLACK); //clear the buffer before rendering to it - } - // go over particles and render them to the buffer - for (i = 0; i < usedParticles; i++) - { - if (particles[i].outofbounds || particles[i].ttl == 0) - continue; - - // generate RGB values for particle - if(firemode) - { - //TODO: decide on a final version... - //brightness = (uint32_t)particles[i].ttl * (1 + (fireintensity >> 4)) + (fireintensity >> 2); //this is good - //brightness = (uint32_t)particles[i].ttl * (fireintensity >> 3) + (fireintensity >> 1); // this is experimental, also works, flamecolor is more even, does not look as good (but less puffy at lower speeds) - //brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + (uint32_t)particles[i].ttl * (fireintensity >> 4) + (fireintensity >> 1); // this is experimental //multiplikation mit weniger als >>4 macht noch mehr puffs bei low speed - //brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + particles[i].ttl + (fireintensity>>1); // this is experimental - //brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + ((particles[i].ttl * fireintensity) >> 5); // this is experimental TODO: test this -> testing... ok but not the best, bit sparky - brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + (fireintensity >> 1); // this is experimental TODO: test this -> testing... does not look too bad! - brightness = brightness > 255 ? 255 : brightness; // faster then using min() - baseRGB = ColorFromPalette(SEGPALETTE, brightness, 255, LINEARBLEND); - } - else{ - brightness = particles[i].ttl > 255 ? 255 : particles[i].ttl; //faster then using min() - baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255, LINEARBLEND); - if (particles[i].sat < 255) - { - CHSV baseHSV = rgb2hsv_approximate(baseRGB); //convert to HSV - baseHSV.s = particles[i].sat; //set the saturation - baseRGB = (CRGB)baseHSV; // convert back to RGB - } - } - - renderParticle(framebuffer, i, brightness, baseRGB, renderbuffer); - } - - if(particlesize > 0) - { - uint32_t passes = particlesize/64 + 1; // number of blur passes, four passes max - uint32_t bluramount = particlesize; - uint32_t bitshift = 0; - - for(int i = 0; i < passes; i++) - { - if(i == 2) // for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) - bitshift = 1; - - if (useLocalBuffer) - blur2D(framebuffer, maxXpixel + 1, maxYpixel + 1, bluramount << bitshift, bluramount << bitshift); - else - SEGMENT.blur(bluramount << bitshift, true); - bluramount -= 64; - } - } - - if (useLocalBuffer) // transfer local buffer back to segment - { - uint32_t yflipped; - for (int y = 0; y <= maxYpixel; y++) - { - yflipped = maxYpixel - y; - for (int x = 0; x <= maxXpixel; x++) - { - SEGMENT.setPixelColorXY(x, yflipped, framebuffer[x][y]); - } - } - free(framebuffer); - } - if(renderbuffer) - free(renderbuffer); + + CRGB baseRGB; + bool useLocalBuffer = true; //use local rendering buffer, gives huge speed boost (at least 30% more FPS) + CRGB **framebuffer = NULL; //local frame buffer + CRGB **renderbuffer = NULL; //local particle render buffer for advanced particles + uint32_t i; + uint32_t brightness; // particle brightness, fades if dying + + if (useLocalBuffer) + { + /* + //memory fragmentation check: + Serial.print("heap: "); + Serial.print(heap_caps_get_free_size(MALLOC_CAP_8BIT)); + Serial.print(" block: "); + Serial.println(heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)); + */ + + // allocate empty memory for the local renderbuffer + framebuffer = allocate2Dbuffer(maxXpixel + 1, maxYpixel + 1); + if (framebuffer == NULL) + { + Serial.println("Frame buffer alloc failed"); + useLocalBuffer = false; //render to segment pixels directly if not enough memory + } + else{ + if (advPartProps) + { + renderbuffer = allocate2Dbuffer(10, 10); //buffer to render individual particles to if size > 0. note: null checking is done when accessing it + } + if (motionBlur > 0) // using SEGMENT.fadeToBlackBy is much slower, this approximately doubles the speed of fade calculation + { + uint32_t yflipped; + for (uint32_t y = 0; y <= maxYpixel; y++) + { + yflipped = maxYpixel - y; + for (uint32_t x = 0; x <= maxXpixel; x++) + { + framebuffer[x][y] = SEGMENT.getPixelColorXY(x, yflipped); //copy to local buffer + fast_color_scale(framebuffer[x][y], motionBlur); + } + } + } + + } + + } + + if (!useLocalBuffer) //disabled or allocation above failed + { + Serial.println("NOT using local buffer!"); + if (motionBlur > 0) + SEGMENT.fadeToBlackBy(255 - motionBlur); + else + SEGMENT.fill(BLACK); //clear the buffer before rendering to it + } + // go over particles and render them to the buffer + for (i = 0; i < usedParticles; i++) + { + if (particles[i].outofbounds || particles[i].ttl == 0) + continue; + + // generate RGB values for particle + if (firemode) + { + //TODO: decide on a final version... + //brightness = (uint32_t)particles[i].ttl * (1 + (fireintensity >> 4)) + (fireintensity >> 2); //this is good + //brightness = (uint32_t)particles[i].ttl * (fireintensity >> 3) + (fireintensity >> 1); // this is experimental, also works, flamecolor is more even, does not look as good (but less puffy at lower speeds) + //brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + (uint32_t)particles[i].ttl * (fireintensity >> 4) + (fireintensity >> 1); // this is experimental //multiplikation mit weniger als >>4 macht noch mehr puffs bei low speed + //brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + particles[i].ttl + (fireintensity>>1); // this is experimental + //brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + ((particles[i].ttl * fireintensity) >> 5); // this is experimental TODO: test this -> testing... ok but not the best, bit sparky + brightness = (((uint32_t)particles[i].ttl * (maxY + PS_P_RADIUS - particles[i].y)) >> 7) + (fireintensity >> 1); // this is experimental TODO: test this -> testing... does not look too bad! + brightness = brightness > 255 ? 255 : brightness; // faster then using min() + baseRGB = ColorFromPalette(SEGPALETTE, brightness, 255, LINEARBLEND); + } + else{ + brightness = particles[i].ttl > 255 ? 255 : particles[i].ttl; //faster then using min() + baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255, LINEARBLEND); + if (particles[i].sat < 255) + { + CHSV baseHSV = rgb2hsv_approximate(baseRGB); //convert to HSV + baseHSV.s = particles[i].sat; //set the saturation + baseRGB = (CRGB)baseHSV; // convert back to RGB + } + } + + renderParticle(framebuffer, i, brightness, baseRGB, renderbuffer); + } + + if (particlesize > 0) + { + uint32_t passes = particlesize/64 + 1; // number of blur passes, four passes max + uint32_t bluramount = particlesize; + uint32_t bitshift = 0; + + for(int i = 0; i < passes; i++) + { + if (i == 2) // for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) + bitshift = 1; + + if (useLocalBuffer) + blur2D(framebuffer, maxXpixel + 1, maxYpixel + 1, bluramount << bitshift, bluramount << bitshift); + else + SEGMENT.blur(bluramount << bitshift, true); + bluramount -= 64; + } + } + + if (useLocalBuffer) // transfer local buffer back to segment + { + uint32_t yflipped; + for (int y = 0; y <= maxYpixel; y++) + { + yflipped = maxYpixel - y; + for (int x = 0; x <= maxXpixel; x++) + { + SEGMENT.setPixelColorXY(x, yflipped, framebuffer[x][y]); + } + } + free(framebuffer); + } + if (renderbuffer) + free(renderbuffer); } // calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, uint32_t brightess, CRGB color, CRGB **renderbuffer) { - int32_t pxlbrightness[4] = {0}; // note: pxlbrightness needs to be set to 0 or checking does not work - int32_t pixco[4][2]; // physical pixel coordinates of the four pixels a particle is rendered to. x,y pairs - bool advancedrender = false; // rendering for advanced particles - - // subtract half a radius as the rendering algorithm always starts at the bottom left, this makes calculations more efficient - int32_t xoffset = particles[particleindex].x - PS_P_HALFRADIUS; - int32_t yoffset = particles[particleindex].y - PS_P_HALFRADIUS; - int32_t dx = xoffset % PS_P_RADIUS; //relativ particle position in subpixel space - int32_t dy = yoffset % PS_P_RADIUS; - int32_t x = xoffset >> PS_P_RADIUS_SHIFT; // divide by PS_P_RADIUS which is 64, so can bitshift (compiler may not optimize automatically) - int32_t y = yoffset >> PS_P_RADIUS_SHIFT; - - // check if particle has advanced size properties and buffer is available - if(advPartProps) - { - if(advPartProps[particleindex].size > 0) - { - if(renderbuffer) - { - advancedrender = true; - memset(renderbuffer[0], 0, 100 * sizeof(CRGB)); // clear the buffer, renderbuffer is 10x10 pixels - } - else - return; // cannot render without buffer, advanced size particles are allowed out of frame - } - } - - // set the four raw pixel coordinates, the order is bottom left [0], bottom right[1], top right [2], top left [3] - pixco[0][0] = pixco[3][0] = x; // bottom left & top left - pixco[0][1] = pixco[1][1] = y; // bottom left & bottom right - pixco[1][0] = pixco[2][0] = x + 1; // bottom right & top right - pixco[2][1] = pixco[3][1] = y + 1; // top right & top left - - // now check if any are out of frame. set values to -1 if they are so they can be easily checked after (no value calculation, no setting of pixelcolor if value < 0) - if (x < 0) // left pixels out of frame - { - dx = PS_P_RADIUS + dx; // if x<0, xoffset becomes negative (and so does dx), must adjust dx as modulo will flip its value (really old bug now finally fixed) - // note: due to inverted shift math, a particel at position -32 (xoffset = -64, dx = 64) is rendered at the wrong pixel position (it should be out of frame) - // checking this above makes this algorithm slower (in frame pixels do not have to be checked), so just correct for it here: - if (dx == PS_P_RADIUS) - { - pxlbrightness[1] = pxlbrightness[2] = -1; // pixel is actually out of matrix boundaries, do not render - } - if (particlesettings.wrapX) // wrap x to the other side if required - pixco[0][0] = pixco[3][0] = maxXpixel; - else - pxlbrightness[0] = pxlbrightness[3] = -1; // pixel is out of matrix boundaries, do not render - } - else if (pixco[1][0] > maxXpixel) // right pixels, only has to be checkt if left pixels did not overflow - { - if (particlesettings.wrapX) // wrap y to the other side if required - pixco[1][0] = pixco[2][0] = 0; - else - pxlbrightness[1] = pxlbrightness[2] = -1; - } - - if (y < 0) // bottom pixels out of frame - { - dy = PS_P_RADIUS + dy; //see note above - if (dy == PS_P_RADIUS) - { - pxlbrightness[2] = pxlbrightness[3] = -1; // pixel is actually out of matrix boundaries, do not render - } - if (particlesettings.wrapY) // wrap y to the other side if required - pixco[0][1] = pixco[1][1] = maxYpixel; - else - pxlbrightness[0] = pxlbrightness[1] = -1; - } - else if (pixco[2][1] > maxYpixel) // top pixels - { - if (particlesettings.wrapY) // wrap y to the other side if required - pixco[2][1] = pixco[3][1] = 0; - else - pxlbrightness[2] = pxlbrightness[3] = -1; - } - - if(advancedrender) // always render full particles in advanced rendering, undo out of frame marking (faster than checking each time in code above) - { - for(uint32_t i = 0; i < 4; i++) - pxlbrightness[i] = 0; - } - - // calculate brightness values for all four pixels representing a particle using linear interpolation - // precalculate values for speed optimization - int32_t precal1 = (int32_t)PS_P_RADIUS - dx; - int32_t precal2 = ((int32_t)PS_P_RADIUS - dy) * brightess; - int32_t precal3 = dy * brightess; - - //calculate the values for pixels that are in frame - if (pxlbrightness[0] >= 0) - pxlbrightness[0] = (precal1 * precal2) >> PS_P_SURFACE; // bottom left value equal to ((PS_P_RADIUS - dx) * (PS_P_RADIUS-dy) * brightess) >> PS_P_SURFACE - if (pxlbrightness[1] >= 0) - pxlbrightness[1] = (dx * precal2) >> PS_P_SURFACE; // bottom right value equal to (dx * (PS_P_RADIUS-dy) * brightess) >> PS_P_SURFACE - if (pxlbrightness[2] >= 0) - pxlbrightness[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightess) >> PS_P_SURFACE - if (pxlbrightness[3] >= 0) - pxlbrightness[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightess) >> PS_P_SURFACE - - if(advancedrender) - { - //render particle to a bigger size - //particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10 - //first, render the pixel to the center of the renderbuffer, then apply 2D blurring - fast_color_add(renderbuffer[4][4], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left - fast_color_add(renderbuffer[5][4], color, pxlbrightness[1]); - fast_color_add(renderbuffer[5][5], color, pxlbrightness[2]); - fast_color_add(renderbuffer[4][5], color, pxlbrightness[3]); //TODO: make this a loop somehow? needs better coordinate handling... - uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4 - uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below) - uint32_t maxsize = advPartProps[particleindex].size; - uint32_t xsize = maxsize; - uint32_t ysize = maxsize; - if(advPartSize) // use advanced size control - { - if(advPartSize[particleindex].asymmetry > 0) - getParticleXYsize(&advPartProps[particleindex], &advPartSize[particleindex], xsize, ysize); - maxsize = xsize; - if(ysize > maxsize) maxsize = ysize; //maxsize is now the bigger of the two - } - maxsize = maxsize/64 + 1; // number of blur passes depends on maxsize, four passes max - uint32_t bitshift = 0; - for(int i = 0; i < maxsize; i++) - { - if(i == 2) //for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) - bitshift = 1; - rendersize += 2; - offset--; - blur2D(renderbuffer, rendersize, rendersize, xsize << bitshift, ysize << bitshift, true, offset, offset, true); //blur to 4x4 - xsize = xsize > 64 ? xsize - 64 : 0; - ysize = ysize > 64 ? ysize - 64 : 0; - } - - // calculate origin coordinates to render the particle to in the framebuffer - uint32_t xfb_orig = x - (rendersize>>1) + 1 - offset; - uint32_t yfb_orig = y - (rendersize>>1) + 1 - offset; - uint32_t xfb, yfb; // coordinates in frame buffer to write to note: by making this uint, only overflow has to be checked - - // transfer particle renderbuffer to framebuffer - for(uint32_t xrb = offset; xrb < rendersize+offset; xrb++) - { - xfb = xfb_orig + xrb; - if(xfb > maxXpixel) - { - if (particlesettings.wrapX) // wrap x to the other side if required - xfb = xfb % (maxXpixel + 1); - else - continue; - } - - for(uint32_t yrb = offset; yrb < rendersize+offset; yrb++) - { - yfb = yfb_orig + yrb; - if(yfb > maxYpixel) - { - if (particlesettings.wrapY) // wrap y to the other side if required - yfb = yfb % (maxYpixel + 1); - else - continue; - } - fast_color_add(framebuffer[xfb][yfb], renderbuffer[xrb][yrb]); - } - } - } - else // standard rendering - { - if (framebuffer) - { - for(uint32_t i = 0; i < 4; i++) - { - if (pxlbrightness[i] > 0) - fast_color_add(framebuffer[pixco[i][0]][pixco[i][1]], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left - } - } - else - { - for(uint32_t i = 0; i < 4; i++) - { - if (pxlbrightness[i] > 0) - SEGMENT.addPixelColorXY(pixco[i][0], maxYpixel - pixco[i][1], color.scale8((uint8_t)pxlbrightness[i])); - } - } - } + int32_t pxlbrightness[4] = {0}; // note: pxlbrightness needs to be set to 0 or checking does not work + int32_t pixco[4][2]; // physical pixel coordinates of the four pixels a particle is rendered to. x,y pairs + bool advancedrender = false; // rendering for advanced particles + + // subtract half a radius as the rendering algorithm always starts at the bottom left, this makes calculations more efficient + int32_t xoffset = particles[particleindex].x - PS_P_HALFRADIUS; + int32_t yoffset = particles[particleindex].y - PS_P_HALFRADIUS; + int32_t dx = xoffset % PS_P_RADIUS; //relativ particle position in subpixel space + int32_t dy = yoffset % PS_P_RADIUS; + int32_t x = xoffset >> PS_P_RADIUS_SHIFT; // divide by PS_P_RADIUS which is 64, so can bitshift (compiler may not optimize automatically) + int32_t y = yoffset >> PS_P_RADIUS_SHIFT; + + // check if particle has advanced size properties and buffer is available + if (advPartProps) + { + if (advPartProps[particleindex].size > 0) + { + if (renderbuffer) + { + advancedrender = true; + memset(renderbuffer[0], 0, 100 * sizeof(CRGB)); // clear the buffer, renderbuffer is 10x10 pixels + } + else + return; // cannot render without buffer, advanced size particles are allowed out of frame + } + } + + // set the four raw pixel coordinates, the order is bottom left [0], bottom right[1], top right [2], top left [3] + pixco[0][0] = pixco[3][0] = x; // bottom left & top left + pixco[0][1] = pixco[1][1] = y; // bottom left & bottom right + pixco[1][0] = pixco[2][0] = x + 1; // bottom right & top right + pixco[2][1] = pixco[3][1] = y + 1; // top right & top left + + // now check if any are out of frame. set values to -1 if they are so they can be easily checked after (no value calculation, no setting of pixelcolor if value < 0) + if (x < 0) // left pixels out of frame + { + dx = PS_P_RADIUS + dx; // if x<0, xoffset becomes negative (and so does dx), must adjust dx as modulo will flip its value (really old bug now finally fixed) + // note: due to inverted shift math, a particel at position -32 (xoffset = -64, dx = 64) is rendered at the wrong pixel position (it should be out of frame) + // checking this above makes this algorithm slower (in frame pixels do not have to be checked), so just correct for it here: + if (dx == PS_P_RADIUS) + { + pxlbrightness[1] = pxlbrightness[2] = -1; // pixel is actually out of matrix boundaries, do not render + } + if (particlesettings.wrapX) // wrap x to the other side if required + pixco[0][0] = pixco[3][0] = maxXpixel; + else + pxlbrightness[0] = pxlbrightness[3] = -1; // pixel is out of matrix boundaries, do not render + } + else if (pixco[1][0] > maxXpixel) // right pixels, only has to be checkt if left pixels did not overflow + { + if (particlesettings.wrapX) // wrap y to the other side if required + pixco[1][0] = pixco[2][0] = 0; + else + pxlbrightness[1] = pxlbrightness[2] = -1; + } + + if (y < 0) // bottom pixels out of frame + { + dy = PS_P_RADIUS + dy; //see note above + if (dy == PS_P_RADIUS) + { + pxlbrightness[2] = pxlbrightness[3] = -1; // pixel is actually out of matrix boundaries, do not render + } + if (particlesettings.wrapY) // wrap y to the other side if required + pixco[0][1] = pixco[1][1] = maxYpixel; + else + pxlbrightness[0] = pxlbrightness[1] = -1; + } + else if (pixco[2][1] > maxYpixel) // top pixels + { + if (particlesettings.wrapY) // wrap y to the other side if required + pixco[2][1] = pixco[3][1] = 0; + else + pxlbrightness[2] = pxlbrightness[3] = -1; + } + + if (advancedrender) // always render full particles in advanced rendering, undo out of frame marking (faster than checking each time in code above) + { + for(uint32_t i = 0; i < 4; i++) + pxlbrightness[i] = 0; + } + + // calculate brightness values for all four pixels representing a particle using linear interpolation + // precalculate values for speed optimization + int32_t precal1 = (int32_t)PS_P_RADIUS - dx; + int32_t precal2 = ((int32_t)PS_P_RADIUS - dy) * brightess; + int32_t precal3 = dy * brightess; + + //calculate the values for pixels that are in frame + if (pxlbrightness[0] >= 0) + pxlbrightness[0] = (precal1 * precal2) >> PS_P_SURFACE; // bottom left value equal to ((PS_P_RADIUS - dx) * (PS_P_RADIUS-dy) * brightess) >> PS_P_SURFACE + if (pxlbrightness[1] >= 0) + pxlbrightness[1] = (dx * precal2) >> PS_P_SURFACE; // bottom right value equal to (dx * (PS_P_RADIUS-dy) * brightess) >> PS_P_SURFACE + if (pxlbrightness[2] >= 0) + pxlbrightness[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightess) >> PS_P_SURFACE + if (pxlbrightness[3] >= 0) + pxlbrightness[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightess) >> PS_P_SURFACE + + if (advancedrender) + { + //render particle to a bigger size + //particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10 + //first, render the pixel to the center of the renderbuffer, then apply 2D blurring + fast_color_add(renderbuffer[4][4], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left + fast_color_add(renderbuffer[5][4], color, pxlbrightness[1]); + fast_color_add(renderbuffer[5][5], color, pxlbrightness[2]); + fast_color_add(renderbuffer[4][5], color, pxlbrightness[3]); //TODO: make this a loop somehow? needs better coordinate handling... + uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4 + uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below) + uint32_t maxsize = advPartProps[particleindex].size; + uint32_t xsize = maxsize; + uint32_t ysize = maxsize; + if (advPartSize) // use advanced size control + { + if (advPartSize[particleindex].asymmetry > 0) + getParticleXYsize(&advPartProps[particleindex], &advPartSize[particleindex], xsize, ysize); + maxsize = xsize; + if (ysize > maxsize) maxsize = ysize; //maxsize is now the bigger of the two + } + maxsize = maxsize/64 + 1; // number of blur passes depends on maxsize, four passes max + uint32_t bitshift = 0; + for(int i = 0; i < maxsize; i++) + { + if (i == 2) //for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) + bitshift = 1; + rendersize += 2; + offset--; + blur2D(renderbuffer, rendersize, rendersize, xsize << bitshift, ysize << bitshift, true, offset, offset, true); //blur to 4x4 + xsize = xsize > 64 ? xsize - 64 : 0; + ysize = ysize > 64 ? ysize - 64 : 0; + } + + // calculate origin coordinates to render the particle to in the framebuffer + uint32_t xfb_orig = x - (rendersize>>1) + 1 - offset; + uint32_t yfb_orig = y - (rendersize>>1) + 1 - offset; + uint32_t xfb, yfb; // coordinates in frame buffer to write to note: by making this uint, only overflow has to be checked + + // transfer particle renderbuffer to framebuffer + for(uint32_t xrb = offset; xrb < rendersize+offset; xrb++) + { + xfb = xfb_orig + xrb; + if (xfb > maxXpixel) + { + if (particlesettings.wrapX) // wrap x to the other side if required + xfb = xfb % (maxXpixel + 1); + else + continue; + } + + for(uint32_t yrb = offset; yrb < rendersize+offset; yrb++) + { + yfb = yfb_orig + yrb; + if (yfb > maxYpixel) + { + if (particlesettings.wrapY) // wrap y to the other side if required + yfb = yfb % (maxYpixel + 1); + else + continue; + } + fast_color_add(framebuffer[xfb][yfb], renderbuffer[xrb][yrb]); + } + } + } + else // standard rendering + { + if (framebuffer) + { + for(uint32_t i = 0; i < 4; i++) + { + if (pxlbrightness[i] > 0) + fast_color_add(framebuffer[pixco[i][0]][pixco[i][1]], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left + } + } + else + { + for(uint32_t i = 0; i < 4; i++) + { + if (pxlbrightness[i] > 0) + SEGMENT.addPixelColorXY(pixco[i][0], maxYpixel - pixco[i][1], color.scale8((uint8_t)pxlbrightness[i])); + } + } + } /* - // debug: check coordinates if out of buffer boundaries print out some info (rendering out of bounds particle causes crash!) - for (uint32_t d = 0; d < 4; d++) - { - if (pixco[d][0] < 0 || pixco[d][0] > maxXpixel) - { - //Serial.print("<"); - if (pxlbrightness[d] >= 0) - { - Serial.print("uncought out of bounds: x:"); - Serial.print(pixco[d][0]); - Serial.print(" y:"); - Serial.print(pixco[d][1]); - Serial.print("particle x="); - Serial.print(particles[particleindex].x); - Serial.print(" y="); - Serial.println(particles[particleindex].y); - pxlbrightness[d] = -1; // do not render - } - } - if (pixco[d][1] < 0 || pixco[d][1] > maxYpixel) - { - //Serial.print("^"); - if (pxlbrightness[d] >= 0) - { - Serial.print("uncought out of bounds: y:"); - Serial.print(pixco[d][0]); - Serial.print(" y:"); - Serial.print(pixco[d][1]); - Serial.print("particle x="); - Serial.print(particles[particleindex].x); - Serial.print(" y="); - Serial.println(particles[particleindex].y); - pxlbrightness[d] = -1; // do not render - } - } - } + // debug: check coordinates if out of buffer boundaries print out some info (rendering out of bounds particle causes crash!) + for (uint32_t d = 0; d < 4; d++) + { + if (pixco[d][0] < 0 || pixco[d][0] > maxXpixel) + { + //Serial.print("<"); + if (pxlbrightness[d] >= 0) + { + Serial.print("uncought out of bounds: x:"); + Serial.print(pixco[d][0]); + Serial.print(" y:"); + Serial.print(pixco[d][1]); + Serial.print("particle x="); + Serial.print(particles[particleindex].x); + Serial.print(" y="); + Serial.println(particles[particleindex].y); + pxlbrightness[d] = -1; // do not render + } + } + if (pixco[d][1] < 0 || pixco[d][1] > maxYpixel) + { + //Serial.print("^"); + if (pxlbrightness[d] >= 0) + { + Serial.print("uncought out of bounds: y:"); + Serial.print(pixco[d][0]); + Serial.print(" y:"); + Serial.print(pixco[d][1]); + Serial.print("particle x="); + Serial.print(particles[particleindex].x); + Serial.print(" y="); + Serial.println(particles[particleindex].y); + pxlbrightness[d] = -1; // do not render + } + } + } */ } @@ -1060,252 +1059,252 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, // particles move upwards faster if ttl is high (i.e. they are hotter) void ParticleSystem::fireParticleupdate() { - //TODO: cleanup this function? check if normal move is much slower, change move function to check y first then this function just needs to add ttl to y befor calling normal move function - uint32_t i = 0; - - for (i = 0; i < usedParticles; i++) - { - if (particles[i].ttl > 0) - { - // age - particles[i].ttl--; - // apply velocity - particles[i].x = particles[i].x + (int32_t)particles[i].vx; - particles[i].y = particles[i].y + (int32_t)particles[i].vy + (particles[i].ttl >> 2); // younger particles move faster upward as they are hotter - //particles[i].y = particles[i].y + (int32_t)particles[i].vy;// + (particles[i].ttl >> 3); // younger particles move faster upward as they are hotter //this is experimental, different shifting - particles[i].outofbounds = 0; - // check if particle is out of bounds, wrap x around to other side if wrapping is enabled - // as fire particles start below the frame, lots of particles are out of bounds in y direction. to improve speed, only check x direction if y is not out of bounds - // y-direction - if (particles[i].y < -PS_P_HALFRADIUS) - particles[i].outofbounds = 1; - else if (particles[i].y > maxY + PS_P_HALFRADIUS) // particle moved out at the top - particles[i].ttl = 0; - else // particle is in frame in y direction, also check x direction now - { - if ((particles[i].x < 0) || (particles[i].x > maxX)) - { - if (particlesettings.wrapX) - { - particles[i].x = (uint16_t)particles[i].x % (maxX + 1); - } - else if ((particles[i].x < -PS_P_HALFRADIUS) || (particles[i].x > maxX + PS_P_HALFRADIUS)) //if fully out of view - { - particles[i].ttl = 0; - } - } - } - } - } + //TODO: cleanup this function? check if normal move is much slower, change move function to check y first then this function just needs to add ttl to y befor calling normal move function + uint32_t i = 0; + + for (i = 0; i < usedParticles; i++) + { + if (particles[i].ttl > 0) + { + // age + particles[i].ttl--; + // apply velocity + particles[i].x = particles[i].x + (int32_t)particles[i].vx; + particles[i].y = particles[i].y + (int32_t)particles[i].vy + (particles[i].ttl >> 2); // younger particles move faster upward as they are hotter + //particles[i].y = particles[i].y + (int32_t)particles[i].vy;// + (particles[i].ttl >> 3); // younger particles move faster upward as they are hotter //this is experimental, different shifting + particles[i].outofbounds = 0; + // check if particle is out of bounds, wrap x around to other side if wrapping is enabled + // as fire particles start below the frame, lots of particles are out of bounds in y direction. to improve speed, only check x direction if y is not out of bounds + // y-direction + if (particles[i].y < -PS_P_HALFRADIUS) + particles[i].outofbounds = 1; + else if (particles[i].y > maxY + PS_P_HALFRADIUS) // particle moved out at the top + particles[i].ttl = 0; + else // particle is in frame in y direction, also check x direction now + { + if ((particles[i].x < 0) || (particles[i].x > maxX)) + { + if (particlesettings.wrapX) + { + particles[i].x = (uint16_t)particles[i].x % (maxX + 1); + } + else if ((particles[i].x < -PS_P_HALFRADIUS) || (particles[i].x > maxX + PS_P_HALFRADIUS)) //if fully out of view + { + particles[i].ttl = 0; + } + } + } + } + } } // detect collisions in an array of particles and handle them void ParticleSystem::handleCollisions() { - // detect and handle collisions - uint32_t i, j; - uint32_t startparticle = 0; - uint32_t endparticle = usedParticles >> 1; // do half the particles, significantly speeds things up - // every second frame, do other half of particles (helps to speed things up as not all collisions are handled each frame, less accurate but good enough) - // if more accurate collisions are needed, just call it twice in a row - if (collisioncounter & 0x01) - { - startparticle = endparticle; - endparticle = usedParticles; - } - collisioncounter++; - - for (i = startparticle; i < endparticle; i++) - { - // go though all 'higher number' particles and see if any of those are in close proximity and if they are, make them collide - if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) // if particle is alive and does collide and is not out of view - { - int32_t dx, dy; // distance to other particles - for (j = i + 1; j < usedParticles; j++) // check against higher number particles - { - if (particles[j].ttl > 0) // if target particle is alive - { - dx = particles[i].x - particles[j].x; - if(advPartProps) //may be using individual particle size - { - particleHardRadius = PS_P_MINHARDRADIUS + particlesize + (((uint32_t)advPartProps[i].size + (uint32_t)advPartProps[j].size)>>1); // collision distance - } - if (dx < particleHardRadius && dx > -particleHardRadius) // check x direction, if close, check y direction - { - dy = particles[i].y - particles[j].y; - if (dy < particleHardRadius && dy > -particleHardRadius) // particles are close - collideParticles(&particles[i], &particles[j]); - } - } - } - } - } + // detect and handle collisions + uint32_t i, j; + uint32_t startparticle = 0; + uint32_t endparticle = usedParticles >> 1; // do half the particles, significantly speeds things up + // every second frame, do other half of particles (helps to speed things up as not all collisions are handled each frame, less accurate but good enough) + // if more accurate collisions are needed, just call it twice in a row + if (collisioncounter & 0x01) + { + startparticle = endparticle; + endparticle = usedParticles; + } + collisioncounter++; + + for (i = startparticle; i < endparticle; i++) + { + // go though all 'higher number' particles and see if any of those are in close proximity and if they are, make them collide + if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) // if particle is alive and does collide and is not out of view + { + int32_t dx, dy; // distance to other particles + for (j = i + 1; j < usedParticles; j++) // check against higher number particles + { + if (particles[j].ttl > 0) // if target particle is alive + { + dx = particles[i].x - particles[j].x; + if (advPartProps) //may be using individual particle size + { + particleHardRadius = PS_P_MINHARDRADIUS + particlesize + (((uint32_t)advPartProps[i].size + (uint32_t)advPartProps[j].size)>>1); // collision distance + } + if (dx < particleHardRadius && dx > -particleHardRadius) // check x direction, if close, check y direction + { + dy = particles[i].y - particles[j].y; + if (dy < particleHardRadius && dy > -particleHardRadius) // particles are close + collideParticles(&particles[i], &particles[j]); + } + } + } + } + } } // handle a collision if close proximity is detected, i.e. dx and/or dy smaller than 2*PS_P_RADIUS // takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard) void ParticleSystem::collideParticles(PSparticle *particle1, PSparticle *particle2) // TODO: dx,dy is calculated just above, can pass it over here to save a few CPU cycles? { - int32_t dx = particle2->x - particle1->x; - int32_t dy = particle2->y - particle1->y; - int32_t distanceSquared = dx * dx + dy * dy; - // Calculate relative velocity (if it is zero, could exit but extra check does not overall speed but deminish it) - int32_t relativeVx = (int16_t)particle2->vx - (int16_t)particle1->vx; - int32_t relativeVy = (int16_t)particle2->vy - (int16_t)particle1->vy; - - // if dx and dy are zero (i.e. they meet at the center) give them an offset, if speeds are also zero, also offset them (pushes them apart if they are clumped before enabling collisions) - if (distanceSquared == 0) - { - // Adjust positions based on relative velocity direction - dx = -1; - if (relativeVx < 0) // if true, particle2 is on the right side - dx = 1; - else if(relativeVx == 0) - relativeVx = 1; - - dy = -1; - if (relativeVy < 0) - dy = 1; - else if (relativeVy == 0) - relativeVy = 1; - - distanceSquared = 2; //1 + 1 - } - - // Calculate dot product of relative velocity and relative distance - int32_t dotProduct = (dx * relativeVx + dy * relativeVy); // is always negative if moving towards each other - int32_t notsorandom = dotProduct & 0x01; //dotprouct LSB should be somewhat random, so no need to calculate a random number - - if (dotProduct < 0) // particles are moving towards each other - { - // integer math used to avoid floats. - // overflow check: dx/dy are 7bit, relativV are 8bit -> dotproduct is 15bit, dotproduct/distsquared ist 8b, multiplied by collisionhardness of 8bit. so a 16bit shift is ok, make it 15 to be sure no overflows happen - // note: cannot use right shifts as bit shifting in right direction is asymmetrical for positive and negative numbers and this needs to be accurate! the trick is: only shift positive numers - // Calculate new velocities after collision - uint32_t surfacehardness = collisionHardness < PS_P_MINSURFACEHARDNESS ? PS_P_MINSURFACEHARDNESS : collisionHardness; // if particles are soft, the impulse must stay above a limit or collisions slip through at higher speeds, 170 seems to be a good value - int32_t impulse = -(((((-dotProduct) << 15) / distanceSquared) * surfacehardness) >> 8); // note: inverting before bitshift corrects for asymmetry in right-shifts (and is slightly faster) - int32_t ximpulse = ((impulse) * dx) / 32767; // cannot use bit shifts here, it can be negative, use division by 2^bitshift - int32_t yimpulse = ((impulse) * dy) / 32767; - particle1->vx += ximpulse; - particle1->vy += yimpulse; - particle2->vx -= ximpulse; - particle2->vy -= yimpulse; - - if (collisionHardness < surfacehardness) // if particles are soft, they become 'sticky' i.e. apply some friction (they do pile more nicely) - { - const uint32_t coeff = collisionHardness + (255 - PS_P_MINSURFACEHARDNESS); - particle1->vx = ((int32_t)particle1->vx * coeff) / 255; - particle1->vy = ((int32_t)particle1->vy * coeff) / 255; - - particle2->vx = ((int32_t)particle2->vx * coeff) / 255; - particle2->vy = ((int32_t)particle2->vy * coeff) / 255; - - if (collisionHardness < 10) // if they are very soft, stop slow particles completely to make them stick to each other - { - particle1->vx = (particle1->vx < 3 && particle1->vx > -3) ? 0 : particle1->vx; - particle1->vy = (particle1->vy < 3 && particle1->vy > -3) ? 0 : particle1->vy; - - particle2->vx = (particle2->vx < 3 && particle2->vx > -3) ? 0 : particle2->vx; - particle2->vy = (particle2->vy < 3 && particle2->vy > -3) ? 0 : particle2->vy; - } - } - - // particles have volume, push particles apart if they are too close - // tried lots of configurations, it works best if not moved but given a little velocity, it tends to oscillate less this way - // a problem with giving velocity is, that on harder collisions, this adds up as it is not dampened enough, so add friction in the FX if required - if (dotProduct > -250) //this means particles are slow (or really really close) so push them apart. - { - int32_t pushamount = 1 + ((250 + dotProduct) >> 6); // the closer dotproduct is to zero, the closer the particles are - int32_t push = 0; - if (dx < 0) // particle 1 is on the right - push = pushamount; - else if (dx > 0) - push = -pushamount; - else // on the same x coordinate, shift it a little so they do not stack - { - if (notsorandom) - particle1->x++; // move it so pile collapses - else - particle1->x--; - } - particle1->vx += push; - push = 0; - if (dy < 0) - push = pushamount; - else if (dy > 0) - push = -pushamount; - else // dy==0 - { - if (notsorandom) - particle1->y++; // move it so pile collapses - else - particle1->y--; - } - particle1->vy += push; - // note: pushing may push particles out of frame, if bounce is active, it will move it back as position will be limited to within frame, if bounce is disabled: bye bye - } - } + int32_t dx = particle2->x - particle1->x; + int32_t dy = particle2->y - particle1->y; + int32_t distanceSquared = dx * dx + dy * dy; + // Calculate relative velocity (if it is zero, could exit but extra check does not overall speed but deminish it) + int32_t relativeVx = (int16_t)particle2->vx - (int16_t)particle1->vx; + int32_t relativeVy = (int16_t)particle2->vy - (int16_t)particle1->vy; + + // if dx and dy are zero (i.e. they meet at the center) give them an offset, if speeds are also zero, also offset them (pushes them apart if they are clumped before enabling collisions) + if (distanceSquared == 0) + { + // Adjust positions based on relative velocity direction + dx = -1; + if (relativeVx < 0) // if true, particle2 is on the right side + dx = 1; + else if (relativeVx == 0) + relativeVx = 1; + + dy = -1; + if (relativeVy < 0) + dy = 1; + else if (relativeVy == 0) + relativeVy = 1; + + distanceSquared = 2; //1 + 1 + } + + // Calculate dot product of relative velocity and relative distance + int32_t dotProduct = (dx * relativeVx + dy * relativeVy); // is always negative if moving towards each other + int32_t notsorandom = dotProduct & 0x01; //dotprouct LSB should be somewhat random, so no need to calculate a random number + + if (dotProduct < 0) // particles are moving towards each other + { + // integer math used to avoid floats. + // overflow check: dx/dy are 7bit, relativV are 8bit -> dotproduct is 15bit, dotproduct/distsquared ist 8b, multiplied by collisionhardness of 8bit. so a 16bit shift is ok, make it 15 to be sure no overflows happen + // note: cannot use right shifts as bit shifting in right direction is asymmetrical for positive and negative numbers and this needs to be accurate! the trick is: only shift positive numers + // Calculate new velocities after collision + uint32_t surfacehardness = collisionHardness < PS_P_MINSURFACEHARDNESS ? PS_P_MINSURFACEHARDNESS : collisionHardness; // if particles are soft, the impulse must stay above a limit or collisions slip through at higher speeds, 170 seems to be a good value + int32_t impulse = -(((((-dotProduct) << 15) / distanceSquared) * surfacehardness) >> 8); // note: inverting before bitshift corrects for asymmetry in right-shifts (and is slightly faster) + int32_t ximpulse = ((impulse) * dx) / 32767; // cannot use bit shifts here, it can be negative, use division by 2^bitshift + int32_t yimpulse = ((impulse) * dy) / 32767; + particle1->vx += ximpulse; + particle1->vy += yimpulse; + particle2->vx -= ximpulse; + particle2->vy -= yimpulse; + + if (collisionHardness < surfacehardness) // if particles are soft, they become 'sticky' i.e. apply some friction (they do pile more nicely) + { + const uint32_t coeff = collisionHardness + (255 - PS_P_MINSURFACEHARDNESS); + particle1->vx = ((int32_t)particle1->vx * coeff) / 255; + particle1->vy = ((int32_t)particle1->vy * coeff) / 255; + + particle2->vx = ((int32_t)particle2->vx * coeff) / 255; + particle2->vy = ((int32_t)particle2->vy * coeff) / 255; + + if (collisionHardness < 10) // if they are very soft, stop slow particles completely to make them stick to each other + { + particle1->vx = (particle1->vx < 3 && particle1->vx > -3) ? 0 : particle1->vx; + particle1->vy = (particle1->vy < 3 && particle1->vy > -3) ? 0 : particle1->vy; + + particle2->vx = (particle2->vx < 3 && particle2->vx > -3) ? 0 : particle2->vx; + particle2->vy = (particle2->vy < 3 && particle2->vy > -3) ? 0 : particle2->vy; + } + } + + // particles have volume, push particles apart if they are too close + // tried lots of configurations, it works best if not moved but given a little velocity, it tends to oscillate less this way + // a problem with giving velocity is, that on harder collisions, this adds up as it is not dampened enough, so add friction in the FX if required + if (dotProduct > -250) //this means particles are slow (or really really close) so push them apart. + { + int32_t pushamount = 1 + ((250 + dotProduct) >> 6); // the closer dotproduct is to zero, the closer the particles are + int32_t push = 0; + if (dx < 0) // particle 1 is on the right + push = pushamount; + else if (dx > 0) + push = -pushamount; + else // on the same x coordinate, shift it a little so they do not stack + { + if (notsorandom) + particle1->x++; // move it so pile collapses + else + particle1->x--; + } + particle1->vx += push; + push = 0; + if (dy < 0) + push = pushamount; + else if (dy > 0) + push = -pushamount; + else // dy==0 + { + if (notsorandom) + particle1->y++; // move it so pile collapses + else + particle1->y--; + } + particle1->vy += push; + // note: pushing may push particles out of frame, if bounce is active, it will move it back as position will be limited to within frame, if bounce is disabled: bye bye + } + } } // calculate the delta speed (dV) value and update the counter for force calculation (is used several times, function saves on codesize) // force is in 3.4 fixedpoint notation, +/-127 int32_t ParticleSystem::calcForce_dv(int8_t force, uint8_t* counter) { - if(force == 0) - return 0; - // for small forces, need to use a delay counter - int32_t force_abs = abs(force); // absolute value (faster than lots of if's only 7 instructions) - int32_t dv; - // for small forces, need to use a delay counter, apply force only if it overflows - if (force_abs < 16) - { - *counter += force_abs; - if (*counter > 15) - { - *counter -= 16; - dv = force < 0 ? -1 : 1; // force is either, 1 or -1 if it is small (zero force is handled above) - } - } - else - { - dv = force >> 4; // MSBs - } - return dv; + if (force == 0) + return 0; + // for small forces, need to use a delay counter + int32_t force_abs = abs(force); // absolute value (faster than lots of if's only 7 instructions) + int32_t dv; + // for small forces, need to use a delay counter, apply force only if it overflows + if (force_abs < 16) + { + *counter += force_abs; + if (*counter > 15) + { + *counter -= 16; + dv = force < 0 ? -1 : 1; // force is either, 1 or -1 if it is small (zero force is handled above) + } + } + else + { + dv = force >> 4; // MSBs + } + return dv; } // limit speed to prevent overflows int32_t ParticleSystem::limitSpeed(int32_t speed) { - return speed > PS_P_MAXSPEED ? PS_P_MAXSPEED : (speed < -PS_P_MAXSPEED ? -PS_P_MAXSPEED : speed); + return speed > PS_P_MAXSPEED ? PS_P_MAXSPEED : (speed < -PS_P_MAXSPEED ? -PS_P_MAXSPEED : speed); } // allocate memory for the 2D array in one contiguous block and set values to zero CRGB **ParticleSystem::allocate2Dbuffer(uint32_t cols, uint32_t rows) -{ - CRGB ** array2D = (CRGB **)calloc(cols, sizeof(CRGB *) + rows * sizeof(CRGB)); - if (array2D == NULL) - DEBUG_PRINT(F("PS buffer alloc failed")); - else - { - // assign pointers of 2D array - CRGB *start = (CRGB *)(array2D + cols); - for (uint i = 0; i < cols; i++) - { - array2D[i] = start + i * rows; - } - } - return array2D; +{ + CRGB ** array2D = (CRGB **)calloc(cols, sizeof(CRGB *) + rows * sizeof(CRGB)); + if (array2D == NULL) + DEBUG_PRINT(F("PS buffer alloc failed")); + else + { + // assign pointers of 2D array + CRGB *start = (CRGB *)(array2D + cols); + for (uint i = 0; i < cols; i++) + { + array2D[i] = start + i * rows; + } + } + return array2D; } // update size and pointers (memory location and size can change dynamically) // note: do not access the PS class in FX befor running this function (or it messes up SEGMENT.data) void ParticleSystem::updateSystem(void) { - // update matrix size - uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; - uint32_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - setMatrixSize(cols, rows); - updatePSpointers(advPartProps != NULL, advPartSize != NULL); + // update matrix size + uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; + uint32_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + setMatrixSize(cols, rows); + updatePSpointers(advPartProps != NULL, advPartSize != NULL); } // set the pointers for the class (this only has to be done once and not on every FX call, only the class pointer needs to be reassigned to SEGENV.data every time) @@ -1313,124 +1312,124 @@ void ParticleSystem::updateSystem(void) // FX handles the PSsources, need to tell this function how many there are void ParticleSystem::updatePSpointers(bool isadvanced, bool sizecontrol) { - // DEBUG_PRINT(F("*** PS pointers ***")); - // DEBUG_PRINTF_P(PSTR("this PS %p "), this); - // Note on memory alignment: - // a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment. - // The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock. - // by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes. - particles = reinterpret_cast(this + 1); // pointer to particle array at data+sizeof(ParticleSystem) - sources = reinterpret_cast(particles + numParticles); // pointer to source(s) - if(isadvanced) - { - advPartProps = reinterpret_cast(sources + numSources); - PSdataEnd = reinterpret_cast(advPartProps + numParticles); - if(sizecontrol) - { - advPartSize = reinterpret_cast(advPartProps + numParticles); - PSdataEnd = reinterpret_cast(advPartSize + numParticles); - } - } - else - { - PSdataEnd = reinterpret_cast(sources + numSources); // pointer to first available byte after the PS for FX additional data - } - /* - DEBUG_PRINTF_P(PSTR(" particles %p "), particles); - DEBUG_PRINTF_P(PSTR(" sources %p "), sources); - DEBUG_PRINTF_P(PSTR(" adv. props %p "), advPartProps); - DEBUG_PRINTF_P(PSTR(" adv. ctrl %p "), advPartSize); - DEBUG_PRINTF_P(PSTR("end %p\n"), PSdataEnd); - */ + // DEBUG_PRINT(F("*** PS pointers ***")); + // DEBUG_PRINTF_P(PSTR("this PS %p "), this); + // Note on memory alignment: + // a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment. + // The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock. + // by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes. + particles = reinterpret_cast(this + 1); // pointer to particle array at data+sizeof(ParticleSystem) + sources = reinterpret_cast(particles + numParticles); // pointer to source(s) + if (isadvanced) + { + advPartProps = reinterpret_cast(sources + numSources); + PSdataEnd = reinterpret_cast(advPartProps + numParticles); + if (sizecontrol) + { + advPartSize = reinterpret_cast(advPartProps + numParticles); + PSdataEnd = reinterpret_cast(advPartSize + numParticles); + } + } + else + { + PSdataEnd = reinterpret_cast(sources + numSources); // pointer to first available byte after the PS for FX additional data + } + /* + DEBUG_PRINTF_P(PSTR(" particles %p "), particles); + DEBUG_PRINTF_P(PSTR(" sources %p "), sources); + DEBUG_PRINTF_P(PSTR(" adv. props %p "), advPartProps); + DEBUG_PRINTF_P(PSTR(" adv. ctrl %p "), advPartSize); + DEBUG_PRINTF_P(PSTR("end %p\n"), PSdataEnd); + */ } //non class functions to use for initialization uint32_t calculateNumberOfParticles(bool isadvanced, bool sizecontrol) { - uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; - uint32_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; + uint32_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); #ifdef ESP8266 - uint32_t numberofParticles = (cols * rows * 3) / 4; // 0.75 particle per pixel - uint32_t particlelimit = ESP8266_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 16x16 and 4k effect ram) + uint32_t numberofParticles = (cols * rows * 3) / 4; // 0.75 particle per pixel + uint32_t particlelimit = ESP8266_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 16x16 and 4k effect ram) #elif ARDUINO_ARCH_ESP32S2 - uint32_t numberofParticles = (cols * rows); // 1 particle per pixel - uint32_t particlelimit = ESP32S2_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 32x32 and 24k effect ram) + uint32_t numberofParticles = (cols * rows); // 1 particle per pixel + uint32_t particlelimit = ESP32S2_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 32x32 and 24k effect ram) #else - uint32_t numberofParticles = (cols * rows); // 1 particle per pixel (for example 512 particles on 32x16) - uint32_t particlelimit = ESP32_MAXPARTICLES; // maximum number of paticles allowed (based on two segments of 32x32 and 40k effect ram) + uint32_t numberofParticles = (cols * rows); // 1 particle per pixel (for example 512 particles on 32x16) + uint32_t particlelimit = ESP32_MAXPARTICLES; // maximum number of paticles allowed (based on two segments of 32x32 and 40k effect ram) #endif - numberofParticles = max((uint32_t)1, min(numberofParticles, particlelimit)); - if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount - numberofParticles = (numberofParticles * sizeof(PSparticle)) / (sizeof(PSparticle) + sizeof(PSadvancedParticle)); - if (sizecontrol) // advanced property array needs ram, reduce number of particles to use the same amount - numberofParticles /= 8; // if size control is used, much fewer particles are needed - - //make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes) - numberofParticles = ((numberofParticles+3) >> 2) << 2; - return numberofParticles; + numberofParticles = max((uint32_t)1, min(numberofParticles, particlelimit)); + if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount + numberofParticles = (numberofParticles * sizeof(PSparticle)) / (sizeof(PSparticle) + sizeof(PSadvancedParticle)); + if (sizecontrol) // advanced property array needs ram, reduce number of particles to use the same amount + numberofParticles /= 8; // if size control is used, much fewer particles are needed + + //make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes) + numberofParticles = ((numberofParticles+3) >> 2) << 2; + return numberofParticles; } uint32_t calculateNumberOfSources(uint8_t requestedsources) { - uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; - uint32_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; + uint32_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); #ifdef ESP8266 - int numberofSources = min((cols * rows) / 8, (uint32_t)requestedsources); - numberofSources = max(1, min(numberofSources, ESP8266_MAXSOURCES)); // limit to 1 - 16 + int numberofSources = min((cols * rows) / 8, (uint32_t)requestedsources); + numberofSources = max(1, min(numberofSources, ESP8266_MAXSOURCES)); // limit to 1 - 16 #elif ARDUINO_ARCH_ESP32S2 - int numberofSources = min((cols * rows) / 6, (uint32_t)requestedsources); - numberofSources = max(1, min(numberofSources, ESP32S2_MAXSOURCES)); // limit to 1 - 48 + int numberofSources = min((cols * rows) / 6, (uint32_t)requestedsources); + numberofSources = max(1, min(numberofSources, ESP32S2_MAXSOURCES)); // limit to 1 - 48 #else - int numberofSources = min((cols * rows) / 4, (uint32_t)requestedsources); - numberofSources = max(1, min(numberofSources, ESP32_MAXSOURCES)); // limit to 1 - 64 + int numberofSources = min((cols * rows) / 4, (uint32_t)requestedsources); + numberofSources = max(1, min(numberofSources, ESP32_MAXSOURCES)); // limit to 1 - 64 #endif - // make sure it is a multiple of 4 for proper memory alignment - numberofSources = ((numberofSources+3) >> 2) << 2; - return numberofSources; + // make sure it is a multiple of 4 for proper memory alignment + numberofSources = ((numberofSources+3) >> 2) << 2; + return numberofSources; } //allocate memory for particle system class, particles, sprays plus additional memory requested by FX bool allocateParticleSystemMemory(uint16_t numparticles, uint16_t numsources, bool isadvanced, bool sizecontrol, uint16_t additionalbytes) { - uint32_t requiredmemory = sizeof(ParticleSystem); - // functions above make sure these are a multiple of 4 bytes (to avoid alignment issues) - requiredmemory += sizeof(PSparticle) * numparticles; - if (isadvanced) - requiredmemory += sizeof(PSadvancedParticle) * numparticles; - if (sizecontrol) - requiredmemory += sizeof(PSsizeControl) * numparticles; - requiredmemory += sizeof(PSsource) * numsources; - requiredmemory += additionalbytes; - //Serial.print("allocating: "); - //Serial.print(requiredmemory); - //Serial.println("Bytes"); - //Serial.print("allocating for segment at"); - //Serial.println((uintptr_t)SEGMENT.data); - return(SEGMENT.allocateData(requiredmemory)); + uint32_t requiredmemory = sizeof(ParticleSystem); + // functions above make sure these are a multiple of 4 bytes (to avoid alignment issues) + requiredmemory += sizeof(PSparticle) * numparticles; + if (isadvanced) + requiredmemory += sizeof(PSadvancedParticle) * numparticles; + if (sizecontrol) + requiredmemory += sizeof(PSsizeControl) * numparticles; + requiredmemory += sizeof(PSsource) * numsources; + requiredmemory += additionalbytes; + //Serial.print("allocating: "); + //Serial.print(requiredmemory); + //Serial.println("Bytes"); + //Serial.print("allocating for segment at"); + //Serial.println((uintptr_t)SEGMENT.data); + return(SEGMENT.allocateData(requiredmemory)); } // initialize Particle System, allocate additional bytes if needed (pointer to those bytes can be read from particle system class: PSdataEnd) bool initParticleSystem(ParticleSystem *&PartSys, uint8_t requestedsources, uint16_t additionalbytes, bool largesizes, bool sizecontrol) { - //Serial.println("PS init function"); - uint32_t numparticles = calculateNumberOfParticles(largesizes, sizecontrol); - uint32_t numsources = calculateNumberOfSources(requestedsources); - //Serial.print("numsources: "); - //Serial.println(numsources); - if (!allocateParticleSystemMemory(numparticles, numsources, largesizes, sizecontrol, additionalbytes)) - { - DEBUG_PRINT(F("PS init failed: memory depleted")); - return false; - } - //Serial.print("segment.data ptr"); - //Serial.println((uintptr_t)(SEGMENT.data)); - uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; - uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - //Serial.println("calling constructor"); - PartSys = new (SEGMENT.data) ParticleSystem(cols, rows, numparticles, numsources, largesizes, sizecontrol); // particle system constructor - //Serial.print("PS pointer at "); - //Serial.println((uintptr_t)PartSys); - return true; + //Serial.println("PS init function"); + uint32_t numparticles = calculateNumberOfParticles(largesizes, sizecontrol); + uint32_t numsources = calculateNumberOfSources(requestedsources); + //Serial.print("numsources: "); + //Serial.println(numsources); + if (!allocateParticleSystemMemory(numparticles, numsources, largesizes, sizecontrol, additionalbytes)) + { + DEBUG_PRINT(F("PS init failed: memory depleted")); + return false; + } + //Serial.print("segment.data ptr"); + //Serial.println((uintptr_t)(SEGMENT.data)); + uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; + uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + //Serial.println("calling constructor"); + PartSys = new (SEGMENT.data) ParticleSystem(cols, rows, numparticles, numsources, largesizes, sizecontrol); // particle system constructor + //Serial.print("PS pointer at "); + //Serial.println((uintptr_t)PartSys); + return true; } /////////////////////// @@ -1443,43 +1442,42 @@ bool initParticleSystem(ParticleSystem *&PartSys, uint8_t requestedsources, uint // note: result is stored in c1, so c1 will contain the result. not using a return value is much faster as the struct does not need to be copied upon return void fast_color_add(CRGB &c1, CRGB &c2, uint32_t scale) { - uint32_t r, g, b; - if(scale < 255) - { - r = c1.r + ((c2.r * scale) >> 8); - g = c1.g + ((c2.g * scale) >> 8); - b = c1.b + ((c2.b * scale) >> 8); - } - else{ - r = c1.r + c2.r; - g = c1.g + c2.g; - b = c1.b + c2.b; - } - uint32_t max = r; - if (g > max) // note: using ? operator would be slower by 2 instructions - max = g; - if (b > max) - max = b; - if (max < 256) - { - c1.r = r; // save result to c1 - c1.g = g; - c1.b = b; - } - else - { - c1.r = (r * 255) / max; - c1.g = (g * 255) / max; - c1.b = (b * 255) / max; - } + uint32_t r, g, b; + if (scale < 255) { + r = c1.r + ((c2.r * scale) >> 8); + g = c1.g + ((c2.g * scale) >> 8); + b = c1.b + ((c2.b * scale) >> 8); + } + else { + r = c1.r + c2.r; + g = c1.g + c2.g; + b = c1.b + c2.b; + } + uint32_t max = r; + if (g > max) // note: using ? operator would be slower by 2 instructions + max = g; + if (b > max) + max = b; + if (max < 256) + { + c1.r = r; // save result to c1 + c1.g = g; + c1.b = b; + } + else + { + c1.r = (r * 255) / max; + c1.g = (g * 255) / max; + c1.b = (b * 255) / max; + } } // faster than fastled color scaling as it uses a 32bit scale factor and pointer void fast_color_scale(CRGB &c, uint32_t scale) { - c.r = ((c.r * scale) >> 8); - c.g = ((c.g * scale) >> 8); - c.b = ((c.b * scale) >> 8); + c.r = ((c.r * scale) >> 8); + c.g = ((c.g * scale) >> 8); + c.b = ((c.b * scale) >> 8); } // blur a matrix in x and y direction, blur can be asymmetric in x and y @@ -1487,57 +1485,57 @@ void fast_color_scale(CRGB &c, uint32_t scale) // to blur a subset of the buffer, change the xsize/ysize and set xstart/ystart to the desired starting coordinates (default start is 0/0) void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool smear, uint32_t xstart, uint32_t ystart, bool isparticle) { - CRGB seeppart, carryover; - uint32_t seep = xblur >> 1; - if(isparticle) //first and last row are always black in particle rendering - { - ystart++; - ysize--; - } - for(uint32_t y = ystart; y < ystart + ysize; y++) - { - carryover = BLACK; - for(uint32_t x = xstart; x < xstart + xsize; x++) - { - seeppart = colorbuffer[x][y]; // create copy of current color - fast_color_scale(seeppart, seep); // scale it and seep to neighbours - if(!smear) // fade current pixel if smear is disabled - fast_color_scale(colorbuffer[x][y], 255 - xblur); - - if(x > 0) - { - fast_color_add(colorbuffer[x-1][y], seeppart); - fast_color_add(colorbuffer[x][y], carryover); // TODO: could check if carryover is > 0, takes 7 instructions, add takes ~35, with lots of same color pixels (like background), it would be faster - } - carryover = seeppart; - } - fast_color_add(colorbuffer[xsize-1][y], carryover); // set last pixel - } - - if(isparticle) // now also do first and last row - { - ystart--; - ysize++; - } - - seep = yblur >> 1; - for(uint32_t x = xstart; x < xstart + xsize; x++) - { - carryover = BLACK; - for(uint32_t y = ystart; y < ystart + ysize; y++) - { - seeppart = colorbuffer[x][y]; // create copy of current color - fast_color_scale(seeppart, seep); // scale it and seep to neighbours - if(!smear) // fade current pixel if smear is disabled - fast_color_scale(colorbuffer[x][y], 255 - yblur); - - if(y > 0) - { - fast_color_add(colorbuffer[x][y-1], seeppart); - fast_color_add(colorbuffer[x][y], carryover); // todo: could check if carryover is > 0, takes 7 instructions, add takes ~35, with lots of same color pixels (like background), it would be faster - } - carryover = seeppart; - } - fast_color_add(colorbuffer[x][ysize-1], carryover); // set last pixel - } + CRGB seeppart, carryover; + uint32_t seep = xblur >> 1; + if (isparticle) //first and last row are always black in particle rendering + { + ystart++; + ysize--; + } + for(uint32_t y = ystart; y < ystart + ysize; y++) + { + carryover = BLACK; + for(uint32_t x = xstart; x < xstart + xsize; x++) + { + seeppart = colorbuffer[x][y]; // create copy of current color + fast_color_scale(seeppart, seep); // scale it and seep to neighbours + if (!smear) // fade current pixel if smear is disabled + fast_color_scale(colorbuffer[x][y], 255 - xblur); + + if (x > 0) + { + fast_color_add(colorbuffer[x-1][y], seeppart); + fast_color_add(colorbuffer[x][y], carryover); // TODO: could check if carryover is > 0, takes 7 instructions, add takes ~35, with lots of same color pixels (like background), it would be faster + } + carryover = seeppart; + } + fast_color_add(colorbuffer[xsize-1][y], carryover); // set last pixel + } + + if (isparticle) // now also do first and last row + { + ystart--; + ysize++; + } + + seep = yblur >> 1; + for(uint32_t x = xstart; x < xstart + xsize; x++) + { + carryover = BLACK; + for(uint32_t y = ystart; y < ystart + ysize; y++) + { + seeppart = colorbuffer[x][y]; // create copy of current color + fast_color_scale(seeppart, seep); // scale it and seep to neighbours + if (!smear) // fade current pixel if smear is disabled + fast_color_scale(colorbuffer[x][y], 255 - yblur); + + if (y > 0) + { + fast_color_add(colorbuffer[x][y-1], seeppart); + fast_color_add(colorbuffer[x][y], carryover); // todo: could check if carryover is > 0, takes 7 instructions, add takes ~35, with lots of same color pixels (like background), it would be faster + } + carryover = seeppart; + } + fast_color_add(colorbuffer[x][ysize-1], carryover); // set last pixel + } } \ No newline at end of file diff --git a/wled00/FXparticleSystem.h b/wled00/FXparticleSystem.h index 281afee228..f3da35b1bf 100644 --- a/wled00/FXparticleSystem.h +++ b/wled00/FXparticleSystem.h @@ -22,51 +22,50 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ #include #include "FastLED.h" -//memory allocation -#define ESP8266_MAXPARTICLES 180// // enough for one 16x16 segment with transitions +// memory allocation +#define ESP8266_MAXPARTICLES 180 // enough for one 16x16 segment with transitions #define ESP8266_MAXSOURCES 16 #define ESP32S2_MAXPARTICLES 840 // enough for four 16x16 segments #define ESP32S2_MAXSOURCES 48 -#define ESP32_MAXPARTICLES 1024 // enough for four 16x16 segments TODO: not enough for one 64x64 panel... +#define ESP32_MAXPARTICLES 1024 // enough for four 16x16 segments TODO: not enough for one 64x64 panel... #define ESP32_MAXSOURCES 64 -//particle dimensions (subpixel division) -#define PS_P_RADIUS 64 //subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines) -#define PS_P_HALFRADIUS 32 +// particle dimensions (subpixel division) +#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines) +#define PS_P_HALFRADIUS 32 #define PS_P_RADIUS_SHIFT 6 // shift for RADIUS #define PS_P_SURFACE 12 // shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2 #define PS_P_MINHARDRADIUS 70 // minimum hard surface radius -#define PS_P_MINSURFACEHARDNESS 128 //minimum hardness used in collision impulse calculation, below this hardness, particles become sticky -#define PS_P_MAXSPEED 120 //maximum speed a particle can have (vx/vy is int8) +#define PS_P_MINSURFACEHARDNESS 128 // minimum hardness used in collision impulse calculation, below this hardness, particles become sticky +#define PS_P_MAXSPEED 120 // maximum speed a particle can have (vx/vy is int8) //struct for a single particle (10 bytes) typedef struct { - int16_t x; //x position in particle system - int16_t y; //y position in particle system - int8_t vx; //horizontal velocity - int8_t vy; //vertical velocity + int16_t x; // x position in particle system + int16_t y; // y position in particle system + int8_t vx; // horizontal velocity + int8_t vy; // vertical velocity uint8_t hue; // color hue - uint8_t sat; //particle color saturation - //two byte bit field: + uint8_t sat; // particle color saturation + // two byte bit field: uint16_t ttl : 12; // time to live, 12 bit or 4095 max (which is 50s at 80FPS) - bool outofbounds : 1; //out of bounds flag, set to true if particle is outside of display area - bool collide : 1; //if set, particle takes part in collisions - bool perpetual : 1; //if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds) - bool flag4 : 1; // unused flag + bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area + bool collide : 1; // if set, particle takes part in collisions + bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds) + bool flag4 : 1; // unused flag } PSparticle; // struct for additional particle settings (optional) typedef struct { - uint8_t size; //particle size, 255 means 10 pixels in diameter - uint8_t forcecounter; //counter for applying forces to individual particles + uint8_t size; // particle size, 255 means 10 pixels in diameter + uint8_t forcecounter; // counter for applying forces to individual particles } PSadvancedParticle; // struct for advanced particle size control (optional) @@ -74,24 +73,24 @@ typedef struct { uint8_t asymmetry; // asymmetrical size (0=symmetrical, 255 fully asymmetric) uint8_t asymdir; // direction of asymmetry, 64 is x, 192 is y (0 and 128 is symmetrical) - uint8_t maxsize; // target size for growing - uint8_t minsize; // target size for shrinking + uint8_t maxsize; // target size for growing + uint8_t minsize; // target size for shrinking uint8_t sizecounter : 4; // counters used for size contol (grow/shrink/wobble) uint8_t wobblecounter : 4; - uint8_t growspeed : 4; - uint8_t shrinkspeed : 4; + uint8_t growspeed : 4; + uint8_t shrinkspeed : 4; uint8_t wobblespeed : 4; bool grow : 1; // flags bool shrink : 1; - bool pulsate : 1; //grows & shrinks & grows & ... - bool wobble : 1; //alternate x and y size + bool pulsate : 1; // grows & shrinks & grows & ... + bool wobble : 1; // alternate x and y size } PSsizeControl; //struct for a particle source (17 bytes) typedef struct { - uint16_t minLife; // minimum ttl of emittet particles - uint16_t maxLife; // maximum ttl of emitted particles + uint16_t minLife; // minimum ttl of emittet particles + uint16_t maxLife; // maximum ttl of emitted particles PSparticle source; // use a particle as the emitter source (speed, position, color) uint8_t var; // variation of emitted speed (use odd numbers for good symmetry) int8_t vx; // emitting speed @@ -103,20 +102,20 @@ typedef struct { typedef union { struct{ - // add a one byte bit field: + // one byte bit field: bool wrapX : 1; bool wrapY : 1; bool bounceX : 1; bool bounceY : 1; bool killoutofbounds : 1; // if set, out of bound particles are killed immediately - bool useGravity : 1; //set to 1 if gravity is used, disables bounceY at the top + bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top bool useCollisions : 1; bool colorByAge : 1; // if set, particle hue is set by ttl value in render function }; - byte asByte; //order is: LSB is first entry in the list above + byte asByte; // order is: LSB is first entry in the list above } PSsettings; -//class uses approximately 60 bytes +// class uses approximately 60 bytes class ParticleSystem { public: @@ -129,49 +128,49 @@ class ParticleSystem // particle emitters int32_t sprayEmit(PSsource &emitter); void flameEmit(PSsource &emitter); - void angleEmit(PSsource& emitter, uint16_t angle, int8_t speed); + void angleEmit(PSsource& emitter, uint16_t angle, int8_t speed); + - // move functions - void particleMoveUpdate(PSparticle &part, PSsettings *options = NULL, PSadvancedParticle *advancedproperties = NULL); - //particle physics + void particleMoveUpdate(PSparticle &part, PSsettings *options = NULL, PSadvancedParticle *advancedproperties = NULL); // move function + //particle physics void applyGravity(PSparticle *part); // applies gravity to single particle (use this for sources) void applyForce(PSparticle *part, int8_t xforce, int8_t yforce, uint8_t *counter); - void applyForce(uint16_t particleindex, int8_t xforce, int8_t yforce); //use this for advanced property particles - void applyForce(int8_t xforce, int8_t yforce); //apply a force to all particles + void applyForce(uint16_t particleindex, int8_t xforce, int8_t yforce); // use this for advanced property particles + void applyForce(int8_t xforce, int8_t yforce); // apply a force to all particles void applyAngleForce(PSparticle *part, int8_t force, uint16_t angle, uint8_t *counter); void applyAngleForce(uint16_t particleindex, int8_t force, uint16_t angle); // use this for advanced property particles - void applyAngleForce(int8_t force, uint16_t angle); //apply angular force to all particles + void applyAngleForce(int8_t force, uint16_t angle); // apply angular force to all particles void applyFriction(PSparticle *part, int32_t coefficient); // apply friction to specific particle void applyFriction(int32_t coefficient); // apply friction to all used particles void pointAttractor(uint16_t particleindex, PSparticle *attractor, uint8_t strength, bool swallow); void lineAttractor(uint16_t particleindex, PSparticle *attractorcenter, uint16_t attractorangle, uint8_t strength); - //set options + // set options void setUsedParticles(uint16_t num); - void setCollisionHardness(uint8_t hardness); //hardness for particle collisions (255 means full hard) - void setWallHardness(uint8_t hardness); //hardness for bouncing on the wall if bounceXY is set - void setWallRoughness(uint8_t roughness); //wall roughness randomizes wall collisions + void setCollisionHardness(uint8_t hardness); // hardness for particle collisions (255 means full hard) + void setWallHardness(uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set + void setWallRoughness(uint8_t roughness); // wall roughness randomizes wall collisions void setMatrixSize(uint16_t x, uint16_t y); void setWrapX(bool enable); void setWrapY(bool enable); void setBounceX(bool enable); void setBounceY(bool enable); - void setKillOutOfBounds(bool enable); //if enabled, particles outside of matrix instantly die - void setSaturation(uint8_t sat); //set global color saturation + void setKillOutOfBounds(bool enable); // if enabled, particles outside of matrix instantly die + void setSaturation(uint8_t sat); // set global color saturation void setColorByAge(bool enable); - void setMotionBlur(uint8_t bluramount); //note: motion blur can only be used if 'particlesize' is set to zero + void setMotionBlur(uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero void setParticleSize(uint8_t size); void setGravity(int8_t force = 8); - void enableParticleCollisions(bool enable, uint8_t hardness = 255); + void enableParticleCollisions(bool enable, uint8_t hardness = 255); PSparticle *particles; // pointer to particle array PSsource *sources; // pointer to sources PSadvancedParticle *advPartProps; // pointer to advanced particle properties (can be NULL) - PSsizeControl *advPartSize; //pointer to advanced particle size control (can be NULL) - uint8_t* PSdataEnd; //points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data - uint16_t maxX, maxY; //particle system size i.e. width-1 / height-1 in subpixels + PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL) + uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data + uint16_t maxX, maxY; // particle system size i.e. width-1 / height-1 in subpixels uint32_t maxXpixel, maxYpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 / height-1 - uint8_t numSources; //number of sources + uint8_t numSources; // number of sources uint16_t numParticles; // number of particles available in this system uint16_t usedParticles; // number of particles used in animation (can be smaller then numParticles) @@ -188,9 +187,9 @@ class ParticleSystem //utility functions void updatePSpointers(bool isadvanced, bool sizecontrol); // update the data pointers to current segment data space - void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); //advanced size control + void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize); - void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition); //bounce on a wall + void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition); // bounce on a wall int16_t wraparound(uint16_t p, uint32_t maxvalue); int32_t calcForce_dv(int8_t force, uint8_t *counter); int32_t limitSpeed(int32_t speed); @@ -202,22 +201,22 @@ class ParticleSystem int32_t collisionHardness; uint8_t wallHardness; uint8_t wallRoughness; - uint8_t gforcecounter; //counter for global gravity - int8_t gforce; //gravity strength, default is 8 (negative is allowed, positive is downwards) - uint8_t collisioncounter; //counter to handle collisions TODO: could use the SEGMENT.call? - uint8_t forcecounter; //counter for globally applied forces - //global particle properties for basic particles - uint8_t particlesize; //global particle size, 0 = 2 pixels, 255 = 10 pixels (note: this is also added to individual sized particles) + uint8_t gforcecounter; // counter for global gravity + int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards) + uint8_t collisioncounter; // counter to handle collisions TODO: could use the SEGMENT.call? + uint8_t forcecounter; // counter for globally applied forces + // global particle properties for basic particles + uint8_t particlesize; // global particle size, 0 = 2 pixels, 255 = 10 pixels (note: this is also added to individual sized particles) int32_t particleHardRadius; // hard surface radius of a particle, used for collision detection - uint8_t motionBlur; //enable motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0 + uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0 }; -//initialization functions (not part of class) +// initialization functions (not part of class) bool initParticleSystem(ParticleSystem *&PartSys, uint8_t requestedsources, uint16_t additionalbytes = 0, bool largesizes = false, bool sizecontrol = false); uint32_t calculateNumberOfParticles(bool advanced, bool sizecontrol); uint32_t calculateNumberOfSources(uint8_t requestedsources); bool allocateParticleSystemMemory(uint16_t numparticles, uint16_t numsources, bool advanced, bool sizecontrol, uint16_t additionalbytes); -//color add function +// color functions void fast_color_add(CRGB &c1, CRGB &c2, uint32_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding) -void fast_color_scale(CRGB &c, uint32_t scale); //fast scaling function using 32bit factor (keep it 0-255) and pointer +void fast_color_scale(CRGB &c, uint32_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255 void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool smear = true, uint32_t xstart = 0, uint32_t ystart = 0, bool isparticle = false); \ No newline at end of file