From 8a1364086e154f0ed20935ba0211b905eb33c07c Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 22 Oct 2024 22:01:39 +0200 Subject: [PATCH] Started cleanup, speed improvement to rendering, renamed class - removed 'smar' parameter in blur functions as smear is always used - improved particle rendering (passing by reference, passing wrap parameters for faster access) - renamed class to ParticleSystem2D - removed some whitespaces - some reformating, removed some comments - minor tweaks - removed non-working line-attractor function --- wled00/FX.cpp | 72 +-- wled00/FXparticleSystem.cpp | 890 ++++++++++++++++-------------------- wled00/FXparticleSystem.h | 127 +++-- 3 files changed, 473 insertions(+), 616 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 439af30235..115f7d2820 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7806,7 +7806,7 @@ uint16_t mode_particlevortex(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint32_t i, j; if (SEGMENT.call == 0) // initialization @@ -7831,7 +7831,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(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -7945,7 +7945,7 @@ uint16_t mode_particlefireworks(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint8_t numRockets; uint32_t i = 0; uint32_t j = 0; @@ -7964,7 +7964,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(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -8118,7 +8118,7 @@ uint16_t mode_particlevolcano(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; PSsettings2D volcanosettings; volcanosettings.asByte = 0b00000100; // PS settings for volcano movement: bounceX is enabled uint8_t numSprays; // note: so far only one tested but more is possible @@ -8145,7 +8145,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(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -8196,7 +8196,7 @@ uint16_t mode_particlefire(void) if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint32_t i; // index variable uint32_t numFlames; // number of flames: depends on fire width. for a fire width of 16 pixels, about 25-30 flames give good results @@ -8208,7 +8208,7 @@ uint16_t mode_particlefire(void) numFlames = PartSys->numSources; } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -8308,7 +8308,7 @@ uint16_t mode_particlepit(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. { @@ -8319,7 +8319,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(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -8391,7 +8391,7 @@ uint16_t mode_particlewaterfall(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint8_t numSprays; uint32_t i = 0; @@ -8416,7 +8416,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(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! (TODO: ask how to handle this so it always works) @@ -8473,7 +8473,7 @@ uint16_t mode_particlebox(void) if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint32_t i; if (SEGMENT.call == 0) // initialization @@ -8500,7 +8500,7 @@ uint16_t mode_particlebox(void) SEGENV.aux0 = rand(); // position in perlin noise } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -8574,7 +8574,7 @@ uint16_t mode_particleperlin(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint32_t i; if (SEGMENT.call == 0) // initialization TODO: make this a PSinit function, this is needed in every particle FX but first, get this working. { @@ -8588,7 +8588,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(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -8643,7 +8643,7 @@ uint16_t mode_particleimpact(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint32_t i = 0; uint8_t MaxNumMeteors; PSsettings2D meteorsettings; @@ -8665,7 +8665,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(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! (TODO: ask how to handle this so it always works) @@ -8776,7 +8776,7 @@ uint16_t mode_particleattractor(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; PSsettings2D sourcesettings; sourcesettings.asByte = 0b00001100; // PS settings for bounceY, bounceY used for source movement (it always bounces whereas particles do not) PSparticle *attractor; // particle pointer to the attractor @@ -8801,7 +8801,7 @@ uint16_t mode_particleattractor(void) PartSys->setWallRoughness(200); //randomize wall bounce } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -8897,7 +8897,7 @@ uint16_t mode_particleattractor(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint32_t i = 0; PSparticle *attractor; // particle pointer to the attractor uint8_t *counters; // counters for the applied force @@ -8930,7 +8930,7 @@ uint16_t mode_particleattractor(void) PartSys->sources[0].var = 4; // emiting variation } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -9012,7 +9012,7 @@ uint16_t mode_particlespray(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; //uint8_t numSprays; const uint8_t hardness = 200; // collision hardness is fixed @@ -9029,7 +9029,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(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -9109,7 +9109,7 @@ uint16_t mode_particleGEQ(void) if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; if (SEGMENT.call == 0) // initialization { @@ -9119,7 +9119,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(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -9205,7 +9205,7 @@ uint16_t mode_particlecenterGEQ(void) if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint8_t numSprays; uint32_t i; @@ -9225,7 +9225,7 @@ if (SEGLEN == 1) PartSys->setKillOutOfBounds(true); } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -9286,7 +9286,7 @@ uint16_t mode_particleghostrider(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; PSsettings2D ghostsettings; ghostsettings.asByte = 0b0000011; //enable wrapX and wrapY @@ -9302,7 +9302,7 @@ uint16_t mode_particleghostrider(void) SEGENV.step = random(MAXANGLESTEP) - (MAXANGLESTEP>>1); // angle increment } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -9378,7 +9378,7 @@ uint16_t mode_particleblobs(void) { if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; if (SEGMENT.call == 0) { @@ -9392,7 +9392,7 @@ uint16_t mode_particleblobs(void) //PartSys->setParticleSize(0); //set global size to zero or motion blur cannot be used (is zero by default) } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -9504,7 +9504,7 @@ uint16_t mode_particlefractal(void) if (SEGLEN == 1) return mode_static(); - ParticleSystem *PartSys = NULL; + ParticleSystem2D *PartSys = NULL; uint32_t i; if (SEGMENT.call == 0) // initialization @@ -9514,7 +9514,7 @@ if (SEGLEN == 1) PartSys->setKillOutOfBounds(true); } else - PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS + PartSys = reinterpret_cast(SEGENV.data); // if not first call, just set the pointer to the PS if (PartSys == NULL) return mode_static(); // something went wrong, no data! @@ -10071,8 +10071,8 @@ uint16_t mode_particleSparkler(void) // Particle System settings PartSys->updateSystem(); // update system properties (dimensions and data pointers) - sparklersettings.wrapX = SEGMENT.check2; - sparklersettings.bounceX = !SEGMENT.check2; + sparklersettings.wrap = SEGMENT.check2; + sparklersettings.bounce = !SEGMENT.check2; numSparklers = PartSys->numSources; PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index 8d1cfcb41d..55551f2236 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -10,18 +10,18 @@ /* TODO: - -add function to 'update sources' so FX does not have to take care of that. FX can still implement its own version if so desired. + -add function to 'update sources' so FX does not have to take care of that. FX can still implement its own version if so desired. -add an x/y struct, do particle rendering using that, much easier to read */ -#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) +#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) #include "FXparticleSystem.h" #endif #ifndef WLED_DISABLE_PARTICLESYSTEM2D -ParticleSystem::ParticleSystem(uint16_t width, uint16_t height, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced, bool sizecontrol) +ParticleSystem2D::ParticleSystem2D(uint16_t width, uint16_t height, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced, bool sizecontrol) { //Serial.println("PS Constructor"); numSources = numberofsources; @@ -52,9 +52,9 @@ ParticleSystem::ParticleSystem(uint16_t width, uint16_t height, uint16_t numbero } // update function applies gravity, moves the particles, handles collisions and renders the particles -void ParticleSystem::update(void) +void ParticleSystem2D::update(void) { - PSadvancedParticle *advprop = NULL; + PSadvancedParticle *advprop = NULL; //apply gravity globally if enabled if (particlesettings.useGravity) applyGravity(); @@ -63,11 +63,11 @@ void ParticleSystem::update(void) if (advPartSize) { for (uint32_t i = 0; i < usedParticles; i++) - { + { updateSize(&advPartProps[i], &advPartSize[i]); } } - + // handle collisions (can push particles, must be done before updating particles or they can render out of bounds, causing a crash if using local buffer for speed) if (particlesettings.useCollisions) handleCollisions(); @@ -96,34 +96,34 @@ void ParticleSystem::update(void) } // update function for fire animation -void ParticleSystem::updateFire(uint32_t intensity, bool renderonly) +void ParticleSystem2D::updateFire(uint32_t intensity, bool renderonly) { if (!renderonly) fireParticleupdate(); ParticleSys_render(true, intensity); } -void ParticleSystem::setUsedParticles(uint32_t num) +void ParticleSystem2D::setUsedParticles(uint32_t num) { usedParticles = min(num, numParticles); //limit to max particles } -void ParticleSystem::setWallHardness(uint8_t hardness) +void ParticleSystem2D::setWallHardness(uint8_t hardness) { wallHardness = hardness; } -void ParticleSystem::setWallRoughness(uint8_t roughness) +void ParticleSystem2D::setWallRoughness(uint8_t roughness) { wallRoughness = roughness; } -void ParticleSystem::setCollisionHardness(uint8_t hardness) -{ +void ParticleSystem2D::setCollisionHardness(uint8_t hardness) +{ collisionHardness = (int)hardness + 1; } -void ParticleSystem::setMatrixSize(uint16_t x, uint16_t y) +void ParticleSystem2D::setMatrixSize(uint16_t x, uint16_t y) { maxXpixel = x - 1; // last physical pixel that can be drawn to maxYpixel = y - 1; @@ -131,44 +131,44 @@ void ParticleSystem::setMatrixSize(uint16_t x, uint16_t y) maxY = y * PS_P_RADIUS - 1; // this value is often needed (also by FX) to calculate positions } -void ParticleSystem::setWrapX(bool enable) +void ParticleSystem2D::setWrapX(bool enable) { particlesettings.wrapX = enable; } -void ParticleSystem::setWrapY(bool enable) +void ParticleSystem2D::setWrapY(bool enable) { particlesettings.wrapY = enable; } -void ParticleSystem::setBounceX(bool enable) +void ParticleSystem2D::setBounceX(bool enable) { particlesettings.bounceX = enable; } -void ParticleSystem::setBounceY(bool enable) +void ParticleSystem2D::setBounceY(bool enable) { particlesettings.bounceY = enable; } -void ParticleSystem::setKillOutOfBounds(bool enable) +void ParticleSystem2D::setKillOutOfBounds(bool enable) { particlesettings.killoutofbounds = enable; } -void ParticleSystem::setColorByAge(bool enable) +void ParticleSystem2D::setColorByAge(bool enable) { particlesettings.colorByAge = enable; } -void ParticleSystem::setMotionBlur(uint8_t bluramount) +void ParticleSystem2D::setMotionBlur(uint8_t bluramount) { if (particlesize == 0) // only allwo motion blurring on default particle size or advanced size(cannot combine motion blur with normal blurring used for particlesize, would require another buffer) motionBlur = bluramount; } // render size using smearing (see blur function) -void ParticleSystem::setParticleSize(uint8_t size) +void ParticleSystem2D::setParticleSize(uint8_t size) { particlesize = size; particleHardRadius = PS_P_MINHARDRADIUS + particlesize; // note: this sets size if not using advanced props @@ -177,25 +177,25 @@ void ParticleSystem::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 ParticleSystem::setGravity(int8_t force) -{ +void ParticleSystem2D::setGravity(int8_t force) +{ if (force) { gforce = force; particlesettings.useGravity = true; } - else + else particlesettings.useGravity = false; } -void ParticleSystem::enableParticleCollisions(bool enable, uint8_t hardness) // enable/disable gravity, optionally, set the force (force=8 is default) can be 1-255, 0 is also disable +void ParticleSystem2D::enableParticleCollisions(bool enable, uint8_t hardness) // enable/disable gravity, optionally, set the force (force=8 is default) can be 1-255, 0 is also disable { particlesettings.useCollisions = enable; collisionHardness = (int)hardness + 1; } // emit one particle with variation, returns index of last emitted particle (or -1 if no particle emitted) -int32_t ParticleSystem::sprayEmit(PSsource &emitter, uint32_t amount) +int32_t ParticleSystem2D::sprayEmit(PSsource &emitter, uint32_t amount) { bool success = false; for (uint32_t a = 0; a < amount; a++) @@ -208,16 +208,16 @@ int32_t ParticleSystem::sprayEmit(PSsource &emitter, uint32_t amount) if (particles[emitIndex].ttl == 0) // find a dead particle { success = true; - particles[emitIndex].vx = emitter.vx + random(-emitter.var, emitter.var); + particles[emitIndex].vx = emitter.vx + random(-emitter.var, emitter.var); particles[emitIndex].vy = emitter.vy + random(-emitter.var, emitter.var); - particles[emitIndex].x = emitter.source.x; - particles[emitIndex].y = emitter.source.y; + particles[emitIndex].x = emitter.source.x; + particles[emitIndex].y = emitter.source.y; particles[emitIndex].hue = emitter.source.hue; particles[emitIndex].sat = emitter.source.sat; particles[emitIndex].collide = emitter.source.collide; particles[emitIndex].ttl = random(emitter.minLife, emitter.maxLife); if (advPartProps) - advPartProps[emitIndex].size = emitter.size; + advPartProps[emitIndex].size = emitter.size; break; } } @@ -229,7 +229,7 @@ int32_t ParticleSystem::sprayEmit(PSsource &emitter, uint32_t amount) } // Spray emitter for particles used for flames (particle TTL depends on source TTL) -void ParticleSystem::flameEmit(PSsource &emitter) +void ParticleSystem2D::flameEmit(PSsource &emitter) { int emitIndex = sprayEmit(emitter); if(emitIndex > 0) particles[emitIndex].ttl += emitter.source.ttl; @@ -237,22 +237,22 @@ void ParticleSystem::flameEmit(PSsource &emitter) // 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 ParticleSystem::angleEmit(PSsource &emitter, uint16_t angle, int8_t speed, uint32_t amount) +int32_t ParticleSystem2D::angleEmit(PSsource &emitter, uint16_t angle, int8_t speed, uint32_t amount) { - emitter.vx = ((int32_t)cos16(angle) * (int32_t)speed) / (int32_t)32600; // cos16() and sin16() return signed 16bit, division should be 32767 but 32600 gives slightly better rounding + emitter.vx = ((int32_t)cos16(angle) * (int32_t)speed) / (int32_t)32600; // cos16() and sin16() return signed 16bit, division should be 32767 but 32600 gives slightly better rounding emitter.vy = ((int32_t)sin16(angle) * (int32_t)speed) / (int32_t)32600; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! return sprayEmit(emitter, amount); } // particle moves, decays and dies, if killoutofbounds is set, out of bounds particles are set to ttl=0 // uses passed settings to set bounce or wrap, if useGravity is set, it will never bounce at the top and killoutofbounds is not applied over the top -void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings2D *options, PSadvancedParticle *advancedproperties) +void ParticleSystem2D::particleMoveUpdate(PSparticle &part, PSsettings2D *options, PSadvancedParticle *advancedproperties) { if (options == NULL) options = &particlesettings; //use PS system settings by default if (part.ttl > 0) { - if (!part.perpetual) + if (!part.perpetual) part.ttl--; // age if (particlesettings.colorByAge) part.hue = part.ttl > 255 ? 255 : part.ttl; //set color to ttl @@ -263,25 +263,25 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings2D *options, part.outofbounds = 0; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) if (advancedproperties) //using individual particle size? - { + { if (advancedproperties->size > 0) usesize = true; // note: variable eases out of frame checking below particleHardRadius = max(PS_P_MINHARDRADIUS, (int)particlesize + (advancedproperties->size)); } // if wall collisions are enabled, bounce them before they reach the edge, it looks much nicer if the particle is not half out of view - if (options->bounceX) + if (options->bounceX) { if ((newX < particleHardRadius) || (newX > maxX - particleHardRadius)) // reached a wall bounce(part.vx, part.vy, newX, maxX); } - + if ((newX < 0) || (newX > maxX)) // check if particle reached an edge (note: this also checks out of bounds and must not be skipped, even if bounce is enabled) - { + { if (options->wrapX) - { - newX = newX % (maxX + 1); + { + newX = newX % (maxX + 1); if (newX < 0) - newX += maxX + 1; + newX += maxX + 1; } else if (((newX <= -PS_P_HALFRADIUS) || (newX > maxX + PS_P_HALFRADIUS))) // particle is leaving, set out of bounds if it has fully left { @@ -289,9 +289,9 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings2D *options, if (usesize) // using individual particle size { if (((newX > -particleHardRadius) && (newX < maxX + particleHardRadius))) // large particle is not yet leaving the view - note: this is not pixel perfect but good enough - isleaving = false; + isleaving = false; } - + if (isleaving) { part.outofbounds = 1; @@ -301,21 +301,21 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings2D *options, } } - if (options->bounceY) + if (options->bounceY) { if ((newY < particleHardRadius) || ((newY > maxY - particleHardRadius) && !options->useGravity)) // reached floor / ceiling - { - bounce(part.vy, part.vx, newY, maxY); + { + bounce(part.vy, part.vx, newY, maxY); } } - + if (((newY < 0) || (newY > maxY))) // check if particle reached an edge (makes sure particles are within frame for rendering) { if (options->wrapY) { - newY = newY % (maxY + 1); + newY = newY % (maxY + 1); if (newY < 0) - newY += maxY + 1; + newY += maxY + 1; } else if (((newY <= -PS_P_HALFRADIUS) || (newY > maxY + PS_P_HALFRADIUS))) // particle is leaving, set out of bounds if it has fully left { @@ -323,7 +323,7 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings2D *options, if (usesize) // using individual particle size { if (((newY > -particleHardRadius) && (newY < maxY + particleHardRadius))) // still withing rendering reach - isleaving = false; + isleaving = false; } if (isleaving) { @@ -343,10 +343,9 @@ void ParticleSystem::particleMoveUpdate(PSparticle &part, PSsettings2D *options, } } -// update advanced particle size control -void ParticleSystem::updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize) -{ - if (advsize == NULL) // just a safety check +// update advanced particle size control +void ParticleSystem2D::updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize) { + if (advsize == NULL) // safety check return; // grow/shrink particle int32_t newsize = advprops->size; @@ -355,77 +354,68 @@ void ParticleSystem::updateSize(PSadvancedParticle *advprops, PSsizeControl *adv // calculate grow speed using 0-8 for low speeds and 9-15 for higher speeds if (advsize->grow) increment = advsize->growspeed; else if (advsize->shrink) increment = advsize->shrinkspeed; - if (increment < 9) // 8 means +1 every frame - { + if (increment < 9) { // 8 means +1 every frame counter += increment; - if (counter > 7) - { + if (counter > 7) { counter -= 8; increment = 1; - } - else + } else increment = 0; - advsize->sizecounter = counter; - } - else{ + advsize->sizecounter = counter; + } else { increment = (increment - 8) << 1; // 9 means +2, 10 means +4 etc. 15 means +14 } - if (advsize->grow) - { - if (newsize < advsize->maxsize) - { - newsize += increment; - if (newsize >= advsize->maxsize) - { + + if (advsize->grow) { + if (newsize < advsize->maxsize) { + newsize += increment; + if (newsize >= advsize->maxsize) { advsize->grow = false; // stop growing, shrink from now on if enabled newsize = advsize->maxsize; // limit if (advsize->pulsate) advsize->shrink = true; } } - } - else if (advsize->shrink) - { - if (newsize > advsize->minsize) - { - newsize -= increment; - if (newsize <= advsize->minsize) - { - //if (advsize->minsize == 0) part.ttl = 0; //TODO: need to pass particle or return kill instruction + } else if (advsize->shrink) { + if (newsize > advsize->minsize) { + newsize -= increment; + if (newsize <= advsize->minsize) { + //if (advsize->minsize == 0) part.ttl = 0; //TODO: need to pass particle or return kill instruction advsize->shrink = false; // disable shrinking - newsize = advsize->minsize; // limit + newsize = advsize->minsize; // limit if (advsize->pulsate) advsize->grow = true; } } } advprops->size = newsize; // handle wobbling - if (advsize->wobble) - { + if (advsize->wobble) { advsize->asymdir += advsize->wobblespeed; // todo: need better wobblespeed control? counter is already in the struct... } } // calculate x and y size for asymmetrical particles (advanced size control) -void ParticleSystem::getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize) +void ParticleSystem2D::getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize) { - if (advsize == NULL) // if advanced size is valid, also advced properties pointer is valid (handled by pointer assignment function) + if (advsize == NULL) // if advsize is valid, also advanced properties pointer is valid (handled by updatePSpointers()) return; - int32_t deviation = ((uint32_t)advprops->size * (uint32_t)advsize->asymmetry) / 255; // deviation from symmetrical size + int32_t size = advprops->size; + int32_t asymdir = advsize->asymdir; + int32_t deviation = ((uint32_t)size * (uint32_t)advsize->asymmetry) / 255; // deviation from symmetrical size // Calculate x and y size based on deviation and direction (0 is symmetrical, 64 is x, 128 is symmetrical, 192 is y) - if (advsize->asymdir < 64) { - deviation = ((int32_t)advsize->asymdir * deviation) / 64; - } else if (advsize->asymdir < 192) { - deviation = ((128 - (int32_t)advsize->asymdir) * deviation) / 64; + if (asymdir < 64) { + deviation = (asymdir * deviation) / 64; + } else if (asymdir < 192) { + deviation = ((128 - asymdir) * deviation) / 64; } else { - deviation = (((int32_t)advsize->asymdir - 255) * deviation) / 64; + deviation = ((asymdir - 255) * deviation) / 64; } // Calculate x and y size based on deviation, limit to 255 (rendering function cannot handle lareger sizes) - xsize = ((int32_t)advprops->size - deviation) > 255 ? 255 : advprops->size - deviation; - ysize = ((int32_t)advprops->size + deviation) > 255 ? 255 : advprops->size + deviation; + xsize = (size - deviation) > 255 ? 255 : size - deviation; + ysize = (size + deviation) > 255 ? 255 : size + deviation; } // 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) +void ParticleSystem2D::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, uint16_t maxposition) { incomingspeed = -incomingspeed; incomingspeed = (incomingspeed * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface @@ -433,12 +423,11 @@ void ParticleSystem::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_ position = particleHardRadius; // fast particles will never reach the edge if position is inverted, this looks better else position = maxposition - particleHardRadius; - if (wallRoughness) - { + if (wallRoughness) { int32_t incomingspeed_abs = abs((int32_t)incomingspeed); int32_t totalspeed = incomingspeed_abs + abs((int32_t)parallelspeed); // transfer an amount of incomingspeed speed to parallel speed - int32_t donatespeed = (random(-incomingspeed_abs, incomingspeed_abs) * (int32_t)wallRoughness) / (int32_t)255; //take random portion of + or - perpendicular speed, scaled by roughness + int32_t donatespeed = (random(-incomingspeed_abs, incomingspeed_abs) * (int32_t)wallRoughness) / (int32_t)255; //take random portion of + or - perpendicular speed, scaled by roughness parallelspeed = limitSpeed((int32_t)parallelspeed + donatespeed); //give the remainder of the speed to perpendicular speed donatespeed = int8_t(totalspeed - abs(parallelspeed)); // keep total speed the same @@ -449,7 +438,7 @@ void ParticleSystem::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_ // 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) -void ParticleSystem::applyForce(PSparticle *part, int8_t xforce, int8_t yforce, uint8_t *counter) +void ParticleSystem2D::applyForce(PSparticle *part, int8_t xforce, int8_t yforce, uint8_t *counter) { // for small forces, need to use a delay counter uint8_t xcounter = (*counter) & 0x0F; // lower four bits @@ -468,8 +457,8 @@ void ParticleSystem::applyForce(PSparticle *part, int8_t xforce, int8_t yforce, part->vy = limitSpeed((int32_t)part->vy + dvy); } -// apply a force in x,y direction to individual particle using advanced particle properties -void ParticleSystem::applyForce(uint16_t particleindex, int8_t xforce, int8_t yforce) +// 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) { if (advPartProps == NULL) return; // no advanced properties available @@ -478,7 +467,7 @@ void ParticleSystem::applyForce(uint16_t particleindex, int8_t xforce, int8_t yf // apply a force in x,y direction to all particles // force is in 3.4 fixed point notation (see above) -void ParticleSystem::applyForce(int8_t xforce, int8_t yforce) +void ParticleSystem2D::applyForce(int8_t xforce, int8_t yforce) { // for small forces, need to use a delay counter uint8_t tempcounter; @@ -487,7 +476,7 @@ void ParticleSystem::applyForce(int8_t xforce, int8_t yforce) { tempcounter = forcecounter; applyForce(&particles[i], xforce, yforce, &tempcounter); - } + } forcecounter = tempcounter; //save value back } @@ -495,7 +484,7 @@ void ParticleSystem::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 ParticleSystem::applyAngleForce(PSparticle *part, int8_t force, uint16_t angle, uint8_t *counter) +void ParticleSystem2D::applyAngleForce(PSparticle *part, int8_t force, uint16_t angle, uint8_t *counter) { int8_t xforce = ((int32_t)force * cos16(angle)) / 32767; // force is +/- 127 int8_t yforce = ((int32_t)force * sin16(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! @@ -503,7 +492,7 @@ void ParticleSystem::applyAngleForce(PSparticle *part, int8_t force, uint16_t an applyForce(part, xforce, yforce, counter); } -void ParticleSystem::applyAngleForce(uint16_t particleindex, int8_t force, uint16_t angle) +void ParticleSystem2D::applyAngleForce(uint16_t particleindex, int8_t force, uint16_t angle) { if (advPartProps == NULL) return; // no advanced properties available @@ -512,7 +501,7 @@ void ParticleSystem::applyAngleForce(uint16_t particleindex, int8_t force, uint1 // apply a force in angular direction to all particles // angle is from 0-65535 (=0-360deg) angle = 0 means in positive x-direction (i.e. to the right) -void ParticleSystem::applyAngleForce(int8_t force, uint16_t angle) +void ParticleSystem2D::applyAngleForce(int8_t force, uint16_t angle) { int8_t xforce = ((int32_t)force * cos16(angle)) / 32767; // force is +/- 127 int8_t yforce = ((int32_t)force * sin16(angle)) / 32767; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! @@ -523,39 +512,39 @@ void ParticleSystem::applyAngleForce(int8_t force, uint16_t angle) // apply gravity to all particles using PS global gforce setting // 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 ParticleSystem::applyGravity() +void ParticleSystem2D::applyGravity() { int32_t dv = calcForce_dv(gforce, &gforcecounter); for (uint32_t i = 0; i < usedParticles; i++) { // note: not checking if particle is dead is faster as most are usually alive and if few are alive, rendering is fast anyways particles[i].vy = limitSpeed((int32_t)particles[i].vy - dv); - } + } } // 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) +void ParticleSystem2D::applyGravity(PSparticle *part) { uint32_t counterbkp = gforcecounter; int32_t dv = calcForce_dv(gforce, &gforcecounter); - gforcecounter = counterbkp; //save it back + 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 ParticleSystem::applyFriction(PSparticle *part, int32_t coefficient) +void ParticleSystem2D::applyFriction(PSparticle *part, int32_t coefficient) { int32_t friction = 255 - coefficient; // note: not checking if particle is dead can be done by caller (or can be omitted) // note2: cannot use right shifts as bit shifting in right direction is asymmetrical for positive and negative numbers and this needs to be accurate or things start to go to the left side. - part->vx = ((int16_t)part->vx * friction) / 255; - part->vy = ((int16_t)part->vy * friction) / 255; + part->vx = ((int16_t)part->vx * friction) / 255; + part->vy = ((int16_t)part->vy * friction) / 255; } // apply friction to all particles -void ParticleSystem::applyFriction(int32_t coefficient) +void ParticleSystem2D::applyFriction(int32_t coefficient) { for (uint32_t i = 0; i < usedParticles; i++) { @@ -565,7 +554,7 @@ void ParticleSystem::applyFriction(int32_t coefficient) } // attracts a particle to an attractor particle using the inverse square-law -void ParticleSystem::pointAttractor(uint16_t particleindex, PSparticle *attractor, uint8_t strength, bool swallow) +void ParticleSystem2D::pointAttractor(uint16_t particleindex, PSparticle *attractor, uint8_t strength, bool swallow) { if (advPartProps == NULL) return; // no advanced properties available @@ -579,9 +568,9 @@ void ParticleSystem::pointAttractor(uint16_t particleindex, PSparticle *attracto if (distanceSquared < 8192) { if (swallow) // particle is close, age it fast so it fades out, do not attract further - { + { if (particles[particleindex].ttl > 7) - particles[particleindex].ttl -= 8; + particles[particleindex].ttl -= 8; else { particles[particleindex].ttl = 0; @@ -598,69 +587,13 @@ void ParticleSystem::pointAttractor(uint16_t particleindex, PSparticle *attracto applyForce(particleindex, xforce, yforce); } -/* -//attract to a line (TODO: this is not yet working) -void ParticleSystem::lineAttractor(uint16_t particleindex, PSparticle *attractorcenter, uint16_t attractorangle, uint8_t strength) -{ - // Calculate the distance between the particle and the attractor - if (advPartProps == NULL) - return; // no advanced properties available - - // calculate a second point on the line - int32_t x1 = attractorcenter->x + (cos16(attractorangle) >> 5); - int32_t y1 = attractorcenter->y + (sin16(attractorangle) >> 5); - // calculate squared distance from particle to the line: - int32_t dx = (x1 - attractorcenter->x) >> 4; - int32_t dy = (y1 - attractorcenter->y) >> 4; - int32_t d = ((dx * (particles[particleindex].y - attractorcenter->y)) - (dy * (particles[particleindex].x - attractorcenter->x))) >> 8; - int32_t distanceSquared = (d * d) / (dx * dx + dy * dy); - - - // Calculate the force based on inverse square law - if (distanceSquared < 2) - { - distanceSquared = 1; - // distanceSquared = 4 * PS_P_RADIUS * PS_P_RADIUS; // limit the distance to avoid very high forces - } - - int32_t force = (((int32_t)strength << 16) / distanceSquared)>>10; - //apply force in a 90° angle to the line - int8_t xforce = (d > 0 ? 1 : -1) * (force * dy) / 100; // scale to a lower value, found by experimenting - int8_t yforce = (d > 0 ? -1 : 1) * (force * dx) / 100; // note: cannot use bit shifts as bit shifting is asymmetrical for positive and negative numbers and this needs to be accurate! - - Serial.print(" partx: "); - Serial.print(particles[particleindex].x); - Serial.print(" party "); - Serial.print(particles[particleindex].y); - Serial.print(" x1 "); - Serial.print(x1); - Serial.print(" y1 "); - Serial.print(y1); - Serial.print(" dx "); - Serial.print(dx); - Serial.print(" dy "); - Serial.print(dy); - Serial.print(" d: "); - Serial.print(d); - Serial.print(" dsq: "); - Serial.print(distanceSquared); - Serial.print(" force: "); - Serial.print(force); - Serial.print(" fx: "); - Serial.print(xforce); - Serial.print(" fy: "); - Serial.println(yforce); - - applyForce(particleindex, xforce, yforce); -}*/ - // 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 // warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds // fireintensity and firemode are optional arguments (fireintensity is only used in firemode) -void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) +void ParticleSystem2D::ParticleSys_render(bool firemode, uint32_t fireintensity) { - + CRGB baseRGB; bool useLocalBuffer = true; //use local rendering buffer, gives huge speed boost (at least 30% more FPS) CRGB **framebuffer = NULL; //local frame buffer @@ -669,7 +602,7 @@ void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) uint32_t brightness; // particle brightness, fades if dying if (useLocalBuffer) - { + { /* //memory fragmentation check: Serial.print("heap: "); @@ -687,7 +620,7 @@ void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) } else{ if (advPartProps) - { + { renderbuffer = allocate2Dbuffer(10, 10); //buffer to render individual particles to if size > 0. note: null checking is done when accessing it } if (motionBlur > 0) // using SEGMENT.fadeToBlackBy is much slower, this approximately doubles the speed of fade calculation @@ -705,15 +638,16 @@ void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) } } } - - if (!useLocalBuffer) //disabled or allocation above failed - { + + if (!useLocalBuffer) { //disabled or allocation above failed //Serial.println("NOT using local buffer!"); if (motionBlur > 0) SEGMENT.fadeToBlackBy(255 - motionBlur); else - SEGMENT.fill(BLACK); //clear the buffer before rendering to it + SEGMENT.fill(BLACK); //clear the buffer before rendering to it } + bool wrapX = particlesettings.wrapX; // use local variables for faster access + bool wrapY = particlesettings.wrapY; // go over particles and render them to the buffer for (i = 0; i < usedParticles; i++) { @@ -723,34 +657,34 @@ void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) // generate RGB values for particle if (firemode) { - brightness = (uint32_t)particles[i].ttl*(3 + (fireintensity >> 5)) + 20; - brightness = brightness > 255 ? 255 : brightness; // faster then using min() + brightness = (uint32_t)particles[i].ttl*(3 + (fireintensity >> 5)) + 20; + brightness = brightness > 255 ? 255 : brightness; baseRGB = ColorFromPalette(SEGPALETTE, brightness, 255); } else{ - brightness = particles[i].ttl > 255 ? 255 : particles[i].ttl; //faster then using min() + brightness = particles[i].ttl > 255 ? 255 : particles[i].ttl; baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255); - if (particles[i].sat < 255) + if (particles[i].sat < 255) { CHSV baseHSV = rgb2hsv_approximate(baseRGB); //convert to HSV baseHSV.s = particles[i].sat; //set the saturation baseRGB = (CRGB)baseHSV; // convert back to RGB } - } - renderParticle(framebuffer, i, brightness, baseRGB, renderbuffer); + } + renderParticle(framebuffer, i, brightness, baseRGB, renderbuffer, wrapX, wrapY); } if (particlesize > 0) { uint32_t passes = particlesize/64 + 1; // number of blur passes, four passes max - uint32_t bluramount = particlesize; + uint32_t bluramount = particlesize; uint32_t bitshift = 0; - + for(uint32_t i = 0; i < passes; i++) { if (i == 2) // for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) bitshift = 1; - + if (useLocalBuffer) blur2D(framebuffer, maxXpixel + 1, maxYpixel + 1, bluramount << bitshift, bluramount << bitshift); else @@ -770,15 +704,14 @@ void ParticleSystem::ParticleSys_render(bool firemode, uint32_t fireintensity) SEGMENT.setPixelColorXY((int)x, (int)yflipped, framebuffer[x][y]); } } - free(framebuffer); + free(framebuffer); } if (renderbuffer) - free(renderbuffer); + free(renderbuffer); } // calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer -void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, uint32_t brightness, CRGB color, CRGB **renderbuffer) -{ +void ParticleSystem2D::renderParticle(CRGB **framebuffer, const uint32_t& particleindex, const uint32_t& brightness, const CRGB& color, CRGB **renderbuffer, const bool& wrapX, const bool& wrapY) { int32_t pxlbrightness[4] = {0}; // note: pxlbrightness needs to be set to 0 or checking does not work int32_t pixco[4][2]; // physical pixel coordinates of the four pixels a particle is rendered to. x,y pairs bool advancedrender = false; // rendering for advanced particles @@ -786,81 +719,72 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, // subtract half a radius as the rendering algorithm always starts at the bottom left, this makes calculations more efficient int32_t xoffset = particles[particleindex].x - PS_P_HALFRADIUS; int32_t yoffset = particles[particleindex].y - PS_P_HALFRADIUS; - int32_t dx = xoffset % PS_P_RADIUS; //relativ particle position in subpixel space - int32_t dy = yoffset % PS_P_RADIUS; + int32_t dx = xoffset & (PS_P_RADIUS - 1); //relativ particle position in subpixel space + int32_t dy = yoffset & (PS_P_RADIUS - 1); // modulo replaced with bitwise AND, as radius is always a power of 2 int32_t x = xoffset >> PS_P_RADIUS_SHIFT; // divide by PS_P_RADIUS which is 64, so can bitshift (compiler may not optimize automatically) int32_t y = yoffset >> PS_P_RADIUS_SHIFT; // check if particle has advanced size properties and buffer is available - if (advPartProps) - { - if (advPartProps[particleindex].size > 0) - { - if (renderbuffer && framebuffer) - { + if (advPartProps && advPartProps[particleindex].size > 0) { + if (renderbuffer && framebuffer) { advancedrender = true; memset(renderbuffer[0], 0, 100 * sizeof(CRGB)); // clear the buffer, renderbuffer is 10x10 pixels } - else - return; // cannot render without buffers - } + else return; // cannot render without buffers } - // set the four raw pixel coordinates, the order is bottom left [0], bottom right[1], top right [2], top left [3] pixco[0][0] = pixco[3][0] = x; // bottom left & top left pixco[0][1] = pixco[1][1] = y; // bottom left & bottom right pixco[1][0] = pixco[2][0] = x + 1; // bottom right & top right pixco[2][1] = pixco[3][1] = y + 1; // top right & top left - // now check if any are out of frame. set values to -1 if they are so they can be easily checked after (no value calculation, no setting of pixelcolor if value < 0) - if (x < 0) // left pixels out of frame - { - dx = PS_P_RADIUS + dx; // if x<0, xoffset becomes negative (and so does dx), must adjust dx as modulo will flip its value (really old bug now finally fixed) + // now check if any are out of frame. set values to -1 if they are so they can be easily checked after (no value calculation, no setting of pixelcolor if value < 0) + if (x < 0) { // left pixels out of frame + dx += PS_P_RADIUS; // if x<0, xoffset becomes negative (and so does dx), must adjust dx as modulo will flip its value (really old bug now finally fixed) // note: due to inverted shift math, a particel at position -32 (xoffset = -64, dx = 64) is rendered at the wrong pixel position (it should be out of frame) // checking this above makes this algorithm slower (in frame pixels do not have to be checked), so just correct for it here: - if (dx == PS_P_RADIUS) - { + if (dx == PS_P_RADIUS) { pxlbrightness[1] = pxlbrightness[2] = -1; // pixel is actually out of matrix boundaries, do not render dx = 2; // fix for advanced renderer (it does render slightly out of frame particles) } - if (particlesettings.wrapX) // wrap x to the other side if required + if (wrapX) { // wrap x to the other side if required pixco[0][0] = pixco[3][0] = maxXpixel; - else + } else { pxlbrightness[0] = pxlbrightness[3] = -1; // pixel is out of matrix boundaries, do not render + } } - else if (pixco[1][0] > maxXpixel) // right pixels, only has to be checkt if left pixels did not overflow - { - if (particlesettings.wrapX) // wrap y to the other side if required + else if (pixco[1][0] > maxXpixel) { // right pixels, only has to be checkt if left pixels did not overflow + if (wrapX) { // wrap y to the other side if required pixco[1][0] = pixco[2][0] = 0; - else + } else { pxlbrightness[1] = pxlbrightness[2] = -1; + } } - if (y < 0) // bottom pixels out of frame - { - dy = PS_P_RADIUS + dy; //see note above - if (dy == PS_P_RADIUS) - { + if (y < 0) { // bottom pixels out of frame + dy += PS_P_RADIUS; //see note above + if (dy == PS_P_RADIUS) { pxlbrightness[2] = pxlbrightness[3] = -1; // pixel is actually out of matrix boundaries, do not render dy = 2; // fix for advanced renderer (it does render slightly out of frame particles) } - if (particlesettings.wrapY) // wrap y to the other side if required + if (wrapY) { // wrap y to the other side if required pixco[0][1] = pixco[1][1] = maxYpixel; - else + } else { pxlbrightness[0] = pxlbrightness[1] = -1; + } } - else if (pixco[2][1] > maxYpixel) // top pixels - { - if (particlesettings.wrapY) // wrap y to the other side if required + else if (pixco[2][1] > maxYpixel) { // top pixels + if (wrapY) { // wrap y to the other side if required pixco[2][1] = pixco[3][1] = 0; - else + } else { pxlbrightness[2] = pxlbrightness[3] = -1; + } } - - if (advancedrender) // always render full particles in advanced rendering, undo out of frame marking (faster than checking each time in code above) - { - for(uint32_t i = 0; i < 4; i++) + + if (advancedrender) { // always render full particles in advanced rendering, undo out of frame marking (faster than checking each time in code above) + for(uint32_t i = 0; i < 4; i++) { pxlbrightness[i] = 0; + } } // calculate brightness values for all four pixels representing a particle using linear interpolation @@ -870,19 +794,14 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, int32_t precal3 = dy * brightness; //calculate the values for pixels that are in frame - if (pxlbrightness[0] >= 0) - pxlbrightness[0] = (precal1 * precal2) >> PS_P_SURFACE; // bottom left value equal to ((PS_P_RADIUS - dx) * (PS_P_RADIUS-dy) * brightness) >> PS_P_SURFACE - if (pxlbrightness[1] >= 0) - pxlbrightness[1] = (dx * precal2) >> PS_P_SURFACE; // bottom right value equal to (dx * (PS_P_RADIUS-dy) * brightness) >> PS_P_SURFACE - if (pxlbrightness[2] >= 0) - pxlbrightness[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightness) >> PS_P_SURFACE - if (pxlbrightness[3] >= 0) - pxlbrightness[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightness) >> PS_P_SURFACE - - if (advancedrender) - { + if (pxlbrightness[0] >= 0) pxlbrightness[0] = (precal1 * precal2) >> PS_P_SURFACE; // bottom left value equal to ((PS_P_RADIUS - dx) * (PS_P_RADIUS-dy) * brightness) >> PS_P_SURFACE + if (pxlbrightness[1] >= 0) pxlbrightness[1] = (dx * precal2) >> PS_P_SURFACE; // bottom right value equal to (dx * (PS_P_RADIUS-dy) * brightness) >> PS_P_SURFACE + if (pxlbrightness[2] >= 0) pxlbrightness[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightness) >> PS_P_SURFACE + if (pxlbrightness[3] >= 0) pxlbrightness[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightness) >> PS_P_SURFACE + + if (advancedrender) { //render particle to a bigger size - //particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10 + //particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10 //first, render the pixel to the center of the renderbuffer, then apply 2D blurring fast_color_add(renderbuffer[4][4], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left fast_color_add(renderbuffer[5][4], color, pxlbrightness[1]); @@ -893,12 +812,10 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, uint32_t maxsize = advPartProps[particleindex].size; uint32_t xsize = maxsize; uint32_t ysize = maxsize; - if (advPartSize) // use advanced size control - { + if (advPartSize) { // use advanced size control if (advPartSize[particleindex].asymmetry > 0) getParticleXYsize(&advPartProps[particleindex], &advPartSize[particleindex], xsize, ysize); - maxsize = xsize; - if (ysize > maxsize) maxsize = ysize; //maxsize is now the bigger of the two + maxsize = (xsize > ysize) ? xsize : ysize; // choose the bigger of the two } maxsize = maxsize/64 + 1; // number of blur passes depends on maxsize, four passes max uint32_t bitshift = 0; @@ -908,11 +825,11 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, bitshift = 1; rendersize += 2; offset--; - blur2D(renderbuffer, rendersize, rendersize, xsize << bitshift, ysize << bitshift, true, offset, offset, true); - xsize = xsize > 64 ? xsize - 64 : 0; + blur2D(renderbuffer, rendersize, rendersize, xsize << bitshift, ysize << bitshift, offset, offset, true); + xsize = xsize > 64 ? xsize - 64 : 0; ysize = ysize > 64 ? ysize - 64 : 0; } - + // calculate origin coordinates to render the particle to in the framebuffer uint32_t xfb_orig = x - (rendersize>>1) + 1 - offset; uint32_t yfb_orig = y - (rendersize>>1) + 1 - offset; @@ -924,7 +841,7 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, xfb = xfb_orig + xrb; if (xfb > maxXpixel) { - if (particlesettings.wrapX) // wrap x to the other side if required + if (wrapX) // wrap x to the other side if required xfb = xfb % (maxXpixel + 1); //TODO: this did not work in 1D system but appears to work in 2D (wrapped pixels were offset) under which conditions does this not work? else continue; @@ -935,15 +852,15 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, yfb = yfb_orig + yrb; if (yfb > maxYpixel) { - if (particlesettings.wrapY) // wrap y to the other side if required + if (wrapY) // wrap y to the other side if required yfb = yfb % (maxYpixel + 1); else continue; } - fast_color_add(framebuffer[xfb][yfb], renderbuffer[xrb][yrb]); + fast_color_add(framebuffer[xfb][yfb], renderbuffer[xrb][yrb]); } } - } + } else // standard rendering { if (framebuffer) @@ -955,11 +872,11 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, } } else - { + { for(uint32_t i = 0; i < 4; i++) { if (pxlbrightness[i] > 0) - SEGMENT.addPixelColorXY(pixco[i][0], maxYpixel - pixco[i][1], color.scale8((uint8_t)pxlbrightness[i])); + SEGMENT.addPixelColorXY(pixco[i][0], maxYpixel - pixco[i][1], color.scale8((uint8_t)pxlbrightness[i])); } } } @@ -989,7 +906,7 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, { //Serial.print("^"); if (pxlbrightness[d] >= 0) - { + { Serial.print("uncought out of bounds: y:"); Serial.print(pixco[d][0]); Serial.print(" y:"); @@ -1008,7 +925,7 @@ void ParticleSystem::renderParticle(CRGB **framebuffer, uint32_t particleindex, // update & move particle, wraps around left/right if settings.wrapX is true, wrap around up/down if settings.wrapY is true // particles move upwards faster if ttl is high (i.e. they are hotter) -void ParticleSystem::fireParticleupdate() +void ParticleSystem2D::fireParticleupdate() { //TODO: cleanup this function? check if normal move is much slower, change move function to check y first then this function just needs to add ttl to y befor calling normal move function (this function uses 274bytes of flash) uint32_t i = 0; @@ -1021,7 +938,7 @@ void ParticleSystem::fireParticleupdate() particles[i].ttl--; // apply velocity particles[i].x = particles[i].x + (int32_t)particles[i].vx; - particles[i].y = particles[i].y + (int32_t)particles[i].vy + (particles[i].ttl >> 2); // younger particles move faster upward as they are hotter + particles[i].y = particles[i].y + (int32_t)particles[i].vy + (particles[i].ttl >> 2); // younger particles move faster upward as they are hotter //particles[i].y = particles[i].y + (int32_t)particles[i].vy;// + (particles[i].ttl >> 3); // younger particles move faster upward as they are hotter //this is experimental, different shifting particles[i].outofbounds = 0; // check if particle is out of bounds, wrap x around to other side if wrapping is enabled @@ -1037,7 +954,7 @@ void ParticleSystem::fireParticleupdate() { if (particlesettings.wrapX) { - particles[i].x = (uint16_t)particles[i].x % (maxX + 1); + particles[i].x = (uint16_t)particles[i].x % (maxX + 1); } else if ((particles[i].x < -PS_P_HALFRADIUS) || (particles[i].x > maxX + PS_P_HALFRADIUS)) //if fully out of view { @@ -1050,7 +967,7 @@ void ParticleSystem::fireParticleupdate() } // detect collisions in an array of particles and handle them -void ParticleSystem::handleCollisions() +void ParticleSystem2D::handleCollisions() { // detect and handle collisions uint32_t i, j; @@ -1058,11 +975,11 @@ void ParticleSystem::handleCollisions() uint32_t endparticle = usedParticles >> 1; // do half the particles, significantly speeds things up // every second frame, do other half of particles (helps to speed things up as not all collisions are handled each frame, less accurate but good enough) // if more accurate collisions are needed, just call it twice in a row - if (collisioncounter & 0x01) - { + if (collisioncounter & 0x01) + { startparticle = endparticle; endparticle = usedParticles; - } + } collisioncounter++; for (i = startparticle; i < endparticle; i++) @@ -1072,14 +989,14 @@ void ParticleSystem::handleCollisions() { int32_t dx, dy; // distance to other particles for (j = i + 1; j < usedParticles; j++) // check against higher number particles - { + { if (particles[j].ttl > 0 && particles[j].collide) // if target particle is alive { dx = particles[i].x - particles[j].x; if (advPartProps) //may be using individual particle size - { + { particleHardRadius = PS_P_MINHARDRADIUS + particlesize + (((uint32_t)advPartProps[i].size + (uint32_t)advPartProps[j].size)>>1); // collision distance - } + } if (dx < particleHardRadius && dx > -particleHardRadius) // check x direction, if close, check y direction { dy = particles[i].y - particles[j].y; @@ -1094,23 +1011,23 @@ void ParticleSystem::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 ParticleSystem::collideParticles(PSparticle *particle1, PSparticle *particle2) // 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) // TODO: dx,dy is calculated just above, can pass it over here to save a few CPU cycles? { int32_t dx = particle2->x - particle1->x; - int32_t dy = particle2->y - particle1->y; + int32_t dy = particle2->y - particle1->y; int32_t distanceSquared = dx * dx + dy * dy; // Calculate relative velocity (if it is zero, could exit but extra check does not overall speed but deminish it) int32_t relativeVx = (int16_t)particle2->vx - (int16_t)particle1->vx; int32_t relativeVy = (int16_t)particle2->vy - (int16_t)particle1->vy; // if dx and dy are zero (i.e. they meet at the center) give them an offset, if speeds are also zero, also offset them (pushes them apart if they are clumped before enabling collisions) - if (distanceSquared == 0) + if (distanceSquared == 0) { - // Adjust positions based on relative velocity direction - dx = -1; + // Adjust positions based on relative velocity direction + dx = -1; if (relativeVx < 0) // if true, particle2 is on the right side dx = 1; - else if (relativeVx == 0) + else if (relativeVx == 0) relativeVx = 1; dy = -1; @@ -1128,7 +1045,7 @@ void ParticleSystem::collideParticles(PSparticle *particle1, PSparticle *particl if (dotProduct < 0) // particles are moving towards each other { - // integer math used to avoid floats. + // integer math used to avoid floats. // overflow check: dx/dy are 7bit, relativV are 8bit -> dotproduct is 15bit, dotproduct/distsquared ist 8b, multiplied by collisionhardness of 8bit. so a 16bit shift is ok, make it 15 to be sure no overflows happen // note: cannot use right shifts as bit shifting in right direction is asymmetrical for positive and negative numbers and this needs to be accurate! the trick is: only shift positive numers // Calculate new velocities after collision @@ -1144,7 +1061,7 @@ void ParticleSystem::collideParticles(PSparticle *particle1, PSparticle *particl if (collisionHardness < surfacehardness) // 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); - particle1->vx = ((int32_t)particle1->vx * 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; @@ -1159,18 +1076,18 @@ void ParticleSystem::collideParticles(PSparticle *particle1, PSparticle *particl particle2->vy = (particle2->vy < 3 && particle2->vy > -3) ? 0 : particle2->vy; }*/ } - + // particles have volume, push particles apart if they are too close // tried lots of configurations, it works best if not moved but given a little velocity, it tends to oscillate less this way // a problem with giving velocity is, that on harder collisions, this adds up as it is not dampened enough, so add friction in the FX if required if (dotProduct > -250) //this means particles are slow (or really really close) so push them apart. - { + { int32_t pushamount = 1 + ((250 + dotProduct) >> 6); // the closer dotproduct is to zero, the closer the particles are int32_t push = 0; if (dx < 0) // particle 1 is on the right - push = pushamount; + push = pushamount; else if (dx > 0) - push = -pushamount; + push = -pushamount; else // on the same x coordinate, shift it a little so they do not stack { if (notsorandom) @@ -1181,9 +1098,9 @@ void ParticleSystem::collideParticles(PSparticle *particle1, PSparticle *particl 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; + push = pushamount; else if (dy > 0) - push = -pushamount; + push = -pushamount; else // dy==0 { if (notsorandom) @@ -1197,7 +1114,7 @@ void ParticleSystem::collideParticles(PSparticle *particle1, PSparticle *particl { particle1->vx = 0; particle1->vy = 0; - particle2->vx = 0; + particle2->vx = 0; particle2->vy = 0; //push them apart particle1->x += push; @@ -1208,8 +1125,8 @@ void ParticleSystem::collideParticles(PSparticle *particle1, PSparticle *particl } // allocate memory for the 2D array in one contiguous block and set values to zero -CRGB **ParticleSystem::allocate2Dbuffer(uint32_t cols, uint32_t rows) -{ +CRGB **ParticleSystem2D::allocate2Dbuffer(uint32_t cols, uint32_t rows) +{ CRGB ** array2D = (CRGB **)calloc(cols, sizeof(CRGB *) + rows * sizeof(CRGB)); if (array2D == NULL) DEBUG_PRINT(F("PS buffer alloc failed")); @@ -1227,7 +1144,7 @@ CRGB **ParticleSystem::allocate2Dbuffer(uint32_t cols, uint32_t rows) // update size and pointers (memory location and size can change dynamically) // note: do not access the PS class in FX befor running this function (or it messes up SEGENV.data) -void ParticleSystem::updateSystem(void) +void ParticleSystem2D::updateSystem(void) { // update matrix size uint32_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; @@ -1239,28 +1156,24 @@ void ParticleSystem::updateSystem(void) // set the pointers for the class (this only has to be done once and not on every FX call, only the class pointer needs to be reassigned to SEGENV.data every time) // 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 ParticleSystem::updatePSpointers(bool isadvanced, bool sizecontrol) -{ +void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) { // DEBUG_PRINT(F("*** PS pointers ***")); // DEBUG_PRINTF_P(PSTR("this PS %p "), this); // Note on memory alignment: // a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment. // The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock. // by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes. - particles = reinterpret_cast(this + 1); // pointer to particle array at data+sizeof(ParticleSystem) + particles = reinterpret_cast(this + 1); // pointer to particle array at data+sizeof(ParticleSystem2D) sources = reinterpret_cast(particles + numParticles); // pointer to source(s) - if (isadvanced) - { + if (isadvanced) { advPartProps = reinterpret_cast(sources + numSources); PSdataEnd = reinterpret_cast(advPartProps + numParticles); - if (sizecontrol) - { + if (sizecontrol) { advPartSize = reinterpret_cast(advPartProps + numParticles); PSdataEnd = reinterpret_cast(advPartSize + numParticles); - } + } } - else - { + else { PSdataEnd = reinterpret_cast(sources + numSources); // pointer to first available byte after the PS for FX additional data } /* @@ -1273,29 +1186,21 @@ void ParticleSystem::updatePSpointers(bool isadvanced, bool sizecontrol) } // 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) or result is undefined +// for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined // to blur a subset of the buffer, change the xsize/ysize and set xstart/ystart to the desired starting coordinates (default start is 0/0) -void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool smear, uint32_t xstart, uint32_t ystart, bool isparticle) -{ +void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, uint32_t xstart, uint32_t ystart, bool isparticle) { CRGB seeppart, carryover; - uint32_t seep = xblur >> 1; - if (isparticle) //first and last row are always black in particle rendering - { + uint32_t seep = xblur >> 1; + if (isparticle) { //first and last row are always black in particle rendering ystart++; ysize--; } - for(uint32_t y = ystart; y < ystart + ysize; y++) - { + for(uint32_t y = ystart; y < ystart + ysize; y++) { carryover = BLACK; - for(uint32_t x = xstart; x < xstart + xsize; x++) - { + for(uint32_t x = xstart; x < xstart + xsize; x++) { seeppart = colorbuffer[x][y]; // create copy of current color fast_color_scale(seeppart, seep); // scale it and seep to neighbours - if (!smear) // fade current pixel if smear is disabled - fast_color_scale(colorbuffer[x][y], 255 - xblur); - - if (x > 0) - { + 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 } @@ -1304,25 +1209,18 @@ void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, fast_color_add(colorbuffer[xsize-1][y], carryover); // set last pixel } - if (isparticle) // now also do first and last row - { + if (isparticle) { // now also do first and last row ystart--; ysize++; } seep = yblur >> 1; - for(uint32_t x = xstart; x < xstart + xsize; x++) - { + for(uint32_t x = xstart; x < xstart + xsize; x++) { carryover = BLACK; - for(uint32_t y = ystart; y < ystart + ysize; y++) - { + for(uint32_t y = ystart; y < ystart + ysize; y++) { seeppart = colorbuffer[x][y]; // create copy of current color fast_color_scale(seeppart, seep); // scale it and seep to neighbours - if (!smear) // fade current pixel if smear is disabled - fast_color_scale(colorbuffer[x][y], 255 - yblur); - - if (y > 0) - { + if (y > 0) { fast_color_add(colorbuffer[x][y-1], seeppart); fast_color_add(colorbuffer[x][y], carryover); // todo: could check if carryover is > 0, takes 7 instructions, add takes ~35, with lots of same color pixels (like background), it would be faster } @@ -1352,7 +1250,7 @@ uint32_t calculateNumberOfParticles2D(bool isadvanced, bool sizecontrol) if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount numberofParticles = (numberofParticles * sizeof(PSparticle)) / (sizeof(PSparticle) + sizeof(PSadvancedParticle)); if (sizecontrol) // advanced property array needs ram, reduce number of particles to use the same amount - numberofParticles /= 8; // if size control is used, much fewer particles are needed + numberofParticles /= 8; // if size control is used, much fewer particles are needed //make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes) numberofParticles = ((numberofParticles+3) >> 2) << 2; @@ -1381,7 +1279,7 @@ uint32_t calculateNumberOfSources2D(uint8_t requestedsources) //allocate memory for particle system class, particles, sprays plus additional memory requested by FX bool allocateParticleSystemMemory2D(uint16_t numparticles, uint16_t numsources, bool isadvanced, bool sizecontrol, uint16_t additionalbytes) { - uint32_t requiredmemory = sizeof(ParticleSystem); + uint32_t requiredmemory = sizeof(ParticleSystem2D); // functions above make sure these are a multiple of 4 bytes (to avoid alignment issues) requiredmemory += sizeof(PSparticle) * numparticles; if (isadvanced) @@ -1399,7 +1297,7 @@ bool allocateParticleSystemMemory2D(uint16_t numparticles, uint16_t numsources, } // initialize Particle System, allocate additional bytes if needed (pointer to those bytes can be read from particle system class: PSdataEnd) -bool initParticleSystem2D(ParticleSystem *&PartSys, uint8_t requestedsources, uint16_t additionalbytes, bool advanced, bool sizecontrol) +bool initParticleSystem2D(ParticleSystem2D *&PartSys, uint8_t requestedsources, uint16_t additionalbytes, bool advanced, bool sizecontrol) { //Serial.println("PS init function"); uint32_t numparticles = calculateNumberOfParticles2D(advanced, sizecontrol); @@ -1416,7 +1314,7 @@ bool initParticleSystem2D(ParticleSystem *&PartSys, uint8_t requestedsources, ui uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); //Serial.println("calling constructor"); - PartSys = new (SEGENV.data) ParticleSystem(cols, rows, numparticles, numsources, advanced, sizecontrol); // particle system constructor + PartSys = new (SEGENV.data) ParticleSystem2D(cols, rows, numparticles, numsources, advanced, sizecontrol); // particle system constructor //Serial.print("PS pointer at "); //Serial.println((uintptr_t)PartSys); return true; @@ -1430,7 +1328,7 @@ bool initParticleSystem2D(ParticleSystem *&PartSys, uint8_t requestedsources, ui //////////////////////// #ifndef WLED_DISABLE_PARTICLESYSTEM1D -ParticleSystem1D::ParticleSystem1D(uint16_t length, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced) +ParticleSystem1D::ParticleSystem1D(uint16_t length, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced) { //Serial.println("PS Constructor"); numSources = numberofsources; @@ -1440,7 +1338,7 @@ ParticleSystem1D::ParticleSystem1D(uint16_t length, uint16_t numberofparticles, //advPartSize = NULL; updatePSpointers(isadvanced); // set the particle and sources pointer (call this before accessing sprays or particles) setSize(length); - setWallHardness(255); // set default wall hardness to max + setWallHardness(255); // set default wall hardness to max setGravity(0); //gravity disabled by default setParticleSize(0); // minimum size by default motionBlur = 0; //no fading by default @@ -1448,16 +1346,16 @@ ParticleSystem1D::ParticleSystem1D(uint16_t length, uint16_t numberofparticles, //initialize some default non-zero values most FX use for (uint32_t i = 0; i < numSources; i++) - { + { sources[i].source.ttl = 1; //set source alive - } + } //Serial.println("PS Constructor done"); } // update function applies gravity, moves the particles, handles collisions and renders the particles void ParticleSystem1D::update(void) { - PSadvancedParticle1D *advprop = NULL; + PSadvancedParticle1D *advprop = NULL; // handle collisions (can push particles, must be done before updating particles or they can render out of bounds, causing a crash if using local buffer for speed) if (particlesettings.useCollisions) handleCollisions(); @@ -1490,8 +1388,8 @@ void ParticleSystem1D::update(void) if (bg_color > 0) //if not black { for(int32_t i = 0; i < maxXpixel + 1; i++) - { - SEGMENT.addPixelColor(i,bg_color); + { + SEGMENT.addPixelColor(i,bg_color); } } } @@ -1515,12 +1413,12 @@ void ParticleSystem1D::setSize(uint16_t x) void ParticleSystem1D::setWrap(bool enable) { - particlesettings.wrapX = enable; + particlesettings.wrap = enable; } void ParticleSystem1D::setBounce(bool enable) { - particlesettings.bounceX = enable; + particlesettings.bounce = enable; } void ParticleSystem1D::setKillOutOfBounds(bool enable) @@ -1542,29 +1440,29 @@ void ParticleSystem1D::setMotionBlur(uint8_t bluramount) { //TODO: currently normal blurring is not used in 1D system. should it be added? advanced rendering is quite fast and allows for motion blurring // if (particlesize < 2) // only allwo motion blurring on default particle size or advanced size(cannot combine motion blur with normal blurring used for particlesize, would require another buffer) - motionBlur = bluramount; + motionBlur = bluramount; } -// render size, 0 = 1 pixel, 1 = 2 pixel (interpolated), bigger sizes require adanced properties +// render size, 0 = 1 pixel, 1 = 2 pixel (interpolated), bigger sizes require adanced properties // note: if size is set larger than 1 without advanced properties, weird things may happen void ParticleSystem1D::setParticleSize(uint8_t size) { particlesize = size; particleHardRadius = PS_P_MINHARDRADIUS_1D >> 1; // 1 pixel sized particles have half the radius (for bounce, not for collisions) if (particlesize) - particleHardRadius = particleHardRadius << 1; // 2 pixel sized particles + particleHardRadius = particleHardRadius << 1; // 2 pixel sized particles } // 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(int8_t force) +{ if (force) { gforce = force; particlesettings.useGravity = true; } - else + else particlesettings.useGravity = false; } @@ -1584,11 +1482,11 @@ int32_t ParticleSystem1D::sprayEmit(PSsource1D &emitter) emitIndex = 0; if (particles[emitIndex].ttl == 0) // find a dead particle { - particles[emitIndex].vx = emitter.v + random(-emitter.var, emitter.var); - particles[emitIndex].x = emitter.source.x; - particles[emitIndex].hue = emitter.source.hue; + particles[emitIndex].vx = emitter.v + random(-emitter.var, emitter.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].reversegrav = emitter.source.reversegrav; particles[emitIndex].ttl = random16(emitter.minLife, emitter.maxLife); if (advPartProps) { @@ -1600,7 +1498,7 @@ int32_t ParticleSystem1D::sprayEmit(PSsource1D &emitter) return emitIndex; } } - return -1; + return -1; } // particle moves, decays and dies, if killoutofbounds is set, out of bounds particles are set to ttl=0 @@ -1611,16 +1509,16 @@ void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *opti options = &particlesettings; //use PS system settings by default if (part.ttl > 0) { - if (!part.perpetual) + if (!part.perpetual) part.ttl--; // age if (particlesettings.colorByAge) - part.hue = part.ttl > 250 ? 250 : part.ttl; //set color to ttl - - bool usesize = false; // particle uses individual size rendering + part.hue = part.ttl > 250 ? 250 : part.ttl; //set color to ttl + + bool usesize = false; // particle uses individual size rendering int32_t newX = part.x + (int16_t)part.vx; part.outofbounds = 0; // reset out of bounds (in case particle was created outside the matrix and is now moving into view) if (advancedproperties) //using individual particle size? - { + { particleHardRadius = PS_P_MINHARDRADIUS_1D + (advancedproperties->size >> 1); if (advancedproperties->size > 1) { @@ -1631,25 +1529,25 @@ void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *opti } // if wall collisions are enabled, bounce them before they reach the edge, it looks much nicer if the particle is not half out of view - if (options->bounceX) + if (options->bounce) { if ((newX < particleHardRadius) || ((newX > maxX - particleHardRadius))) // reached a wall { bool bouncethis = true; if (options->useGravity) { - if (part.reversegrav) //skip at x = 0 + if (part.reversegrav) //skip at x = 0 { - if (newX < particleHardRadius) - bouncethis = false; + if (newX < particleHardRadius) + bouncethis = false; } - else //skip at x = max + else //skip at x = max { - if (newX > particleHardRadius) + if (newX > particleHardRadius) bouncethis = false; } } - + if (bouncethis) { part.vx = -part.vx; //invert speed @@ -1662,12 +1560,12 @@ void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *opti } } if ((newX < 0) || (newX > maxX)) // check if particle reached an edge (note: this also checks out of bounds and must not be skipped, even if bounce is enabled) - { - if (options->wrapX) - { - newX = newX % (maxX + 1); + { + if (options->wrap) + { + newX = newX % (maxX + 1); if (newX < 0) - newX += maxX + 1; + newX += maxX + 1; } else if (((newX <= -PS_P_HALFRADIUS_1D) || (newX > maxX + PS_P_HALFRADIUS_1D))) // particle is leaving, set out of bounds if it has fully left { @@ -1675,29 +1573,29 @@ void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *opti if (usesize) // using individual particle size { if (((newX > -particleHardRadius) && (newX < maxX + particleHardRadius))) // large particle is not yet leaving the view - note: this is not pixel perfect but good enough - isleaving = false; + isleaving = false; } if (isleaving) { part.outofbounds = 1; 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 + if (part.reversegrav) //skip at x = 0 { - if (newX < 0) - killthis = false; + if (newX < 0) + killthis = false; } - else //skip at x = max + else //skip at x = max { - if (newX > 0) + if (newX > 0) killthis = false; } } if (killthis) - part.ttl = 0; + part.ttl = 0; } } } @@ -1731,7 +1629,7 @@ void ParticleSystem1D::applyForce(int8_t xforce) { tempcounter = forcecounter; applyForce(&particles[i], xforce, &tempcounter); - } + } forcecounter = tempcounter; //save value back } @@ -1746,7 +1644,7 @@ void ParticleSystem1D::applyGravity() if (particles[i].reversegrav) dv = -dv_raw; // 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 = limitSpeed((int32_t)particles[i].vx - dv); - } + } } // apply gravity to single particle using system settings (use this for sources) @@ -1755,8 +1653,8 @@ void ParticleSystem1D::applyGravity(PSparticle1D *part) { uint32_t counterbkp = gforcecounter; int32_t dv = calcForce_dv(gforce, &gforcecounter); - if (part->reversegrav) dv = -dv; - gforcecounter = counterbkp; //save it back + if (part->reversegrav) dv = -dv; + gforcecounter = counterbkp; //save it back part->vx = limitSpeed((int32_t)part->vx - dv); } @@ -1769,7 +1667,7 @@ void ParticleSystem1D::applyFriction(int32_t coefficient) for (uint32_t i = 0; i < usedParticles; i++) { if (particles[i].ttl) - particles[i].vx = ((int16_t)particles[i].vx * friction) / 255; + particles[i].vx = ((int16_t)particles[i].vx * friction) / 255; } } @@ -1779,16 +1677,16 @@ void ParticleSystem1D::applyFriction(int32_t coefficient) // warning: do not render out of bounds particles or system will crash! rendering does not check if particle is out of bounds void ParticleSystem1D::ParticleSys_render() { - + CRGB baseRGB; bool useLocalBuffer = true; //use local rendering buffer, gives huge speed boost (at least 30% more FPS) CRGB *framebuffer = NULL; //local frame buffer CRGB *renderbuffer = NULL; //local particle render buffer for advanced particles uint32_t i; uint32_t brightness; // particle brightness, fades if dying - + if (useLocalBuffer) - { + { // allocate empty memory for the local renderbuffer framebuffer = allocate1Dbuffer(maxXpixel + 1); @@ -1799,11 +1697,11 @@ void ParticleSystem1D::ParticleSys_render() } else{ if (advPartProps) - { + { renderbuffer = allocate1Dbuffer(10); //buffer to render individual particles to if size > 0. note: null checking is done when accessing it } if (motionBlur > 0) // using SEGMENT.fadeToBlackBy is much slower, this approximately doubles the speed of fade calculation - { + { for (uint32_t x = 0; x <= maxXpixel; x++) { framebuffer[x] = SEGMENT.getPixelColor(x); //copy to local buffer @@ -1812,15 +1710,16 @@ void ParticleSystem1D::ParticleSys_render() } } } - + if (!useLocalBuffer) //disabled or allocation above failed { //Serial.println("NOT using local buffer!"); if (motionBlur > 0) SEGMENT.fadeToBlackBy(255 - motionBlur); else - SEGMENT.fill(BLACK); //clear the buffer before rendering to it + SEGMENT.fill(BLACK); //clear the buffer before rendering to it } + bool wrap = particlesettings.wrap; // local copy for speed // go over particles and render them to the buffer for (i = 0; i < usedParticles; i++) { @@ -1830,17 +1729,17 @@ void ParticleSystem1D::ParticleSys_render() // generate RGB values for particle brightness = particles[i].ttl > 255 ? 255 : particles[i].ttl; //faster then using min() baseRGB = ColorFromPalette(SEGPALETTE, particles[i].hue, 255, LINEARBLEND); - + if (advPartProps) //saturation is advanced property in 1D system { - if (advPartProps[i].sat < 255) + if (advPartProps[i].sat < 255) { CHSV baseHSV = rgb2hsv_approximate(baseRGB); //convert to HSV baseHSV.s = advPartProps[i].sat; //set the saturation baseRGB = (CRGB)baseHSV; // convert back to RGB } } - renderParticle(framebuffer, i, brightness, baseRGB, renderbuffer); + renderParticle(framebuffer, i, brightness, baseRGB, renderbuffer, wrap); } if (useLocalBuffer) // transfer local buffer back to segment @@ -1848,137 +1747,120 @@ void ParticleSystem1D::ParticleSys_render() for (int x = 0; x <= maxXpixel; x++) { SEGMENT.setPixelColor(x, framebuffer[x]); - } - free(framebuffer); + } + free(framebuffer); } if (renderbuffer) - free(renderbuffer); + free(renderbuffer); } // calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer -void ParticleSystem1D::renderParticle(CRGB *framebuffer, uint32_t particleindex, uint32_t brightness, CRGB color, CRGB *renderbuffer) -{ +void ParticleSystem1D::renderParticle(CRGB *framebuffer, const uint32_t &particleindex, const uint32_t &brightness, const CRGB &color, CRGB *renderbuffer, const bool &wrap) { uint32_t size = particlesize; - if (advPartProps) // use advanced size properties - { + if (advPartProps) {// use advanced size properties size = advPartProps[particleindex].size; } if (size == 0) //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles { uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D; - if (x <= maxXpixel) //by making x unsigned there is no need to check < 0 as it will overflow - { - if (framebuffer) - fast_color_add(framebuffer[x], color, brightness); - else - SEGMENT.addPixelColor(x, color.scale8((uint8_t)brightness)); - } + if (x <= maxXpixel) { //by making x unsigned there is no need to check < 0 as it will overflow + if (framebuffer) + fast_color_add(framebuffer[x], color, brightness); + else + SEGMENT.addPixelColor(x, color.scale8((uint8_t)brightness)); + } } else { //render larger particles bool pxlisinframe[2] = {true, true}; - int32_t pxlbrightness[2]; - int32_t pixco[2]; // physical pixel coordinates of the two pixels representing a particle + int32_t pxlbrightness[2]; + int32_t pixco[2]; // physical pixel coordinates of the two pixels representing a particle // subtract half a radius as the rendering algorithm always starts at the left, this makes calculations more efficient int32_t xoffset = particles[particleindex].x - PS_P_HALFRADIUS_1D; int32_t dx = xoffset % PS_P_RADIUS_1D; //relativ particle position in subpixel space int32_t x = xoffset >> PS_P_RADIUS_SHIFT_1D; // divide by PS_P_RADIUS, bitshift of negative number stays negative -> checking below for x < 0 works (but does not when using division) - // set the raw pixel coordinates pixco[0] = x; // left pixel pixco[1] = x + 1; // right pixel - - // now check if any are out of frame. set values to -1 if they are so they can be easily checked after (no value calculation, no setting of pixelcolor if value < 0) - if (x < 0) // left pixels out of frame - { - dx = PS_P_RADIUS_1D + dx; // if x<0, xoffset becomes negative (and so does dx), must adjust dx as modulo will flip its value + + // now check if any are out of frame. set values to -1 if they are so they can be easily checked after (no value calculation, no setting of pixelcolor if value < 0) + if (x < 0) { // left pixels out of frame + dx = PS_P_RADIUS_1D + dx; // if x<0, xoffset becomes negative (and so does dx), must adjust dx as modulo will flip its value // note: due to inverted shift math, a particel at position -16 (xoffset = -32, dx = 32) is rendered at the wrong pixel position (it should be out of frame) // checking this above would make this algorithm slower (in frame pixels do not have to be checked), so just correct for it here: - if (dx == PS_P_RADIUS_1D) - { + if (dx == PS_P_RADIUS_1D) { pxlisinframe[1] = false; // pixel is actually out of matrix boundaries, do not render dx = 0; // fix for out of frame advanced particles (dx=0 is changed to dx=PS_P_RADIUS_1D in above statement, 0 is correct) } - if (particlesettings.wrapX) // wrap x to the other side if required + if (wrap) // wrap x to the other side if required pixco[0] = maxXpixel; else pxlisinframe[0] = false; // pixel is out of matrix boundaries, do not render } - else if (pixco[1] > maxXpixel) // right pixel, only has to be checkt if left pixel did not overflow - { - if (particlesettings.wrapX) // wrap y to the other side if required + else if (pixco[1] > maxXpixel) { // right pixel, only has to be checkt if left pixel did not overflow + if (wrap) // wrap y to the other side if required pixco[1] = 0; else pxlisinframe[1] = false; } //calculate the brightness values for both pixels using linear interpolation (note: in standard rendering out of frame pixels could be skipped but if checks add more clock cycles over all) - pxlbrightness[0] = (((int32_t)PS_P_RADIUS_1D - dx) * brightness) >> PS_P_SURFACE_1D; - pxlbrightness[1] = (dx * brightness) >> PS_P_SURFACE_1D; + pxlbrightness[0] = (((int32_t)PS_P_RADIUS_1D - dx) * brightness) >> PS_P_SURFACE_1D; + pxlbrightness[1] = (dx * brightness) >> PS_P_SURFACE_1D; // check if particle has advanced size properties and buffer is available - if (advPartProps && advPartProps[particleindex].size > 1) - { - if (renderbuffer && framebuffer) - { + if (advPartProps && advPartProps[particleindex].size > 1) { + if (renderbuffer && framebuffer) { memset(renderbuffer, 0, 10 * sizeof(CRGB)); // clear the buffer, renderbuffer is 10 pixels } else return; // cannot render advanced particles without buffer - //render particle to a bigger size //particle size to pixels: 2 - 63 is 4 pixels, < 128 is 6pixels, < 192 is 8 pixels, bigger is 10 pixels //first, render the pixel to the center of the renderbuffer, then apply 1D blurring - fast_color_add(renderbuffer[4], color, pxlbrightness[0]); - fast_color_add(renderbuffer[5], color, pxlbrightness[1]); + fast_color_add(renderbuffer[4], color, pxlbrightness[0]); + fast_color_add(renderbuffer[5], color, pxlbrightness[1]); uint32_t rendersize = 2; // initialize render size, minimum is 4 pixels, it is incremented int he loop below to start with 4 uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below) uint32_t blurpasses = size/64 + 1; // number of blur passes depends on size, four passes max uint32_t bitshift = 0; - for(int i = 0; i < blurpasses; i++) - { + for(int i = 0; i < blurpasses; i++) { if (i == 2) //for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) bitshift = 1; rendersize += 2; offset--; - blur1D(renderbuffer, rendersize, size << bitshift, true, offset); - size = size > 64 ? size - 64 : 0; + blur1D(renderbuffer, rendersize, size << bitshift, offset); + size = size > 64 ? size - 64 : 0; } - + // calculate origin coordinates to render the particle to in the framebuffer uint32_t xfb_orig = x - (rendersize>>1) + 1 - offset; //note: using uint is fine uint32_t xfb; // coordinates in frame buffer to write to note: by making this uint, only overflow has to be checked // transfer particle renderbuffer to framebuffer - for(uint32_t xrb = offset; xrb < rendersize+offset; xrb++) - { + for(uint32_t xrb = offset; xrb < rendersize+offset; xrb++) { xfb = xfb_orig + xrb; - if (xfb > maxXpixel) - { - if (particlesettings.wrapX) // wrap x to the other side if required - { + if (xfb > maxXpixel) { + if (wrap) { // wrap x to the other side if required if (xfb > maxXpixel << 1) // xfb is "negative" (note: for some reason, this check is needed in 1D but works without in 2D...) xfb = (maxXpixel +1) + (int32_t)xfb; else - xfb = xfb % (maxXpixel + 1); + xfb = xfb % (maxXpixel + 1); } else continue; } - fast_color_add(framebuffer[xfb], renderbuffer[xrb]); + fast_color_add(framebuffer[xfb], renderbuffer[xrb]); } } - else // standard rendering (2 pixels per particle) - { - for(uint32_t i = 0; i < 2; i++) - { - if (pxlisinframe[i]) - { + else { // standard rendering (2 pixels per particle) + for(uint32_t i = 0; i < 2; i++) { + if (pxlisinframe[i]) { if (framebuffer) - fast_color_add(framebuffer[pixco[i]], color, pxlbrightness[i]); + fast_color_add(framebuffer[pixco[i]], color, pxlbrightness[i]); else - SEGMENT.addPixelColor(pixco[i], color.scale8((uint8_t)pxlbrightness[i])); + SEGMENT.addPixelColor(pixco[i], color.scale8((uint8_t)pxlbrightness[i])); } } } @@ -1995,33 +1877,33 @@ void ParticleSystem1D::handleCollisions() // every second frame, do other half of particles (helps to speed things up as not all collisions are handled each frame, less accurate but good enough) // if more accurate collisions are needed, just call it twice in a row /*if (SEGMENT.call & 0x01) //every other frame, do the other half - { + { startparticle = endparticle; endparticle = usedParticles; } */ int32_t collisiondistance = PS_P_MINHARDRADIUS_1D; - + for (i = startparticle; i < endparticle; i++) { // go though all 'higher number' particles and see if any of those are in close proximity and if they are, make them collide if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) // if particle is alive and does collide and is not out of view { - int32_t dx; // distance to other particles + int32_t dx; // distance to other particles for (j = i + 1; j < usedParticles; j++) // check against higher number particles - { + { if (particles[j].ttl > 0 && particles[j].collide) // if target particle is alive { if (advPartProps) // use advanced size properties { collisiondistance = PS_P_MINHARDRADIUS_1D + ((uint32_t)advPartProps[i].size + (uint32_t)advPartProps[j].size)>>1; } - dx = particles[j].x - particles[i].x; - int32_t dv = (int32_t)particles[j].vx - (int32_t)particles[i].vx; + dx = particles[j].x - particles[i].x; + int32_t dv = (int32_t)particles[j].vx - (int32_t)particles[i].vx; int32_t proximity = collisiondistance; if (dv >= proximity) //particles would go past each other in next move upate proximity += abs(dv); //add speed difference to catch fast particles if (dx < proximity && dx > -proximity) // check if close - { + { collideParticles(&particles[i], &particles[j], dx, dv, collisiondistance); } } @@ -2032,24 +1914,24 @@ 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, PSparticle1D *particle2, int32_t dx, int32_t relativeVx, uint32_t collisiondistance) { // Calculate dot product of relative velocity and relative distance int32_t dotProduct = (dx * relativeVx); // is always negative if moving towards each other // int32_t notsorandom = dotProduct & 0x01; //dotprouct LSB should be somewhat random, so no need to calculate a random number - //Serial.print(" dp"); Serial.print(dotProduct); + //Serial.print(" dp"); Serial.print(dotProduct); if (dotProduct < 0) // particles are moving towards each other { - // integer math used to avoid floats. + // integer math used to avoid floats. // Calculate new velocities after collision uint32_t surfacehardness = collisionHardness < PS_P_MINSURFACEHARDNESS_1D ? PS_P_MINSURFACEHARDNESS_1D : collisionHardness; // 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) - - int32_t impulse = relativeVx * surfacehardness / 255; - particle1->vx += impulse; + + int32_t impulse = relativeVx * surfacehardness / 255; + 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; @@ -2059,17 +1941,17 @@ void ParticleSystem1D::collideParticles(PSparticle1D *particle1, PSparticle1D *p 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; + particle1->vx = ((int32_t)particle1->vx * coeff) / 255; particle2->vx = ((int32_t)particle2->vx * coeff) / 255; - } + } } - uint32_t distance = abs(dx); - // particles have volume, push particles apart if they are too close + uint32_t distance = abs(dx); + // particles have volume, push particles apart if they are too close // behaviour is different than in 2D, we need pixel accurate stacking here, push the top particle to full radius (direction is well defined in 1D) // also need to give the top particle some speed to counteract gravity or stacks just collapse if (distance < collisiondistance) //particles are too close, push the upper particle away - { + { int32_t pushamount = 1 + ((collisiondistance - distance) >> 1); //add half the remaining distance note: this works best, if less or more is added, it gets more chaotic //int32_t pushamount = collisiondistance - distance; if (particlesettings.useGravity) //using gravity, push the 'upper' particle only @@ -2082,10 +1964,10 @@ void ParticleSystem1D::collideParticles(PSparticle1D *particle1, PSparticle1D *p particle2->vx--; } else if (!particle1->reversegrav && !particle1->fixed) - { + { particle1->x += pushamount; particle1->vx++; - } + } } else { @@ -2109,7 +1991,7 @@ void ParticleSystem1D::collideParticles(PSparticle1D *particle1, PSparticle1D *p particle1->vx -= pushamount; particle2->vx += pushamount; - } + } } } @@ -2117,10 +1999,10 @@ void ParticleSystem1D::collideParticles(PSparticle1D *particle1, PSparticle1D *p // allocate memory for the 1D array in one contiguous block and set values to zero CRGB *ParticleSystem1D::allocate1Dbuffer(uint32_t length) -{ +{ CRGB *array = (CRGB *)calloc(length, sizeof(CRGB)); //if (array == NULL) - // DEBUG_PRINT(F("PS 1D buffer alloc failed")); + // DEBUG_PRINT(F("PS 1D buffer alloc failed")); return array; } @@ -2155,9 +2037,9 @@ void ParticleSystem1D::updatePSpointers(bool isadvanced) //{ // advPartSize = reinterpret_cast(advPartProps + numParticles); // PSdataEnd = reinterpret_cast(advPartSize + numParticles); - //} + //} } - + /* DEBUG_PRINTF_P(PSTR(" particles %p "), particles); DEBUG_PRINTF_P(PSTR(" sources %p "), sources); @@ -2173,11 +2055,11 @@ uint32_t calculateNumberOfParticles1D(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 + uint32_t particlelimit = ESP8266_MAXPARTICLES_1D; // maximum number of paticles allowed #elif ARDUINO_ARCH_ESP32S2 - uint32_t particlelimit = ESP32S2_MAXPARTICLES_1D; // maximum number of paticles allowed + uint32_t particlelimit = ESP32S2_MAXPARTICLES_1D; // maximum number of paticles allowed #else - uint32_t particlelimit = ESP32_MAXPARTICLES_1D; // maximum number of paticles allowed + uint32_t particlelimit = ESP32_MAXPARTICLES_1D; // maximum number of paticles allowed #endif numberofParticles = max((uint32_t)1, min(numberofParticles, particlelimit)); if (isadvanced) // advanced property array needs ram, reduce number of particles to use the same amount @@ -2188,12 +2070,12 @@ uint32_t calculateNumberOfParticles1D(bool isadvanced) } uint32_t calculateNumberOfSources1D(uint8_t requestedsources) -{ -#ifdef ESP8266 +{ +#ifdef ESP8266 int numberofSources = max(1, min((int)requestedsources,ESP8266_MAXSOURCES_1D)); // limit to 1 - 8 #elif ARDUINO_ARCH_ESP32S2 int numberofSources = max(1, min((int)requestedsources, ESP32S2_MAXSOURCES_1D)); // limit to 1 - 16 -#else +#else int numberofSources = max(1, min((int)requestedsources, ESP32_MAXSOURCES_1D)); // limit to 1 - 32 #endif // make sure it is a multiple of 4 for proper memory alignment @@ -2243,35 +2125,30 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, uint32_t requestedsources, } -// blur a 1D buffer, sub-size blurring can be done using start and size -// for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined -// to blur a subset of the buffer, change the size and set start to the desired starting coordinates (default start is 0/0) -void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, bool smear, uint32_t start) +// blur a 1D buffer, sub-size blurring can be done using start and size +// for speed, 32bit variables are used, make sure to limit them to 8bit (0-255) or result is undefined +// to blur a subset of the buffer, change the size and set start to the desired starting coordinates +void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start) { CRGB seeppart, carryover; - uint32_t seep = blur >> 1; - - carryover = BLACK; - for(uint32_t x = start; x < start + size; x++) - { - seeppart = colorbuffer[x]; // 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], 255 - blur); - if (x > 0) - { - fast_color_add(colorbuffer[x-1], seeppart); - fast_color_add(colorbuffer[x], carryover); // is black on first pass - } - carryover = seeppart; + uint32_t seep = blur >> 1; + carryover = BLACK; + for(uint32_t x = start; x < start + size; x++) { + seeppart = colorbuffer[x]; // create copy of current color + fast_color_scale(seeppart, seep); // scale it and seep to neighbours + if (x > 0) { + fast_color_add(colorbuffer[x-1], seeppart); + fast_color_add(colorbuffer[x], carryover); // is black on first pass } - fast_color_add(colorbuffer[size-1], carryover); // set last pixel + carryover = seeppart; + } + fast_color_add(colorbuffer[size-1], carryover); // set last pixel } #endif // WLED_DISABLE_PARTICLESYSTEM1D -#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) +#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) ////////////////////////////// // Shared Utility Functions // @@ -2281,10 +2158,10 @@ void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, bool smear, uint32_ // force is in 3.4 fixedpoint notation, +/-127 int32_t calcForce_dv(int8_t force, uint8_t* counter) { - if (force == 0) + 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 force_abs = abs(force); // absolute value (faster than lots of if's only 7 instructions) int32_t dv = 0; // for small forces, need to use a delay counter, apply force only if it overflows if (force_abs < 16) @@ -2313,8 +2190,7 @@ int32_t limitSpeed(int32_t speed) // 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) // note: result is stored in c1, so c1 will contain the result. not using a return value is much faster as the struct does not need to be copied upon return -void fast_color_add(CRGB &c1, CRGB &c2, uint32_t scale) -{ +void fast_color_add(CRGB &c1, const CRGB &c2, uint32_t scale) { //note: function is manly used to add scaled colors, so checking if one color is black is slower uint32_t r, g, b; if (scale < 255) { @@ -2336,15 +2212,15 @@ void fast_color_add(CRGB &c1, CRGB &c2, uint32_t scale) c1.b = b; } else { - c1.r = (r * 255) / max; - c1.g = (g * 255) / max; - c1.b = (b * 255) / max; + uint32_t scale = (255 << 16) / max; // to avoid multiple divisions + c1.r = (r * scale) >> 16; // (c * 255) / max; + c1.g = (g * scale) >> 16; + c1.b = (b * scale) >> 16; } } // faster than fastled color scaling as it uses a 32bit scale factor and pointer -void fast_color_scale(CRGB &c, uint32_t scale) -{ +void fast_color_scale(CRGB &c, uint32_t scale) { c.r = ((c.r * scale) >> 8); c.g = ((c.g * scale) >> 8); c.b = ((c.b * scale) >> 8); diff --git a/wled00/FXparticleSystem.h b/wled00/FXparticleSystem.h index 83e6789c41..a14751bf33 100644 --- a/wled00/FXparticleSystem.h +++ b/wled00/FXparticleSystem.h @@ -8,20 +8,18 @@ Licensed under the EUPL v. 1.2 or later */ -#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) +#if !defined(WLED_DISABLE_PARTICLESYSTEM2D) || !defined(WLED_DISABLE_PARTICLESYSTEM1D) #include #include "wled.h" #define PS_P_MAXSPEED 120 // maximum speed a particle can have (vx/vy is int8) - //shared functions (used both in 1D and 2D system) -int32_t calcForce_dv(int8_t force, uint8_t *counter); //TODO: same as 2D function, could share -int32_t limitSpeed(int32_t speed); //TODO: same as 2D function, could share -void fast_color_add(CRGB &c1, CRGB &c2, uint32_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding) +int32_t calcForce_dv(int8_t force, uint8_t *counter); +int32_t limitSpeed(int32_t speed); +void fast_color_add(CRGB &c1, const 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 variable and pointer. note: keep 'scale' within 0-255 - #endif #ifndef WLED_DISABLE_PARTICLESYSTEM2D @@ -34,7 +32,7 @@ void fast_color_scale(CRGB &c, uint32_t scale); // fast scaling function using 3 #define ESP32_MAXSOURCES 64 // particle dimensions (subpixel division) -#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines) +#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement (must be a power of 2) #define PS_P_HALFRADIUS 32 #define PS_P_RADIUS_SHIFT 6 // shift for RADIUS #define PS_P_SURFACE 12 // shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2 @@ -42,11 +40,10 @@ void fast_color_scale(CRGB &c, uint32_t scale); // fast scaling function using 3 #define PS_P_MINSURFACEHARDNESS 128 // minimum hardness used in collision impulse calculation, below this hardness, particles become sticky // struct for PS settings (shared for 1D and 2D class) -typedef union -{ +typedef union { struct{ // one byte bit field for 2D settings - bool wrapX : 1; + bool wrapX : 1; bool wrapY : 1; bool bounceX : 1; bool bounceY : 1; @@ -61,7 +58,7 @@ typedef union //struct for a single particle (10 bytes) typedef struct { int16_t x; // x position in particle system - int16_t y; // y position in particle system + int16_t y; // y position in particle system int8_t vx; // horizontal velocity int8_t vy; // vertical velocity uint8_t hue; // color hue @@ -75,21 +72,19 @@ typedef struct { } PSparticle; // struct for additional particle settings (optional) -typedef struct -{ +typedef struct { uint8_t size; // particle size, 255 means 10 pixels in diameter uint8_t forcecounter; // counter for applying forces to individual particles } PSadvancedParticle; -// struct for advanced particle size control (optional) -typedef struct -{ +// struct for advanced particle size control (optional) +typedef struct { uint8_t asymmetry; // asymmetrical size (0=symmetrical, 255 fully asymmetric) uint8_t asymdir; // direction of asymmetry, 64 is x, 192 is y (0 and 128 is symmetrical) - uint8_t maxsize; // target size for growing + uint8_t maxsize; // target size for growing uint8_t minsize; // target size for shrinking uint8_t sizecounter : 4; // counters used for size contol (grow/shrink/wobble) - uint8_t wobblecounter : 4; + uint8_t wobblecounter : 4; uint8_t growspeed : 4; uint8_t shrinkspeed : 4; uint8_t wobblespeed : 4; @@ -107,27 +102,23 @@ typedef struct { PSparticle source; // use a particle as the emitter source (speed, position, color) int8_t var; // variation of emitted speed (adds random(+/- var) to speed) int8_t vx; // emitting speed - int8_t vy; + int8_t vy; uint8_t size; // particle size (advanced property) -} PSsource; +} PSsource; // class uses approximately 60 bytes -class ParticleSystem -{ +class ParticleSystem2D { public: - ParticleSystem(uint16_t width, uint16_t height, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced = false, bool sizecontrol = false); // constructor + ParticleSystem2D(uint16_t width, uint16_t height, uint16_t numberofparticles, uint16_t numberofsources, bool isadvanced = false, 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 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 - // particle emitters int32_t sprayEmit(PSsource &emitter, uint32_t amount = 1); void flameEmit(PSsource &emitter); int32_t angleEmit(PSsource& emitter, uint16_t angle, int8_t speed, uint32_t amount = 1); - - //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); @@ -140,7 +131,6 @@ class ParticleSystem void applyFriction(int32_t coefficient); // apply friction to all used particles void pointAttractor(uint16_t particleindex, PSparticle *attractor, uint8_t strength, bool swallow); void lineAttractor(uint16_t particleindex, PSparticle *attractorcenter, uint16_t attractorangle, uint8_t strength); - // set options void setUsedParticles(uint32_t num); void setCollisionHardness(uint8_t hardness); // hardness for particle collisions (255 means full hard) @@ -154,11 +144,11 @@ class ParticleSystem void setKillOutOfBounds(bool enable); // if enabled, particles outside of matrix instantly die void setSaturation(uint8_t sat); // set global color saturation void setColorByAge(bool enable); - void setMotionBlur(uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero + void setMotionBlur(uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero void setParticleSize(uint8_t size); void setGravity(int8_t force = 8); void enableParticleCollisions(bool enable, uint8_t hardness = 255); - + PSparticle *particles; // pointer to particle array PSsource *sources; // pointer to sources PSadvancedParticle *advPartProps; // pointer to advanced particle properties (can be NULL) @@ -168,19 +158,17 @@ class ParticleSystem int32_t maxXpixel, maxYpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 / height-1 uint32_t numSources; // number of sources uint32_t numParticles; // number of particles available in this system - uint32_t usedParticles; // number of particles used in animation (can be smaller then numParticles) + uint32_t usedParticles; // number of particles used in animation (can be smaller then numParticles) -private: +private: //rendering functions void ParticleSys_render(bool firemode = false, uint32_t fireintensity = 128); - void renderParticle(CRGB **framebuffer, uint32_t particleindex, uint32_t brightness, CRGB color, CRGB **renderbuffer); - + void renderParticle(CRGB **framebuffer, const uint32_t &particleindex, const uint32_t &brightness, const CRGB &color, CRGB **renderbuffer, 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); void fireParticleupdate(); - //utility functions void updatePSpointers(bool isadvanced, bool sizecontrol); // update the data pointers to current segment data space void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control @@ -188,7 +176,6 @@ class ParticleSystem 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); CRGB **allocate2Dbuffer(uint32_t cols, uint32_t rows); - // 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 emitIndex; // index to count through particles to emit so searching for dead pixels is faster @@ -205,13 +192,12 @@ class ParticleSystem uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0 }; -void blur2D(CRGB **colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblur, uint32_t yblur, bool smear = true, uint32_t xstart = 0, uint32_t ystart = 0, bool isparticle = false); +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); // initialization functions (not part of class) -bool initParticleSystem2D(ParticleSystem *&PartSys, uint8_t requestedsources, uint16_t additionalbytes = 0, bool advanced = false, bool sizecontrol = false); +bool initParticleSystem2D(ParticleSystem2D *&PartSys, uint8_t requestedsources, uint16_t additionalbytes = 0, bool advanced = false, bool sizecontrol = false); uint32_t calculateNumberOfParticles2D(bool advanced, bool sizecontrol); uint32_t calculateNumberOfSources2D(uint8_t requestedsources); bool allocateParticleSystemMemory2D(uint16_t numparticles, uint16_t numsources, bool advanced, bool sizecontrol, uint16_t additionalbytes); - #endif // WLED_DISABLE_PARTICLESYSTEM2D //////////////////////// @@ -219,8 +205,7 @@ bool allocateParticleSystemMemory2D(uint16_t numparticles, uint16_t numsources, //////////////////////// #ifndef WLED_DISABLE_PARTICLESYSTEM1D // memory allocation -//MAX_SEGMENT_DATA -#define ESP8266_MAXPARTICLES_1D 400 +#define ESP8266_MAXPARTICLES_1D 400 #define ESP8266_MAXSOURCES_1D 8 #define ESP32S2_MAXPARTICLES_1D 1900 #define ESP32S2_MAXSOURCES_1D 16 @@ -232,16 +217,15 @@ bool allocateParticleSystemMemory2D(uint16_t numparticles, uint16_t numsources, #define PS_P_HALFRADIUS_1D 16 #define PS_P_RADIUS_SHIFT_1D 5 //TODO: may need to adjust #define PS_P_SURFACE_1D 5 // shift: 2^PS_P_SURFACE = PS_P_RADIUS_1D -#define PS_P_MINHARDRADIUS_1D 32 // minimum hard surface radius +#define PS_P_MINHARDRADIUS_1D 32 // minimum hard surface radius #define PS_P_MINSURFACEHARDNESS_1D 50 // minimum hardness used in collision impulse calculation // struct for PS settings (shared for 1D and 2D class) -typedef union -{ +typedef union { struct{ // one byte bit field for 1D settings - bool wrapX : 1; - bool bounceX : 1; + bool wrap : 1; + bool bounce : 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; @@ -255,7 +239,7 @@ typedef union //struct for a single particle (6 bytes) typedef struct { int16_t x; // x position in particle system - int8_t vx; // horizontal velocity + 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) @@ -263,14 +247,13 @@ typedef struct { 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), + bool fixed : 1; // if set, particle does not move (and collisions make other particles revert direction), } PSparticle1D; // struct for additional particle settings (optional) -typedef struct -{ +typedef struct { uint8_t sat; //color saturation - uint8_t size; // particle size, 255 means 10 pixels in diameter + uint8_t size; // particle size, 255 means 10 pixels in diameter uint8_t forcecounter; } PSadvancedParticle1D; @@ -282,8 +265,8 @@ typedef struct { 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; + uint8_t size; // particle size (advanced property) +} PSsource1D; class ParticleSystem1D @@ -291,67 +274,65 @@ class ParticleSystem1D public: ParticleSystem1D(uint16_t length, uint16_t numberofparticles, uint16_t numberofsources, 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 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); + int32_t sprayEmit(PSsource1D &emitter); void particleMoveUpdate(PSparticle1D &part, 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 applyGravity(PSparticle1D *part); // applies gravity to single particle (use this for sources) void applyFriction(int32_t coefficient); // apply friction to all used particles - // set options - void setUsedParticles(uint32_t num); - void setWallHardness(uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set + void setUsedParticles(uint32_t num); + 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 setBounce(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 setColorByPosition(bool enable); - void setMotionBlur(uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero + void setMotionBlur(uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero void setParticleSize(uint8_t size); //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); - + PSparticle1D *particles; // pointer to particle 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) uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data - int32_t maxX; // particle system size i.e. width-1 - int32_t maxXpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 + int32_t maxX; // particle system size i.e. width-1 + int32_t maxXpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 uint32_t numSources; // number of sources uint32_t numParticles; // number of particles available in this system - uint32_t usedParticles; // number of particles used in animation (can be smaller then numParticles) + uint32_t usedParticles; // number of particles used in animation (can be smaller then numParticles) -private: +private: //rendering functions void ParticleSys_render(void); - void renderParticle(CRGB *framebuffer, uint32_t particleindex, uint32_t brightness, CRGB color, CRGB *renderbuffer); + void renderParticle(CRGB *framebuffer, const uint32_t &particleindex, const uint32_t &brightness, const CRGB &color, CRGB *renderbuffer, const bool &wrap); //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, PSparticle1D *particle2, int32_t dx, int32_t relativeV, uint32_t collisiondistance); //utility functions void updatePSpointers(bool isadvanced); // 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 - CRGB *allocate1Dbuffer(uint32_t length); + CRGB *allocate1Dbuffer(uint32_t length); // note: variables that are accessed often are 32bit for speed - PSsettings1D particlesettings; // settings used when updating particles + PSsettings1D particlesettings; // settings used when updating particles 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 wallHardness; uint8_t gforcecounter; // counter for global gravity - int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards) + int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards) uint8_t forcecounter; // counter for globally applied forces //uint8_t collisioncounter; // counter to handle collisions TODO: could use the SEGMENT.call? -> currently unused // global particle properties for basic particles @@ -364,5 +345,5 @@ bool initParticleSystem1D(ParticleSystem1D *&PartSys, uint32_t requestedsources, uint32_t calculateNumberOfParticles1D(bool isadvanced); uint32_t calculateNumberOfSources1D(uint8_t requestedsources); bool allocateParticleSystemMemory1D(uint16_t numparticles, uint16_t numsources, bool isadvanced, uint16_t additionalbytes); -void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, bool smear = true, uint32_t start = 0); +void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start = 0); #endif // WLED_DISABLE_PARTICLESYSTEM1D