From bcea39d21ac514e9381d746aee1ee7a1fc70823c Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 10 Jan 2025 20:17:07 +0100 Subject: [PATCH] moved particle flags to seperate array to save on RAM, refactoring - moving the flags optimizes ram alignment, saving memory for each particle - changed lots of parameters to `const` - moved fire intensity to a variable instead of passing it to every render() call - changed passing pointers to passing reference where possible - saves a total of 340 bytes of flash --- wled00/FX.cpp | 150 +++++++-------- wled00/FXparticleSystem.cpp | 370 ++++++++++++++++++------------------ wled00/FXparticleSystem.h | 230 ++++++++++++---------- 3 files changed, 379 insertions(+), 371 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 480b6ddd49..6ad69cb377 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7810,14 +7810,14 @@ uint16_t mode_particlefireworks(void) { PartSys->setGravity(map(SEGMENT.custom3, 0, 31, SEGMENT.check2 ? 1 : 0, 10)); // if bounded, set gravity to minimum of 1 or they will bounce at top PartSys->setMotionBlur(map(SEGMENT.custom2, 0, 255, 0, 170)); // anable motion blur uint8_t smearing = 0; - if(SEGMENT.custom2 > 200) + if(SEGMENT.custom2 > 200) smearing = SEGMENT.custom2 - 200; PartSys->setSmearBlur(smearing); // enable 2D blurring (smearing) // update the rockets, set the speed state for (j = 0; j < numRockets; j++) { PartSys->applyGravity(PartSys->sources[j].source); - PartSys->particleMoveUpdate(PartSys->sources[j].source); + PartSys->particleMoveUpdate(PartSys->sources[j].source, PartSys->sources[j].sourceFlags); if (PartSys->sources[j].source.ttl == 0) { if (PartSys->sources[j].source.vy > 0) { // rocket has died and is moving up. stop it so it will explode (is handled in the code below) PartSys->sources[j].source.vy = 0; @@ -7947,8 +7947,8 @@ uint16_t mode_particlevolcano(void) { 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.perpetual = true; // source never dies + PartSys->sources[i].sourceFlags.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[i].sourceFlags.perpetual = true; // source never dies } } else @@ -7981,7 +7981,7 @@ uint16_t mode_particlevolcano(void) { PartSys->sources[i].vx = 0; 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, &volcanosettings); //move the source + PartSys->particleMoveUpdate(PartSys->sources[i].source, PartSys->sources[i].sourceFlags, &volcanosettings); //move the source } } @@ -8076,7 +8076,7 @@ uint16_t mode_particlefire(void) { PartSys->flameEmit(PartSys->sources[j]); } - PartSys->updateFire(SEGMENT.intensity); // update and render the fire + PartSys->updateFire(SEGMENT.intensity, false); // update and render the fire return FRAMETIME; } @@ -8126,7 +8126,7 @@ uint16_t mode_particlepit(void) { PartSys->particles[i].vx = (int16_t)hw_random16(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 = hw_random16(); // set random color - PartSys->particles[i].collide = true; // enable collision for particle + PartSys->particleFlags[i].collide = true; // enable collision for particle PartSys->particles[i].sat = ((SEGMENT.custom3) << 3) + 7; // set particle size if (SEGMENT.custom1 == 255) { @@ -8174,7 +8174,7 @@ uint16_t mode_particlewaterfall(void) { PartSys->setSmearBlur(30); // enable 2D blurring (smearing) for (i = 0; i < PartSys->numSources; i++) { PartSys->sources[i].source.hue = i*90; - PartSys->sources[i].source.collide = true; // seeded particles will collide + PartSys->sources[i].sourceFlags.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].minLife = 100; @@ -8257,11 +8257,11 @@ uint16_t mode_particlebox(void) { for (i = 0; i < PartSys->usedParticles; i++) { if(PartSys->particles[i].ttl < 260) { // initialize handed over particles and dead particles PartSys->particles[i].ttl = 260; // full brigthness - PartSys->particles[i].perpetual = true; // never die PartSys->particles[i].x = hw_random16(PartSys->maxX); PartSys->particles[i].y = hw_random16(PartSys->maxY); PartSys->particles[i].hue = hw_random8(); // make it colorful - PartSys->particles[i].collide = true; // all particles colllide + PartSys->particleFlags[i].perpetual = true; // never die + PartSys->particleFlags[i].collide = true; // all particles colllide break; // only spawn one particle per frame for less chaotic transitions } } @@ -8347,7 +8347,7 @@ uint16_t mode_particleperlin(void) { PartSys->particles[i].ttl = hw_random16(500) + 200; PartSys->particles[i].x = hw_random(PartSys->maxX); PartSys->particles[i].y = hw_random(PartSys->maxY); - PartSys->particles[i].collide = true; // particle colllides + PartSys->particleFlags[i].collide = true; // particle colllides } uint32_t scale = 16 - ((31 - SEGMENT.custom3) >> 1); uint16_t xnoise = PartSys->particles[i].x / scale; // position in perlin noise, scaled by slider @@ -8444,13 +8444,13 @@ uint16_t mode_particleimpact(void) { 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); - PartSys->particleMoveUpdate(PartSys->sources[i].source, &meteorsettings); + PartSys->particleMoveUpdate(PartSys->sources[i].source, PartSys->sources[i].sourceFlags, &meteorsettings); // 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.collide = true; + PartSys->sources[i].sourceFlags.collide = true; #ifdef ESP8266 PartSys->sources[i].maxLife = 180; PartSys->sources[i].minLife = 20; @@ -8472,7 +8472,7 @@ uint16_t mode_particleimpact(void) { PartSys->sources[i].source.vx = hw_random16(50) - 25; // TODO: make this dependent on position so they do not move out of frame PartSys->sources[i].source.hue = hw_random16(); // random color 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].sourceFlags.collide = false; // trail particles will not collide PartSys->sources[i].maxLife = 60; // spark particle life PartSys->sources[i].minLife = 20; PartSys->sources[i].vy = -9; // emitting speed (down) @@ -8497,14 +8497,16 @@ uint16_t mode_particleattractor(void) { ParticleSystem2D *PartSys = NULL; PSsettings2D sourcesettings; sourcesettings.asByte = 0b00001100; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) + PSparticleFlags attractorFlags; + attractorFlags.asByte = 0; // no flags set PSparticle *attractor; // particle pointer to the attractor if (SEGMENT.call == 0) { // initialization if (!initParticleSystem2D(PartSys, 1, sizeof(PSparticle), true)) // init using 1 source and advanced particle settings return mode_static(); // allocation failed or not 2D PartSys->sources[0].source.hue = hw_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 + PartSys->sources[0].sourceFlags.collide = true; // seeded particles will collide + PartSys->sources[0].sourceFlags.perpetual = true; //source does not age #ifdef ESP8266 PartSys->sources[0].maxLife = 200; // lifetime in frames (ESP8266 has less particles) PartSys->sources[0].minLife = 30; @@ -8539,14 +8541,13 @@ uint16_t mode_particleattractor(void) { if (SEGMENT.call == 0) { attractor->vx = PartSys->sources[0].source.vy; // set to spray movemement but reverse x and y attractor->vy = PartSys->sources[0].source.vx; - attractor->ttl = 100; - attractor->perpetual = true; } // set attractor properties + attractor->ttl = 100; // never dies if (SEGMENT.check2) { if ((SEGMENT.call % 3) == 0) // move slowly - PartSys->particleMoveUpdate(*attractor, &sourcesettings); // move the attractor + PartSys->particleMoveUpdate(*attractor, attractorFlags, &sourcesettings); // move the attractor } else { attractor->x = PartSys->maxX >> 1; // set to center @@ -8579,13 +8580,13 @@ uint16_t mode_particleattractor(void) { } #else // no AR for (uint32_t i = 0; i < PartSys->usedParticles; i++) { - PartSys->pointAttractor(i, attractor, SEGMENT.speed, SEGMENT.check3); + PartSys->pointAttractor(i, *attractor, SEGMENT.speed, SEGMENT.check3); } #endif if (SEGMENT.call % (33 - SEGMENT.custom3) == 0) PartSys->applyFriction(2); - PartSys->particleMoveUpdate(PartSys->sources[0].source, &sourcesettings); // move the source + PartSys->particleMoveUpdate(PartSys->sources[0].source, PartSys->sources[0].sourceFlags, &sourcesettings); // move the source PartSys->update(); // update and render return FRAMETIME; } @@ -8595,6 +8596,7 @@ static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Size,Collide,Friction,AgeColor,Move,Swallow;;!;2;pal=9,sx=100,ix=82,c1=0,c2=0"; #endif + /* Particle Spray, just a particle spray with many parameters Uses palette for particle color @@ -8613,7 +8615,7 @@ uint16_t mode_particlespray(void) { PartSys->setBounceY(true); PartSys->setMotionBlur(200); // anable motion blur PartSys->sources[0].source.hue = hw_random16(); - PartSys->sources[0].source.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[0].sourceFlags.collide = true; // seeded particles will collide (if enabled) PartSys->sources[0].var = 3; } else @@ -8895,7 +8897,7 @@ uint16_t mode_particleghostrider(void) { PartSys->sources[0].source.vx = ((int32_t)cos16_t(SEGENV.aux0) * speed) / (int32_t)32767; PartSys->sources[0].source.vy = ((int32_t)sin16_t(SEGENV.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); + PartSys->particleMoveUpdate(PartSys->sources[0].source, PartSys->sources[0].sourceFlags, &ghostsettings); // 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; @@ -8920,6 +8922,7 @@ PS Blobs: large particles bouncing around, changing size and form Uses palette for particle color by DedeHai (Damian Schneider) */ + uint16_t mode_particleblobs(void) { ParticleSystem2D *PartSys = NULL; @@ -8957,7 +8960,7 @@ uint16_t mode_particleblobs(void) { PartSys->particles[i].x = hw_random(PartSys->maxX); PartSys->particles[i].y = hw_random16(PartSys->maxY); PartSys->particles[i].hue = hw_random16(); // set random color - PartSys->particles[i].collide = true; // enable collision for particle + PartSys->particleFlags[i].collide = true; // enable collision for particle PartSys->advPartProps[i].size = 0; // start out small PartSys->advPartSize[i].asymmetry = hw_random16(220); PartSys->advPartSize[i].asymdir = hw_random16(255); @@ -8997,7 +9000,7 @@ static const char _data_FX_MODE_PARTICLEBLOBS[] PROGMEM = "PS Blobs@Speed,Blobs, * particles move, then split to form a fractal tree EXPERIMENTAL and non working! * by DedeHai (Damian Schneider) */ - + uint16_t mode_particlefractal(void) { ParticleSystem2D *PartSys = NULL; uint32_t i; @@ -9033,14 +9036,10 @@ uint16_t mode_particlefractal(void) { PartSys->sources[0].source.hue = PartSys->particles[i].hue + 50; // todo: make color schemes uint16_t angle = currentangle - angleoffset; int32_t index = PartSys->angleEmit(PartSys->sources[0], angle, emitspeed); //upward TODO: make angle adjustable - Serial.print("branch emit1 at idx = "); - Serial.println(index); //TODO: check if index >=0!!! PartSys->advPartProps[index].forcecounter = angle >> 7; angle = currentangle + angleoffset; index = PartSys->angleEmit(PartSys->sources[0], angle, emitspeed); - Serial.print("branch emit2 at idx = "); - Serial.println(index); PartSys->advPartProps[index].forcecounter = angle >> 7; } } @@ -9053,8 +9052,6 @@ uint16_t mode_particlefractal(void) { PartSys->sources[0].minLife = 270; uint32_t angle = ((uint32_t)SEGMENT.custom1) << 7; //16 bit angle, 0° to 180° int32_t index = PartSys->angleEmit(PartSys->sources[0], angle, emitspeed); //upward TODO: make angle adjustable - Serial.print("base emit at idx = "); - Serial.println(index); //set the forcecounter to track the angle (only 8 bit precision...) PartSys->advPartProps[index].forcecounter = angle >> 7; } @@ -9113,7 +9110,7 @@ uint16_t mode_particleDrip(void) { else PartSys->enableParticleCollisions(false); - PartSys->sources[0].source.collide = false; //drops do not collide + PartSys->sources[0].sourceFlags.collide = false; //drops do not collide if (SEGMENT.check1) { //rain mode, emit at random position, short life (3-8 seconds at 50fps) if (SEGMENT.custom1 == 0) //splash disabled, do not bounce raindrops @@ -9150,7 +9147,7 @@ uint16_t mode_particleDrip(void) { } for (uint32_t i = 0; i < PartSys->usedParticles; i++) { //check all particles - if (PartSys->particles[i].ttl && PartSys->particles[i].collide == false) { // use collision flag to identify splash particles + if (PartSys->particles[i].ttl && PartSys->particleFlags[i].collide == false) { // use collision flag to identify splash particles if (SEGMENT.custom1 > 0 && PartSys->particles[i].x < (PS_P_RADIUS_1D << 1)) { //splash enabled and reached bottom PartSys->particles[i].ttl = 0; //kill origin particle PartSys->sources[0].maxLife = 80; @@ -9159,7 +9156,7 @@ uint16_t mode_particleDrip(void) { PartSys->sources[0].v = 0; PartSys->sources[0].source.hue = PartSys->particles[i].hue; PartSys->sources[0].source.x = PS_P_RADIUS_1D; - PartSys->sources[0].source.collide = true; //splashes do collide if enabled + PartSys->sources[0].sourceFlags.collide = true; //splashes do collide if enabled for (int j = 0; j < 2 + (SEGMENT.custom1 >> 2); j++) { PartSys->sprayEmit(PartSys->sources[0]); } @@ -9172,7 +9169,7 @@ uint16_t mode_particleDrip(void) { } //increase speed on high settings by calling the move function twice if (SEGMENT.speed > 200) - PartSys->particleMoveUpdate(PartSys->particles[i]); + PartSys->particleMoveUpdate(PartSys->particles[i], PartSys->particleFlags[i]); } PartSys->update(); // update and render @@ -9194,7 +9191,7 @@ uint16_t mode_particleBouncingBalls(void) { if (SEGMENT.call == 0) { // initialization if (!initParticleSystem1D(PartSys, 1, 128, 0, true)) // init return mode_static(); // allocation failed or is single pixel - PartSys->sources[0].source.collide = true; // seeded particles will collide (if enabled) + PartSys->sources[0].sourceFlags.collide = true; // seeded particles will collide (if enabled) PartSys->sources[0].source.x = PS_P_RADIUS_1D; //emit at bottom PartSys->sources[0].maxLife = 900; // maximum lifetime in frames PartSys->sources[0].minLife = PartSys->sources[0].maxLife; @@ -9231,7 +9228,7 @@ uint16_t mode_particleBouncingBalls(void) { PartSys->particles[i].ttl = 260; //set alive at full intensity if (updateballs || PartSys->particles[i].ttl == 0) { //speed changed or particle died, set particle properties PartSys->particles[i].ttl = 260 + SEGMENT.speed; - PartSys->particles[i].collide = true; + PartSys->particleFlags[i].collide = true; int32_t newspeed = hw_random16(20 + (SEGMENT.speed >> 2)) + (SEGMENT.speed >> 3); PartSys->particles[i].vx = PartSys->particles[i].vx > 0 ? newspeed : -newspeed; //keep the direction PartSys->particles[i].hue = hw_random8(); //set ball colors to random @@ -9260,7 +9257,7 @@ uint16_t mode_particleBouncingBalls(void) { SEGENV.aux1 = SEGMENT.speed + SEGMENT.intensity + SEGMENT.check2 + SEGMENT.custom1; for (uint32_t i = 0; i < PartSys->usedParticles; i++) { if (SEGMENT.speed > 200) - PartSys->particleMoveUpdate(PartSys->particles[i]); //increase speed on high settings by calling the move function twice + PartSys->particleMoveUpdate(PartSys->particles[i], PartSys->particleFlags[i]); //increase speed on high settings by calling the move function twice } PartSys->update(); // update and render @@ -9308,10 +9305,10 @@ uint16_t mode_particleDancingShadows(void) { uint32_t deadparticles = 0; //kill out of bounds and moving away plus change color for (uint32_t i = 0; i < PartSys->usedParticles; i++) { - if(((SEGMENT.call & 0x07) == 0) && PartSys->particles[i].outofbounds) { //check if out of bounds particle move away from strip, only update every 8th frame + if(((SEGMENT.call & 0x07) == 0) && PartSys->particleFlags[i].outofbounds) { //check if out of bounds particle move away from strip, only update every 8th frame if((int32_t)PartSys->particles[i].vx * PartSys->particles[i].x > 0) PartSys->particles[i].ttl = 0; //particle is moving away, kill it } - PartSys->particles[i].perpetual = true; //particles do not age + PartSys->particleFlags[i].perpetual = true; //particles do not age if (SEGMENT.call % (32 / (1 + (SEGMENT.custom2 >> 3))) == 0) PartSys->particles[i].hue += 2 + (SEGMENT.custom2 >> 5); //note: updating speed on the fly is not accurately possible, since it is unknown which particles are assigned to which spot @@ -9403,7 +9400,7 @@ uint16_t mode_particleFireworks1D(void) { if (!initParticleSystem1D(PartSys, 4, 150, 4, true)) // init return mode_static(); // allocation failed or is single pixel PartSys->setKillOutOfBounds(true); - PartSys->sources[0].source.perpetual = 1; // set rocket state to standby + PartSys->sources[0].sourceFlags.perpetual = 1; // set rocket state to standby } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS @@ -9422,7 +9419,7 @@ uint16_t mode_particleFireworks1D(void) { else PartSys->setGravity(gravity); // set gravity - if(PartSys->sources[0].source.perpetual == 1) { // rocket is on standby + if(PartSys->sources[0].sourceFlags.custom1 == 1) { // rocket is on standby PartSys->sources[0].source.ttl--; if(PartSys->sources[0].source.ttl == 0) { // time is up, relaunch @@ -9431,7 +9428,7 @@ uint16_t mode_particleFireworks1D(void) { else SEGENV.aux0 = 0; - PartSys->sources[0].source.perpetual = 0; //flag abused for rocket state + PartSys->sources[0].sourceFlags.custom1 = 0; //flag used for rocket state PartSys->sources[0].source.hue = hw_random16(); PartSys->sources[0].var = 10; PartSys->sources[0].minLife = 100; @@ -9442,10 +9439,10 @@ uint16_t mode_particleFireworks1D(void) { PartSys->sources[0].source.ttl = 4000; PartSys->sources[0].sat = 30; // low saturation exhaust PartSys->sources[0].size = 0; // default size - PartSys->sources[0].source.reversegrav = false ; // normal gravity + PartSys->sources[0].sourceFlags.reversegrav = false ; // normal gravity if(SEGENV.aux0) { // inverted rockets launch from end - PartSys->sources[0].source.reversegrav = true; + PartSys->sources[0].sourceFlags.reversegrav = true; PartSys->sources[0].source.x = PartSys->maxX; // start from top PartSys->sources[0].source.vx = -PartSys->sources[0].source.vx; // revert direction } @@ -9458,16 +9455,16 @@ uint16_t mode_particleFireworks1D(void) { rocketgravity = -rocketgravity; speed = -speed; } - PartSys->applyForce(&PartSys->sources[0].source, rocketgravity, &forcecounter[0]); - PartSys->particleMoveUpdate(PartSys->sources[0].source); - PartSys->particleMoveUpdate(PartSys->sources[0].source); // increase speed by calling the move function twice, also ages twice + PartSys->applyForce(PartSys->sources[0].source, rocketgravity, forcecounter[0]); + PartSys->particleMoveUpdate(PartSys->sources[0].source, PartSys->sources[0].sourceFlags); + PartSys->particleMoveUpdate(PartSys->sources[0].source, PartSys->sources[0].sourceFlags); // increase speed by calling the move function twice, also ages twice uint32_t rocketheight = SEGENV.aux0 ? PartSys->maxX - PartSys->sources[0].source.x : PartSys->sources[0].source.x; if(speed < 0 && PartSys->sources[0].source.ttl > 50) // reached apogee PartSys->sources[0].source.ttl = min((uint32_t)50, rocketheight >> (PS_P_RADIUS_SHIFT_1D + 3)); // alive for a few more frames if(PartSys->sources[0].source.ttl < 2) { // explode - PartSys->sources[0].source.perpetual = 1; // set standby state + PartSys->sources[0].sourceFlags.custom1 = 1; // set standby state PartSys->sources[0].var = 5 + ((((PartSys->maxX >> 1) + rocketheight) * (200 + SEGMENT.intensity)) / (PartSys->maxX << 2)); // set explosion particle speed PartSys->sources[0].minLife = 600; PartSys->sources[0].maxLife = 1300; @@ -9481,14 +9478,9 @@ uint16_t mode_particleFireworks1D(void) { PartSys->sources[0].source.hue = hw_random16(); //random color for each particle PartSys->sprayEmit(PartSys->sources[0]); // emit a particle } - //!!! DEBUG, remove: - //PartSys->sources[0].source.x = -500; // set out of frame until relaunch - // for(unsigned i = 0; i < PartSys->usedParticles; i++) { // TODO: this can probably be removed now, was a bug patch - // Serial.println("particle " + String(i) + " ttl: " + String(PartSys->particles[i].ttl) + " x: " + String(PartSys->particles[i].x >> 5) + " vx: " + String(PartSys->particles[i].vx) + " sat: " + String(PartSys->advPartProps[i].sat) + " size: " + String(PartSys->advPartProps[i].size)); - // } } } - if((SEGMENT.call & 0x01) == 0 && PartSys->sources[0].source.perpetual == false) // every second frame and not in standby + if((SEGMENT.call & 0x01) == 0 && PartSys->sources[0].sourceFlags.perpetual == false) // every second frame and not in standby PartSys->sprayEmit(PartSys->sources[0]); // emit exhaust particle if((SEGMENT.call & 0x03) == 0) // every fourth frame PartSys->applyFriction(1); // apply friction to all particles @@ -9546,7 +9538,7 @@ uint16_t mode_particleSparkler(void) { PartSys->sources[i].source.vx = speed; //update speed, do not change direction PartSys->sources[i].source.ttl = 400; //replenish its life (setting it perpetual uses more code) PartSys->sources[i].sat = SEGMENT.custom1; //color saturation - PartSys->particleMoveUpdate(PartSys->sources[i].source, &sparklersettings); //move sparkler + PartSys->particleMoveUpdate(PartSys->sources[i].source, PartSys->sources[i].sourceFlags, &sparklersettings); //move sparkler } for(i = 0; i < PartSys->usedParticles; i++) { @@ -9613,7 +9605,7 @@ uint16_t mode_particleHourglass(void) { *basehue = hw_random16(); //choose new random color SEGENV.step = SEGMENT.intensity | (PartSys->getAvailableParticles() << 8); for(uint32_t i = 0; i < PartSys->usedParticles; i++) { - PartSys->particles[i].reversegrav = true; + PartSys->particleFlags[i].reversegrav = true; *direction = 0; SEGENV.aux1 = 1; //initialize below } @@ -9622,14 +9614,14 @@ uint16_t mode_particleHourglass(void) { for(uint32_t i = 0; i < PartSys->usedParticles; i++) { //check if particle reached target position after falling int32_t targetposition; - if (PartSys->particles[i].fixed == false) { + if (PartSys->particleFlags[i].fixed == false) { //calculate target position depending on direction - if(PartSys->particles[i].reversegrav) + if(PartSys->particleFlags[i].reversegrav) targetposition = PartSys->maxX - (i * PS_P_RADIUS_1D + positionoffset); // target resting position else targetposition = (PartSys->usedParticles - i) * PS_P_RADIUS_1D - positionoffset; // target resting position if(PartSys->particles[i].x == targetposition) //particle has reached target position, pin it. if not pinned, they do not stack well on larger piles - PartSys->particles[i].fixed = true; + PartSys->particleFlags[i].fixed = true; } if(colormode == 7) PartSys->setColorByPosition(true); //color fixed by position @@ -9646,24 +9638,24 @@ uint16_t mode_particleHourglass(void) { default: break; } } - if(SEGMENT.check1 && !PartSys->particles[i].reversegrav) // flip color when fallen + if(SEGMENT.check1 && !PartSys->particleFlags[i].reversegrav) // flip color when fallen PartSys->particles[i].hue += 120; } if(SEGENV.aux1 == 1) { //last countdown call before dropping starts, reset all particles for(uint32_t i = 0; i < PartSys->usedParticles; i++) { - PartSys->particles[i].collide = true; - PartSys->particles[i].perpetual = true; + PartSys->particleFlags[i].collide = true; + PartSys->particleFlags[i].perpetual = true; PartSys->particles[i].ttl = 260; uint32_t targetposition; //calculate target position depending on direction - if(PartSys->particles[i].reversegrav) + if(PartSys->particleFlags[i].reversegrav) targetposition = PartSys->maxX - (i * PS_P_RADIUS_1D + positionoffset); // target resting position else targetposition = (PartSys->usedParticles - i) * PS_P_RADIUS_1D - positionoffset; // target resting position -5 - PS_P_RADIUS_1D/2 PartSys->particles[i].x = targetposition; - PartSys->particles[i].fixed = true; + PartSys->particleFlags[i].fixed = true; } } @@ -9673,8 +9665,8 @@ uint16_t mode_particleHourglass(void) { interval = 3; if(SEGMENT.call % interval == 0) { //drop a particle, do not drop more often than every second frame or particles tangle up quite badly if(SEGENV.aux0 < PartSys->usedParticles) { - PartSys->particles[SEGENV.aux0].reversegrav = *direction; //let this particle fall or rise - PartSys->particles[SEGENV.aux0].fixed = false; // unpin + PartSys->particleFlags[SEGENV.aux0].reversegrav = *direction; //let this particle fall or rise + PartSys->particleFlags[SEGENV.aux0].fixed = false; // unpin } else { //overflow, flip direction *direction = !(*direction); @@ -9733,7 +9725,7 @@ uint16_t mode_particle1Dspray(void) { PartSys->sources[0].maxLife = 400; PartSys->sources[0].source.x = map(SEGMENT.custom1, 0 , 255, 0, PartSys->maxX); // spray position PartSys->sources[0].v = map(SEGMENT.speed, 0 , 255, -127 + PartSys->sources[0].var, 127 - PartSys->sources[0].var); // particle emit speed - PartSys->sources[0].source.reversegrav = gravity < 0 ? true : false; + PartSys->sources[0].sourceFlags.reversegrav = gravity < 0 ? true : false; if(hw_random() % (1 + ((255 - SEGMENT.intensity) >> 3)) == 0) PartSys->sprayEmit(PartSys->sources[0]); // emit a particle @@ -9742,7 +9734,7 @@ uint16_t mode_particle1Dspray(void) { PartSys->setColorByAge(SEGMENT.check1); // overruled by 'color by position' PartSys->setColorByPosition(SEGMENT.check3); for(uint i = 0; i < PartSys->usedParticles; i++) { - PartSys->particles[i].reversegrav = PartSys->sources[0].source.reversegrav; // update gravity direction + PartSys->particleFlags[i].reversegrav = PartSys->sources[0].sourceFlags.reversegrav; // update gravity direction } PartSys->update(); // update and render @@ -9764,7 +9756,7 @@ uint16_t mode_particleBalance(void) { return mode_static(); // allocation failed or is single pixel //PartSys->setKillOutOfBounds(true); PartSys->setParticleSize(1); - SEGENV.aux0 = 0; // + SEGENV.aux0 = 0; } else PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS @@ -9787,8 +9779,8 @@ uint16_t mode_particleBalance(void) { PartSys->particles[i].x = i * PS_P_RADIUS_1D; PartSys->particles[i].hue = (i * 1024) / PartSys->usedParticles; // multi gradient distribution PartSys->particles[i].ttl = 300; - PartSys->particles[i].perpetual = true; // TODO: is this a good idea? need to check how to handle it in transitions - PartSys->particles[i].collide = true; + PartSys->particleFlags[i].perpetual = true; // TODO: is this a good idea? need to check how to handle it in transitions + PartSys->particleFlags[i].collide = true; } } SEGENV.aux1 = PartSys->usedParticles; @@ -9961,7 +9953,7 @@ uint16_t mode_particleStarburst(void) { PartSys->sources[0].source.ttl = 10 + hw_random16(255 - SEGMENT.speed); PartSys->sources[0].size = SEGMENT.custom1; // Fragment size PartSys->setParticleSize(SEGMENT.custom1); // enable advanced size rendering - PartSys->sources[0].source.collide = SEGMENT.check3; + PartSys->sources[0].sourceFlags.collide = SEGMENT.check3; for (uint32_t e = 0; e < explosionsize; e++) { // emit particles if (SEGMENT.check2) PartSys->sources[0].source.hue = hw_random16(); //random color for each particle @@ -10189,7 +10181,7 @@ uint16_t mode_particle1Dsonicstream(void) { // particle manipulation for (uint32_t i = 0; i < PartSys->usedParticles; i++) { - if(PartSys->sources[0].source.perpetual == false) { // age faster if not perpetual + if(PartSys->sources[0].sourceFlags.perpetual == false) { // age faster if not perpetual if (PartSys->particles[i].ttl > 2) { PartSys->particles[i].ttl -= 2; //ttl is linked to brightness, this allows to use higher brightness but still a short lifespan } @@ -10215,7 +10207,7 @@ uint16_t mode_particle1Dsonicstream(void) { PartSys->update(); // update and render (needs to be done before manipulation for initial particle spacing to be right) if(SEGMENT.check3) { // push mode - PartSys->sources[0].source.perpetual = true; // emitted particles dont age + PartSys->sources[0].sourceFlags.perpetual = true; // emitted particles dont age PartSys->applyFriction(1); //slow down particles int32_t movestep = (((int)SEGMENT.speed + 2) * loudness) >> 10; if(movestep) { @@ -10228,12 +10220,12 @@ uint16_t mode_particle1Dsonicstream(void) { } } else { - PartSys->sources[0].source.perpetual = false; // emitted particles age + PartSys->sources[0].sourceFlags.perpetual = false; // emitted particles age //move all particles (again) to allow faster speeds for (uint32_t i = 0; i < PartSys->usedParticles; i++) { if (PartSys->particles[i].vx == 0) PartSys->particles[i].vx = PartSys->sources[0].v; // move static particles (after disabling push mode) - PartSys->particleMoveUpdate(PartSys->particles[i], nullptr, &PartSys->advPartProps[i]); + PartSys->particleMoveUpdate(PartSys->particles[i], PartSys->particleFlags[i], nullptr, &PartSys->advPartProps[i]); } } /* @@ -10466,7 +10458,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_2DCRAZYBEES, &mode_2Dcrazybees, _data_FX_MODE_2DCRAZYBEES); #ifndef DISABLE_2D_PS_REPLACEMENTS addEffect(FX_MODE_2DGHOSTRIDER, &mode_2Dghostrider, _data_FX_MODE_2DGHOSTRIDER); - addEffect(FX_MODE_2DBLOBS, &mode_2Dfloatingblobs, _data_FX_MODE_2DBLOBS); + //addEffect(FX_MODE_2DBLOBS, &mode_2Dfloatingblobs, _data_FX_MODE_2DBLOBS); #endif addEffect(FX_MODE_2DSCROLLTEXT, &mode_2Dscrollingtext, _data_FX_MODE_2DSCROLLTEXT); addEffect(FX_MODE_2DDRIFTROSE, &mode_2Ddriftrose, _data_FX_MODE_2DDRIFTROSE); @@ -10519,7 +10511,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_PARTICLEPERLIN, &mode_particleperlin, _data_FX_MODE_PARTICLEPERLIN); addEffect(FX_MODE_PARTICLEPIT, &mode_particlepit, _data_FX_MODE_PARTICLEPIT); addEffect(FX_MODE_PARTICLEBOX, &mode_particlebox, _data_FX_MODE_PARTICLEBOX); - addEffect(FX_MODE_PARTICLEATTRACTOR, &mode_particleattractor, _data_FX_MODE_PARTICLEATTRACTOR); + addEffect(FX_MODE_PARTICLEATTRACTOR, &mode_particleattractor, _data_FX_MODE_PARTICLEATTRACTOR); // 872 bytes addEffect(FX_MODE_PARTICLEIMPACT, &mode_particleimpact, _data_FX_MODE_PARTICLEIMPACT); addEffect(FX_MODE_PARTICLEWATERFALL, &mode_particlewaterfall, _data_FX_MODE_PARTICLEWATERFALL); addEffect(FX_MODE_PARTICLESPRAY, &mode_particlespray, _data_FX_MODE_PARTICLESPRAY); diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index 7898ac01fc..a5fbd41650 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -21,9 +21,9 @@ #ifndef WLED_DISABLE_PARTICLESYSTEM2D // local shared functions (used both in 1D and 2D system) -static int32_t calcForce_dv(int8_t force, uint8_t *counter); +static int32_t calcForce_dv(const int8_t force, uint8_t &counter); static int32_t limitSpeed(int32_t speed); -static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, bool wrap); // returns false if out of bounds by more than particleradius +static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, const bool wrap); // returns false if out of bounds by more than particleradius static void fast_color_add(CRGB &c1, const CRGB &c2, uint32_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding) static void fast_color_scale(CRGB &c, const uint32_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255 //static CRGB *allocateCRGBbuffer(uint32_t length); @@ -86,21 +86,21 @@ void ParticleSystem2D::update(void) { handleCollisions(); //move all particles + //TODO: split this loop into two separate loops? avoids repeated null checking which is faster. + for (uint32_t i = 0; i < usedParticles; i++) { - if (advPartProps) { - advprop = &advPartProps[i]; - } - particleMoveUpdate(particles[i], &particlesettings, advprop); + particleMoveUpdate(particles[i], particleFlags[i], &particlesettings, advPartProps ? &advPartProps[i] : nullptr); } ParticleSys_render(); } // update function for fire animation -void ParticleSystem2D::updateFire(uint32_t intensity, bool renderonly) { +void ParticleSystem2D::updateFire(const uint8_t intensity,const bool renderonly) { if (!renderonly) fireParticleupdate(); - ParticleSys_render(true, intensity); + fireIntesity = intensity > 0 ? intensity : 1; // minimum of 1, zero checking is used in render function + ParticleSys_render(); } // set percentage of used particles as uint8_t i.e 127 means 50% for example @@ -117,6 +117,7 @@ void ParticleSystem2D::setUsedParticles(uint8_t percentage) { PSPRINTLN(usedParticles); } +//TODO: inline these functions void ParticleSystem2D::setWallHardness(uint8_t hardness) { wallHardness = hardness; } @@ -129,7 +130,7 @@ void ParticleSystem2D::setCollisionHardness(uint8_t hardness) { collisionHardness = (int)hardness + 1; } -void ParticleSystem2D::setMatrixSize(uint16_t x, uint16_t y) { +void ParticleSystem2D::setMatrixSize(uint32_t x, uint32_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 @@ -194,28 +195,26 @@ void ParticleSystem2D::enableParticleCollisions(bool enable, uint8_t hardness) { collisionHardness = (int)hardness + 1; } -// emit one particle with variation, returns index of last emitted particle (or -1 if no particle emitted) -int32_t ParticleSystem2D::sprayEmit(PSsource &emitter, uint32_t amount) { +// emit one particle with variation, returns index of emitted particle (or -1 if no particle emitted) +int32_t ParticleSystem2D::sprayEmit(const PSsource &emitter) { bool success = false; - for (uint32_t a = 0; a < amount; a++) { - for (uint32_t i = 0; i < usedParticles; i++) { - emitIndex++; - if (emitIndex >= usedParticles) - emitIndex = 0; - if (particles[emitIndex].ttl == 0) { // find a dead particle - success = true; - particles[emitIndex].vx = emitter.vx + hw_random16(emitter.var << 1) - emitter.var; // random(-var, var) - particles[emitIndex].vy = emitter.vy + hw_random16(emitter.var << 1) - emitter.var; // random(-var, 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 = hw_random16(emitter.minLife, emitter.maxLife); - if (advPartProps) - advPartProps[emitIndex].size = emitter.size; - break; - } + for (uint32_t i = 0; i < usedParticles; i++) { + emitIndex++; + if (emitIndex >= usedParticles) + emitIndex = 0; + if (particles[emitIndex].ttl == 0) { // find a dead particle + success = true; + particles[emitIndex].vx = emitter.vx + hw_random16(emitter.var << 1) - emitter.var; // random(-var, var) + particles[emitIndex].vy = emitter.vy + hw_random16(emitter.var << 1) - emitter.var; // random(-var, 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; + particleFlags[emitIndex].collide = emitter.sourceFlags.collide; + particles[emitIndex].ttl = hw_random16(emitter.minLife, emitter.maxLife); + if (advPartProps) + advPartProps[emitIndex].size = emitter.size; + break; } } if (success) @@ -225,27 +224,27 @@ int32_t ParticleSystem2D::sprayEmit(PSsource &emitter, uint32_t amount) { } // Spray emitter for particles used for flames (particle TTL depends on source TTL) -void ParticleSystem2D::flameEmit(PSsource &emitter) { +void ParticleSystem2D::flameEmit(const PSsource &emitter) { int emitIndex = sprayEmit(emitter); - if(emitIndex > 0) particles[emitIndex].ttl += emitter.source.ttl; + if(emitIndex > 0) particles[emitIndex].ttl += emitter.source.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) -int32_t ParticleSystem2D::angleEmit(PSsource &emitter, uint16_t angle, int32_t speed, uint32_t amount) { +int32_t ParticleSystem2D::angleEmit(PSsource &emitter, const uint16_t angle, const int32_t speed) { emitter.vx = ((int32_t)cos16_t(angle) * speed) / (int32_t)32600; // cos16_t() and sin16_t() return signed 16bit, division should be 32767 but 32600 gives slightly better rounding emitter.vy = ((int32_t)sin16_t(angle) * 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! - return sprayEmit(emitter, amount); + return 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 enabled, it will never bounce at the top and killoutofbounds is not applied over the top -void ParticleSystem2D::particleMoveUpdate(PSparticle &part, PSsettings2D *options, PSadvancedParticle *advancedproperties) { +void ParticleSystem2D::particleMoveUpdate(PSparticle &part, PSparticleFlags &partFlags, PSsettings2D *options, PSadvancedParticle *advancedproperties) { if (options == NULL) options = &particlesettings; //use PS system settings by default if (part.ttl > 0) { - if (!part.perpetual) + if (!partFlags.perpetual) part.ttl--; // age if (options->colorByAge) part.hue = min(part.ttl, (uint16_t)255); //set color to ttl @@ -253,7 +252,7 @@ void ParticleSystem2D::particleMoveUpdate(PSparticle &part, PSsettings2D *option int32_t renderradius = PS_P_HALFRADIUS; // used to check out of bounds int32_t newX = part.x + (int32_t)part.vx; int32_t newY = part.y + (int32_t)part.vy; - part.outofbounds = false; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) + partFlags.outofbounds = false; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) TODO: move this below, setting a flag is slow, only set if actually in bounds if (advancedproperties) { //using individual particle size? if (advancedproperties->size > 0) { @@ -269,7 +268,7 @@ void ParticleSystem2D::particleMoveUpdate(PSparticle &part, PSsettings2D *option } if(!checkBoundsAndWrap(newY, maxY, renderradius, options->wrapY)) { // check out of bounds note: this must not be skipped, if gravity is enabled, particles will never bounce at the top - part.outofbounds = true; + partFlags.outofbounds = true; if (options->killoutofbounds) { if (newY < 0) // if gravity is enabled, only kill particles below ground part.ttl = 0; @@ -284,7 +283,7 @@ void ParticleSystem2D::particleMoveUpdate(PSparticle &part, PSsettings2D *option bounce(part.vx, part.vy, newX, maxX); } else if(!checkBoundsAndWrap(newX, maxX, renderradius, options->wrapX)) { // check out of bounds TODO: not checking out of bounds when bounce is enabled used to lead to crashes, seems fixed now. test more. - part.outofbounds = true; + partFlags.outofbounds = true; if (options->killoutofbounds) part.ttl = 0; } @@ -302,16 +301,16 @@ void ParticleSystem2D::fireParticleupdate() { { particles[i].ttl--; // age int32_t newY = particles[i].y + (int32_t)particles[i].vy + (particles[i].ttl >> 2); // younger particles move faster upward as they are hotter - particles[i].outofbounds = 0; // reset out of bounds flag + int32_t newX = particles[i].x + (int32_t)particles[i].vx; + particleFlags[i].outofbounds = 0; // reset out of bounds flag //TODO: can this be moved to else statements below? // 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 if (newY < -PS_P_HALFRADIUS) - particles[i].outofbounds = 1; + particleFlags[i].outofbounds = 1; else if (newY > int32_t(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 Note: using checkBoundsAndWrap() is slower, only saves a few bytes { - int32_t newX = particles[i].x + (int32_t)particles[i].vx; if ((newX < 0) || (newX > (int32_t)maxX)) { // handle out of bounds & wrap if (particlesettings.wrapX) { newX = newX % (maxX + 1); @@ -409,7 +408,7 @@ void ParticleSystem2D::getParticleXYsize(PSadvancedParticle *advprops, PSsizeCon } // function to bounce a particle from a wall using set parameters (wallHardness and wallRoughness) -void ParticleSystem2D::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition) { +void ParticleSystem2D::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition) { incomingspeed = -incomingspeed; incomingspeed = (incomingspeed * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface if (position < (int32_t)particleHardRadius) @@ -431,40 +430,40 @@ void ParticleSystem2D::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int3 // apply a force in x,y direction to individual particle // caller needs to provide a 8bit counter (for each particle) that holds its value between calls // 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 ParticleSystem2D::applyForce(PSparticle *part, int8_t xforce, int8_t yforce, uint8_t *counter) { +void ParticleSystem2D::applyForce(PSparticle &part, const int8_t xforce, const 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 + 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); + 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 + 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); + 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 ParticleSystem2D::applyForce(uint16_t particleindex, int8_t xforce, int8_t yforce) { +void ParticleSystem2D::applyForce(const uint32_t particleindex, const int8_t xforce, const int8_t yforce) { if (advPartProps == NULL) return; // no advanced properties available - applyForce(&particles[particleindex], xforce, yforce, &advPartProps[particleindex].forcecounter); + 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 ParticleSystem2D::applyForce(int8_t xforce, int8_t yforce) { +void ParticleSystem2D::applyForce(const int8_t xforce, const int8_t yforce) { // for small forces, need to use a delay counter uint8_t tempcounter; // note: this is not the most computationally efficient way to do this, but it saves on duplicate code and is fast enough for (uint32_t i = 0; i < usedParticles; i++) { tempcounter = forcecounter; - applyForce(&particles[i], xforce, yforce, &tempcounter); + applyForce(particles[i], xforce, yforce, tempcounter); } forcecounter = tempcounter; // save value back } @@ -473,21 +472,21 @@ void ParticleSystem2D::applyForce(int8_t xforce, int8_t yforce) { // caller needs to provide a 8bit counter that holds its value between calls (if using single particles, a counter for each particle is needed) // angle is from 0-65535 (=0-360deg) angle = 0 means in positive x-direction (i.e. to the right) // force is in 3.4 fixed point notation so force=16 means apply v+1 each frame (useful force range is +/- 127) -void ParticleSystem2D::applyAngleForce(PSparticle *part, int8_t force, uint16_t angle, uint8_t *counter) { +void ParticleSystem2D::applyAngleForce(PSparticle &part, const int8_t force, const uint16_t angle, uint8_t &counter) { int8_t xforce = ((int32_t)force * cos16_t(angle)) / 32767; // force is +/- 127 int8_t yforce = ((int32_t)force * sin16_t(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! applyForce(part, xforce, yforce, counter); } -void ParticleSystem2D::applyAngleForce(uint16_t particleindex, int8_t force, uint16_t angle) { +void ParticleSystem2D::applyAngleForce(const uint32_t particleindex, const int8_t force, const uint16_t angle) { if (advPartProps == NULL) return; // no advanced properties available - applyAngleForce(&particles[particleindex], force, angle, &advPartProps[particleindex].forcecounter); + 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 ParticleSystem2D::applyAngleForce(int8_t force, uint16_t angle) { +void ParticleSystem2D::applyAngleForce(const int8_t force, const uint16_t angle) { int8_t xforce = ((int32_t)force * cos16_t(angle)) / 32767; // force is +/- 127 int8_t yforce = ((int32_t)force * sin16_t(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); @@ -497,7 +496,7 @@ void ParticleSystem2D::applyAngleForce(int8_t force, uint16_t angle) { // force is in 3.4 fixed point notation, see note above // note: faster than apply force since direction is always down and counter is fixed for all particles void ParticleSystem2D::applyGravity() { - int32_t dv = calcForce_dv(gforce, &gforcecounter); + int32_t dv = calcForce_dv(gforce, gforcecounter); if(dv == 0) return; 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 @@ -509,37 +508,39 @@ void ParticleSystem2D::applyGravity() { // function does not increment gravity counter, if gravity setting is disabled, this cannot be used void ParticleSystem2D::applyGravity(PSparticle &part) { uint32_t counterbkp = gforcecounter; // backup PS gravity counter - int32_t dv = calcForce_dv(gforce, &gforcecounter); + int32_t dv = calcForce_dv(gforce, gforcecounter); gforcecounter = counterbkp; //save it back 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 ParticleSystem2D::applyFriction(PSparticle *part, int32_t coefficient) { +void ParticleSystem2D::applyFriction(PSparticle &part, const 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 - part->vx = ((int32_t)part->vx * friction) / 255; - part->vy = ((int32_t)part->vy * friction) / 255; + part.vx = ((int32_t)part.vx * friction) / 255; + part.vy = ((int32_t)part.vy * friction) / 255; } // apply friction to all particles -void ParticleSystem2D::applyFriction(int32_t coefficient) { +void ParticleSystem2D::applyFriction(const int32_t coefficient) { + int32_t friction = 255 - coefficient; for (uint32_t i = 0; i < usedParticles; i++) { - if (particles[i].ttl) - applyFriction(&particles[i], coefficient); + // 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].vx = ((int32_t)particles[i].vx * friction) / 255; + particles[i].vy = ((int32_t)particles[i].vy * friction) / 255; } } // attracts a particle to an attractor particle using the inverse square-law -void ParticleSystem2D::pointAttractor(uint16_t particleindex, PSparticle *attractor, uint8_t strength, bool swallow) { +void ParticleSystem2D::pointAttractor(const uint32_t particleindex, PSparticle &attractor, const uint8_t strength, const 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; + 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; @@ -565,7 +566,7 @@ void ParticleSystem2D::pointAttractor(uint16_t particleindex, PSparticle *attrac // if wrap is set, particles half out of bounds are rendered to the other side of the matrix // warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds // firemode is only used for PS Fire FX -void ParticleSystem2D::ParticleSys_render(bool firemode, uint32_t fireintensity) { +void ParticleSystem2D::ParticleSys_render() { CRGB baseRGB; uint32_t brightness; // particle brightness, fades if dying static bool useAdditiveTransfer = false; // use add instead of set for buffer transferring @@ -643,11 +644,11 @@ void ParticleSystem2D::ParticleSys_render(bool firemode, uint32_t fireintensity) bool wrapY = particlesettings.wrapY; // go over particles and render them to the buffer for (uint32_t i = 0; i < usedParticles; i++) { - if (particles[i].outofbounds || particles[i].ttl == 0) + if (particles[i].ttl == 0 || particleFlags[i].outofbounds) continue; // generate RGB values for particle - if (firemode) { - brightness = (uint32_t)particles[i].ttl * (3 + (fireintensity >> 5)) + 20; + if (fireIntesity) { + brightness = (uint32_t)particles[i].ttl * (3 + (fireIntesity >> 5)) + 20; brightness = min(brightness, (uint32_t)255); baseRGB = ColorFromPalette(SEGPALETTE, brightness, 255); } @@ -861,7 +862,7 @@ void ParticleSystem2D::handleCollisions() { // fill the binIndices array for this bin for (uint32_t i = collisionStartIdx; i < usedParticles; i++) { - if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) { // colliding particle + if (particles[i].ttl > 0 && particleFlags[i].outofbounds == 0 && particleFlags[i].collide) { // colliding particle if (particles[i].x >= binStart && particles[i].x <= binEnd) { // >= and <= to include particles on the edge of the bin (overlap to ensure boarder particles collide with adjacent bins) if (binParticleCount >= maxBinParticles) { // bin is full, more particles in this bin so do the rest next frame nextFrameStartIdx = i; // bin overflow can only happen once as bin size is at least half of the particles (or half +1) @@ -884,7 +885,7 @@ void ParticleSystem2D::handleCollisions() { if (dx * dx < collDistSq) { // check x direction, if close, check y direction (squaring is faster than abs() or dual compare) int32_t dy = particles[idx_j].y - particles[idx_i].y; if (dy * dy < collDistSq) // particles are close - collideParticles(&particles[idx_i], &particles[idx_j], dx, dy); + collideParticles(particles[idx_i], particles[idx_j], dx, dy); } } } @@ -894,11 +895,11 @@ void ParticleSystem2D::handleCollisions() { // 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 ParticleSystem2D::collideParticles(PSparticle *particle1, PSparticle *particle2, int32_t dx, int32_t dy) { // TODO: dx,dy is calculated just above, can pass it over here to save a few CPU cycles? +void ParticleSystem2D::collideParticles(PSparticle &particle1, PSparticle &particle2, int32_t dx, int32_t dy) { 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 = (int32_t)particle2->vx - (int32_t)particle1->vx; - int32_t relativeVy = (int32_t)particle2->vy - (int32_t)particle1->vy; + int32_t relativeVx = (int32_t)particle2.vx - (int32_t)particle1.vx; + int32_t relativeVy = (int32_t)particle2.vy - (int32_t)particle1.vy; // if dx and dy are zero (i.e. same position) give them an offset, if speeds are also zero, also offset them (pushes particles apart if they are clumped before enabling collisions) if (distanceSquared == 0) { @@ -930,18 +931,18 @@ void ParticleSystem2D::collideParticles(PSparticle *particle1, PSparticle *parti 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; + particle1.vx += ximpulse; + particle1.vy += yimpulse; + particle2.vx -= ximpulse; + particle2.vy -= yimpulse; if (collisionHardness < surfacehardness && (SEGMENT.call & 0x03) == 0) { // if particles are soft, they become 'sticky' i.e. apply some friction (they do pile more nicely and stop sloshing around) const uint32_t coeff = collisionHardness + (255 - PS_P_MINSURFACEHARDNESS); // Note: could call applyFriction, but this is faster and speed is key here - particle1->vx = ((int32_t)particle1->vx * coeff) / 255; - particle1->vy = ((int32_t)particle1->vy * coeff) / 255; + 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; + particle2.vx = ((int32_t)particle2.vx * coeff) / 255; + particle2.vy = ((int32_t)particle2.vy * coeff) / 255; } // particles have volume, push particles apart if they are too close @@ -957,11 +958,11 @@ void ParticleSystem2D::collideParticles(PSparticle *particle1, PSparticle *parti 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 + particle1.x++; // move it so pile collapses else - particle1->x--; + particle1.x--; } - particle1->vx += push; //TODO: what happens if particle2 is also pushed? in 1D it stacks better, maybe also just reverse the comparison order so they flip roles? + particle1.vx += push; //TODO: what happens if particle2 is also pushed? in 1D it stacks better, maybe also just reverse the comparison order so they flip roles? push = 0; if (dy < 0) push = pushamount; @@ -969,20 +970,20 @@ void ParticleSystem2D::collideParticles(PSparticle *particle1, PSparticle *parti push = -pushamount; else { // dy==0 if (notsorandom) - particle1->y++; // move it so pile collapses + particle1.y++; // move it so pile collapses else - particle1->y--; + particle1.y--; } - particle1->vy += push; + 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 if (collisionHardness < 16) { // if they are very soft, stop slow particles completely to make them stick to each other - particle1->vx = 0; - particle1->vy = 0; - particle2->vx = 0; - particle2->vy = 0; + particle1.vx = 0; + particle1.vy = 0; + particle2.vx = 0; + particle2.vy = 0; //push them apart - particle1->x += push; - particle1->y += push; + particle1.x += push; + particle1.y += push; } } } @@ -1007,7 +1008,7 @@ void ParticleSystem2D::updateSystem(void) { // function returns the pointer to the next byte available for the FX (if it assigned more memory for other stuff using the above allocate function) // FX handles the PSsources, need to tell this function how many there are void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) { -PSPRINTLN("updatePSpointers"); + PSPRINTLN("updatePSpointers"); // DEBUG_PRINT(F("*** PS pointers ***")); // DEBUG_PRINTF_P(PSTR("this PS %p "), this); // Note on memory alignment: @@ -1018,7 +1019,8 @@ PSPRINTLN("updatePSpointers"); // memory manager needs to know how many particles the FX wants to use so transitions can be handled properly (i.e. pointer will stop changing if enough particles are available during transitions) uint32_t usedByFX = (numParticles * ((uint32_t)fractionOfParticlesUsed + 1)) >> 8; // final number of particles the FX wants to use (fractionOfParticlesUsed is 0-255) particles = reinterpret_cast(particleMemoryManager(0, sizeof(PSparticle), availableParticles, usedByFX, effectID)); // get memory, leave buffer size as is (request 0) - sources = reinterpret_cast(this + 1); // pointer to source(s) at data+sizeof(ParticleSystem2D) + particleFlags = reinterpret_cast(this + 1); // pointer to particle flags + sources = reinterpret_cast(particleFlags + numParticles); // pointer to source(s) at data+sizeof(ParticleSystem2D) PSdataEnd = reinterpret_cast(sources + numSources); // pointer to first available byte after the PS for FX additional data if (isadvanced) { advPartProps = reinterpret_cast(sources + numSources); @@ -1091,7 +1093,7 @@ void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, u } //non class functions to use for initialization -uint32_t calculateNumberOfParticles2D(uint32_t pixels, bool isadvanced, bool sizecontrol) { +uint32_t calculateNumberOfParticles2D(uint32_t const pixels, const bool isadvanced, const bool sizecontrol) { uint32_t numberofParticles = pixels; // 1 particle per pixel (for example 512 particles on 32x16) #ifdef ESP8266 uint32_t particlelimit = ESP8266_MAXPARTICLES; // maximum number of paticles allowed (based on one segment of 16x16 and 4k effect ram) @@ -1136,6 +1138,7 @@ bool allocateParticleSystemMemory2D(uint32_t numparticles, uint32_t numsources, return false; // not enough memory, function ensures a minimum of numparticles are available // functions above make sure these are a multiple of 4 bytes (to avoid alignment issues) + requiredmemory += sizeof(PSparticleFlags) * numparticles; if (isadvanced) requiredmemory += sizeof(PSadvancedParticle) * numparticles; if (sizecontrol) @@ -1234,9 +1237,7 @@ void ParticleSystem1D::update(void) { //move all particles for (uint32_t i = 0; i < usedParticles; i++) { - if (advPartProps) - advprop = &advPartProps[i]; - particleMoveUpdate(particles[i], &particlesettings, advprop); + particleMoveUpdate(particles[i], particleFlags[i], &particlesettings, advPartProps ? &advPartProps[i] : nullptr); } if (particlesettings.colorByPosition) { @@ -1257,7 +1258,7 @@ void ParticleSystem1D::update(void) { } // set percentage of used particles as uint8_t i.e 127 means 50% for example -void ParticleSystem1D::setUsedParticles(uint8_t percentage) { +void ParticleSystem1D::setUsedParticles(const uint8_t percentage) { fractionOfParticlesUsed = percentage; // note usedParticles is updated in memory manager updateUsedParticles(numParticles, availableParticles, fractionOfParticlesUsed, usedParticles); PSPRINT(" SetUsedpaticles: allocated particles: "); @@ -1270,45 +1271,45 @@ void ParticleSystem1D::setUsedParticles(uint8_t percentage) { PSPRINTLN(usedParticles); } -void ParticleSystem1D::setWallHardness(uint8_t hardness) { +void ParticleSystem1D::setWallHardness(const uint8_t hardness) { wallHardness = hardness; } -void ParticleSystem1D::setSize(uint16_t x) { +void ParticleSystem1D::setSize(const uint32_t x) { maxXpixel = x - 1; // last physical pixel that can be drawn to maxX = x * PS_P_RADIUS_1D - 1; // particle system boundary for movements } -void ParticleSystem1D::setWrap(bool enable) { +void ParticleSystem1D::setWrap(const bool enable) { particlesettings.wrap = enable; } -void ParticleSystem1D::setBounce(bool enable) { +void ParticleSystem1D::setBounce(const bool enable) { particlesettings.bounce = enable; } -void ParticleSystem1D::setKillOutOfBounds(bool enable) { +void ParticleSystem1D::setKillOutOfBounds(const bool enable) { particlesettings.killoutofbounds = enable; } -void ParticleSystem1D::setColorByAge(bool enable) { +void ParticleSystem1D::setColorByAge(const bool enable) { particlesettings.colorByAge = enable; } -void ParticleSystem1D::setColorByPosition(bool enable) { +void ParticleSystem1D::setColorByPosition(const bool enable) { particlesettings.colorByPosition = enable; } -void ParticleSystem1D::setMotionBlur(uint8_t bluramount) { +void ParticleSystem1D::setMotionBlur(const uint8_t bluramount) { motionBlur = bluramount; } -void ParticleSystem1D::setSmearBlur(uint8_t bluramount) { +void ParticleSystem1D::setSmearBlur(const uint8_t bluramount) { smearBlur = bluramount; } // render size, 0 = 1 pixel, 1 = 2 pixel (interpolated), bigger sizes require adanced properties -void ParticleSystem1D::setParticleSize(uint8_t size) { +void ParticleSystem1D::setParticleSize(const uint8_t size) { particlesize = size > 0 ? 1 : 0; // TODO: add support for global sizes? see not abover (motion blur) if (particlesize) particleHardRadius = PS_P_MINHARDRADIUS_1D; // 2 pixel sized particles @@ -1319,7 +1320,7 @@ void ParticleSystem1D::setParticleSize(uint8_t size) { // enable/disable gravity, optionally, set the force (force=8 is default) can be -127 to +127, 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 ParticleSystem1D::setGravity(int8_t force) { +void ParticleSystem1D::setGravity(const int8_t force) { if (force) { gforce = force; particlesettings.useGravity = true; @@ -1328,13 +1329,13 @@ void ParticleSystem1D::setGravity(int8_t force) { particlesettings.useGravity = false; } -void ParticleSystem1D::enableParticleCollisions(bool enable, uint8_t hardness) { +void ParticleSystem1D::enableParticleCollisions(const bool enable, const uint8_t hardness) { particlesettings.useCollisions = enable; collisionHardness = hardness; } // emit one particle with variation, returns index of last emitted particle (or -1 if no particle emitted) -int32_t ParticleSystem1D::sprayEmit(PSsource1D &emitter) { +int32_t ParticleSystem1D::sprayEmit(const PSsource1D &emitter) { for (uint32_t i = 0; i < usedParticles; i++) { emitIndex++; if (emitIndex >= usedParticles) @@ -1343,10 +1344,10 @@ int32_t ParticleSystem1D::sprayEmit(PSsource1D &emitter) { particles[emitIndex].vx = emitter.v + hw_random16(emitter.var << 1) - emitter.var; // random(-var,var) particles[emitIndex].x = emitter.source.x; particles[emitIndex].hue = emitter.source.hue; - particles[emitIndex].collide = emitter.source.collide; - particles[emitIndex].reversegrav = emitter.source.reversegrav; particles[emitIndex].ttl = hw_random16(emitter.minLife, emitter.maxLife); - particles[emitIndex].perpetual = emitter.source.perpetual; + particleFlags[emitIndex].collide = emitter.sourceFlags.collide; + particleFlags[emitIndex].reversegrav = emitter.sourceFlags.reversegrav; + particleFlags[emitIndex].perpetual = emitter.sourceFlags.perpetual; if (advPartProps) { advPartProps[emitIndex].sat = emitter.sat; advPartProps[emitIndex].size = emitter.size; @@ -1359,19 +1360,19 @@ int32_t ParticleSystem1D::sprayEmit(PSsource1D &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 ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *options, PSadvancedParticle1D *advancedproperties) { +void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSparticleFlags1D &partFlags, PSsettings1D *options, PSadvancedParticle1D *advancedproperties) { if (options == NULL) options = &particlesettings; // use PS system settings by default if (part.ttl > 0) { - if (!part.perpetual) + if (!partFlags.perpetual) part.ttl--; // age if (options->colorByAge) part.hue = min(part.ttl, (uint16_t)255); // set color to ttl int32_t renderradius = PS_P_HALFRADIUS_1D; // used to check out of bounds, default for 2 pixel rendering int32_t newX = part.x + (int32_t)part.vx; - part.outofbounds = false; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) + partFlags.outofbounds = false; // 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 > 1) @@ -1386,7 +1387,7 @@ void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *opti if ((newX < (int32_t)particleHardRadius) || ((newX > (int32_t)(maxX - particleHardRadius)))) { // reached a wall bool bouncethis = true; if (options->useGravity) { - if (part.reversegrav) { // skip bouncing at x = 0 + if (partFlags.reversegrav) { // skip bouncing at x = 0 if (newX < (int32_t)particleHardRadius) bouncethis = false; } else if (newX > (int32_t)particleHardRadius) { // skip bouncing at x = max @@ -1405,11 +1406,11 @@ void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *opti } if (!checkBoundsAndWrap(newX, maxX, renderradius, options->wrap)) { // check out of bounds note: this must not be skipped or it can lead to crashes - part.outofbounds = true; + partFlags.outofbounds = true; if (options->killoutofbounds) { bool killthis = true; if (options->useGravity) { // if gravity is used, only kill below 'floor level' - if (part.reversegrav) { // skip at x = 0, do not skip far out of bounds + if (partFlags.reversegrav) { // skip at x = 0, do not skip far out of bounds if (newX < 0 || newX > maxX << 2) killthis = false; } else { // skip at x = max, do not skip far out of bounds @@ -1422,7 +1423,7 @@ void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *opti } } - if (!part.fixed) + if (!partFlags.fixed) part.x = newX; // set new position else part.vx = 0; // set speed to zero. note: particle can get speed in collisions, if unfixed, it should not speed away @@ -1432,15 +1433,15 @@ void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *opti // apply a force in x direction to individual particle (or source) // caller needs to provide a 8bit counter (for each paticle) that holds its value between calls // force is in 3.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame -void ParticleSystem1D::applyForce(PSparticle1D *part, int8_t xforce, uint8_t *counter) { +void ParticleSystem1D::applyForce(PSparticle1D &part, const int8_t xforce, uint8_t &counter) { int32_t dv = calcForce_dv(xforce, counter); // velocity increase - part->vx = limitSpeed((int32_t)part->vx + dv); // apply the force to particle + part.vx = limitSpeed((int32_t)part.vx + dv); // apply the force to particle } // apply a force to all particles // force is in 3.4 fixed point notation (see above) -void ParticleSystem1D::applyForce(int8_t xforce) { - int32_t dv = calcForce_dv(xforce, &forcecounter); // velocity increase +void ParticleSystem1D::applyForce(const int8_t xforce) { + int32_t dv = calcForce_dv(xforce, forcecounter); // velocity increase for (uint32_t i = 0; i < usedParticles; i++) { particles[i].vx = limitSpeed((int32_t)particles[i].vx + dv); } @@ -1449,10 +1450,10 @@ void ParticleSystem1D::applyForce(int8_t xforce) { // apply gravity to all particles using PS global gforce setting // gforce is in 3.4 fixed point notation, see note above void ParticleSystem1D::applyGravity() { - int32_t dv_raw = calcForce_dv(gforce, &gforcecounter); + int32_t dv_raw = calcForce_dv(gforce, gforcecounter); for (uint32_t i = 0; i < usedParticles; i++) { int32_t dv = dv_raw; - if (particles[i].reversegrav) dv = -dv_raw; + if (particleFlags[i].reversegrav) dv = -dv_raw; // note: not checking if particle is dead is omitted as most are usually alive and if few are alive, rendering is fast anyways particles[i].vx = limitSpeed((int32_t)particles[i].vx - dv); } @@ -1460,12 +1461,12 @@ void ParticleSystem1D::applyGravity() { // 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 ParticleSystem1D::applyGravity(PSparticle1D *part) { +void ParticleSystem1D::applyGravity(PSparticle1D &part, bool reverse) { uint32_t counterbkp = gforcecounter; - int32_t dv = calcForce_dv(gforce, &gforcecounter); - if (part->reversegrav) dv = -dv; + int32_t dv = calcForce_dv(gforce, gforcecounter); + if (reverse) dv = -dv; gforcecounter = counterbkp; //save it back - part->vx = limitSpeed((int32_t)part->vx - dv); + part.vx = limitSpeed((int32_t)part.vx - dv); } @@ -1524,7 +1525,7 @@ void ParticleSystem1D::ParticleSys_render() { bool wrap = particlesettings.wrap; // local copy for speed // go over particles and render them to the buffer for (uint32_t i = 0; i < usedParticles; i++) { - if (particles[i].outofbounds || particles[i].ttl == 0) + if ( particles[i].ttl == 0 || particleFlags[i].outofbounds) continue; // generate RGB values for particle @@ -1543,7 +1544,7 @@ void ParticleSystem1D::ParticleSys_render() { // apply smear-blur to rendered frame if(globalSmear > 0) { if (framebuffer) - blur1D(framebuffer, maxXpixel + 1, globalSmear); + blur1D(framebuffer, maxXpixel + 1, globalSmear, 0); else SEGMENT.blur(globalSmear, true); } @@ -1676,7 +1677,7 @@ void ParticleSystem1D::handleCollisions() { // fill the binIndices array for this bin for (uint32_t i = collisionStartIdx; i < usedParticles; i++) { - if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) { // colliding particle + if (particles[i].ttl > 0 && particleFlags[i].outofbounds == 0 && particleFlags[i].collide) { // colliding particle if (particles[i].x >= binStart && particles[i].x <= binEnd) { // >= and <= to include particles on the edge of the bin (overlap to ensure boarder particles collide with adjacent bins) if (binParticleCount >= maxBinParticles) { // bin is full, more particles in this bin so do the rest next frame nextFrameStartIdx = i; // bin overflow can only happen once as bin size is at least half of the particles (or half +1) @@ -1700,7 +1701,7 @@ void ParticleSystem1D::handleCollisions() { if (dv >= proximity) // particles would go past each other in next move update proximity += abs(dv); // add speed difference to catch fast particles if (dx < proximity && dx > -proximity) { // check if close - collideParticles(&particles[idx_i], &particles[idx_j], dx, dv, collisiondistance); + collideParticles(particles[idx_i], particleFlags[idx_i], particles[idx_j], particleFlags[idx_j], dx, dv, collisiondistance); } } } @@ -1709,26 +1710,26 @@ void ParticleSystem1D::handleCollisions() { // 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 ParticleSystem1D::collideParticles(PSparticle1D *particle1, PSparticle1D *particle2, int32_t dx, int32_t relativeVx, uint32_t collisiondistance) { +void ParticleSystem1D::collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, int32_t dx, int32_t relativeVx, uint32_t collisiondistance) { int32_t dotProduct = (dx * relativeVx); // is always negative if moving towards each other if (dotProduct < 0) { // particles are moving towards each other uint32_t surfacehardness = max(collisionHardness, (int32_t)PS_P_MINSURFACEHARDNESS_1D); // if particles are soft, the impulse must stay above a limit or collisions slip through // TODO: if soft collisions are not needed, the above line can be done in set hardness function and skipped here (which is what it currently looks like) // Calculate new velocities after collision int32_t impulse = relativeVx * surfacehardness / 255; - particle1->vx += impulse; - particle2->vx -= impulse; + particle1.vx += impulse; + particle2.vx -= impulse; // if one of the particles is fixed, transfer the impulse back so it bounces - if (particle1->fixed) - particle2->vx = -particle1->vx; - else if (particle2->fixed) - particle1->vx = -particle2->vx; + if (particle1flags.fixed) + particle2.vx = -particle1.vx; + else if (particle2flags.fixed) + particle1.vx = -particle2.vx; if (collisionHardness < PS_P_MINSURFACEHARDNESS_1D) { // if particles are soft, they become 'sticky' i.e. apply some friction (they do pile more nicely and correctly) const uint32_t coeff = collisionHardness + (255 - PS_P_MINSURFACEHARDNESS_1D); - particle1->vx = ((int32_t)particle1->vx * coeff) / 255; - particle2->vx = ((int32_t)particle2->vx * coeff) / 255; + particle1.vx = ((int32_t)particle1.vx * coeff) / 255; + particle2.vx = ((int32_t)particle2.vx * coeff) / 255; } } @@ -1741,20 +1742,20 @@ void ParticleSystem1D::collideParticles(PSparticle1D *particle1, PSparticle1D *p //int32_t pushamount = collisiondistance - distance; if (particlesettings.useGravity) { //using gravity, push the 'upper' particle only if (dx < 0) { // particle2.x < particle1.x - if (particle2->reversegrav && !particle2->fixed) { - particle2->x -= pushamount; - particle2->vx--; - } else if (!particle1->reversegrav && !particle1->fixed) { - particle1->x += pushamount; - particle1->vx++; + if (particle2flags.reversegrav && !particle2flags.fixed) { + particle2.x -= pushamount; + particle2.vx--; + } else if (!particle1flags.reversegrav && !particle1flags.fixed) { + particle1.x += pushamount; + particle1.vx++; } } else { - if (particle1->reversegrav && !particle1->fixed) { - particle1->x -= pushamount; - particle1->vx--; - } else if (!particle2->reversegrav && !particle2->fixed) { - particle2->x += pushamount; - particle2->vx++; + if (particle1flags.reversegrav && !particle1flags.fixed) { + particle1.x -= pushamount; + particle1.vx--; + } else if (!particle2flags.reversegrav && !particle2flags.fixed) { + particle2.x += pushamount; + particle2.vx++; } } } @@ -1762,8 +1763,8 @@ void ParticleSystem1D::collideParticles(PSparticle1D *particle1, PSparticle1D *p pushamount = 1; if (dx < 0) // particle2.x < particle1.x pushamount = -1; - particle1->vx -= pushamount; - particle2->vx += pushamount; + particle1.vx -= pushamount; + particle2.vx += pushamount; } } } @@ -1793,7 +1794,8 @@ void ParticleSystem1D::updatePSpointers(bool isadvanced) { // memory manager needs to know how many particles the FX wants to use so transitions can be handled properly (i.e. pointer will stop changing if enough particles are available during transitions) uint32_t usedByFX = (numParticles * ((uint32_t)fractionOfParticlesUsed + 1)) >> 8; // final number of particles the FX wants to use (fractionOfParticlesUsed is 0-255) particles = reinterpret_cast(particleMemoryManager(0, sizeof(PSparticle1D), availableParticles, usedByFX, effectID)); // get memory, leave buffer size as is (request 0) - sources = reinterpret_cast(this + 1); // pointer to source(s) + particleFlags = reinterpret_cast(this + 1); // pointer to particle flags + sources = reinterpret_cast(particleFlags + numParticles); // pointer to source(s) PSdataEnd = reinterpret_cast(sources + numSources); // pointer to first available byte after the PS for FX additional data if (isadvanced) { advPartProps = reinterpret_cast(sources + numSources); @@ -1811,7 +1813,7 @@ void ParticleSystem1D::updatePSpointers(bool isadvanced) { } //non class functions to use for initialization, fraction is uint8_t: 255 means 100% -uint32_t calculateNumberOfParticles1D(uint32_t fraction, bool isadvanced) { +uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced) { uint32_t numberofParticles = SEGMENT.virtualLength(); // one particle per pixel (if possible) #ifdef ESP8266 uint32_t particlelimit = ESP8266_MAXPARTICLES_1D; // maximum number of paticles allowed @@ -1830,7 +1832,7 @@ uint32_t calculateNumberOfParticles1D(uint32_t fraction, bool isadvanced) { return numberofParticles; } -uint32_t calculateNumberOfSources1D(uint32_t requestedsources) { +uint32_t calculateNumberOfSources1D(const uint32_t requestedsources) { #ifdef ESP8266 int numberofSources = max(1, min((int)requestedsources,ESP8266_MAXSOURCES_1D)); // limit to 1 - 8 #elif ARDUINO_ARCH_ESP32S2 @@ -1844,12 +1846,13 @@ uint32_t calculateNumberOfSources1D(uint32_t requestedsources) { } //allocate memory for particle system class, particles, sprays plus additional memory requested by FX -bool allocateParticleSystemMemory1D(uint32_t numparticles, uint32_t numsources, bool isadvanced, uint32_t additionalbytes) { +bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t numsources, const bool isadvanced, const uint32_t additionalbytes) { uint32_t requiredmemory = sizeof(ParticleSystem1D); uint32_t dummy; // dummy variable if(particleMemoryManager(numparticles, sizeof(PSparticle1D), dummy, dummy, SEGMENT.mode) == nullptr) // allocate memory for particles return false; // not enough memory, function ensures a minimum of numparticles are avialable // functions above make sure these are a multiple of 4 bytes (to avoid alignment issues) + requiredmemory += sizeof(PSparticleFlags1D) * numparticles; if (isadvanced) requiredmemory += sizeof(PSadvancedParticle1D) * numparticles; requiredmemory += sizeof(PSsource1D) * numsources; @@ -1859,7 +1862,7 @@ bool allocateParticleSystemMemory1D(uint32_t numparticles, uint32_t numsources, // initialize Particle System, allocate additional bytes if needed (pointer to those bytes can be read from particle system class: PSdataEnd) // note: percentofparticles is in uint8_t, for example 191 means 75%, (deafaults to 255 or 100% meaning one particle per pixel), can be more than 100% (but not recommended, can cause out of memory) -bool initParticleSystem1D(ParticleSystem1D *&PartSys, uint32_t requestedsources, uint8_t fractionofparticles, uint32_t additionalbytes, bool advanced) { +bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedsources, const uint8_t fractionofparticles, const uint32_t additionalbytes, const bool advanced) { if (SEGLEN == 1) return false; // single pixel not supported updateRenderingBuffer(SEGMENT.vLength(), true, true); // update/create frame rendering buffer if(advanced) @@ -1903,7 +1906,7 @@ void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start) // 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 -static int32_t calcForce_dv(int8_t force, uint8_t* counter) { +static int32_t calcForce_dv(const int8_t force, uint8_t &counter) { if (force == 0) return 0; // for small forces, need to use a delay counter @@ -1911,9 +1914,9 @@ static int32_t calcForce_dv(int8_t force, uint8_t* counter) { int32_t dv = 0; // 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; + 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) } } @@ -1924,13 +1927,14 @@ static int32_t calcForce_dv(int8_t force, uint8_t* counter) { } // limit speed to prevent overflows +//TODO: inline this function? check if that uses a lot more flash. static int32_t limitSpeed(int32_t speed) { return min((int32_t)PS_P_MAXSPEED, max((int32_t)-PS_P_MAXSPEED, speed)); //return speed > PS_P_MAXSPEED ? PS_P_MAXSPEED : (speed < -PS_P_MAXSPEED ? -PS_P_MAXSPEED : speed); // note: this uses more code, not sure due to speed or inlining } // check if particle is out of bounds and wrap it around if required, returns false if out of bounds -static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, bool wrap) { +static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, const bool wrap) { if ((uint32_t)position > (uint32_t)max) { // check if particle reached an edge, cast to uint32_t to save negative checking (max is always positive) if (wrap) { position = position % (max + 1); // note: cannot optimize modulo, particles can be far out of bounds when wrap is enabled @@ -2153,13 +2157,9 @@ void particleHandover(void *buffer, size_t structSize, int32_t numToTransfer) { if (structSize == sizeof(PSparticle)) { // 2D particle PSparticle *particles = (PSparticle *)buffer; for (int32_t i = 0; i < numToTransfer; i++) { - particles[i].perpetual = false; // particle ages - if (particles[i].outofbounds) - particles[i].ttl = 0; // kill out of bounds - else if (particles[i].ttl > 200) + if (particles[i].ttl > 200) particles[i].ttl = 150 + hw_random16(50); // reduce TTL so it will die soon particles[i].sat = 255; // full saturation - particles[i].collide = true; // enable collisions (in case new FX uses them) } } else // 1D particle system @@ -2168,11 +2168,7 @@ void particleHandover(void *buffer, size_t structSize, int32_t numToTransfer) { #ifndef WLED_DISABLE_PARTICLESYSTEM1D PSparticle1D *particles = (PSparticle1D *)buffer; for (int32_t i = 0; i < numToTransfer; i++) { - particles[i].perpetual = false; // particle ages - particles[i].fixed = false; // unfix all particles - if (particles[i].outofbounds) - particles[i].ttl = 0; // kill out of bounds - else if (particles[i].ttl > 200) + if (particles[i].ttl > 200) particles[i].ttl = 150 + hw_random16(50); // reduce TTL so it will die soon } #endif diff --git a/wled00/FXparticleSystem.h b/wled00/FXparticleSystem.h index ff7f282be9..acf48504b5 100644 --- a/wled00/FXparticleSystem.h +++ b/wled00/FXparticleSystem.h @@ -16,7 +16,7 @@ #define PS_P_MAXSPEED 120 // maximum speed a particle can have (vx/vy is int8) #define MAX_MEMIDLE 10 // max idle time (in frames) before memory is deallocated (if deallocated during an effect, it will crash!) -//#define WLED_DEBUG_PS +#define WLED_DEBUG_PS #ifdef WLED_DEBUG_PS #define PSPRINT(x) Serial.print(x) @@ -66,35 +66,44 @@ void servicePSmem(); // increments watchdog, frees memory if idle too long // struct for PS settings (shared for 1D and 2D class) typedef union { - struct{ - // one byte bit field for 2D settings - 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 useCollisions : 1; - bool colorByAge : 1; // if set, particle hue is set by ttl value in render function + struct{ // one byte bit field for 2D settings + 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 useCollisions : 1; + bool colorByAge : 1; // if set, particle hue is set by ttl value in render function }; byte asByte; // access as a byte, order is: LSB is first entry in the list above } PSsettings2D; //struct for a single particle -typedef struct { // 12 bytes - 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 - uint16_t ttl; // time to live in frames - //uint16_t ttl : 12; // time to live, 12 bit or 4095 max (which is 50s at 80FPS) +typedef struct { // 10 bytes + int16_t x; // x position in particle system + int16_t y; // y position in particle system + uint16_t ttl; // time to live in frames + int8_t vx; // horizontal velocity + int8_t vy; // vertical velocity + uint8_t hue; // color hue + uint8_t sat; // particle color saturation +} PSparticle; + +//struct for particle flags note: this is separate from the particle struct to save memory (ram alignment) +typedef union { + struct { // 1 byte 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 state : 1; //can be used by FX to track state, not used in PS -} PSparticle; + bool custom1 : 1; // unused custom flags, can be used by FX to track particle states + bool custom2 : 1; + bool custom3 : 1; + bool custom4 : 1; + bool custom5 : 1; + }; + byte asByte; // access as a byte, order is: LSB is first entry in the list above +} PSparticleFlags; // struct for additional particle settings (option) typedef struct { // 2 bytes @@ -125,6 +134,7 @@ typedef struct { 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) + PSparticleFlags sourceFlags; // flags for the source particle int8_t var; // variation of emitted speed (adds random(+/- var) to speed) int8_t vx; // emitting speed int8_t vy; @@ -134,49 +144,49 @@ typedef struct { // class uses approximately 60 bytes class ParticleSystem2D { public: - ParticleSystem2D(uint32_t width, uint32_t height, uint32_t numberofparticles, uint32_t numberofsources, bool isadvanced = false, bool sizecontrol = false); // constructor + ParticleSystem2D(const uint32_t width, const uint32_t height, const uint32_t numberofparticles, const uint32_t numberofsources, const bool isadvanced = false, const bool sizecontrol = false); // constructor // note: memory is allcated in the FX function, no deconstructor needed void update(void); //update the particles according to set options and render to the matrix - void updateFire(uint32_t intensity, bool renderonly = false); // update function for fire, if renderonly is set, particles are not updated (required to fix transitions with frameskips) + void updateFire(const uint8_t intensity, const bool renderonly); // update function for fire, if renderonly is set, particles are not updated (required to fix transitions with frameskips) void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions - void particleMoveUpdate(PSparticle &part, PSsettings2D *options = NULL, PSadvancedParticle *advancedproperties = NULL); // move function + void particleMoveUpdate(PSparticle &part, PSparticleFlags &partFlags, PSsettings2D *options = NULL, PSadvancedParticle *advancedproperties = NULL); // move function // particle emitters - int32_t sprayEmit(PSsource &emitter, uint32_t amount = 1); - void flameEmit(PSsource &emitter); - int32_t angleEmit(PSsource& emitter, uint16_t angle, int32_t speed, uint32_t amount = 1); + int32_t sprayEmit(const PSsource &emitter); + void flameEmit(const PSsource &emitter); + int32_t angleEmit(PSsource& emitter, const uint16_t angle, const int32_t speed); //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 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 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); + void applyForce(PSparticle &part, const int8_t xforce, const int8_t yforce, uint8_t &counter); + void applyForce(const uint32_t particleindex, const int8_t xforce, const int8_t yforce); // use this for advanced property particles + void applyForce(const int8_t xforce, const int8_t yforce); // apply a force to all particles + void applyAngleForce(PSparticle &part, const int8_t force, const uint16_t angle, uint8_t &counter); + void applyAngleForce(const uint32_t particleindex, const int8_t force, const uint16_t angle); // use this for advanced property particles + void applyAngleForce(const int8_t force, const uint16_t angle); // apply angular force to all particles + void applyFriction(PSparticle &part, const int32_t coefficient); // apply friction to specific particle + void applyFriction(const int32_t coefficient); // apply friction to all used particles + void pointAttractor(const uint32_t particleindex, PSparticle &attractor, const uint8_t strength, const bool swallow); // set options - void setUsedParticles(uint8_t percentage); // set the percentage of particles used in the system, 255=100% + void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100% inline uint32_t getAvailableParticles(void) { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init - 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 setColorByAge(bool enable); - void setMotionBlur(uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero - void setSmearBlur(uint8_t bluramount); // enable 2D smeared blurring of full frame - void setParticleSize(uint8_t size); - void setGravity(int8_t force = 8); - void enableParticleCollisions(bool enable, uint8_t hardness = 255); + void setCollisionHardness(const uint8_t hardness); // hardness for particle collisions (255 means full hard) + void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set + void setWallRoughness(const uint8_t roughness); // wall roughness randomizes wall collisions + void setMatrixSize(const uint32_t x, const uint32_t y); + void setWrapX(const bool enable); + void setWrapY(const bool enable); + void setBounceX(const bool enable); + void setBounceY(const bool enable); + void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die + void setSaturation(const uint8_t sat); // set global color saturation + void setColorByAge(const bool enable); + void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero + void setSmearBlur(const uint8_t bluramount); // enable 2D smeared blurring of full frame + void setParticleSize(const uint8_t size); + void setGravity(const int8_t force = 8); + void enableParticleCollisions(const bool enable, const uint8_t hardness = 255); PSparticle *particles; // pointer to particle array + PSparticleFlags *particleFlags; // pointer to particle flags 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) @@ -189,30 +199,30 @@ class ParticleSystem2D { private: //rendering functions - void ParticleSys_render(bool firemode = false, uint32_t fireintensity = 128); + void ParticleSys_render(); void renderParticle(const uint32_t particleindex, const uint32_t brightness, const CRGB& color, const bool wrapX, const bool wrapY); //paricle physics applied by system if flags are set void applyGravity(); // applies gravity to all particles void handleCollisions(); - void collideParticles(PSparticle *particle1, PSparticle *particle2, int32_t dx, int32_t dy); + void collideParticles(PSparticle &particle1, PSparticle &particle2, const int32_t dx, const int32_t dy); void fireParticleupdate(); //utility functions - void updatePSpointers(bool isadvanced, bool sizecontrol); // update the data pointers to current segment data space + void updatePSpointers(const bool isadvanced, const bool sizecontrol); // update the data pointers to current segment data space 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 - int16_t wraparound(uint16_t p, uint32_t maxvalue); + void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall // note: variables that are accessed often are 32bit for speed PSsettings2D particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above uint32_t numParticles; // total number of particles allocated by this system note: during transitions, less are available, use availableParticles uint32_t availableParticles; // number of particles available for use (can be more or less than numParticles, assigned by memory manager) - uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster int32_t collisionHardness; uint32_t wallHardness; - uint32_t wallRoughness; // randomizes wall collisions - uint16_t collisionStartIdx; // particle array start index for collision detection + uint32_t wallRoughness; // randomizes wall collisions uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed) + uint16_t collisionStartIdx; // particle array start index for collision detection + uint8_t fireIntesity = 0; // fire intensity, used for fire mode (flash use optimization, better than passing an argument to render function) + uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates uint8_t forcecounter; // counter for globally applied forces uint8_t gforcecounter; // counter for global gravity int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards) @@ -223,12 +233,12 @@ class ParticleSystem2D { uint8_t effectID; // ID of the effect that is using this particle system, used for transitions }; -void blur2D(CRGB *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, uint32_t xstart = 0, uint32_t ystart = 0, bool isparticle = false); +void blur2D(CRGB *colorbuffer, const uint32_t xsize, uint32_t ysize, const uint32_t xblur, const uint32_t yblur, const uint32_t xstart = 0, uint32_t ystart = 0, const bool isparticle = false); // initialization functions (not part of class) -bool initParticleSystem2D(ParticleSystem2D *&PartSys, uint32_t requestedsources, uint32_t additionalbytes = 0, bool advanced = false, bool sizecontrol = false); -uint32_t calculateNumberOfParticles2D(uint32_t pixels, bool advanced, bool sizecontrol); -uint32_t calculateNumberOfSources2D(uint32_t pixels, uint32_t requestedsources); -bool allocateParticleSystemMemory2D(uint32_t numparticles, uint32_t numsources, bool advanced, bool sizecontrol, uint32_t additionalbytes); +bool initParticleSystem2D(ParticleSystem2D *&PartSys, const uint32_t requestedsources, const uint32_t additionalbytes = 0, const bool advanced = false, const bool sizecontrol = false); +uint32_t calculateNumberOfParticles2D(const uint32_t pixels, const bool advanced, const bool sizecontrol); +uint32_t calculateNumberOfSources2D(const uint32_t pixels, const uint32_t requestedsources); +bool allocateParticleSystemMemory2D(const uint32_t numparticles, const uint32_t numsources, const bool advanced, const bool sizecontrol, const uint32_t additionalbytes); #endif // WLED_DISABLE_PARTICLESYSTEM2D //////////////////////// @@ -269,20 +279,26 @@ typedef union { //struct for a single particle (8 bytes) typedef struct { - int32_t x; // x position in particle system - uint16_t ttl; // time to live in frames - int8_t vx; // horizontal velocity - uint8_t hue; // color hue - // two byte bit field: - //uint16_t ttl : 11; // time to live, 11 bit or 2047 max (which is 25s at 80FPS) + int32_t x; // x position in particle system + uint16_t ttl; // time to live in frames + int8_t vx; // horizontal velocity + uint8_t hue; // color hue +} PSparticle1D; + +//struct for particle flags +typedef union { + struct { // 1 byte 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 reversegrav : 1; // if set, gravity is reversed on this particle bool fixed : 1; // if set, particle does not move (and collisions make other particles revert direction), - // note: there is on byte of padding added here, making TTL a 16bit variable saves 500bytes of flash so much faster than a bit field - // TODO: can this be optimized? wastes a lot of ram... -> yes, TODO: make the flags a seperate struct array and handle it everywhere. -} PSparticle1D; + bool custom1 : 1; // unused custom flags, can be used by FX to track particle states + bool custom2 : 1; + bool custom3 : 1; + }; + byte asByte; // access as a byte, order is: LSB is first entry in the list above +} PSparticleFlags1D; // struct for additional particle settings (optional) typedef struct { @@ -296,46 +312,50 @@ typedef struct { uint16_t minLife; // minimum ttl of emittet particles uint16_t maxLife; // maximum ttl of emitted particles PSparticle1D source; // use a particle as the emitter source (speed, position, color) + PSparticleFlags1D sourceFlags; // flags for the source particle int8_t var; // variation of emitted speed (adds random(+/- var) to speed) int8_t v; // emitting speed uint8_t sat; // color saturation (advanced property) uint8_t size; // particle size (advanced property) } PSsource1D; - +//TODO: 1D function cleanup: add const where possible, replace pointers with reference where possible +//TODO: match all functions with declarations in header file for consistency +// TODO: make all 1-line function inline (check first if this makes code larger or smaller with one) class ParticleSystem1D { public: - ParticleSystem1D(uint32_t length, uint32_t numberofparticles, uint32_t numberofsources, bool isadvanced = false); // constructor + ParticleSystem1D(const uint32_t length, const uint32_t numberofparticles, const uint32_t numberofsources, const bool isadvanced = false); // constructor // note: memory is allcated in the FX function, no deconstructor needed void update(void); //update the particles according to set options and render to the matrix void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions // particle emitters - int32_t sprayEmit(PSsource1D &emitter); - void particleMoveUpdate(PSparticle1D &part, PSsettings1D *options = NULL, PSadvancedParticle1D *advancedproperties = NULL); // move function + int32_t sprayEmit(const PSsource1D &emitter); + void particleMoveUpdate(PSparticle1D &part, PSparticleFlags1D &partFlags, PSsettings1D *options = NULL, PSadvancedParticle1D *advancedproperties = NULL); // move function //particle physics - void applyForce(PSparticle1D *part, int8_t xforce, uint8_t *counter); //apply a force to a single particle - void applyForce(int8_t xforce); // apply a force to all particles - void applyGravity(PSparticle1D *part); // applies gravity to single particle (use this for sources) - void applyFriction(int32_t coefficient); // apply friction to all used particles + void applyForce(PSparticle1D &part, const int8_t xforce, uint8_t &counter); //apply a force to a single particle + void applyForce(const int8_t xforce); // apply a force to all particles + void applyGravity(PSparticle1D &part,const bool reverse); // applies gravity to single particle (use this for sources) + void applyFriction(const int32_t coefficient); // apply friction to all used particles // set options - void setUsedParticles(uint8_t percentage); // set the percentage of particles used in the system, 255=100% + void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100% inline uint32_t getAvailableParticles(void) { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init - void setWallHardness(uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set - void setSize(uint16_t x); //set particle system size (= strip length) - void setWrap(bool enable); - void setBounce(bool enable); - void setKillOutOfBounds(bool enable); // if enabled, particles outside of matrix instantly die + void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set + void setSize(const uint32_t x); //set particle system size (= strip length) + void setWrap(const bool enable); + void setBounce(const bool enable); + void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die // void setSaturation(uint8_t sat); // set global color saturation - void setColorByAge(bool enable); - void setColorByPosition(bool enable); - void setMotionBlur(uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero - void setSmearBlur(uint8_t bluramount); // enable 1D smeared blurring of full frame - void setParticleSize(uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled by advanced particle size + void setColorByAge(const bool enable); + void setColorByPosition(const bool enable); + void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero + void setSmearBlur(const uint8_t bluramount); // enable 1D smeared blurring of full frame + void setParticleSize(const uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled by advanced particle size void setGravity(int8_t force = 8); - void enableParticleCollisions(bool enable, uint8_t hardness = 255); + void enableParticleCollisions(bool enable, const uint8_t hardness = 255); PSparticle1D *particles; // pointer to particle array + PSparticleFlags1D *particleFlags; // pointer to particle flags array PSsource1D *sources; // pointer to sources PSadvancedParticle1D *advPartProps; // pointer to advanced particle properties (can be NULL) //PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL) @@ -353,12 +373,12 @@ class ParticleSystem1D //paricle physics applied by system if flags are set void applyGravity(); // applies gravity to all particles void handleCollisions(); - void collideParticles(PSparticle1D *particle1, PSparticle1D *particle2, int32_t dx, int32_t relativeV, uint32_t collisiondistance); + void collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, int32_t dx, int32_t relativeVx, uint32_t collisiondistance); //utility functions - void updatePSpointers(bool isadvanced); // update the data pointers to current segment data space + void updatePSpointers(const bool isadvanced); // update the data pointers to current segment data space //void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control - 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, const uint32_t maxposition); // bounce on a wall // note: variables that are accessed often are 32bit for speed PSsettings1D particlesettings; // settings used when updating particles uint32_t numParticles; // total number of particles allocated by this system note: never use more than this, even if more are available (only this many advanced particles are allocated) @@ -379,9 +399,9 @@ class ParticleSystem1D uint8_t effectID; // ID of the effect that is using this particle system, used for transitions }; -bool initParticleSystem1D(ParticleSystem1D *&PartSys, uint32_t requestedsources, uint8_t fractionofparticles = 255, uint32_t additionalbytes = 0, bool advanced = false); -uint32_t calculateNumberOfParticles1D(uint32_t fraction, bool isadvanced); -uint32_t calculateNumberOfSources1D(uint32_t requestedsources); -bool allocateParticleSystemMemory1D(uint32_t numparticles, uint32_t numsources, bool isadvanced, uint32_t additionalbytes); -void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start = 0); +bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedsources, const uint8_t fractionofparticles = 255, const uint32_t additionalbytes = 0, const bool advanced = false); +uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced); +uint32_t calculateNumberOfSources1D(const uint32_t requestedsources); +bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t numsources, const bool isadvanced, const uint32_t additionalbytes); +void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start); #endif // WLED_DISABLE_PARTICLESYSTEM1D