mirror of
https://github.com/wled/WLED.git
synced 2025-07-15 06:46:34 +00:00
Updates to particle system (#4630)
* added Sonic Boom AR FX, some tweaks to Sonic Stream * added white color option to Sonic Stream * improvements to collisions (speed look-ahead) * code prettified * added "playful" mode to PS Chase plus some minor speed optimizations * Adding new FX: PS Springy with many config options
This commit is contained in:
parent
e979c58c98
commit
02f14baad4
405
wled00/FX.cpp
405
wled00/FX.cpp
@ -7818,7 +7818,7 @@ uint16_t mode_particlefireworks(void) {
|
||||
else if (PartSys->sources[j].source.vy < 0) { // rocket is exploded and time is up (ttl=0 and negative speed), relaunch it
|
||||
PartSys->sources[j].source.y = PS_P_RADIUS; // start from bottom
|
||||
PartSys->sources[j].source.x = (PartSys->maxX >> 2) + hw_random(PartSys->maxX >> 1); // centered half
|
||||
PartSys->sources[j].source.vy = (SEGMENT.custom3) + random16(SEGMENT.custom1 >> 3) + 5; // rocket speed TODO: need to adjust for segment height
|
||||
PartSys->sources[j].source.vy = (SEGMENT.custom3) + hw_random16(SEGMENT.custom1 >> 3) + 5; // rocket speed TODO: need to adjust for segment height
|
||||
PartSys->sources[j].source.vx = hw_random16(7) - 3; // not perfectly straight up
|
||||
PartSys->sources[j].source.sat = 30; // low saturation -> exhaust is off-white
|
||||
PartSys->sources[j].source.ttl = hw_random16(SEGMENT.custom1) + (SEGMENT.custom1 >> 1); // set fuse time
|
||||
@ -7888,7 +7888,7 @@ uint16_t mode_particlefireworks(void) {
|
||||
counter = 0;
|
||||
speed += 3 + ((SEGMENT.intensity >> 6)); // increase speed to form a second wave
|
||||
PartSys->sources[j].source.hue += hueincrement; // new color for next circle
|
||||
PartSys->sources[j].source.sat = min((uint16_t)150, random16());
|
||||
PartSys->sources[j].source.sat = 100 + hw_random16(156);
|
||||
}
|
||||
angle += angleincrement; // set angle for next particle
|
||||
}
|
||||
@ -9514,44 +9514,36 @@ uint16_t mode_particleHourglass(void) {
|
||||
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
|
||||
settingTracker = reinterpret_cast<uint32_t *>(PartSys->PSdataEnd); //assign data pointer
|
||||
direction = reinterpret_cast<bool *>(PartSys->PSdataEnd + 4); //assign data pointer
|
||||
PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 1, 255));
|
||||
PartSys->setUsedParticles(1 + ((SEGMENT.intensity * 255) >> 8));
|
||||
PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur
|
||||
PartSys->setGravity(map(SEGMENT.custom3, 0, 31, 1, 30));
|
||||
PartSys->enableParticleCollisions(true, 34); // hardness value found by experimentation on different settings
|
||||
PartSys->enableParticleCollisions(true, 32); // hardness value found by experimentation on different settings
|
||||
|
||||
uint32_t colormode = SEGMENT.custom1 >> 5; // 0-7
|
||||
|
||||
if ((SEGMENT.intensity | (PartSys->getAvailableParticles() << 8)) != *settingTracker) { // initialize, getAvailableParticles changes while in FX transition
|
||||
*settingTracker = SEGMENT.intensity | (PartSys->getAvailableParticles() << 8);
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
|
||||
PartSys->particleFlags[i].reversegrav = true;
|
||||
PartSys->particleFlags[i].reversegrav = true; // resting particles dont fall
|
||||
*direction = 0; // down
|
||||
SEGENV.aux1 = 1; // initialize below
|
||||
}
|
||||
SEGENV.aux0 = PartSys->usedParticles - 1; // initial state, start with highest number particle
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) { // check if particle reached target position after falling
|
||||
int32_t targetposition;
|
||||
if (PartSys->particleFlags[i].fixed == false) { // && abs(PartSys->particles[i].vx) < 8) {
|
||||
// calculate target position depending on direction
|
||||
bool closeToTarget = false;
|
||||
bool reachedTarget = false;
|
||||
if (PartSys->particleFlags[i].reversegrav) { // up
|
||||
targetposition = PartSys->maxX - (i * PS_P_RADIUS_1D) - positionOffset; // target resting position
|
||||
if (targetposition - PartSys->particles[i].x <= 5 * PS_P_RADIUS_1D)
|
||||
closeToTarget = true;
|
||||
if (PartSys->particles[i].x >= targetposition) // particle has reached target position, pin it. if not pinned, they do not stack well on larger piles
|
||||
reachedTarget = true;
|
||||
}
|
||||
else { // down, highest index particle drops first
|
||||
targetposition = (PartSys->usedParticles - i) * PS_P_RADIUS_1D - positionOffset; // target resting position note: using -offset instead of -1 + offset
|
||||
if (PartSys->particles[i].x - targetposition <= 5 * PS_P_RADIUS_1D)
|
||||
closeToTarget = true;
|
||||
if (PartSys->particles[i].x <= targetposition) // particle has reached target position, pin it. if not pinned, they do not stack well on larger piles
|
||||
reachedTarget = true;
|
||||
}
|
||||
if (reachedTarget || (closeToTarget && abs(PartSys->particles[i].vx) < 10)) { // reached target or close to target and slow speed
|
||||
auto calcTargetPos = [&](size_t i) {
|
||||
return PartSys->particleFlags[i].reversegrav ?
|
||||
PartSys->maxX - i * PS_P_RADIUS_1D - positionOffset
|
||||
: (PartSys->usedParticles - i) * PS_P_RADIUS_1D - positionOffset;
|
||||
};
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) { // check if particle reached target position after falling
|
||||
if (PartSys->particleFlags[i].fixed == false && abs(PartSys->particles[i].vx) < 5) {
|
||||
int32_t targetposition = calcTargetPos(i);
|
||||
bool closeToTarget = abs(targetposition - PartSys->particles[i].x) < 3 * PS_P_RADIUS_1D;
|
||||
if (closeToTarget) { // close to target and slow speed
|
||||
PartSys->particles[i].x = targetposition; // set exact position
|
||||
PartSys->particleFlags[i].fixed = true; // pin particle
|
||||
}
|
||||
@ -9576,19 +9568,20 @@ uint16_t mode_particleHourglass(void) {
|
||||
PartSys->particles[i].hue += 120;
|
||||
}
|
||||
|
||||
// re-order particles in case collisions flipped particles (highest number index particle is on the "bottom")
|
||||
for (int i = 0; i < PartSys->usedParticles - 1; i++) {
|
||||
if (PartSys->particles[i].x < PartSys->particles[i+1].x && PartSys->particleFlags[i].fixed == false && PartSys->particleFlags[i+1].fixed == false) {
|
||||
std::swap(PartSys->particles[i].x, PartSys->particles[i+1].x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (SEGENV.aux1 == 1) { // last countdown call before dropping starts, reset all particles
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
|
||||
PartSys->particleFlags[i].collide = true;
|
||||
PartSys->particleFlags[i].perpetual = true;
|
||||
PartSys->particles[i].ttl = 260;
|
||||
uint32_t targetposition;
|
||||
//calculate target position depending on direction
|
||||
if (PartSys->particleFlags[i].reversegrav)
|
||||
targetposition = PartSys->maxX - (i * PS_P_RADIUS_1D + positionOffset); // target resting position
|
||||
else
|
||||
targetposition = (PartSys->usedParticles - i) * PS_P_RADIUS_1D - positionOffset; // target resting position -5 - PS_P_RADIUS_1D/2
|
||||
|
||||
PartSys->particles[i].x = targetposition;
|
||||
PartSys->particles[i].x = calcTargetPos(i);
|
||||
PartSys->particleFlags[i].fixed = true;
|
||||
}
|
||||
}
|
||||
@ -9699,7 +9692,7 @@ uint16_t mode_particleBalance(void) {
|
||||
|
||||
// Particle System settings
|
||||
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
|
||||
PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur
|
||||
PartSys->setMotionBlur(SEGMENT.custom2); // enable motion blur
|
||||
PartSys->setBounce(!SEGMENT.check2);
|
||||
PartSys->setWrap(SEGMENT.check2);
|
||||
uint8_t hardness = SEGMENT.custom1 > 0 ? map(SEGMENT.custom1, 0, 255, 50, 250) : 200; // set hardness, make the walls hard if collisions are disabled
|
||||
@ -9716,6 +9709,17 @@ uint16_t mode_particleBalance(void) {
|
||||
}
|
||||
SEGENV.aux1 = PartSys->usedParticles;
|
||||
|
||||
// re-order particles in case collisions flipped particles
|
||||
for (i = 0; i < PartSys->usedParticles - 1; i++) {
|
||||
if (PartSys->particles[i].x > PartSys->particles[i+1].x) {
|
||||
if (SEGMENT.check2) { // check for wrap around
|
||||
if (PartSys->particles[i].x - PartSys->particles[i+1].x > 3 * PS_P_RADIUS_1D)
|
||||
continue;
|
||||
}
|
||||
std::swap(PartSys->particles[i].x, PartSys->particles[i+1].x);
|
||||
}
|
||||
}
|
||||
|
||||
if (SEGMENT.call % (((255 - SEGMENT.speed) >> 6) + 1) == 0) { // how often the force is applied depends on speed setting
|
||||
int32_t xgravity;
|
||||
int32_t increment = (SEGMENT.speed >> 6) + 1;
|
||||
@ -9756,7 +9760,7 @@ by DedeHai (Damian Schneider)
|
||||
uint16_t mode_particleChase(void) {
|
||||
ParticleSystem1D *PartSys = nullptr;
|
||||
if (SEGMENT.call == 0) { // initialization
|
||||
if (!initParticleSystem1D(PartSys, 1, 255, 3, true)) // init
|
||||
if (!initParticleSystem1D(PartSys, 1, 255, 2, true)) // init
|
||||
return mode_static(); // allocation failed or is single pixel
|
||||
SEGENV.aux0 = 0xFFFF; // invalidate
|
||||
*PartSys->PSdataEnd = 1; // huedir
|
||||
@ -9766,39 +9770,43 @@ uint16_t mode_particleChase(void) {
|
||||
PartSys = reinterpret_cast<ParticleSystem1D *>(SEGENV.data); // if not first call, just set the pointer to the PS
|
||||
if (PartSys == nullptr)
|
||||
return mode_static(); // something went wrong, no data!
|
||||
|
||||
// Particle System settings
|
||||
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
|
||||
PartSys->setColorByPosition(SEGMENT.check3);
|
||||
PartSys->setMotionBlur(8 + ((SEGMENT.custom3) << 3)); // anable motion blur
|
||||
// uint8_t* basehue = (PartSys->PSdataEnd + 2); //assign data pointer
|
||||
|
||||
PartSys->setMotionBlur(7 + ((SEGMENT.custom3) << 3)); // anable motion blur
|
||||
uint32_t numParticles = 1 + map(SEGMENT.intensity, 0, 255, 2, 255 / (1 + (SEGMENT.custom1 >> 6))); // depends on intensity and particle size (custom1), minimum 1
|
||||
numParticles = min(numParticles, PartSys->usedParticles); // limit to available particles
|
||||
int32_t huestep = 1 + ((((uint32_t)SEGMENT.custom2 << 19) / numParticles) >> 16); // hue increment
|
||||
uint32_t settingssum = SEGMENT.speed + SEGMENT.intensity + SEGMENT.custom1 + SEGMENT.custom2 + SEGMENT.check1 + SEGMENT.check2 + SEGMENT.check3 + PartSys->getAvailableParticles(); // note: getAvailableParticles is used to enforce update during transitions
|
||||
if (SEGENV.aux0 != settingssum) { // settings changed changed, update
|
||||
uint32_t numParticles = map(SEGMENT.intensity, 0, 255, 2, 255 / (1 + (SEGMENT.custom1 >> 6))); // depends on intensity and particle size (custom1)
|
||||
if (numParticles == 0) numParticles = 1; // minimum 1 particle
|
||||
PartSys->setUsedParticles(numParticles);
|
||||
SEGENV.step = (PartSys->maxX + (PS_P_RADIUS_1D << 5)) / PartSys->usedParticles; // spacing between particles
|
||||
if (SEGMENT.check1)
|
||||
SEGENV.step = PartSys->advPartProps[0].size / 2 + (PartSys->maxX / numParticles);
|
||||
else
|
||||
SEGENV.step = (PartSys->maxX + (PS_P_RADIUS_1D << 5)) / numParticles; // spacing between particles
|
||||
for (int32_t i = 0; i < (int32_t)PartSys->usedParticles; i++) {
|
||||
PartSys->advPartProps[i].sat = 255;
|
||||
PartSys->particles[i].x = (i - 1) * SEGENV.step; // distribute evenly (starts out of frame for i=0)
|
||||
PartSys->particles[i].vx = SEGMENT.speed >> 1;
|
||||
PartSys->particles[i].vx = SEGMENT.speed >> 2;
|
||||
PartSys->advPartProps[i].size = SEGMENT.custom1;
|
||||
if (SEGMENT.custom2 < 255)
|
||||
PartSys->particles[i].hue = (i * (SEGMENT.custom2 << 3)) / PartSys->usedParticles; // gradient distribution
|
||||
PartSys->particles[i].hue = i * huestep; // gradient distribution
|
||||
else
|
||||
PartSys->particles[i].hue = hw_random16();
|
||||
}
|
||||
SEGENV.aux0 = settingssum;
|
||||
}
|
||||
|
||||
int32_t huestep = (((uint32_t)SEGMENT.custom2 << 19) / PartSys->usedParticles) >> 16; // hue increment
|
||||
if(SEGMENT.check1) {
|
||||
huestep = 1 + (max((int)huestep, 3) * ((int(sin16_t(strip.now * 3) + 32767))) >> 15); // changes gradient spread (scale hue step)
|
||||
}
|
||||
|
||||
// wrap around (cannot use particle system wrap if distributing colors manually, it also wraps rendering which does not look good)
|
||||
for (int32_t i = (int32_t)PartSys->usedParticles - 1; i >= 0; i--) { // check from the back, last particle wraps first, multiple particles can overrun per frame
|
||||
if (PartSys->particles[i].x > PartSys->maxX + PS_P_RADIUS_1D + PartSys->advPartProps[i].size) { // wrap it around
|
||||
uint32_t nextindex = (i + 1) % PartSys->usedParticles;
|
||||
PartSys->particles[i].x = PartSys->particles[nextindex].x - (int)SEGENV.step;
|
||||
if(SEGMENT.check1) // playful mode, vary size
|
||||
PartSys->advPartProps[i].size = max(1 + (SEGMENT.custom1 >> 1), ((int(sin16_t(strip.now << 1) + 32767)) >> 8)); // cycle size
|
||||
if (SEGMENT.custom2 < 255)
|
||||
PartSys->particles[i].hue = PartSys->particles[nextindex].hue - huestep;
|
||||
else
|
||||
@ -9807,11 +9815,37 @@ uint16_t mode_particleChase(void) {
|
||||
PartSys->particles[i].ttl = 300; // reset ttl, cannot use perpetual because memmanager can change pointer at any time
|
||||
}
|
||||
|
||||
if (SEGMENT.check1) { // playful mode, changes hue, size, speed, density dynamically
|
||||
int8_t* huedir = reinterpret_cast<int8_t *>(PartSys->PSdataEnd); //assign data pointer
|
||||
int8_t* stepdir = reinterpret_cast<int8_t *>(PartSys->PSdataEnd + 1);
|
||||
if(*stepdir == 0) *stepdir = 1; // initialize directions
|
||||
if(*huedir == 0) *huedir = 1;
|
||||
if (SEGENV.step >= (PartSys->advPartProps[0].size + PS_P_RADIUS_1D * 4) + PartSys->maxX / numParticles)
|
||||
*stepdir = -1; // increase density (decrease space between particles)
|
||||
else if (SEGENV.step <= (PartSys->advPartProps[0].size >> 1) + ((PartSys->maxX / numParticles)))
|
||||
*stepdir = 1; // decrease density
|
||||
if (SEGENV.aux1 > 512)
|
||||
*huedir = -1;
|
||||
else if (SEGENV.aux1 < 50)
|
||||
*huedir = 1;
|
||||
if (SEGMENT.call % (1024 / (1 + (SEGMENT.speed >> 2))) == 0)
|
||||
SEGENV.aux1 += *huedir;
|
||||
int8_t globalhuestep = 0; // global hue increment
|
||||
if (SEGMENT.call % (1 + (int(sin16_t(strip.now) + 32767) >> 12)) == 0)
|
||||
globalhuestep = 2; // global hue change to add some color variation
|
||||
if ((SEGMENT.call & 0x1F) == 0)
|
||||
SEGENV.step += *stepdir; // change density
|
||||
for(int32_t i = 0; i < PartSys->usedParticles; i++) {
|
||||
PartSys->particles[i].hue -= globalhuestep; // shift global hue (both directions)
|
||||
PartSys->particles[i].vx = 1 + (SEGMENT.speed >> 2) + ((int32_t(sin16_t(strip.now >> 1) + 32767) * (SEGMENT.speed >> 2)) >> 16);
|
||||
}
|
||||
}
|
||||
|
||||
PartSys->setParticleSize(SEGMENT.custom1); // if custom1 == 0 this sets rendering size to one pixel
|
||||
PartSys->update(); // update and render
|
||||
return FRAMETIME;
|
||||
}
|
||||
static const char _data_FX_MODE_PS_CHASE[] PROGMEM = "PS Chase@!,Density,Size,Hue,Blur,,,Position Color;,!;!;1;pal=11,sx=50,c2=5,c3=0";
|
||||
static const char _data_FX_MODE_PS_CHASE[] PROGMEM = "PS Chase@!,Density,Size,Hue,Blur,Playful,,Position Color;,!;!;1;pal=11,sx=50,c2=5,c3=0";
|
||||
|
||||
/*
|
||||
Particle Fireworks Starburst replacement (smoother rendering, more settings)
|
||||
@ -10016,10 +10050,9 @@ static const char _data_FX_MODE_PS_FIRE1D[] PROGMEM = "PS Fire 1D@!,!,Cooling,Bl
|
||||
|
||||
/*
|
||||
Particle based AR effect, swoop particles along the strip with selected frequency loudness
|
||||
Uses palette for particle color
|
||||
by DedeHai (Damian Schneider)
|
||||
*/
|
||||
uint16_t mode_particle1Dsonicstream(void) {
|
||||
uint16_t mode_particle1DsonicStream(void) {
|
||||
ParticleSystem1D *PartSys = nullptr;
|
||||
|
||||
if (SEGMENT.call == 0) { // initialization
|
||||
@ -10029,7 +10062,6 @@ uint16_t mode_particle1Dsonicstream(void) {
|
||||
PartSys->sources[0].source.x = 0; // at start
|
||||
//PartSys->sources[1].source.x = PartSys->maxX; // at end
|
||||
PartSys->sources[0].var = 0;//SEGMENT.custom1 >> 3;
|
||||
PartSys->sources[0].sat = 255;
|
||||
}
|
||||
else
|
||||
PartSys = reinterpret_cast<ParticleSystem1D *>(SEGENV.data); // if not first call, just set the pointer to the PS
|
||||
@ -10040,7 +10072,6 @@ uint16_t mode_particle1Dsonicstream(void) {
|
||||
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
|
||||
PartSys->setMotionBlur(20 + (SEGMENT.custom2 >> 1)); // anable motion blur
|
||||
PartSys->setSmearBlur(200); // smooth out the edges
|
||||
|
||||
PartSys->sources[0].v = 5 + (SEGMENT.speed >> 2);
|
||||
|
||||
// FFT processing
|
||||
@ -10050,11 +10081,10 @@ uint16_t mode_particle1Dsonicstream(void) {
|
||||
uint32_t baseBin = SEGMENT.custom3 >> 1; // 0 - 15 map(SEGMENT.custom3, 0, 31, 0, 14);
|
||||
|
||||
loudness = fftResult[baseBin];// + fftResult[baseBin + 1];
|
||||
int mids = sqrt16((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h)
|
||||
if (baseBin > 12)
|
||||
loudness = loudness << 2; // double loudness for high frequencies (better detecion)
|
||||
|
||||
uint32_t threshold = 150 - (SEGMENT.intensity >> 1);
|
||||
uint32_t threshold = 140 - (SEGMENT.intensity >> 1);
|
||||
if (SEGMENT.check2) { // enable low pass filter for dynamic threshold
|
||||
SEGMENT.step = (SEGMENT.step * 31500 + loudness * (32768 - 31500)) >> 15; // low pass filter for simple beat detection: add average to base threshold
|
||||
threshold = 20 + (threshold >> 1) + SEGMENT.step; // add average to threshold
|
||||
@ -10062,6 +10092,7 @@ uint16_t mode_particle1Dsonicstream(void) {
|
||||
|
||||
// color
|
||||
uint32_t hueincrement = (SEGMENT.custom1 >> 3); // 0-31
|
||||
PartSys->sources[0].sat = SEGMENT.custom1 > 0 ? 255 : 0; // color slider at zero: set to white
|
||||
PartSys->setColorByPosition(SEGMENT.custom1 == 255);
|
||||
|
||||
// particle manipulation
|
||||
@ -10072,9 +10103,11 @@ uint16_t mode_particle1Dsonicstream(void) {
|
||||
}
|
||||
else PartSys->particles[i].ttl = 0;
|
||||
}
|
||||
if (SEGMENT.check1) // modulate colors by mid frequencies
|
||||
if (SEGMENT.check1) { // modulate colors by mid frequencies
|
||||
int mids = sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h)
|
||||
PartSys->particles[i].hue += (mids * perlin8(PartSys->particles[i].x << 2, SEGMENT.step << 2)) >> 9; // color by perlin noise from mid frequencies
|
||||
}
|
||||
}
|
||||
|
||||
if (loudness > threshold) {
|
||||
SEGMENT.aux0 += hueincrement; // change color
|
||||
@ -10117,6 +10150,266 @@ uint16_t mode_particle1Dsonicstream(void) {
|
||||
return FRAMETIME;
|
||||
}
|
||||
static const char _data_FX_MODE_PS_SONICSTREAM[] PROGMEM = "PS Sonic Stream@!,!,Color,Blur,Bin,Mod,Filter,Push;,!;!;1f;c3=0,o2=1";
|
||||
|
||||
|
||||
/*
|
||||
Particle based AR effect, creates exploding particles on beats
|
||||
by DedeHai (Damian Schneider)
|
||||
*/
|
||||
uint16_t mode_particle1DsonicBoom(void) {
|
||||
ParticleSystem1D *PartSys = nullptr;
|
||||
if (SEGMENT.call == 0) { // initialization
|
||||
if (!initParticleSystem1D(PartSys, 1, 255, 0, true)) // init, no additional data needed
|
||||
return mode_static(); // allocation failed or is single pixel
|
||||
PartSys->setKillOutOfBounds(true);
|
||||
}
|
||||
else
|
||||
PartSys = reinterpret_cast<ParticleSystem1D *>(SEGENV.data); // if not first call, just set the pointer to the PS
|
||||
if (PartSys == nullptr)
|
||||
return mode_static(); // something went wrong, no data!
|
||||
|
||||
// Particle System settings
|
||||
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
|
||||
PartSys->setMotionBlur(180 * SEGMENT.check3);
|
||||
PartSys->setSmearBlur(64 * SEGMENT.check3);
|
||||
PartSys->sources[0].var = map(SEGMENT.speed, 0, 255, 10, 127);
|
||||
|
||||
// FFT processing
|
||||
um_data_t *um_data = getAudioData();
|
||||
uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255
|
||||
uint32_t loudness;
|
||||
uint32_t baseBin = SEGMENT.custom3 >> 1; // 0 - 15 map(SEGMENT.custom3, 0, 31, 0, 14);
|
||||
loudness = fftResult[baseBin];// + fftResult[baseBin + 1];
|
||||
|
||||
if (baseBin > 12)
|
||||
loudness = loudness << 2; // double loudness for high frequencies (better detecion)
|
||||
uint32_t threshold = 150 - (SEGMENT.intensity >> 1);
|
||||
if (SEGMENT.check2) { // enable low pass filter for dynamic threshold
|
||||
SEGMENT.step = (SEGMENT.step * 31500 + loudness * (32768 - 31500)) >> 15; // low pass filter for simple beat detection: add average to base threshold
|
||||
threshold = 20 + (threshold >> 1) + SEGMENT.step; // add average to threshold
|
||||
}
|
||||
|
||||
// particle manipulation
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
|
||||
if (SEGMENT.check1) { // modulate colors by mid frequencies
|
||||
int mids = sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h)
|
||||
PartSys->particles[i].hue += (mids * perlin8(PartSys->particles[i].x << 2, SEGMENT.step << 2)) >> 9; // color by perlin noise from mid frequencies
|
||||
}
|
||||
if (PartSys->particles[i].ttl > 16) {
|
||||
PartSys->particles[i].ttl -= 16; //ttl is linked to brightness, this allows to use higher brightness but still a (very) short lifespan
|
||||
}
|
||||
}
|
||||
|
||||
if (loudness > threshold) {
|
||||
if (SEGMENT.aux1 == 0) { // edge detected, code only runs once per "beat"
|
||||
// update position
|
||||
if (SEGMENT.custom2 < 128) // fixed position
|
||||
PartSys->sources[0].source.x = map(SEGMENT.custom2, 0, 127, 0, PartSys->maxX);
|
||||
else if (SEGMENT.custom2 < 255) { // advances on each "beat"
|
||||
int32_t step = PartSys->maxX / (((270 - SEGMENT.custom2) >> 3)); // step: 2 - 33 steps for full segment width
|
||||
PartSys->sources[0].source.x = (PartSys->sources[0].source.x + step) % PartSys->maxX;
|
||||
if (PartSys->sources[0].source.x < step) // align to be symmetrical by making the first position half a step from start
|
||||
PartSys->sources[0].source.x = step >> 1;
|
||||
}
|
||||
else // position set to max, use random postion per beat
|
||||
PartSys->sources[0].source.x = hw_random(PartSys->maxX);
|
||||
|
||||
// update color
|
||||
//PartSys->setColorByPosition(SEGMENT.custom1 == 255); // color slider at max: particle color by position
|
||||
PartSys->sources[0].sat = SEGMENT.custom1 > 0 ? 255 : 0; // color slider at zero: set to white
|
||||
if (SEGMENT.custom1 == 255) // emit color by position
|
||||
SEGMENT.aux0 = map(PartSys->sources[0].source.x , 0, PartSys->maxX, 0, 255);
|
||||
else if (SEGMENT.custom1 > 0)
|
||||
SEGMENT.aux0 += (SEGMENT.custom1 >> 1); // change emit color per "beat"
|
||||
}
|
||||
SEGMENT.aux1 = 1; // track edge detection
|
||||
|
||||
PartSys->sources[0].minLife = 200;
|
||||
PartSys->sources[0].maxLife = PartSys->sources[0].minLife + (((unsigned)SEGMENT.intensity * loudness * loudness) >> 13);
|
||||
PartSys->sources[0].source.hue = SEGMENT.aux0;
|
||||
PartSys->sources[0].size = 1; //SEGMENT.speed>>3;
|
||||
uint32_t explosionsize = 4 + (PartSys->maxXpixel >> 2);
|
||||
explosionsize = hw_random16((explosionsize * loudness) >> 10);
|
||||
for (uint32_t e = 0; e < explosionsize; e++) { // emit explosion particles
|
||||
PartSys->sprayEmit(PartSys->sources[0]); // emit a particle
|
||||
}
|
||||
}
|
||||
else
|
||||
SEGMENT.aux1 = 0; // reset edge detection
|
||||
|
||||
PartSys->update(); // update and render (needs to be done before manipulation for initial particle spacing to be right)
|
||||
return FRAMETIME;
|
||||
}
|
||||
static const char _data_FX_MODE_PS_SONICBOOM[] PROGMEM = "PS Sonic Boom@!,!,Color,Position,Bin,Mod,Filter,Blur;,!;!;1f;c2=63,c3=0,o2=1";
|
||||
|
||||
/*
|
||||
Particles bound by springs
|
||||
by DedeHai (Damian Schneider)
|
||||
*/
|
||||
uint16_t mode_particleSpringy(void) {
|
||||
ParticleSystem1D *PartSys = nullptr;
|
||||
if (SEGMENT.call == 0) { // initialization
|
||||
if (!initParticleSystem1D(PartSys, 1, 128, 0, true)) // init
|
||||
return mode_static(); // allocation failed or is single pixel
|
||||
SEGENV.aux0 = SEGENV.aux1 = 0xFFFF; // invalidate settings
|
||||
}
|
||||
else
|
||||
PartSys = reinterpret_cast<ParticleSystem1D *>(SEGENV.data); // if not first call, just set the pointer to the PS
|
||||
if (PartSys == nullptr)
|
||||
return mode_static(); // something went wrong, no data!
|
||||
// Particle System settings
|
||||
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
|
||||
PartSys->setMotionBlur(220 * SEGMENT.check1); // anable motion blur
|
||||
PartSys->setSmearBlur(50); // smear a little
|
||||
PartSys->setUsedParticles(map(SEGMENT.custom1, 0, 255, 30 >> SEGMENT.check2, 255 >> (SEGMENT.check2*2))); // depends on density and particle size
|
||||
// PartSys->enableParticleCollisions(true, 140); // enable particle collisions, can not be set too hard or impulses will not strech the springs if soft.
|
||||
int32_t springlength = PartSys->maxX / (PartSys->usedParticles); // spring length (spacing between particles)
|
||||
int32_t springK = map(SEGMENT.speed, 0, 255, 5, 35); // spring constant (stiffness)
|
||||
|
||||
uint32_t settingssum = SEGMENT.custom1 + SEGMENT.check2 + PartSys->getAvailableParticles(); // note: getAvailableParticles is used to enforce update during transitions
|
||||
if (SEGENV.aux0 != settingssum) { // number of particles changed, update distribution
|
||||
for (int32_t i = 0; i < (int32_t)PartSys->usedParticles; i++) {
|
||||
PartSys->advPartProps[i].sat = 255; // full saturation
|
||||
//PartSys->particleFlags[i].collide = true; // enable collision for particles
|
||||
PartSys->particles[i].x = (i+1) * ((PartSys->maxX) / (PartSys->usedParticles)); // distribute
|
||||
//PartSys->particles[i].vx = 0; //reset speed
|
||||
PartSys->advPartProps[i].size = SEGMENT.check2 ? 190 : 2; // set size, small or big
|
||||
}
|
||||
SEGENV.aux0 = settingssum;
|
||||
}
|
||||
int dxlimit = (2 + ((255 - SEGMENT.speed) >> 5)) * springlength; // limit for spring length to avoid overstretching
|
||||
|
||||
int springforce[PartSys->usedParticles]; // spring forces
|
||||
memset(springforce, 0, PartSys->usedParticles * sizeof(int32_t)); // reset spring forces
|
||||
|
||||
// calculate spring forces and limit particle positions
|
||||
if (PartSys->particles[0].x < -springlength)
|
||||
PartSys->particles[0].x = -springlength; // limit the spring length
|
||||
else if (PartSys->particles[0].x > dxlimit)
|
||||
PartSys->particles[0].x = dxlimit; // limit the spring length
|
||||
springforce[0] += ((springlength >> 1) - (PartSys->particles[0].x)) * springK; // first particle anchors to x=0
|
||||
|
||||
for (int32_t i = 1; i < PartSys->usedParticles; i++) {
|
||||
// reorder particles if they are out of order to prevent chaos
|
||||
if (PartSys->particles[i].x < PartSys->particles[i-1].x)
|
||||
std::swap(PartSys->particles[i].x, PartSys->particles[i-1].x); // swap particle positions to maintain order
|
||||
int dx = PartSys->particles[i].x - PartSys->particles[i-1].x; // distance, always positive
|
||||
if (dx > dxlimit) { // limit the spring length
|
||||
PartSys->particles[i].x = PartSys->particles[i-1].x + dxlimit;
|
||||
dx = dxlimit;
|
||||
}
|
||||
int dxleft = (springlength - dx); // offset from spring resting position
|
||||
springforce[i] += dxleft * springK;
|
||||
springforce[i-1] -= dxleft * springK;
|
||||
if (i == (PartSys->usedParticles - 1)) {
|
||||
if (PartSys->particles[i].x >= PartSys->maxX + springlength)
|
||||
PartSys->particles[i].x = PartSys->maxX + springlength;
|
||||
int dxright = (springlength >> 1) - (PartSys->maxX - PartSys->particles[i].x); // last particle anchors to x=maxX
|
||||
springforce[i] -= dxright * springK;
|
||||
}
|
||||
}
|
||||
// apply spring forces to particles
|
||||
bool dampenoscillations = (SEGMENT.call % (9 - (SEGMENT.speed >> 5))) == 0; // dampen oscillation if particles are slow, more damping on stiffer springs
|
||||
for (int32_t i = 0; i < PartSys->usedParticles; i++) {
|
||||
springforce[i] = springforce[i] / 64; // scale spring force (cannot use shifts because of negative values)
|
||||
int maxforce = 120; // limit spring force
|
||||
springforce[i] = springforce[i] > maxforce ? maxforce : springforce[i] < -maxforce ? -maxforce : springforce[i]; // limit spring force
|
||||
PartSys->applyForce(PartSys->particles[i], springforce[i], PartSys->advPartProps[i].forcecounter);
|
||||
//dampen slow particles to avoid persisting oscillations on higher stiffness
|
||||
if (dampenoscillations) {
|
||||
if (abs(PartSys->particles[i].vx) < 3 && abs(springforce[i]) < (springK >> 2))
|
||||
PartSys->particles[i].vx = (PartSys->particles[i].vx * 254) / 256; // take out some energy
|
||||
}
|
||||
PartSys->particles[i].ttl = 300; // reset ttl, cannot use perpetual
|
||||
}
|
||||
|
||||
if (SEGMENT.call % ((65 - ((SEGMENT.intensity * (1 + (SEGMENT.speed>>3))) >> 7))) == 0) // more damping for higher stiffness
|
||||
PartSys->applyFriction((SEGMENT.intensity >> 2));
|
||||
|
||||
// add a small resetting force so particles return to resting position even under high damping
|
||||
for (int32_t i = 1; i < PartSys->usedParticles - 1; i++) {
|
||||
int restposition = (springlength >> 1) + i * springlength; // resting position
|
||||
int dx = restposition - PartSys->particles[i].x; // distance, always positive
|
||||
PartSys->applyForce(PartSys->particles[i], dx > 0 ? 1 : (dx < 0 ? -1 : 0), PartSys->advPartProps[i].forcecounter);
|
||||
}
|
||||
|
||||
// Modes
|
||||
if (SEGMENT.check3) { // use AR, custom 3 becomes frequency band to use, applies velocity to center particle according to loudness
|
||||
um_data_t *um_data = getAudioData();
|
||||
uint8_t *fftResult = (uint8_t *)um_data->u_data[2]; // 16 bins with FFT data, log mapped already, each band contains frequency amplitude 0-255
|
||||
uint32_t baseBin = map(SEGMENT.custom3, 0, 31, 0, 14);
|
||||
uint32_t loudness = fftResult[baseBin] + fftResult[baseBin+1];
|
||||
uint32_t threshold = 80; //150 - (SEGMENT.intensity >> 1);
|
||||
if (loudness > threshold) {
|
||||
int offset = (PartSys->maxX >> 1) - PartSys->particles[PartSys->usedParticles>>1].x; // offset from center
|
||||
if (abs(offset) < PartSys->maxX >> 5) // push particle around in center sector
|
||||
PartSys->particles[PartSys->usedParticles>>1].vx = ((PartSys->particles[PartSys->usedParticles>>1].vx > 0 ? 1 : -1)) * (loudness >> 3);
|
||||
}
|
||||
}
|
||||
else{
|
||||
if (SEGMENT.custom3 <= 10) { // periodic pulse: 0-5 apply at start, 6-10 apply at center
|
||||
if (strip.now > SEGMENT.step) {
|
||||
int speed = (SEGMENT.custom3 > 5) ? (SEGMENT.custom3 - 6) : SEGMENT.custom3;
|
||||
SEGMENT.step = strip.now + 7500 - ((SEGMENT.speed << 3) + (speed << 10));
|
||||
int amplitude = 40 + (SEGMENT.custom1 >> 2);
|
||||
int index = (SEGMENT.custom3 > 5) ? (PartSys->usedParticles / 2) : 0; // center or start particle
|
||||
PartSys->particles[index].vx += amplitude;
|
||||
}
|
||||
}
|
||||
else if (SEGMENT.custom3 <= 30) { // sinusoidal wave: 11-20 apply at start, 21-30 apply at center
|
||||
int index = (SEGMENT.custom3 > 20) ? (PartSys->usedParticles / 2) : 0; // center or start particle
|
||||
int restposition = 0;
|
||||
if (index > 0) restposition = PartSys->maxX >> 1; // center
|
||||
//int amplitude = 5 + (SEGMENT.speed >> 3) + (SEGMENT.custom1 >> 2); // amplitude depends on density
|
||||
int amplitude = 5 + (SEGMENT.custom1 >> 2); // amplitude depends on density
|
||||
int speed = SEGMENT.custom3 - 10 - (index ? 10 : 0); // map 11-20 and 21-30 to 1-10
|
||||
int phase = strip.now * ((1 + (SEGMENT.speed >> 4)) * speed);
|
||||
if (SEGMENT.check2) amplitude <<= 1; // double amplitude for XL particles
|
||||
//PartSys->applyForce(PartSys->particles[index], (sin16_t(phase) * amplitude) >> 15, PartSys->advPartProps[index].forcecounter); // apply acceleration
|
||||
PartSys->particles[index].x = restposition + ((sin16_t(phase) * amplitude) >> 12); // apply position
|
||||
}
|
||||
else {
|
||||
if (hw_random16() < 656) { // ~1% chance to add a pulse
|
||||
int amplitude = 60;
|
||||
if (SEGMENT.check2) amplitude <<= 1; // double amplitude for XL particles
|
||||
PartSys->particles[PartSys->usedParticles >> 1].vx += hw_random16(amplitude << 1) - amplitude; // apply acceleration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < PartSys->usedParticles; i++) {
|
||||
if (SEGMENT.custom2 == 255) { // map speed to hue
|
||||
int speedclr = ((int8_t(abs(PartSys->particles[i].vx))) >> 2) << 4; // scale for greater color variation, dump small values to avoid flickering
|
||||
//int speed = PartSys->particles[i].vx << 2; // +/- 512
|
||||
if (speedclr > 240) speedclr = 240; // limit color to non-wrapping part of palette
|
||||
PartSys->particles[i].hue = speedclr;
|
||||
}
|
||||
else if (SEGMENT.custom2 > 0)
|
||||
PartSys->particles[i].hue = i * (SEGMENT.custom2 >> 2); // gradient distribution
|
||||
else {
|
||||
// map hue to particle density
|
||||
int deviation;
|
||||
if (i == 0) // First particle: measure density based on distance to anchor point
|
||||
deviation = springlength/2 - PartSys->particles[i].x;
|
||||
else if (i == PartSys->usedParticles - 1) // Last particle: measure density based on distance to right boundary
|
||||
deviation = springlength/2 - (PartSys->maxX - PartSys->particles[i].x);
|
||||
else {
|
||||
// Middle particles: average of compression/expansion from both sides
|
||||
int leftDx = PartSys->particles[i].x - PartSys->particles[i-1].x;
|
||||
int rightDx = PartSys->particles[i+1].x - PartSys->particles[i].x;
|
||||
int avgDistance = (leftDx + rightDx) >> 1;
|
||||
if (avgDistance < 0) avgDistance = 0; // avoid negative distances (not sure why this happens)
|
||||
deviation = (springlength - avgDistance);
|
||||
}
|
||||
deviation = constrain(deviation, -127, 112); // limit deviation to -127..112 (do not go intwo wrapping part of palette)
|
||||
PartSys->particles[i].hue = 127 + deviation; // map density to hue
|
||||
}
|
||||
}
|
||||
PartSys->update(); // update and render
|
||||
return FRAMETIME;
|
||||
}
|
||||
static const char _data_FX_MODE_PS_SPRINGY[] PROGMEM = "PS Springy@Stiffness,Damping,Density,Hue,Mode,Smear,XL,AR;,!;!;1f;pal=54,c2=0,c3=23";
|
||||
|
||||
#endif // WLED_DISABLE_PARTICLESYSTEM1D
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -10385,7 +10678,9 @@ addEffect(FX_MODE_PSCHASE, &mode_particleChase, _data_FX_MODE_PS_CHASE);
|
||||
addEffect(FX_MODE_PSSTARBURST, &mode_particleStarburst, _data_FX_MODE_PS_STARBURST);
|
||||
addEffect(FX_MODE_PS1DGEQ, &mode_particle1DGEQ, _data_FX_MODE_PS_1D_GEQ);
|
||||
addEffect(FX_MODE_PSFIRE1D, &mode_particleFire1D, _data_FX_MODE_PS_FIRE1D);
|
||||
addEffect(FX_MODE_PS1DSONICSTREAM, &mode_particle1Dsonicstream, _data_FX_MODE_PS_SONICSTREAM);
|
||||
addEffect(FX_MODE_PS1DSONICSTREAM, &mode_particle1DsonicStream, _data_FX_MODE_PS_SONICSTREAM);
|
||||
addEffect(FX_MODE_PS1DSONICBOOM, &mode_particle1DsonicBoom, _data_FX_MODE_PS_SONICBOOM);
|
||||
addEffect(FX_MODE_PS1DSPRINGY, &mode_particleSpringy, _data_FX_MODE_PS_SPRINGY);
|
||||
#endif // WLED_DISABLE_PARTICLESYSTEM1D
|
||||
|
||||
}
|
||||
|
@ -351,7 +351,9 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
|
||||
#define FX_MODE_PS1DGEQ 212
|
||||
#define FX_MODE_PSFIRE1D 213
|
||||
#define FX_MODE_PS1DSONICSTREAM 214
|
||||
#define MODE_COUNT 215
|
||||
#define FX_MODE_PS1DSONICBOOM 215
|
||||
#define FX_MODE_PS1DSPRINGY 216
|
||||
#define MODE_COUNT 217
|
||||
|
||||
|
||||
#define BLEND_STYLE_FADE 0x00 // universal
|
||||
|
@ -18,8 +18,8 @@
|
||||
// local shared functions (used both in 1D and 2D system)
|
||||
static int32_t calcForce_dv(const int8_t force, uint8_t &counter);
|
||||
static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, const bool wrap); // returns false if out of bounds by more than particleradius
|
||||
static void fast_color_add(CRGB &c1, const CRGB &c2, uint32_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding)
|
||||
static void fast_color_scale(CRGB &c, const uint32_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255
|
||||
static void fast_color_add(CRGB &c1, const CRGB &c2, uint8_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding)
|
||||
static void fast_color_scale(CRGB &c, const uint8_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255
|
||||
//static CRGB *allocateCRGBbuffer(uint32_t length);
|
||||
|
||||
// global variables for memory management
|
||||
@ -387,14 +387,14 @@ void ParticleSystem2D::getParticleXYsize(PSadvancedParticle *advprops, PSsizeCon
|
||||
return;
|
||||
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
|
||||
int32_t deviation = ((uint32_t)size * (uint32_t)advsize->asymmetry + 255) >> 8; // 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 (asymdir < 64) {
|
||||
deviation = (asymdir * deviation) / 64;
|
||||
deviation = (asymdir * deviation) >> 6;
|
||||
} else if (asymdir < 192) {
|
||||
deviation = ((128 - asymdir) * deviation) / 64;
|
||||
deviation = ((128 - asymdir) * deviation) >> 6;
|
||||
} else {
|
||||
deviation = ((asymdir - 255) * deviation) / 64;
|
||||
deviation = ((asymdir - 255) * deviation) >> 6;
|
||||
}
|
||||
// Calculate x and y size based on deviation, limit to 255 (rendering function cannot handle larger sizes)
|
||||
xsize = min((size - deviation), (int32_t)255);
|
||||
@ -404,7 +404,7 @@ void ParticleSystem2D::getParticleXYsize(PSadvancedParticle *advprops, PSsizeCon
|
||||
// function to bounce a particle from a wall using set parameters (wallHardness and wallRoughness)
|
||||
void ParticleSystem2D::bounce(int8_t &incomingspeed, int8_t ¶llelspeed, int32_t &position, const uint32_t maxposition) {
|
||||
incomingspeed = -incomingspeed;
|
||||
incomingspeed = (incomingspeed * wallHardness) / 255; // reduce speed as energy is lost on non-hard surface
|
||||
incomingspeed = (incomingspeed * wallHardness + 128) >> 8; // reduce speed as energy is lost on non-hard surface
|
||||
if (position < (int32_t)particleHardRadius)
|
||||
position = particleHardRadius; // fast particles will never reach the edge if position is inverted, this looks better
|
||||
else
|
||||
@ -694,7 +694,7 @@ void ParticleSystem2D::ParticleSys_render() {
|
||||
}
|
||||
|
||||
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
|
||||
void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint32_t brightness, const CRGB& color, const bool wrapX, const bool wrapY) {
|
||||
void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY) {
|
||||
if (particlesize == 0) { // single pixel rendering
|
||||
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT;
|
||||
uint32_t y = particles[particleindex].y >> PS_P_RADIUS_SHIFT;
|
||||
@ -706,7 +706,7 @@ void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint32
|
||||
}
|
||||
return;
|
||||
}
|
||||
int32_t pxlbrightness[4]; // brightness values for the four pixels representing a particle
|
||||
uint8_t pxlbrightness[4]; // brightness values for the four pixels representing a particle
|
||||
int32_t pixco[4][2]; // physical pixel coordinates of the four pixels a particle is rendered to. x,y pairs
|
||||
bool pixelvalid[4] = {true, true, true, true}; // is set to false if pixel is out of bounds
|
||||
bool advancedrender = false; // rendering for advanced particles
|
||||
@ -911,9 +911,9 @@ void ParticleSystem2D::handleCollisions() {
|
||||
collDistSq = (particleHardRadius << 1) + (((uint32_t)advPartProps[idx_i].size + (uint32_t)advPartProps[idx_j].size) >> 1); // collision distance note: not 100% clear why the >> 1 is needed, but it is.
|
||||
collDistSq = collDistSq * collDistSq; // square it for faster comparison
|
||||
}
|
||||
int32_t dx = particles[idx_j].x - particles[idx_i].x;
|
||||
int32_t dx = (particles[idx_j].x + particles[idx_j].vx) - (particles[idx_i].x + particles[idx_i].vx); // distance with lookahead
|
||||
if (dx * dx < collDistSq) { // check x direction, if close, check y direction (squaring is faster than abs() or dual compare)
|
||||
int32_t dy = particles[idx_j].y - particles[idx_i].y;
|
||||
int32_t dy = (particles[idx_j].y + particles[idx_j].vy) - (particles[idx_i].y + particles[idx_i].vy); // distance with lookahead
|
||||
if (dy * dy < collDistSq) // particles are close
|
||||
collideParticles(particles[idx_i], particles[idx_j], dx, dy, collDistSq);
|
||||
}
|
||||
@ -927,7 +927,7 @@ void ParticleSystem2D::handleCollisions() {
|
||||
// takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard)
|
||||
void ParticleSystem2D::collideParticles(PSparticle &particle1, PSparticle &particle2, int32_t dx, int32_t dy, const int32_t collDistSq) {
|
||||
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)
|
||||
// Calculate relative velocity note: could zero check but that does not improve overall speed but deminish it as that is rarely the case and pushing is still required
|
||||
int32_t relativeVx = (int32_t)particle2.vx - (int32_t)particle1.vx;
|
||||
int32_t relativeVy = (int32_t)particle2.vy - (int32_t)particle1.vy;
|
||||
|
||||
@ -1522,7 +1522,6 @@ void ParticleSystem1D::applyFriction(int32_t coefficient) {
|
||||
particles[i].vx = ((int32_t)particles[i].vx * friction) / 255;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1618,7 +1617,7 @@ void ParticleSystem1D::ParticleSys_render() {
|
||||
}
|
||||
|
||||
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
|
||||
void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint32_t brightness, const CRGB &color, const bool wrap) {
|
||||
void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap) {
|
||||
uint32_t size = particlesize;
|
||||
if (advPartProps) { // use advanced size properties
|
||||
size = advPartProps[particleindex].size;
|
||||
@ -1629,7 +1628,7 @@ void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint32
|
||||
if (framebuffer)
|
||||
fast_color_add(framebuffer[x], color, brightness);
|
||||
else
|
||||
SEGMENT.addPixelColor(x, color.scale8((uint8_t)brightness), true);
|
||||
SEGMENT.addPixelColor(x, color.scale8(brightness), true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -1736,7 +1735,7 @@ void ParticleSystem1D::handleCollisions() {
|
||||
int32_t overlap = particleHardRadius << 1; // overlap bins to include edge particles to neighbouring bins
|
||||
if (advPartProps) //may be using individual particle size
|
||||
overlap += 256; // add 2 * max radius (approximately)
|
||||
uint32_t maxBinParticles = max((uint32_t)50, (usedParticles + 1) / 4); // do not bin small amounts, limit max to 1/2 of particles
|
||||
uint32_t maxBinParticles = max((uint32_t)50, (usedParticles + 1) / 4); // do not bin small amounts, limit max to 1/4 of particles
|
||||
uint32_t numBins = (maxX + (BIN_WIDTH - 1)) / BIN_WIDTH; // calculate number of bins
|
||||
uint16_t binIndices[maxBinParticles]; // array to store indices of particles in a bin
|
||||
uint32_t binParticleCount; // number of particles in the current bin
|
||||
@ -1767,15 +1766,12 @@ void ParticleSystem1D::handleCollisions() {
|
||||
for (uint32_t j = i + 1; j < binParticleCount; j++) { // check against higher number particles
|
||||
uint32_t idx_j = binIndices[j];
|
||||
if (advPartProps) { // use advanced size properties
|
||||
collisiondistance = (PS_P_MINHARDRADIUS_1D << particlesize) + (((uint32_t)advPartProps[idx_i].size + (uint32_t)advPartProps[idx_j].size) >> 1);
|
||||
collisiondistance = (PS_P_MINHARDRADIUS_1D << particlesize) + ((advPartProps[idx_i].size + advPartProps[idx_j].size) >> 1);
|
||||
}
|
||||
int32_t dx = particles[idx_j].x - particles[idx_i].x;
|
||||
int32_t dv = (int32_t)particles[idx_j].vx - (int32_t)particles[idx_i].vx;
|
||||
int32_t proximity = collisiondistance;
|
||||
if (dv >= proximity) // particles would go past each other in next move update
|
||||
proximity += abs(dv); // add speed difference to catch fast particles
|
||||
if (dx <= proximity && dx >= -proximity) { // collide if close
|
||||
collideParticles(particles[idx_i], particleFlags[idx_i], particles[idx_j], particleFlags[idx_j], dx, dv, collisiondistance);
|
||||
int32_t dx = (particles[idx_j].x + particles[idx_j].vx) - (particles[idx_i].x + particles[idx_i].vx); // distance between particles with lookahead
|
||||
uint32_t dx_abs = abs(dx);
|
||||
if (dx_abs <= collisiondistance) { // collide if close
|
||||
collideParticles(particles[idx_i], particleFlags[idx_i], particles[idx_j], particleFlags[idx_j], dx, dx_abs, collisiondistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1784,13 +1780,18 @@ 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, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, int32_t dx, int32_t relativeVx, const int32_t collisiondistance) {
|
||||
int32_t dotProduct = (dx * relativeVx); // is always negative if moving towards each other
|
||||
uint32_t distance = abs(dx);
|
||||
void ParticleSystem1D::collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, const int32_t dx, const uint32_t dx_abs, const int32_t collisiondistance) {
|
||||
int32_t dv = particle2.vx - particle1.vx;
|
||||
int32_t dotProduct = (dx * dv); // is always negative if moving towards each other
|
||||
|
||||
if (dotProduct < 0) { // particles are moving towards each other
|
||||
uint32_t surfacehardness = max(collisionHardness, (int32_t)PS_P_MINSURFACEHARDNESS_1D); // if particles are soft, the impulse must stay above a limit or collisions slip through
|
||||
// Calculate new velocities after collision
|
||||
int32_t impulse = relativeVx * surfacehardness / 255; // note: not using dot product like in 2D as impulse is purely speed depnedent
|
||||
// Calculate new velocities after collision note: not using dot product like in 2D as impulse is purely speed depnedent
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster)
|
||||
int32_t impulse = ((dv * surfacehardness) + ((dv >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts
|
||||
#else // division is faster on ESP32, S2 and S3
|
||||
int32_t impulse = (dv * surfacehardness) / 255;
|
||||
#endif
|
||||
particle1.vx += impulse;
|
||||
particle2.vx -= impulse;
|
||||
|
||||
@ -1802,13 +1803,17 @@ void ParticleSystem1D::collideParticles(PSparticle1D &particle1, const PSparticl
|
||||
|
||||
if (collisionHardness < PS_P_MINSURFACEHARDNESS_1D && (SEGMENT.call & 0x07) == 0) { // if particles are soft, they become 'sticky' i.e. apply some friction
|
||||
const uint32_t coeff = collisionHardness + (250 - PS_P_MINSURFACEHARDNESS_1D);
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(ESP8266) // use bitshifts with rounding instead of division (2x faster)
|
||||
particle1.vx = ((int32_t)particle1.vx * coeff + (((int32_t)particle1.vx >> 31) & 0xFF)) >> 8; // note: (v>>31) & 0xFF)) extracts the sign and adds 255 if negative for correct rounding using shifts
|
||||
particle2.vx = ((int32_t)particle2.vx * coeff + (((int32_t)particle2.vx >> 31) & 0xFF)) >> 8;
|
||||
#else // division is faster on ESP32, S2 and S3
|
||||
particle1.vx = ((int32_t)particle1.vx * coeff) / 255;
|
||||
particle2.vx = ((int32_t)particle2.vx * coeff) / 255;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (distance < (collisiondistance - 8) && abs(relativeVx) < 5) // overlapping and moving slowly
|
||||
{
|
||||
if (dx_abs < (collisiondistance - 8) && abs(dv) < 5) { // overlapping and moving slowly
|
||||
// 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
|
||||
// note: like in 2D, pushing by a distance makes softer piles collapse, giving particles speed prevents that and looks nicer
|
||||
@ -1818,8 +1823,8 @@ void ParticleSystem1D::collideParticles(PSparticle1D &particle1, const PSparticl
|
||||
particle1.vx -= pushamount;
|
||||
particle2.vx += pushamount;
|
||||
|
||||
if(distance < collisiondistance >> 1) { // too close, force push particles so they dont collapse
|
||||
pushamount = 1 + ((collisiondistance - distance) >> 3); // note: push amount found by experimentation
|
||||
if (dx_abs < collisiondistance >> 1) { // too close, force push particles so they dont collapse
|
||||
pushamount = 1 + ((collisiondistance - dx_abs) >> 3); // note: push amount found by experimentation
|
||||
|
||||
if (particle1.x < (maxX >> 1)) { // lower half, push particle with larger x in positive direction
|
||||
if (dx < 0 && !particle1flags.fixed) { // particle2.x < particle1.x -> push particle 1
|
||||
@ -1836,7 +1841,7 @@ void ParticleSystem1D::collideParticles(PSparticle1D &particle1, const PSparticl
|
||||
particle2.vx--;// -= pushamount;
|
||||
particle2.x -= pushamount;
|
||||
}
|
||||
else if (!particle2flags.fixed) { // particle1.x < particle2.x -> push particle 1
|
||||
else if (!particle1flags.fixed) { // particle1.x < particle2.x -> push particle 1
|
||||
particle1.vx--;// -= pushamount;
|
||||
particle1.x -= pushamount;
|
||||
}
|
||||
@ -2022,7 +2027,7 @@ static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32
|
||||
// note: result is stored in c1, not using a return value is faster as the CRGB struct does not need to be copied upon return
|
||||
// note2: function is mainly used to add scaled colors, so checking if one color is black is slower
|
||||
// note3: scale is 255 when using blur, checking for that makes blur faster
|
||||
static void fast_color_add(CRGB &c1, const CRGB &c2, const uint32_t scale) {
|
||||
__attribute__((optimize("O2"))) static void fast_color_add(CRGB &c1, const CRGB &c2, const uint8_t scale) {
|
||||
uint32_t r, g, b;
|
||||
if (scale < 255) {
|
||||
r = c1.r + ((c2.r * scale) >> 8);
|
||||
@ -2034,9 +2039,9 @@ static void fast_color_add(CRGB &c1, const CRGB &c2, const uint32_t scale) {
|
||||
b = c1.b + c2.b;
|
||||
}
|
||||
|
||||
uint32_t max = std::max(r,g); // check for overflow, using max() is faster as the compiler can optimize
|
||||
max = std::max(max,b);
|
||||
if (max < 256) {
|
||||
// note: this chained comparison is the fastest method for max of 3 values (faster than std:max() or using xor)
|
||||
uint32_t max = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b);
|
||||
if (max <= 255) {
|
||||
c1.r = r; // save result to c1
|
||||
c1.g = g;
|
||||
c1.b = b;
|
||||
@ -2049,7 +2054,7 @@ static void fast_color_add(CRGB &c1, const CRGB &c2, const uint32_t scale) {
|
||||
}
|
||||
|
||||
// faster than fastled color scaling as it does in place scaling
|
||||
static void fast_color_scale(CRGB &c, const uint32_t scale) {
|
||||
__attribute__((optimize("O2"))) static void fast_color_scale(CRGB &c, const uint8_t scale) {
|
||||
c.r = ((c.r * scale) >> 8);
|
||||
c.g = ((c.g * scale) >> 8);
|
||||
c.b = ((c.b * scale) >> 8);
|
||||
|
@ -211,7 +211,7 @@ public:
|
||||
private:
|
||||
//rendering functions
|
||||
void ParticleSys_render();
|
||||
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint32_t brightness, const CRGB& color, const bool wrapX, const bool wrapY);
|
||||
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY);
|
||||
//paricle physics applied by system if flags are set
|
||||
void applyGravity(); // applies gravity to all particles
|
||||
void handleCollisions();
|
||||
@ -378,12 +378,12 @@ public:
|
||||
private:
|
||||
//rendering functions
|
||||
void ParticleSys_render(void);
|
||||
void renderParticle(const uint32_t particleindex, const uint32_t brightness, const CRGB &color, const bool wrap);
|
||||
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap);
|
||||
|
||||
//paricle physics applied by system if flags are set
|
||||
void applyGravity(); // applies gravity to all particles
|
||||
void handleCollisions();
|
||||
[[gnu::hot]] void collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, int32_t dx, int32_t relativeVx, const int32_t collisiondistance);
|
||||
[[gnu::hot]] void collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, const int32_t dx, const uint32_t dx_abs, const int32_t collisiondistance);
|
||||
|
||||
//utility functions
|
||||
void updatePSpointers(const bool isadvanced); // update the data pointers to current segment data space
|
||||
|
Loading…
x
Reference in New Issue
Block a user