diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 99a16518a9..8e4ec1c941 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7929,7 +7929,7 @@ uint16_t mode_particlevortex(void) PartSys->setKillOutOfBounds(true); } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -8025,7 +8025,7 @@ uint16_t mode_particlevortex(void) uint32_t skip = PS_P_HALFRADIUS/SEGMENT.intensity + 1; if (SEGMENT.call % skip == 0) { - j = random16(spraycount); // start with random spray so all get a chance to emit a particle if maximum number of particles alive is reached. + j = random(spraycount); // start with random spray so all get a chance to emit a particle if maximum number of particles alive is reached. for (i = 0; i < spraycount; i++) // emit one particle per spray (if available) { PartSys->sources[j].var = (SEGMENT.custom3 >> 1); //update speed variation @@ -8076,7 +8076,7 @@ uint16_t mode_particlefireworks(void) } } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -8196,7 +8196,7 @@ uint16_t mode_particlefireworks(void) PartSys->sources[j].source.y = PS_P_RADIUS<<1; // start from bottom PartSys->sources[j].source.x = (rand() % (PartSys->maxX >> 1)) + (PartSys->maxX >> 2); // centered half PartSys->sources[j].source.vy = random16(SEGMENT.custom1 >> 3) + 5; // rocket speed depends also on rocket fuse - PartSys->sources[j].source.vx = random16(5) - 2; //i.e. not perfectly straight up + PartSys->sources[j].source.vx = random(5) - 2; //i.e. not perfectly straight up PartSys->sources[j].sat = 30; // low saturation -> exhaust is off-white PartSys->sources[j].source.ttl = random16(SEGMENT.custom1) + (SEGMENT.custom1 >> 1); // sets explosion height (rockets explode at the top if set too high as paticle update set speed to zero if moving out of matrix) PartSys->sources[j].maxLife = 40; // exhaust particle life @@ -8231,10 +8231,11 @@ uint16_t mode_particlevolcano(void) if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. { if (!initParticleSystem(PartSys, NUMBEROFSOURCES)) // init, no additional data needed - return mode_static(); // allocation failed; //allocation failed + return mode_static(); // allocation failed PartSys->setBounceY(true); PartSys->setGravity(); // enable with default gforce - PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting) + PartSys->setMotionBlur(190); //anable motion blur numSprays = min(PartSys->numSources, (uint8_t)NUMBEROFSOURCES); // number of sprays for (i = 0; i < numSprays; i++) { @@ -8247,7 +8248,7 @@ uint16_t mode_particlevolcano(void) } } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -8327,7 +8328,7 @@ uint16_t mode_particlefire(void) DEBUG_PRINTF_P(PSTR("segment data ptr in fireFX %p\n"), SEGMENT.data); } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -8375,7 +8376,7 @@ uint16_t mode_particlefire(void) PartSys->sources[i].source.ttl = 5 + random16((SEGMENT.custom1 * SEGMENT.custom1) >> 7) / (2 + (firespeed >> 4)); //'hotness' of fire, faster flames reduce the effect or flame height will scale too much with speed -> new, this works! PartSys->sources[i].maxLife = random16(7) + 13; // defines flame height together with the vy speed, vy speed*maxlife/PS_P_RADIUS is the average flame height PartSys->sources[i].minLife = 4; - PartSys->sources[i].vx = (int8_t)random16(4) - 2; // emitting speed (sideways) + PartSys->sources[i].vx = (int8_t)random(4) - 2; // emitting speed (sideways) PartSys->sources[i].vy = 5 + (firespeed >> 2); // emitting speed (upwards) -> this is good PartSys->sources[i].var = (random16(2 + (firespeed >> 5)) + 3) | 0x01; // speed variation around vx,vy (+/- var/2), only use odd numbers } @@ -8409,11 +8410,11 @@ uint16_t mode_particlefire(void) } } - uint8_t j = random16(numFlames); // start with a random flame (so each flame gets the chance to emit a particle if available particles is smaller than number of flames) + uint8_t j = random16(); // start with a random flame (so each flame gets the chance to emit a particle if available particles is smaller than number of flames) for(i=0; i < percycle; i++) { - PartSys->flameEmit(PartSys->sources[j]); j = (j + 1) % numFlames; + PartSys->flameEmit(PartSys->sources[j]); } PartSys->updateFire(SEGMENT.intensity); // update and render the fire @@ -8444,7 +8445,7 @@ uint16_t mode_particlepit(void) PartSys->setUsedParticles((PartSys->numParticles*3)/2); //use 2/3 of available particles } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -8466,8 +8467,8 @@ uint16_t mode_particlepit(void) PartSys->setSaturation(((SEGMENT.custom3) << 3) + 7); // set global rendering saturation so individual saturation rendering is enabled - uint32_t i; // index variable - if (SEGMENT.call % (64 - (SEGMENT.intensity >> 2)) == 0 && SEGMENT.intensity > 1) // every nth frame emit particles, stop emitting if set to zero + uint32_t i; + if (SEGMENT.call % (128 - (SEGMENT.intensity >> 1)) == 0 && SEGMENT.intensity > 0) // every nth frame emit particles, stop emitting if set to zero { for (i = 0; i < PartSys->usedParticles; i++) // emit particles { @@ -8477,7 +8478,7 @@ uint16_t mode_particlepit(void) PartSys->particles[i].ttl = 1500 - (SEGMENT.speed << 2) + random16(500); // if speed is higher, make them die sooner PartSys->particles[i].x = random(PartSys->maxX >> 1) + (PartSys->maxX >> 2); PartSys->particles[i].y = (PartSys->maxY<<1); // particles appear somewhere above the matrix, maximum is double the height - PartSys->particles[i].vx = (int16_t)random(100) - 50; // side speed is +/- + PartSys->particles[i].vx = (int16_t)random(SEGMENT.speed >> 1) - (SEGMENT.speed >> 2); // side speed is +/- PartSys->particles[i].vy = map(SEGMENT.speed, 0, 255, -5, -100); // downward speed PartSys->particles[i].hue = random16(); // set random color PartSys->particles[i].collide = true; //enable collision for particle @@ -8492,11 +8493,11 @@ uint16_t mode_particlepit(void) if (SEGMENT.speed < 50) // for low speeds, apply more friction frictioncoefficient = 50 - SEGMENT.speed; - if (SEGMENT.call % (3 + (SEGMENT.custom2 >> 2)) == 0) + //if (SEGMENT.call % (3 + (SEGMENT.custom2 >> 2)) == 0) + if (SEGMENT.call % (3 + (SEGMENT.speed >> 2)) == 0) PartSys->applyFriction(frictioncoefficient); - //PartSys->setMotionBlur(8); //TODO: what happens with motion blur and frame blur? -> works, but needs to be kept at low value, below 128? it depends on particle size, need to adjust when particle size is set (or disable) but gives interesting effect... - //maybe better disable it. blur of 1 at size 255 does not motion blur at all, blur of 5 also not really, blur of 10 fills the frame -> TODO: disable it!!! + PartSys->setParticleSize(SEGMENT.custom1); PartSys->update(); // update and render @@ -8665,7 +8666,7 @@ uint16_t mode_particlewaterfall(void) } } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -8737,7 +8738,7 @@ uint16_t mode_particlebox(void) return mode_static(); // allocation failed; //allocation failed } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -8849,7 +8850,7 @@ uint16_t mode_particleperlin(void) } } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -8864,28 +8865,27 @@ uint16_t mode_particleperlin(void) PartSys->enableParticleCollisions(SEGMENT.check3, SEGMENT.custom1); // enable collisions and set particle collision hardness uint32_t displayparticles = map(SEGMENT.intensity, 0, 255, 10, PartSys->numParticles>>1); PartSys->setUsedParticles(displayparticles); - PartSys->setMotionBlur(200); // anable motion blur + PartSys->setMotionBlur(230); // anable motion blur // apply 'gravity' from a 2D perlin noise map SEGMENT.aux0 += 1+(SEGMENT.speed >> 5); // noise z-position - // update position in noise for (i = 0; i < displayparticles; i++) { if (PartSys->particles[i].ttl == 0) // revive dead particles (do not keep them alive forever, they can clump up, need to reseed) { PartSys->particles[i].ttl = random16(500) + 200; - PartSys->particles[i].x = random16(PartSys->maxX); - PartSys->particles[i].y = random16(PartSys->maxY); + PartSys->particles[i].x = random(PartSys->maxX); + PartSys->particles[i].y = random(PartSys->maxY); } uint32_t scale = 16 - ((31 - SEGMENT.custom3) >> 1); uint16_t xnoise = PartSys->particles[i].x / scale; // position in perlin noise, scaled by slider uint16_t ynoise = PartSys->particles[i].y / scale; int16_t baseheight = inoise8(xnoise, ynoise, SEGMENT.aux0); // noise value at particle position PartSys->particles[i].hue = baseheight; // color particles to perlin noise value - if (SEGMENT.call % 3 == 0) // do not apply the force every frame, is too chaotic + if (SEGMENT.call % 8 == 0) // do not apply the force every frame, is too chaotic { - int8_t xslope = (baseheight - (int16_t)inoise8(xnoise + 10, ynoise, SEGMENT.aux0)); - int8_t yslope = (baseheight - (int16_t)inoise8(xnoise, ynoise + 10, SEGMENT.aux0)); + int8_t xslope = (baseheight + (int16_t)inoise8(xnoise - 10, ynoise, SEGMENT.aux0)); + int8_t yslope = (baseheight + (int16_t)inoise8(xnoise, ynoise - 10, SEGMENT.aux0)); PartSys->applyForce(i, xslope, yslope); } } @@ -8896,7 +8896,7 @@ uint16_t mode_particleperlin(void) PartSys->update(); // update and render return FRAMETIME; } -static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,,Collisions;;!;2;pal=54,sx=75,ix=200,c1=130,c2=30,c3=5,o1=0,o3=1"; +static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,,Collisions;;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5,o1=0,o3=1"; /* * Particle smashing down like meteors and exploding as they hit the ground, has many parameters to play with @@ -8921,8 +8921,7 @@ uint16_t mode_particleimpact(void) return mode_static(); // allocation failed; //allocation failed PartSys->setKillOutOfBounds(false); //explosions out of frame ar allowed, set to true to save particles (TODO: better enable it in ESP8266?) PartSys->setGravity(); //enable default gravity - PartSys->setBounceY(true); //always use ground bounce - // PartSys->setUsedParticles((PartSys->numParticles * 3) / 2); // use 2/3 of available particles + PartSys->setBounceY(true); //always use ground bounce MaxNumMeteors = min(PartSys->numSources, (uint8_t)NUMBEROFSOURCES); for (i = 0; i < MaxNumMeteors; i++) { @@ -8933,7 +8932,7 @@ uint16_t mode_particleimpact(void) } } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -9018,9 +9017,9 @@ uint16_t mode_particleimpact(void) { // reinitialize meteor PartSys->sources[i].source.y = PartSys->maxY + (PS_P_RADIUS << 2); // start 4 pixels above the top - PartSys->sources[i].source.x = random16(PartSys->maxX); + PartSys->sources[i].source.x = random(PartSys->maxX); PartSys->sources[i].source.vy = -random16(30) - 30; // meteor downward speed - PartSys->sources[i].source.vx = random16(30) - 15; + PartSys->sources[i].source.vx = random(30) - 15; PartSys->sources[i].source.hue = random16(); // random color PartSys->sources[i].source.ttl = 2000; // long life, will explode at bottom PartSys->sources[i].source.collide = false; // trail particles will not collide @@ -9084,7 +9083,7 @@ uint16_t mode_particleattractor(void) PartSys->setWallHardness(255); //bounce forever } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -9095,13 +9094,14 @@ uint16_t mode_particleattractor(void) PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->setColorByAge(SEGMENT.check1); + PartSys->setParticleSize(SEGMENT.custom1 >> 1); if (SEGMENT.custom2 > 0) // collisions enabled PartSys->enableParticleCollisions(true, map(SEGMENT.custom2, 1, 255, 120, 255)); // enable collisions and set particle collision hardness else PartSys->enableParticleCollisions(false); - uint16_t lastusedparticle = (PartSys->numParticles * 2) / 3; + uint16_t lastusedparticle = (PartSys->numParticles * 3) >> 2; //use 3/4 of particles uint32_t displayparticles = map(SEGMENT.intensity, 0, 255, 10, lastusedparticle); PartSys->setUsedParticles(displayparticles); @@ -9135,9 +9135,11 @@ uint16_t mode_particleattractor(void) SEGMENT.aux0 += 256; //emitting angle, one full turn in 255 frames (0xFFFF is 360°) if (SEGMENT.call % 2 == 0) // alternate direction of emit - PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0, SEGMENT.custom1 >> 4); + PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0, 12); + //PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0, SEGMENT.custom1 >> 4); else - PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0 + 0x7FFF, SEGMENT.custom1 >> 4); // emit at 180° as well + PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0 + 0x7FFF, 12); // emit at 180° as well + //PartSys->angleEmit(PartSys->sources[0], SEGMENT.aux0 + 0x7FFF, SEGMENT.custom1 >> 4); // emit at 180° as well // apply force for(i = 0; i < displayparticles; i++) @@ -9151,7 +9153,7 @@ uint16_t mode_particleattractor(void) PartSys->update(); // update and render return FRAMETIME; } -static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Emit Speed,Collisions,Friction,Color by Age,Move,Swallow;;!;2;pal=9,sx=100,ix=82,c1=190,c2=0,o1=0,o2=0,o3=0"; +static const char _data_FX_MODE_PARTICLEATTRACTOR[] PROGMEM = "PS Attractor@Mass,Particles,Particle Size,Collisions,Friction,Color by Age,Move,Swallow;;!;2;pal=9,sx=100,ix=82,c1=0,c2=0,o1=0,o2=0,o3=0"; /* @@ -9198,7 +9200,7 @@ uint16_t mode_particleattractor(void) PartSys->sources[0].var = 7; // emiting variation } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -9300,7 +9302,7 @@ uint16_t mode_particlespray(void) } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -9360,7 +9362,7 @@ uint16_t mode_particleGEQ(void) PartSys->setUsedParticles((PartSys->numParticles * 3) / 2); // use 2/3 of available particles } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGMENT.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) { @@ -9420,11 +9422,11 @@ uint16_t mode_particleGEQ(void) { if (PartSys->particles[i].ttl == 0) // find a dead particle { - //set particle properties + //set particle properties TODO: could also use the spray... PartSys->particles[i].ttl = 20 + map(SEGMENT.intensity, 0,255, emitspeed>>1, emitspeed + random16(emitspeed)) ; // set particle alive, particle lifespan is in number of frames PartSys->particles[i].x = xposition + random16(binwidth) - (binwidth>>1); //position randomly, deviating half a bin width PartSys->particles[i].y = PS_P_RADIUS; //tart at the bottom (PS_P_RADIUS is minimum position a particle is fully in frame) - PartSys->particles[i].vx = random16(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation: +/- custom1/4 + PartSys->particles[i].vx = random16(SEGMENT.custom1>>1)-(SEGMENT.custom1>>2) ; //x-speed variation: +/- custom1/4 TODO: ok to use random16 here? PartSys->particles[i].vy = emitspeed; PartSys->particles[i].hue = (bin<<4) + random16(17) - 8; // color from palette according to bin emitparticles--; @@ -9469,10 +9471,10 @@ uint16_t mode_particlecenterGEQ(void) // allocate memory and divide it into proper pointers, max is 32kB for all segments, 100 particles use 1200bytes uint32_t dataSize = sizeof(PSparticle) * numParticles; dataSize += sizeof(PSsource) * (numSprays); - if (!SEGENV.allocateData(dataSize)) + if (!SEGMENT.allocateData(dataSize)) return mode_static(); // allocation failed; //allocation failed - spray = reinterpret_cast(SEGENV.data); + spray = reinterpret_cast(SEGMENT.data); // 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 diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index 4a486dc68d..effda26010 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -56,6 +56,7 @@ ParticleSystem::ParticleSystem(uint16_t width, uint16_t height, uint16_t numbero updatePSpointers(isadvanced); // set the particle and sources pointer (call this before accessing sprays or particles) setMatrixSize(width, height); setWallHardness(255); // set default wall hardness to max + setWallRoughness(200); // smooth walls by default !!! testing this setGravity(0); //gravity disabled by default setSaturation(255); //full saturation by default setParticleSize(0); // minimum size by default @@ -129,6 +130,11 @@ void ParticleSystem::setWallHardness(uint8_t hardness) wallHardness = hardness; } +void ParticleSystem::setWallRoughness(uint8_t roughness) +{ + wallRoughness = roughness; +} + void ParticleSystem::setCollisionHardness(uint8_t hardness) { collisionHardness = hardness; @@ -179,14 +185,16 @@ void ParticleSystem::setColorByAge(bool enable) void ParticleSystem::setMotionBlur(uint8_t bluramount) { - motionBlur = bluramount; + if(particlesize == 0) //only allwo motion blurring on default particle size + motionBlur = bluramount; } // render size using smearing void ParticleSystem::setParticleSize(uint8_t size) { particlesize = size; - particleHardRadius = max(PS_P_MINHARDRADIUS, (int)particlesize); + particleHardRadius = max(PS_P_MINHARDRADIUS, (int)particlesize); + motionBlur = 0; //disable motion blur if particle size is set } // enable/disable gravity, optionally, set the force (force=8 is default) can be 1-255, 0 is disable // if enabled, gravity is applied to all particles in ParticleSystemUpdate() @@ -302,15 +310,8 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings &options) //if wall collisions are enabled, bounce them before they reach the edge, it looks much nicer if the particle is not half out of vew if (options.bounceX) { - if ((newX < particleHardRadius) || (newX > maxX - particleHardRadius)) // reached a wall - { - part.vx = -part.vx; // invert speed - part.vx = (part.vx * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface - if (newX < particleHardRadius) - newX = particleHardRadius; // fast particles will never reach the edge if position is inverted - else - newX = maxX - particleHardRadius; - } + if ((newX < particleHardRadius) || (newX > maxX - particleHardRadius)) // reached a wall + bounce(part.vx, part.vy, newX, maxX); } if ((newX < 0) || (newX > maxX)) // check if particle reached an edge @@ -332,11 +333,7 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings &options) if ((newY < particleHardRadius) || (newY > maxY - particleHardRadius)) // reached floor / ceiling { if (newY < particleHardRadius) // bounce at bottom - { - 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 = particleHardRadius; - } + bounce(part.vy, part.vx, newY, maxY); else { if (options.useGravity) // do not bounce on top if using gravity (open container) if this is needed implement it in the FX @@ -346,9 +343,7 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings &options) } else { - 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 = maxY - particleHardRadius; + bounce(part.vy, part.vx, newY, maxY); } } } @@ -379,6 +374,27 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings &options) } } +//function to bounce a particle from a wall using set parameters (wallHardness and wallRoughness) +void ParticleSystem::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition) +{ + incomingspeed = -incomingspeed; // invert speed + incomingspeed = (incomingspeed * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface + if (position < particleHardRadius) + position = particleHardRadius; // fast particles will never reach the edge if position is inverted + else + position = maxposition - particleHardRadius; + if(wallRoughness) + { + //transfer an amount of incomingspeed speed to parallel speed + int32_t donatespeed = abs(incomingspeed); + donatespeed = ((random(donatespeed << 1) - donatespeed) * wallRoughness) / 255; //take random portion of + or - x speed, scaled by roughness + parallelspeed += donatespeed; + donatespeed = abs(donatespeed); + incomingspeed -= incomingspeed > 0 ? donatespeed : -donatespeed; + } + +} + // apply a force in x,y direction to individual particle // 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 (gives good results) @@ -616,7 +632,7 @@ void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) yflipped = maxYpixel - y; for (int x = 0; x <= maxXpixel; x++) { - colorbuffer[x][y] = SEGMENT.getPixelColorXY(x, yflipped); + colorbuffer[x][y] = SEGMENT.getPixelColorXY(x, yflipped); //copy to local buffer fast_color_scale(colorbuffer[x][y], motionBlur); } } @@ -695,42 +711,19 @@ void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) }*/ if (useLocalBuffer) { - if (pxlbrightness[0] > 0) - fast_color_add(colorbuffer[pixco[0][0]][pixco[0][1]], baseRGB, pxlbrightness[0]); // bottom left - if (pxlbrightness[1] > 0) - fast_color_add(colorbuffer[pixco[1][0]][pixco[1][1]], baseRGB, pxlbrightness[1]); // bottom right - if (pxlbrightness[2] > 0) - fast_color_add(colorbuffer[pixco[2][0]][pixco[2][1]], baseRGB, pxlbrightness[2]); // top right - if (pxlbrightness[3] > 0) - fast_color_add(colorbuffer[pixco[3][0]][pixco[3][1]], baseRGB, pxlbrightness[3]); // top left + for(uint32_t i = 0; i < 4; i ++) + { + if (pxlbrightness[i] > 0) + fast_color_add(colorbuffer[pixco[i][0]][pixco[i][1]], baseRGB, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left + } } else - { - if (pxlbrightness[0] > 0) - SEGMENT.addPixelColorXY(pixco[0][0], maxYpixel - pixco[0][1], baseRGB.scale8((uint8_t)pxlbrightness[0])); // bottom left - if (pxlbrightness[1] > 0) - SEGMENT.addPixelColorXY(pixco[1][0], maxYpixel - pixco[1][1], baseRGB.scale8((uint8_t)pxlbrightness[1])); // bottom right - if (pxlbrightness[2] > 0) - SEGMENT.addPixelColorXY(pixco[2][0], maxYpixel - pixco[2][1], baseRGB.scale8((uint8_t)pxlbrightness[2])); // top right - if (pxlbrightness[3] > 0) - SEGMENT.addPixelColorXY(pixco[3][0], maxYpixel - pixco[3][1], baseRGB.scale8((uint8_t)pxlbrightness[3])); // top left - /* - uint32_t color = RGBW32(baseRGB.r, baseRGB.g, baseRGB.b, 0); - if (pxlbrightness[0] > 0) - SEGMENT.addPixelColorXY(pixco[0][0], maxYpixel - pixco[0][1], color_scale(color, pxlbrightness[0])); // bottom left - if (pxlbrightness[1] > 0) - SEGMENT.addPixelColorXY(pixco[1][0], maxYpixel - pixco[1][1], color_scale(color, pxlbrightness[1])); // bottom right - if (pxlbrightness[2] > 0) - SEGMENT.addPixelColorXY(pixco[2][0], maxYpixel - pixco[2][1], color_scale(color, pxlbrightness[2])); // top right - if (pxlbrightness[3] > 0) - SEGMENT.addPixelColorXY(pixco[3][0], maxYpixel - pixco[3][1], color_scale(color, pxlbrightness[3])); // top left - */ - - - - // test to render larger pixels with minimal effort (not working yet, need to calculate coordinate from actual dx position but brightness seems right), could probably be extended to 3x3 - // SEGMENT.addPixelColorXY(pixco[1][0] + 1, maxYpixel - pixco[1][1], baseRGB.scale8((uint8_t)((brightness>>1) - pxlbrightness[0])), fastcoloradd); - // SEGMENT.addPixelColorXY(pixco[2][0] + 1, maxYpixel - pixco[2][1], baseRGB.scale8((uint8_t)((brightness>>1) -pxlbrightness[3])), fastcoloradd); + { + for(uint32_t i = 0; i < 4; i ++) + { + if (pxlbrightness[i] > 0) + SEGMENT.addPixelColorXY(pixco[i][0], maxYpixel - pixco[i][1], baseRGB.scale8((uint8_t)pxlbrightness[i])); + } } } @@ -738,6 +731,7 @@ void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) { if (useLocalBuffer) { + //uint32_t firstblur = particlesize > 64 ? 64 : particlesize; //attempt to add blurring, but does not work... blur2D(colorbuffer, maxYpixel + 1, maxYpixel + 1, particlesize, particlesize); if (particlesize > 64) blur2D(colorbuffer, maxYpixel + 1, maxYpixel + 1, particlesize - 64, particlesize - 64); @@ -745,8 +739,6 @@ void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) blur2D(colorbuffer, maxYpixel + 1, maxYpixel + 1, (particlesize - 128) << 1, (particlesize - 128) << 1); if (particlesize > 192) blur2D(colorbuffer, maxYpixel + 1, maxYpixel + 1, (particlesize - 192) << 1, (particlesize - 192) << 1); - - //Serial.println("blr"); } else { @@ -855,6 +847,11 @@ void ParticleSystem::renderParticle(PSparticle* particle, uint32_t brightess, in pixelvalues[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightess) >> PS_P_SURFACE if (pixelvalues[3] >= 0) pixelvalues[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightess) >> PS_P_SURFACE + + //TODO: for advance pixels, render them to larger size in a local buffer. or better make a new function for that? + //easiest would be to create a 2x2 buffer for the original values, but that may not be as fast for smaller pixels... + //just make a new function that these colors are rendered to and then blurr it so it stays fast for normal rendering. + /* Serial.print("x:"); Serial.print(particle->x); @@ -922,8 +919,7 @@ void ParticleSystem::renderParticle(PSparticle* particle, uint32_t brightess, in // particles move upwards faster if ttl is high (i.e. they are hotter) void ParticleSystem::fireParticleupdate() { - //TODO: cleanup this function? check if normal move is much slower, change move function to check y first and check again - //todo: kill out of bounds funktioniert nicht? + //TODO: cleanup this function? check if normal move is much slower, change move function to check y first then this function just needs to add ttl to y befor calling normal move function uint32_t i = 0; for (i = 0; i < usedParticles; i++) @@ -935,7 +931,7 @@ void ParticleSystem::fireParticleupdate() // apply velocity particles[i].x = particles[i].x + (int32_t)particles[i].vx; particles[i].y = particles[i].y + (int32_t)particles[i].vy + (particles[i].ttl >> 2); // younger particles move faster upward as they are hotter - //particles[i].y = particles[i].y + (int32_t)particles[i].vy;// + (particles[i].ttl >> 3); // younger particles move faster upward as they are hotter //!! shift ttl by 2 is the original value, this is experimental + //particles[i].y = particles[i].y + (int32_t)particles[i].vy;// + (particles[i].ttl >> 3); // younger particles move faster upward as they are hotter //this is experimental, different shifting particles[i].outofbounds = 0; // check if particle is out of bounds, wrap x around to other side if wrapping is enabled // as fire particles start below the frame, lots of particles are out of bounds in y direction. to improve speed, only check x direction if y is not out of bounds @@ -1233,6 +1229,8 @@ int32_t ParticleSystem::wraparound(int32_t p, int32_t maxvalue) //force is in 3.4 fixedpoint notation, +/-127 int32_t ParticleSystem::calcForce_dv(int8_t force, uint8_t* counter) { + if(force == 0) + return 0; // for small forces, need to use a delay counter int32_t force_abs = abs(force); // absolute value (faster than lots of if's only 7 instructions) int32_t dv; @@ -1243,7 +1241,7 @@ int32_t ParticleSystem::calcForce_dv(int8_t force, uint8_t* counter) if (*counter > 15) { *counter -= 16; - dv = (force < 0) ? -1 : ((force > 0) ? 1 : 0); // force is either, 1, 0 or -1 if it is small + dv = force < 0 ? -1 : 1; // force is either, 1 or -1 if it is small (zero force is handled above) } } else @@ -1396,6 +1394,11 @@ bool initParticleSystem(ParticleSystem *&PartSys, uint8_t requestedsources, bool return true; } +/////////////////////// +// Utility Functions // +/////////////////////// + + // fastled color adding is very inaccurate in color preservation // a better color add function is implemented in colors.cpp but it uses 32bit RGBW. to use it colors need to be shifted just to then be shifted back by that function, which is slow // this is a fast version for RGB (no white channel, PS does not handle white) and with native CRGB including scaling of second color (fastled scale8 can be made faster using native 32bit on ESP) @@ -1444,9 +1447,9 @@ void fast_color_scale(CRGB &c, uint32_t scale) //blur a matrix in x and y direction, blur can be asymmetric in x and y -//for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) +//for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined //note: classic WLED blurrint is not (yet) supportet, this is a smearing function that does not fade original image, just blurrs it -void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool particleblur) +void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool smear, bool particleblur) { //uint32_t keep = smear ? 255 : 255 - xblur; CRGB seeppart, carryover; @@ -1467,17 +1470,19 @@ void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, { seeppart = colorbuffer[x][y]; //create copy of current color fast_color_scale(seeppart, seep); //scale it and seep to neighbours + if(!smear) //fade current pixel if smear is disabled + fast_color_scale(colorbuffer[x][y], 255 - xblur); + if(x > 0) { fast_color_add(colorbuffer[x-1][y], seeppart); fast_color_add(colorbuffer[x][y], carryover); //todo: could check if carryover is > 0, takes 7 instructions, add takes ~35, with lots of same color pixels (like background), it would be faster - } + } carryover = seeppart; } fast_color_add(colorbuffer[xsize-1][y], carryover); // set last pixel } - //uint32_t keep = smear ? 255 : 255 - xblur; seep = yblur >> 1; for(uint32_t x = 0; x < xsize; x++) { @@ -1486,6 +1491,9 @@ void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, { seeppart = colorbuffer[x][y]; //create copy of current color fast_color_scale(seeppart, seep); //scale it and seep to neighbours + if(!smear) //fade current pixel if smear is disabled + fast_color_scale(colorbuffer[x][y], 255 - xblur); + if(y > 0) { fast_color_add(colorbuffer[x][y-1], seeppart); diff --git a/wled00/FXparticleSystem.h b/wled00/FXparticleSystem.h index 2c90b9e53d..55696e052e 100644 --- a/wled00/FXparticleSystem.h +++ b/wled00/FXparticleSystem.h @@ -156,6 +156,7 @@ class ParticleSystem void setUsedParticles(uint16_t num); void setCollisionHardness(uint8_t hardness); //hardness for particle collisions (255 means full hard) void setWallHardness(uint8_t hardness); //hardness for bouncing on the wall if bounceXY is set + void setWallRoughness(uint8_t roughness); //wall roughness randomizes wall collisions void setMatrixSize(uint16_t x, uint16_t y); void setWrapX(bool enable); void setWrapY(bool enable); @@ -193,7 +194,8 @@ class ParticleSystem //utility functions void updatePSpointers(bool isadvanced); // update the data pointers to current segment data space - int32_t wraparound(int32_t w, int32_t maxvalue); + void bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition); //bounce on a wall + int32_t wraparound(int32_t p, int32_t maxvalue); int32_t calcForce_dv(int8_t force, uint8_t *counter); int32_t limitSpeed(int32_t speed); CRGB **allocate2Dbuffer(uint32_t cols, uint32_t rows); @@ -201,7 +203,8 @@ class ParticleSystem // note: variables that are accessed often are 32bit for speed uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster int32_t collisionHardness; - int32_t wallHardness; + uint8_t wallHardness; + uint8_t wallRoughness; uint8_t gforcecounter; //counter for global gravity int8_t gforce; //gravity strength, default is 8 (negative is allowed, positive is downwards) uint8_t collisioncounter; //counter to handle collisions TODO: could use the SEGMENT.call? @@ -210,7 +213,7 @@ class ParticleSystem uint8_t saturation; //note: on advanced particles, set this to 255 to disable saturation rendering, any other value uses particle sat value uint8_t particlesize; //global particle size, 0 = 2 pixels, 255 = 10 pixels int32_t particleHardRadius; // hard surface radius of a particle, used for collision detection - uint8_t motionBlur; + uint8_t motionBlur; //enable motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0 }; //initialization functions (not part of class) @@ -221,4 +224,4 @@ bool allocateParticleSystemMemory(uint16_t numparticles, uint16_t numsources, bo //color add function void fast_color_add(CRGB &c1, CRGB &c2, uint32_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding) void fast_color_scale(CRGB &c, uint32_t scale); //fast scaling function using 32bit factor (keep it 0-255) and pointer -void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool particleblur = false); \ No newline at end of file +void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool smear = true, bool particleblur = false); \ No newline at end of file