diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a6d965c3d5..0be46bcc3d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7890,7 +7890,7 @@ uint16_t mode_particlerotatingspray(void) const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - const uint16_t numParticles = 400; + const uint32_t numParticles = 400; const uint8_t numSprays = 8; // maximum number of sprays PSparticle *particles; @@ -7906,8 +7906,8 @@ uint16_t mode_particlerotatingspray(void) // calculate the end of the spray data and assign it as the data pointer for the particles: particles = reinterpret_cast(spray + numSprays); // cast the data array into a particle pointer - uint16_t i = 0; - uint16_t j = 0; + uint32_t i = 0; + uint32_t j = 0; uint8_t spraycount = 1 + (SEGMENT.custom2 >> 5); // number of sprays to display, 1-8 if (SEGMENT.call == 0) // initialization @@ -8008,10 +8008,10 @@ uint16_t mode_particlefireworks(void) const uint16_t PS_MAX_Y(rows * PS_P_RADIUS - 1); #ifdef ESP8266 - const uint16_t numParticles = 250; + const uint32_t numParticles = 250; const uint8_t MaxNumRockets = 4; #else - const uint16_t numParticles = 650; + const uint32_t numParticles = 650; const uint8_t MaxNumRockets = 8; #endif @@ -8032,8 +8032,8 @@ uint16_t mode_particlefireworks(void) // calculate the end of the spray data and assign it as the data pointer for the particles: particles = reinterpret_cast(rockets + MaxNumRockets); // cast the data array into a particle pointer - uint16_t i = 0; - uint16_t j = 0; + uint32_t i = 0; + uint32_t j = 0; uint8_t numRockets = 1+ ((SEGMENT.custom3) >> 2); //1 to 8 if (SEGMENT.call == 0) // initialization @@ -8185,7 +8185,7 @@ uint16_t mode_particlespray(void) const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - const uint16_t numParticles = 450; + const uint32_t numParticles = 450; const uint8_t numSprays = 1; uint8_t percycle = numSprays; // maximum number of particles emitted per cycle @@ -8205,8 +8205,8 @@ uint16_t mode_particlespray(void) // calculate the end of the spray data and assign it as the data pointer for the particles: particles = reinterpret_cast(spray + numSprays); // cast the data array into a particle pointer - uint16_t i = 0; - uint16_t j = 0; + uint32_t i = 0; + uint32_t j = 0; if (SEGMENT.call == 0) // initialization { @@ -8330,8 +8330,8 @@ uint16_t mode_particlefire(void) const uint16_t PS_MAX_X(cols * PS_P_RADIUS - 1); const uint16_t PS_MAX_Y(rows * PS_P_RADIUS - 1); - const uint16_t numFlames = (cols << 1); // number of flames: depends on fire width. for a fire width of 16 pixels, about 25-30 flames give good results, add a few for the base flames - const uint16_t numParticles = numFlames * 25; + const uint32_t numFlames = (cols << 1); // number of flames: depends on fire width. for a fire width of 16 pixels, about 25-30 flames give good results, add a few for the base flames + const uint32_t numParticles = numFlames * 25; uint8_t percycle = numFlames >> 1; // maximum number of particles emitted per cycle PSparticle *particles; PSpointsource *flames; @@ -8353,7 +8353,7 @@ uint16_t mode_particlefire(void) // calculate the end of the spray data and assign it as the data pointer for the particles: particles = reinterpret_cast(flames + numFlames); // cast the data array into a particle pointer - uint16_t i; + uint32_t i; if (SEGMENT.call == 0) // initialization { @@ -8494,7 +8494,7 @@ uint16_t mode_particlefall(void) const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - const uint16_t numParticles = 500; + const uint32_t numParticles = 500; PSparticle *particles; @@ -8506,8 +8506,8 @@ uint16_t mode_particlefall(void) // calculate the end of the spray data and assign it as the data pointer for the particles: particles = reinterpret_cast(SEGENV.data); // cast the data array into a particle pointer - uint16_t i = 0; - uint16_t j = 0; + uint32_t i = 0; + uint32_t j = 0; if (SEGMENT.call == 0) // initialization { @@ -8582,7 +8582,7 @@ uint16_t mode_particlepile(void) const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - const uint16_t numParticles = 400; + const uint32_t numParticles = 400; const uint8_t numSprays = 1; uint8_t percycle = numSprays; // maximum number of particles emitted per cycle @@ -8599,8 +8599,8 @@ uint16_t mode_particlepile(void) // calculate the end of the spray data and assign it as the data pointer for the particles: particles = reinterpret_cast(spray + numSprays); // cast the data array into a particle pointer - uint16_t i = 0; - uint16_t j = 0; + uint32_t i = 0; + uint32_t j = 0; if (SEGMENT.call == 0) // initialization { @@ -8711,7 +8711,7 @@ uint16_t mode_particlebox(void) const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - const uint16_t numParticles = 255; // maximum number of particles + const uint32_t numParticles = 255; // maximum number of particles PSparticle *particles; @@ -8721,8 +8721,8 @@ uint16_t mode_particlebox(void) return mode_static(); // allocation failed; //allocation failed particles = reinterpret_cast(SEGENV.data); // cast the data array into a particle pointer - uint16_t i = 0; - uint16_t j = 0; + uint32_t i = 0; + uint32_t j = 0; if (SEGMENT.call == 0) // initialization { @@ -8828,7 +8828,7 @@ uint16_t mode_particlebox(void) return FRAMETIME; } -static const char _data_FX_MODE_PARTICLEBOX[] PROGMEM = "Particle Box@Speed,Particles,Tilt strength,Bouncyness,,Rocking Boat,Fastcolors;;!;012;pal=9,sx=100,ix=82,c1=190,c2=210,o1=0"; +static const char _data_FX_MODE_PARTICLEBOX[] PROGMEM = "Particle Box@Speed,Particles,Tilt strength,Bouncyness,,Rocking Boat,Fastcolors;;!;012;pal=1,sx=100,ix=82,c1=190,c2=210,o1=0"; /* perlin noise 'gravity' mapping as in particles on noise hills viewed from above @@ -8847,7 +8847,7 @@ uint16_t mode_particleperlin(void) const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - const uint16_t numParticles = 255; + const uint32_t numParticles = 255; PSparticle *particles; @@ -8857,8 +8857,8 @@ uint16_t mode_particleperlin(void) return mode_static(); // allocation failed; //allocation failed particles = reinterpret_cast(SEGENV.data); // cast the data array into a particle pointer - uint16_t i = 0; - uint16_t j = 0; + uint32_t i = 0; + uint32_t j = 0; if (SEGMENT.call == 0) // initialization { @@ -8872,7 +8872,7 @@ uint16_t mode_particleperlin(void) } } - uint16_t displayparticles = SEGMENT.intensity; + uint32_t displayparticles = SEGMENT.intensity; // apply 'gravity' from a 2D perlin noise map SEGMENT.aux0 += SEGMENT.speed >> 4; // noise z-position; @@ -8940,10 +8940,10 @@ uint16_t mode_particleimpact(void) const uint16_t PS_MAX_Y(rows * PS_P_RADIUS - 1); #ifdef ESP8266 - const uint16_t numParticles = 250; - const uint8_t MaxNumRockets = 4; + const uint32_t numParticles = 250; + const uint8_t MaxNumMeteors = 4; #else - const uint16_t numParticles = 550; + const uint32_t numParticles = 550; const uint8_t MaxNumMeteors = 8; #endif @@ -8960,8 +8960,8 @@ uint16_t mode_particleimpact(void) // calculate the end of the spray data and assign it as the data pointer for the particles: particles = reinterpret_cast(meteors + MaxNumMeteors); // cast the data array into a particle pointer - uint16_t i = 0; - uint16_t j = 0; + uint32_t i = 0; + uint32_t j = 0; uint8_t numMeteors = map(SEGMENT.custom3, 0, 31, 1, MaxNumMeteors); // number of meteors to use for animation if (SEGMENT.call == 0) // initialization @@ -8981,7 +8981,7 @@ uint16_t mode_particleimpact(void) // update particles, create particles // check each rocket's state and emit particles according to its state: moving up = emit exhaust, at top = explode; falling down = standby time - uint16_t emitparticles; // number of particles to emit for each rocket's state + uint32_t emitparticles; // number of particles to emit for each rocket's state i = 0; for (j = 0; j < numMeteors; j++) { @@ -9031,7 +9031,7 @@ uint16_t mode_particleimpact(void) } // update the meteors, set the speed state - for (i = 0; i < numMeteors; i++) + for (i = 0; i < numMeteors; i++) { Serial.print(meteors[i].source.vy); Serial.print(" "); @@ -9093,7 +9093,7 @@ uint16_t mode_particleattractor(void) const uint16_t PS_MAX_X(cols * PS_P_RADIUS - 1); const uint16_t PS_MAX_Y(rows * PS_P_RADIUS - 1); - const uint16_t numParticles = 265; // maximum number of particles + const uint32_t numParticles = 265; // maximum number of particles PSparticle *particles; PSparticle *attractor; @@ -9114,8 +9114,8 @@ uint16_t mode_particleattractor(void) spray = reinterpret_cast(attractor + 1); counters = reinterpret_cast(spray + 1); - uint16_t i = 0; - uint16_t j = 0; + uint32_t i = 0; + uint32_t j = 0; if (SEGMENT.call == 0) // initialization { diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index 46e85877ea..5112fa908e 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -26,6 +26,12 @@ */ +/* +Note: on ESP32 using 32bit integer is faster than 16bit or 8bit, each operation takes on less instruction, can be testen on https://godbolt.org/ + it does not matter if using int, unsigned int, uint32_t or int32_t, the compiler will make int into 32bit + this should be used to optimize speed but not if memory is affected much +*/ + #include "FXparticleSystem.h" #include "wled.h" #include "FastLED.h" @@ -60,20 +66,14 @@ void Emitter_Angle_emit(PSpointsource *emitter, PSparticle *part, uint8_t angle, { emitter->vx = (((int16_t)cos8(angle)-127) * speed) >> 7; //cos is signed 8bit, so 1 is 127, -1 is -127, shift by 7 emitter->vy = (((int16_t)sin8(angle)-127) * speed) >> 7; - Serial.print(angle); - Serial.print(" "); - Serial.print(emitter->vx); - Serial.print(" "); - Serial.print(emitter->vy); - Serial.print(" "); Emitter_Fountain_emit(emitter, part); } // attracts a particle to an attractor particle using the inverse square-law void Particle_attractor(PSparticle *particle, PSparticle *attractor, uint8_t *counter, uint8_t strength, bool swallow) // todo: add a parameter 'swallow' so the attractor can 'suck up' particles that are very close, also could use hue of attractor particle for strength { // Calculate the distance between the particle and the attractor - int16_t dx = attractor->x - particle->x; - int16_t dy = attractor->y - particle->y; + int dx = attractor->x - particle->x; + int dy = attractor->y - particle->y; // Calculate the force based on inverse square law int32_t distanceSquared = dx * dx + dy * dy + 1; @@ -84,7 +84,7 @@ void Particle_attractor(PSparticle *particle, PSparticle *attractor, uint8_t *co particle->ttl = 0; return; } - distanceSquared = 4096; // limit the distance to 64 (=size of a particle) to avoid very high forces.TODO: could make this depending on the #define for particle size + distanceSquared = PS_P_RADIUS * PS_P_RADIUS + PS_P_RADIUS * PS_P_RADIUS; // limit the distance of particle size to avoid very high forces } // check if distance is small enough to even cause a force (for that strength<<10 must be bigger than the distance squared) int32_t shiftedstrength = (int32_t)strength << 16; @@ -319,9 +319,8 @@ void Particle_Gravity_update(PSparticle *part, bool wrapX, bool bounceX, bool bo if (bounceY) { part->vy = -part->vy; // invert speed - part->vy = (((int16_t)part->vy) * (int16_t)hardness) >> 8; // reduce speed as energy is lost on non-hard surface - part->y += (int16_t)part->vy; // move particle back to within boundaries so it does not disappear for one frame - newY = max(newY, (int16_t)0); // limit to positive + part->vy = (((int16_t)part->vy) * (int16_t)hardness) >> 8; // reduce speed as energy is lost on non-hard surface + newY = max(newY, (int16_t)0); // limit to positive (helps with piling as that can push particles out of frame) // newY = min(newY, (int16_t)PS_MAX_Y); //limit to matrix boundaries } else // not bouncing and out of matrix @@ -335,7 +334,7 @@ void Particle_Gravity_update(PSparticle *part, bool wrapX, bool bounceX, bool bo // render particles to the LED buffer (uses palette to render the 8bit particle color value) // if wrap is set, particles half out of bounds are rendered to the other side of the matrix // saturation is color saturation, if not set to 255, hsv instead of palette is used (palette does not support saturation) -void ParticleSys_render(PSparticle *particles, uint16_t numParticles, bool wrapX, bool wrapY, bool fastcoloradd) +void ParticleSys_render(PSparticle *particles, uint32_t numParticles, bool wrapX, bool wrapY, bool fastcoloradd) { const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; @@ -349,7 +348,7 @@ void ParticleSys_render(PSparticle *particles, uint16_t numParticles, bool wrapX uint8_t dx, dy; uint32_t intensity; // todo: can this be an uint8_t or will it mess things up? CRGB baseRGB; - uint16_t i; + uint32_t i; uint8_t brightess; // particle brightness, fades if dying @@ -527,20 +526,17 @@ void FireParticle_update(PSparticle *part, bool wrapX = false, bool wrapY = fals // render simple particles to the LED buffer using heat to color // each particle adds heat according to its 'age' (ttl) which is then rendered to a fire color in the 'add heat' function -void ParticleSys_renderParticleFire(PSparticle *particles, uint16_t numParticles, bool wrapX) +void ParticleSys_renderParticleFire(PSparticle *particles, uint32_t numParticles, bool wrapX) { const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - // particle box dimensions - const uint16_t PS_MAX_X(cols * PS_P_RADIUS - 1); - const uint16_t PS_MAX_Y(rows * PS_P_RADIUS - 1); - int16_t x, y; + int32_t x, y; uint8_t dx, dy; uint32_t tempVal; - uint16_t i; + uint32_t i; // go over particles and update matrix cells on the way for (i = 0; i < numParticles; i++) @@ -713,12 +709,12 @@ void PartMatrix_addHeat(uint8_t col, uint8_t row, uint16_t heat) } /*detect collisions in an array of particles and handle them*/ -void detectCollisions(PSparticle* particles, uint16_t numparticles, uint8_t hardness) +void detectCollisions(PSparticle* particles, uint32_t numparticles, uint8_t hardness) { // detect and handle collisions - uint16_t i,j; - int16_t startparticle = 0; - int16_t endparticle = numparticles >> 1; // do half the particles + uint32_t i,j; + int32_t startparticle = 0; + int32_t endparticle = numparticles >> 1; // do half the particles if (SEGMENT.call % 2 == 0) { // every second frame, do other half of particles (helps to speed things up as not all collisions are handled each frame which is overkill) @@ -756,13 +752,13 @@ void detectCollisions(PSparticle* particles, uint16_t numparticles, uint8_t hard void handleCollision(PSparticle *particle1, PSparticle *particle2, const uint8_t hardness) { - int16_t dx = particle2->x - particle1->x; - int16_t dy = particle2->y - particle1->y; + int32_t dx = particle2->x - particle1->x; + int32_t dy = particle2->y - particle1->y; int32_t distanceSquared = dx * dx + dy * dy; // Calculate relative velocity - int16_t relativeVx = (int16_t)particle2->vx - (int16_t)particle1->vx; - int16_t relativeVy = (int16_t)particle2->vy - (int16_t)particle1->vy; + int32_t relativeVx = (int16_t)particle2->vx - (int16_t)particle1->vx; + int32_t relativeVy = (int16_t)particle2->vy - (int16_t)particle1->vy; if (distanceSquared == 0) // add distance in case particles exactly meet at center, prevents dotProduct=0 (this can only happen if they move towards each other) { @@ -800,7 +796,7 @@ void handleCollision(PSparticle *particle1, PSparticle *particle2, const uint8_t particle2->vx -= (impulse * dx) >> bitshift; particle2->vy -= (impulse * dy) >> bitshift; - if (hardness < 150) // if particles are soft, they become 'sticky' i.e. no slow movements + if (hardness < 50) // if particles are soft, they become 'sticky' i.e. no slow movements { if (particle1->vx < 2 && particle1->vx > -2) particle1->vx = 0; @@ -811,35 +807,42 @@ void handleCollision(PSparticle *particle1, PSparticle *particle2, const uint8_t if (particle2->vy < 2 && particle1->vy > -2) particle1->vy = 0; } + } - // particles have volume, push particles apart if they are too close by moving each particle by a fixed amount away from the other particle - - int8_t push; - - if (distanceSquared < (2 * PS_P_HARDRADIUS) * (2 * PS_P_HARDRADIUS)) - { - if (dx < 2 * PS_P_HARDRADIUS && dx > -2 * PS_P_HARDRADIUS) - { // distance is too small - push = 1+random8(3); //make push distance a little random to avoid oscillations - if (dx < 0) // dx is negative - { - push = -push; // invert push direction - } - particle1->x -= push; - particle2->x += push; - } - if (dy < 2 * PS_P_HARDRADIUS && dy > -2 * PS_P_HARDRADIUS) - { // distance is too small (or negative) - push = 1+random8(3); - if (dy < 0) // dy is negative - { - push = -push; // invert push direction - } - particle1->y -= push; - particle2->y += push; - } + // particles have volume, push particles apart if they are too close by moving each particle by a fixed amount away from the other particle + // if pushing is made dependent on hardness, things start to oscillate much more, better to just add a fixed, small increment (tried lots of configurations, this one works best) + // one problem remaining is, particles get squished if (external) force applied is higher than the pushback but this may also be desirable if particles are soft. also some oscillations cannot be avoided without addigng a counter + if (distanceSquared < 2 * PS_P_HARDRADIUS * PS_P_HARDRADIUS) + { + int32_t push; + //uint8_t rndchoice = random8(2); + const uint32_t HARDDIAMETER = PS_P_HARDRADIUS <<1; + if (dx < HARDDIAMETER && dx > -HARDDIAMETER) + { // distance is too small, push them apart + push = 1; + if (dx < 0) // dx is negative + push =-push; // negative push direction + + //if (rndchoice) // randomly chose one of the particles to push, avoids oscillations + // particle1->x -= push; + //else + particle2->x += push; //only push one particle to avoid oscillations } + if (dy < HARDDIAMETER && dy > -HARDDIAMETER) + { // distance is too small (or negative) + push = 1; + if (dy < 0) // dy is negative + push = -push;//negative push direction + + + //if (rndchoice) // randomly chose one of the particles to push, avoids oscillations + // particle1->y -= push; + //else + particle2->y += push; // only push one particle to avoid oscillations + } + //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 } + } diff --git a/wled00/FXparticleSystem.h b/wled00/FXparticleSystem.h index 3de4cb69f9..80be538a70 100644 --- a/wled00/FXparticleSystem.h +++ b/wled00/FXparticleSystem.h @@ -35,6 +35,9 @@ #define PS_P_SURFACE 12 //shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2 +//todo: can add bitfields to add in more stuff +//but when using bit fields, computation time increases as instructions are needed to mask the fields, only do it with variables that do not get updated too often + //struct for a single particle typedef struct { int16_t x; //x position in particle system @@ -74,10 +77,10 @@ void Particle_attractor(PSparticle *particle, PSparticle *attractor, uint8_t *co void Particle_Move_update(PSparticle *part); void Particle_Bounce_update(PSparticle *part, const uint8_t hardness); void Particle_Gravity_update(PSparticle *part, bool wrapX, bool bounceX, bool bounceY, const uint8_t hardness); -void ParticleSys_render(PSparticle *particles, uint16_t numParticles, bool wrapX, bool wrapY, bool fastcoloradd = false); +void ParticleSys_render(PSparticle *particles, uint32_t numParticles, bool wrapX, bool wrapY, bool fastcoloradd = false); void FireParticle_update(PSparticle *part, bool wrapX, bool WrapY); -void ParticleSys_renderParticleFire(PSparticle *particles, uint16_t numParticles, bool wrapX); +void ParticleSys_renderParticleFire(PSparticle *particles, uint32_t numParticles, bool wrapX); void PartMatrix_addHeat(uint8_t col, uint8_t row, uint16_t heat); -void detectCollisions(PSparticle *particles, uint16_t numparticles, uint8_t hardness); +void detectCollisions(PSparticle *particles, uint32_t numparticles, uint8_t hardness); void handleCollision(PSparticle *particle1, PSparticle *particle2, const uint8_t hardness); void applyFriction(PSparticle *particle, uint8_t coefficient);