diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 9fb83d736b..6cc90a5d18 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7910,8 +7910,8 @@ uint16_t mode_particlevortex(void) for (i = 0; i < numSprays; i++) { PartSys->sources[i].source.sat = 255; // set saturation - PartSys->sources[i].source.x = (PartSys->maxX - PS_P_HALFRADIUS + 1) >> 1; // center - PartSys->sources[i].source.y = (PartSys->maxY - PS_P_HALFRADIUS + 1) >> 1; // center + PartSys->sources[i].source.x = (PartSys->maxX + 1) >> 1; // center + PartSys->sources[i].source.y = (PartSys->maxY + 1) >> 1; // center PartSys->sources[i].source.vx = 0; PartSys->sources[i].source.vy = 0; PartSys->sources[i].maxLife = 900; @@ -8294,7 +8294,7 @@ uint16_t mode_particlefire(void) { if (!initParticleSystem(PartSys)) return mode_static(); // allocation failed; //allocation failed - Serial.println("fireinit done"); + // Serial.println("fireinit done"); SEGMENT.aux0 = rand(); // aux0 is wind position (index) in the perlin noise // initialize the flame sprays numFlames = PartSys->numSources; @@ -8379,7 +8379,7 @@ uint16_t mode_particlefire(void) { //int32_t curl = ((int16_t)inoise8(PartSys->particles[i].x , PartSys->particles[i].y >> 1, SEGMENT.step<<2 ) - 127); //int32_t curl = ((int16_t)inoise8(PartSys->particles[i].x, PartSys->particles[i].y , SEGMENT.step << 4) - 127); - int32_t curl = ((int16_t)inoise8(PartSys->particles[i].x, PartSys->particles[i].y , SEGMENT.step << 4) - 127); //-> this is good! + int32_t curl = ((int32_t)inoise8(PartSys->particles[i].x, PartSys->particles[i].y , SEGMENT.step << 4) - 127); //-> this is good! //int32_t curl = ((int16_t)inoise8(PartSys->particles[i].x>>1, SEGMENT.step<<5) - 127); // curl = ((curl * PartSys->particles[i].y) / PartSys->maxY); //'curl' stronger at the top @@ -8424,13 +8424,21 @@ uint16_t mode_particlefire(void) } Serial.println("B"); }*/ - + if(SEGMENT.check1) //low fps is enabled + { + static uint32_t lastcall; //!!! put this in heap + while(millis()-lastcall < 25) + { + yield(); + } + lastcall = millis(); + } PartSys->updateFire(SEGMENT.intensity); // update and render the fire return FRAMETIME; } -static const char _data_FX_MODE_PARTICLEFIRE[] PROGMEM = "PS Fire@Speed,Intensity,Base Heat,Wind,Spread,,Cylinder,Turbulence;;!;2;pal=35,sx=130,ix=120,c1=110,c2=128,c3=22,o1=0"; +static const char _data_FX_MODE_PARTICLEFIRE[] PROGMEM = "PS Fire@Speed,Intensity,Base Heat,Wind,Spread,FPS Limit,Cylinder,Turbulence;;!;2;pal=35,sx=130,ix=120,c1=110,c2=128,c3=22,o1=0"; /* PS Ballpit: particles falling down, user can enable these three options: X-wraparound, side bounce, ground bounce @@ -8551,7 +8559,7 @@ uint16_t mode_particlewaterfall(void) if (PartSys == NULL) { - Serial.println("ERROR: paticle system not found, nullpointer"); + DEBUG_PRINT(F("ERROR: FX PartSys nullpointer")); return mode_static(); // something went wrong, no data! (TODO: ask how to handle this so it always works) } // Particle System settings @@ -8623,7 +8631,7 @@ uint16_t mode_particlebox(void) if (PartSys == NULL) { - Serial.println("ERROR: paticle system not found, nullpointer"); + DEBUG_PRINT(F("ERROR: FX PartSys nullpointer")); return mode_static(); // something went wrong, no data! } @@ -8819,7 +8827,7 @@ uint16_t mode_particleimpact(void) if (PartSys == NULL) { - Serial.println("ERROR: paticle system not found, nullpointer"); + DEBUG_PRINT(F("ERROR: FX PartSys nullpointer")); return mode_static(); // something went wrong, no data! (TODO: ask how to handle this so it always works) } @@ -8961,7 +8969,7 @@ uint16_t mode_particleattractor(void) if (PartSys == NULL) { - Serial.println("ERROR: paticle system not found, nullpointer"); + DEBUG_PRINT(F("ERROR: FX PartSys nullpointer")); return mode_static(); // something went wrong, no data! (TODO: ask how to handle this so it always works) } // Particle System settings @@ -9130,7 +9138,7 @@ uint16_t mode_particleGEQ(void) if (PartSys == NULL) { - Serial.println("ERROR: paticle system not found, nullpointer"); + DEBUG_PRINT(F("ERROR: FX PartSys nullpointer")); return mode_static(); // something went wrong, no data! (TODO: ask how to handle this so it always works) } @@ -9142,7 +9150,7 @@ uint16_t mode_particleGEQ(void) PartSys->setBounceY(SEGMENT.check3); PartSys->enableParticleCollisions(false); PartSys->setWallHardness(SEGMENT.custom2); - PartSys->enableGravity(true, SEGMENT.custom3<<1); //set gravity strength + PartSys->enableGravity(true, SEGMENT.custom3<<2); //set gravity strength um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) @@ -9166,7 +9174,7 @@ uint16_t mode_particleGEQ(void) for (bin = 0; bin < 16; bin++) { uint32_t xposition = binwidth*bin + (binwidth>>1); // emit position according to frequency band - uint8_t emitspeed = 5 + (((uint32_t)fftResult[bin]*(uint32_t)SEGMENT.speed)>>9); // emit speed according to loudness of band + uint8_t emitspeed = ((uint32_t)fftResult[bin] * (uint32_t)SEGMENT.speed) >> 9; // emit speed according to loudness of band (127 max!) emitparticles = 0; if (fftResult[bin] > threshold) @@ -9187,10 +9195,10 @@ uint16_t mode_particleGEQ(void) if (PartSys->particles[i].ttl == 0) // find a dead particle { //set particle properties - PartSys->particles[i].ttl = map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames + PartSys->particles[i].ttl = 20 + map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames PartSys->particles[i].x = xposition + random16(binwidth) - (binwidth>>1); //position randomly, deviating half a bin width - PartSys->particles[i].y = 0; //start at the bottom - PartSys->particles[i].vx = random16(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation + PartSys->particles[i].y = PS_P_RADIUS; //tart at the bottom (PS_P_RADIUS is minimum position a particle is fully in frame) + PartSys->particles[i].vx = random16(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation: +/- custom1/4 PartSys->particles[i].vy = emitspeed; PartSys->particles[i].hue = (bin<<4) + random16(17) - 8; // color from palette according to bin PartSys->particles[i].sat = 255; // set saturation diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index 1cd99e41c0..4fee58577c 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -47,7 +47,7 @@ ParticleSystem::ParticleSystem(uint16_t width, uint16_t height, uint16_t numberofparticles, uint16_t numberofsources) { - Serial.println("PS Constructor"); + //Serial.println("PS Constructor"); numSources = numberofsources; numParticles = numberofparticles; // set number of particles in the array usedParticles = numberofparticles; // use all particles by default @@ -69,7 +69,7 @@ ParticleSystem::ParticleSystem(uint16_t width, uint16_t height, uint16_t numbero Serial.println(particles[i].y); } }*/ - Serial.println("PS Constructor done"); + //Serial.println("PS Constructor done"); } //update function applies gravity, moves the particles, handles collisions and renders the particles @@ -285,9 +285,12 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings &options) { if (newY < PS_P_RADIUS) // bounce at bottom { - part.vy = -part.vy; // invert speed - part.vy = (part.vy * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface - newY = PS_P_RADIUS; + if(part.vy < -10) + { + part.vy = -part.vy; // invert speed + part.vy = ((int32_t)part.vy * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface + newY = PS_P_RADIUS; + } } else { @@ -299,7 +302,7 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings &options) else { part.vy = -part.vy; // invert speed - part.vy = (part.vy * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface + part.vy = ((int32_t)part.vy * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface newY = maxY - PS_P_RADIUS; } } @@ -341,8 +344,8 @@ void ParticleSystem::applyForce(PSparticle *part, uint32_t numparticles, int8_t 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 @@ -352,30 +355,18 @@ void ParticleSystem::applyForce(PSparticle *part, uint32_t numparticles, int8_t int32_t i = 0; if (dvx != 0) { - if (numparticles == 1) // for single particle, skip the for loop to make it faster + for (i = 0; i < numparticles; i++) { - part[0].vx = part[0].vx + dvx > PS_P_MAXSPEED ? PS_P_MAXSPEED : part[0].vx + dvx; // limit the force, this is faster than min or if/else - } - else - { - for (i = 0; i < numparticles; i++) - { - // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is faster so no speed penalty - part[i].vx = part[i].vx + dvx > PS_P_MAXSPEED ? PS_P_MAXSPEED : part[i].vx + dvx; - } + // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is faster so no speed penalty + part[i].vx = limitSpeed((int32_t)particles[i].vx + dvx); } } if (dvy != 0) { - if (numparticles == 1) // for single particle, skip the for loop to make it faster - part[0].vy = part[0].vy + dvy > PS_P_MAXSPEED ? PS_P_MAXSPEED : part[0].vy + dvy; - else + for (i = 0; i < numparticles; i++) { - for (i = 0; i < numparticles; i++) - { - part[i].vy = part[i].vy + dvy > PS_P_MAXSPEED ? PS_P_MAXSPEED : part[i].vy + dvy; - } - } + part[i].vy = limitSpeed((int32_t)particles[i].vy + dvy); + } } } @@ -386,8 +377,8 @@ void ParticleSystem::applyForce(PSparticle *part, uint32_t numparticles, int8_t for (uint i = 0; i < numparticles; i++) { // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is faster so no speed penalty - part[i].vx = part[i].vx + xforce > PS_P_MAXSPEED ? PS_P_MAXSPEED : part[i].vx + xforce; - part[i].vy = part[i].vy + yforce > PS_P_MAXSPEED ? PS_P_MAXSPEED : part[i].vy + yforce; + part[i].vx = limitSpeed((int32_t)part[i].vx + (int32_t)xforce); + part[i].vy = limitSpeed((int32_t)part[i].vy + (int32_t)yforce); } } @@ -415,27 +406,17 @@ void ParticleSystem::applyAngleForce(PSparticle *part, uint32_t numparticles, ui // apply gravity to a group of particles // faster than apply force since direction is always down and counter is fixed for all particles // caller needs to provide a 8bit counter that holds its value between calls -// force is in 4.4 fixed point notation so force=16 means apply v+1 each frame default of 8 is every other frame (gives good results), force above 127 are VERY strong -void ParticleSystem::applyGravity(PSparticle *part, uint32_t numarticles, uint8_t force, uint8_t *counter) +// 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) +// positive force means down +void ParticleSystem::applyGravity(PSparticle *part, uint32_t numarticles, int8_t force, uint8_t *counter) { - int32_t dv; // velocity increase - if (force > 15) - dv = (force >> 4); // apply the 4 MSBs - else - dv = 1; - - *counter += force; - - if (*counter > 15) - { - *counter -= 16; - // apply force to all used particles - for (uint32_t i = 0; i < numarticles; i++) - { - // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways - particles[i].vy = particles[i].vy - dv > PS_P_MAXSPEED ? PS_P_MAXSPEED : particles[i].vy - dv; // limit the force, this is faster than min or if/else - } + int32_t dv = calcForce_dv(force, counter); + for (uint32_t i = 0; i < numarticles; i++) + { + // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways + part[i].vy = limitSpeed((int32_t)particles[i].vy - dv); } + } //apply gravity using PS global gforce @@ -445,6 +426,7 @@ void ParticleSystem::applyGravity(PSparticle *part, uint32_t numarticles, uint8_ } //apply gravity to single particle using system settings (use this for sources) +//function does not increment gravity counter, if gravity setting is disabled, this cannot be used void ParticleSystem::applyGravity(PSparticle *part) { int32_t dv; // velocity increase @@ -454,8 +436,8 @@ void ParticleSystem::applyGravity(PSparticle *part) dv = 1; if (gforcecounter + gforce > 15) //counter is updated in global update when applying gravity - { - part->vy = part->vy - dv > PS_P_MAXSPEED ? PS_P_MAXSPEED : part->vy - dv; // limit the force, this is faster than min or if/else + { + part->vy = limitSpeed((int32_t)part->vy - dv); } } @@ -1093,11 +1075,11 @@ int32_t ParticleSystem::wraparound(int32_t p, int32_t maxvalue) //calculate the delta speed (dV) value and update the counter for force calculation (is used several times, function saves on codesize) //force is in 3.4 fixedpoint notation, +/-127 -int32_t ParticleSystem::calcForce_dV(int8_t force, uint8_t* counter) +int32_t ParticleSystem::calcForce_dv(int8_t force, uint8_t* counter) { // for small forces, need to use a delay counter int32_t force_abs = abs(force); // absolute value (faster than lots of if's only 7 instructions) - int32_t dv = 0; + int32_t dv; // for small forces, need to use a delay counter, apply force only if it overflows if (force_abs < 16) { @@ -1115,6 +1097,12 @@ int32_t ParticleSystem::calcForce_dV(int8_t force, uint8_t* counter) return dv; } +//limit speed to prevent overflows +int32_t ParticleSystem::limitSpeed(int32_t speed) +{ + return speed > PS_P_MAXSPEED ? PS_P_MAXSPEED : (speed < -PS_P_MAXSPEED ? -PS_P_MAXSPEED : speed); +} + // allocate memory for the 2D array in one contiguous block and set values to zero CRGB **ParticleSystem::allocate2Dbuffer(uint32_t cols, uint32_t rows) { @@ -1207,18 +1195,18 @@ bool allocateParticleSystemMemory(uint16_t numparticles, uint16_t numsources, ui requiredmemory += sizeof(PSparticle) * numparticles; requiredmemory += sizeof(PSsource) * numsources; requiredmemory += additionalbytes; - Serial.print("allocating: "); - Serial.print(requiredmemory); - Serial.println("Bytes"); - Serial.print("allocating for segment at"); - Serial.println((uintptr_t)SEGMENT.data); + //Serial.print("allocating: "); + //Serial.print(requiredmemory); + //Serial.println("Bytes"); + //Serial.print("allocating for segment at"); + //Serial.println((uintptr_t)SEGMENT.data); return(SEGMENT.allocateData(requiredmemory)); } // initialize Particle System, allocate additional bytes if needed (pointer to those bytes can be read from particle system class: PSdataEnd) bool initParticleSystem(ParticleSystem *&PartSys, uint16_t additionalbytes) { - Serial.println("PS init function"); + //Serial.println("PS init function"); uint32_t numparticles = calculateNumberOfParticles(); uint32_t numsources = calculateNumberOfSources(); if (!allocateParticleSystemMemory(numparticles, numsources, additionalbytes)) @@ -1226,14 +1214,14 @@ bool initParticleSystem(ParticleSystem *&PartSys, uint16_t additionalbytes) DEBUG_PRINT(F("PS init failed: memory depleted")); return false; } - Serial.print("segment.data ptr"); - Serial.println((uintptr_t)(SEGMENT.data)); + //Serial.print("segment.data ptr"); + //Serial.println((uintptr_t)(SEGMENT.data)); uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); - Serial.println("calling constructor"); + //Serial.println("calling constructor"); PartSys = new (SEGMENT.data) ParticleSystem(cols, rows, numparticles, numsources); // particle system constructor TODO: why does VS studio thinkt this is bad? - Serial.print("PS pointer at "); - Serial.println((uintptr_t)PartSys); + //Serial.print("PS pointer at "); + //Serial.println((uintptr_t)PartSys); return true; } diff --git a/wled00/FXparticleSystem.h b/wled00/FXparticleSystem.h index af8c37d1bc..2b9a91dec2 100644 --- a/wled00/FXparticleSystem.h +++ b/wled00/FXparticleSystem.h @@ -44,7 +44,7 @@ #define PS_P_SURFACE 12 // shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2 #define PS_P_HARDRADIUS 80 //hard surface radius of a particle, used for collision detection proximity #define PS_P_MINSURFACEHARDNESS 128 //minimum hardness used in collision impulse calculation, below this hardness, particles become sticky -#define PS_P_MAXSPEED 200 //maximum speed a particle can have +#define PS_P_MAXSPEED 120 //maximum speed a particle can have (vx/vy is int8) //struct for a single particle typedef struct { @@ -105,7 +105,7 @@ class ParticleSystem void particleMoveUpdate(PSparticle &part, PSsettings &options); //particle physics - void applyGravity(PSparticle *part, uint32_t numarticles, uint8_t force, uint8_t *counter); + void applyGravity(PSparticle *part, uint32_t numarticles, int8_t force, uint8_t *counter); void applyGravity(PSparticle *part, uint32_t numarticles, uint8_t *counter); //use global gforce void applyGravity(PSparticle *part); //use global system settings void applyForce(PSparticle *part, uint32_t numparticles, int8_t xforce, int8_t yforce, uint8_t *counter); @@ -153,7 +153,8 @@ class ParticleSystem //utility functions void updatePSpointers(); // update the data pointers to current segment data space int32_t wraparound(int32_t w, int32_t maxvalue); - int32_t calcForce_dV(int8_t force, uint8_t *counter); + int32_t calcForce_dv(int8_t force, uint8_t *counter); + int32_t limitSpeed(int32_t speed); CRGB **allocate2Dbuffer(uint32_t cols, uint32_t rows); // note: variables that are accessed often are 32bit for speed @@ -161,7 +162,7 @@ class ParticleSystem int32_t collisionHardness; int32_t wallHardness; uint8_t gforcecounter; //counter for global gravity - uint8_t gforce; //gravity strength, default is 8 + int8_t gforce; //gravity strength, default is 8 (negative is allowed) uint8_t collisioncounter; //counter to handle collisions };