lots of tweaks, updated 1D rendering and collisions

- bugfix in mass based 2D collisions
- added improved and faster large size rendering to 1D system
- added per-particle size rendering to 1D system
- improved and simplified collision handling in 1D system
- removed local blurring functions in PS as they are not needed anymore for particle rendering
- adapted FX to work with the new rendering
- fixed outdated AR handling in PS FX
- fixed infinite loop if not enough memory
- updated PS Hourglass drop interval to simpler math: speed / 10 = time in seconds and improved particle handling
- reduced speed in PS Pinball to fix collision slip-through
- PS Box now auto-adjusts number of particles based on matrix size and particle size
- added safety check to 2D particle rendering to not crash if something goes wrong with out-of bounds particle rendering
- improved binning for particle collisions: dont use binning for small number of particles (faster)
- Some code cleanup
This commit is contained in:
Damian Schneider
2025-12-13 19:05:21 +01:00
parent a421cfeabe
commit 19bc3c513a
3 changed files with 422 additions and 444 deletions

View File

@@ -8243,7 +8243,7 @@ uint16_t mode_particlefire(void) {
uint32_t numFlames; // number of flames: depends on fire width. for a fire width of 16 pixels, about 25-30 flames give good results
if (SEGMENT.call == 0) { // initialization
if (!initParticleSystem2D(PartSys, SEGMENT.virtualWidth(), 4)) //maximum number of source (PS may limit based on segment size); need 4 additional bytes for time keeping (uint32_t lastcall)
if (!initParticleSystem2D(PartSys, SEGMENT.vWidth(), 4)) //maximum number of source (PS may limit based on segment size); need 4 additional bytes for time keeping (uint32_t lastcall)
return mode_static(); // allocation failed or not 2D
SEGENV.aux0 = hw_random16(); // aux0 is wind position (index) in the perlin noise
}
@@ -8283,10 +8283,10 @@ uint16_t mode_particlefire(void) {
PartSys->sources[i].source.x = (PartSys->maxX >> 1) - (spread >> 1) + hw_random(spread); // change flame position: distribute randomly on chosen width
PartSys->sources[i].source.y = -(PS_P_RADIUS << 2); // set the source below the frame
PartSys->sources[i].source.ttl = 20 + hw_random16((SEGMENT.custom1 * SEGMENT.custom1) >> 8) / (1 + (firespeed >> 5)); //'hotness' of fire, faster flames reduce the effect or flame height will scale too much with speed
PartSys->sources[i].maxLife = hw_random16(SEGMENT.virtualHeight() >> 1) + 16; // defines flame height together with the vy speed, vy speed*maxlife/PS_P_RADIUS is the average flame height
PartSys->sources[i].maxLife = hw_random16(SEGMENT.vHeight() >> 1) + 16; // defines flame height together with the vy speed, vy speed*maxlife/PS_P_RADIUS is the average flame height
PartSys->sources[i].minLife = PartSys->sources[i].maxLife >> 1;
PartSys->sources[i].vx = hw_random16(5) - 2; // emitting speed (sideways)
PartSys->sources[i].vy = (SEGMENT.virtualHeight() >> 1) + (firespeed >> 4) + (SEGMENT.custom1 >> 4); // emitting speed (upwards)
PartSys->sources[i].vy = (SEGMENT.vHeight() >> 1) + (firespeed >> 4) + (SEGMENT.custom1 >> 4); // emitting speed (upwards)
PartSys->sources[i].var = 2 + hw_random16(2 + (firespeed >> 4)); // speed variation around vx,vy (+/- var)
}
}
@@ -8316,11 +8316,11 @@ uint16_t mode_particlefire(void) {
if(hw_random8() < 10 + (SEGMENT.intensity >> 2)) {
for (i = 0; i < PartSys->usedParticles; i++) {
if (PartSys->particles[i].ttl == 0) { // find a dead particle
PartSys->particles[i].ttl = hw_random16(SEGMENT.virtualHeight()) + 30;
PartSys->particles[i].ttl = hw_random16(SEGMENT.vHeight()) + 30;
PartSys->particles[i].x = PartSys->sources[0].source.x;
PartSys->particles[i].y = PartSys->sources[0].source.y;
PartSys->particles[i].vx = PartSys->sources[0].source.vx;
PartSys->particles[i].vy = (SEGMENT.virtualHeight() >> 1) + (firespeed >> 4) + ((30 + (SEGMENT.intensity >> 1) + SEGMENT.custom1) >> 4); // emitting speed (upwards)
PartSys->particles[i].vy = (SEGMENT.vHeight() >> 1) + (firespeed >> 4) + ((30 + (SEGMENT.intensity >> 1) + SEGMENT.custom1) >> 4); // emitting speed (upwards)
break; // emit only one particle
}
}
@@ -8508,9 +8508,11 @@ uint16_t mode_particlebox(void) {
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
PartSys->setWallHardness(min(SEGMENT.custom2, (uint8_t)200)); // wall hardness is 200 or more
PartSys->enableParticleCollisions(true, max(2, (int)SEGMENT.custom2)); // enable collisions and set particle collision hardness
PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 2, 153)); // 1% - 60%
int maxParticleSize = min(((SEGMENT.vWidth() * SEGMENT.vHeight()) >> 2), 255U); // max particle size based on matrix size
unsigned currentParticleSize = map(SEGMENT.custom3, 0, 31, 0, maxParticleSize);
PartSys->setUsedParticles(map(SEGMENT.intensity, 0, 255, 2, 153) / (1 + (currentParticleSize >> 4))); // 1% - 60%, reduce if using larger size
if (SEGMENT.custom3 < 31)
PartSys->setParticleSize(SEGMENT.custom3<<3); // set global size if not max (resets perParticleSize)
PartSys->setParticleSize(currentParticleSize); // set global size if not max (resets perParticleSize)
else
PartSys->perParticleSize = true; // per particle size, uses advPartProps.size (randomized below)
@@ -8523,7 +8525,7 @@ uint16_t mode_particlebox(void) {
PartSys->particles[i].hue = hw_random8(); // make it colorful
PartSys->particleFlags[i].perpetual = true; // never die
PartSys->particleFlags[i].collide = true; // all particles colllide
PartSys->advPartProps[i].size = hw_random8(); // random size, used only if size is set to max (SEGMENT.custom3=31)
PartSys->advPartProps[i].size = hw_random8(maxParticleSize); // random size, used only if size is set to max (SEGMENT.custom3=31)
break; // only spawn one particle per frame for less chaotic transitions
}
}
@@ -8813,13 +8815,11 @@ uint16_t mode_particleattractor(void) {
PartSys->angleEmit(PartSys->sources[0], SEGENV.aux0 + 0x7FFF, 12); // emit at 180° as well
// apply force
uint32_t strength = SEGMENT.speed;
#ifdef USERMOD_AUDIOREACTIVE
um_data_t *um_data;
if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // AR active, do not use simulated data
uint32_t volumeSmth = (uint32_t)(*(float*) um_data->u_data[0]); // 0-255
strength = (SEGMENT.speed * volumeSmth) >> 8;
}
#endif
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
PartSys->pointAttractor(i, *attractor, strength, SEGMENT.check3);
}
@@ -8878,7 +8878,6 @@ uint16_t mode_particlespray(void) {
PartSys->sources[0].source.y = map(SEGMENT.custom2, 0, 255, 0, PartSys->maxY);
uint16_t angle = (256 - (((int32_t)SEGMENT.custom3 + 1) << 3)) << 8;
#ifdef USERMOD_AUDIOREACTIVE
um_data_t *um_data;
if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // get AR data, do not use simulated data
uint32_t volumeSmth = (uint8_t)(*(float*) um_data->u_data[0]); //0 to 255
@@ -8902,17 +8901,6 @@ uint16_t mode_particlespray(void) {
PartSys->angleEmit(PartSys->sources[0], angle, SEGMENT.speed >> 2);
}
}
#else
// change source properties
if (SEGMENT.call % (11 - (SEGMENT.intensity / 25)) == 0) { // every nth frame, cycle color and emit particles
PartSys->sources[0].maxLife = 300; // lifetime in frames. note: could be done in init part, but AR moderequires this to be dynamic
PartSys->sources[0].minLife = 100;
PartSys->sources[0].source.hue++; // = hw_random16(); //change hue of spray source
// PartSys->sources[i].var = SEGMENT.custom3; // emiting variation = nozzle size (custom 3 goes from 0-32)
// spray[j].source.hue = hw_random16(); //set random color for each particle (using palette)
PartSys->angleEmit(PartSys->sources[0], angle, SEGMENT.speed >> 2);
}
#endif
PartSys->update(); // update and render
return FRAMETIME;
@@ -9204,16 +9192,14 @@ uint16_t mode_particleblobs(void) {
SEGENV.aux0 = SEGMENT.speed; //write state back
SEGENV.aux1 = SEGMENT.custom1;
#ifdef USERMOD_AUDIOREACTIVE
um_data_t *um_data;
if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // get AR data, do not use simulated data
if (UsermodManager::getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { // get AR data if available, do not use simulated data
uint8_t volumeSmth = (uint8_t)(*(float*)um_data->u_data[0]);
for (uint32_t i = 0; i < PartSys->usedParticles; i++) { // update particles
if (SEGMENT.check3) //pulsate selected
PartSys->advPartProps[i].size = volumeSmth;
}
}
#endif
PartSys->setMotionBlur(((SEGMENT.custom3) << 3) + 7);
PartSys->update(); // update and render
@@ -9422,7 +9408,7 @@ uint16_t mode_particleDrip(void) {
if (PartSys->particles[i].hue < 245)
PartSys->particles[i].hue += 8;
}
//increase speed on high settings by calling the move function twice
//increase speed on high settings by calling the move function twice note: this can lead to missed collisions
if (SEGMENT.speed > 200)
PartSys->particleMoveUpdate(PartSys->particles[i], PartSys->particleFlags[i]);
}
@@ -9434,8 +9420,8 @@ static const char _data_FX_MODE_PARTICLEDRIP[] PROGMEM = "PS DripDrop@Speed,!,Sp
/*
Particle Replacement for "Bbouncing Balls by Aircoookie"
Also replaces rolling balls and juggle (and maybe popcorn)
Particle Version of "Bouncing Balls by Aircoookie"
Also does rolling balls and juggle (and popcorn)
Uses palette for particle color
by DedeHai (Damian Schneider)
*/
@@ -9446,10 +9432,10 @@ uint16_t mode_particlePinball(void) {
if (!initParticleSystem1D(PartSys, 1, 128, 0, true)) // init
return mode_static(); // allocation failed or is single pixel
PartSys->sources[0].sourceFlags.collide = true; // seeded particles will collide (if enabled)
PartSys->sources[0].source.x = PS_P_RADIUS_1D; //emit at bottom
PartSys->setKillOutOfBounds(true); // out of bounds particles dont return
PartSys->sources[0].source.x = -1000; // shoot up from below
//PartSys->setKillOutOfBounds(true); // out of bounds particles dont return (except on top, taken care of by gravity setting)
SEGENV.aux0 = 1;
SEGENV.aux1 = 5000; //set out of range to ensure uptate on first call
SEGENV.aux1 = 5000; // set settings out of range to ensure uptate on first call
}
else
PartSys = reinterpret_cast<ParticleSystem1D *>(SEGENV.data); // if not first call, just set the pointer to the PS
@@ -9460,69 +9446,71 @@ uint16_t mode_particlePinball(void) {
// Particle System settings
//uint32_t hardness = 240 + (SEGMENT.custom1>>4);
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
PartSys->setGravity(map(SEGMENT.custom3, 0 , 31, 0 , 16)); // set gravity (8 is default strength)
PartSys->setGravity(map(SEGMENT.custom3, 0 , 31, 0 , 8)); // set gravity (8 is default strength)
PartSys->setBounce(SEGMENT.custom3); // disables bounce if no gravity is used
PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur
PartSys->enableParticleCollisions(SEGMENT.check1, 255); // enable collisions and set particle collision to high hardness
PartSys->setUsedParticles(SEGMENT.intensity);
PartSys->setColorByPosition(SEGMENT.check3);
/*
// TODO: update 1D system to use the same logic for per particle size as 2D system
uint32_t maxParticles = max(20, SEGMENT.intensity / (1 + (SEGMENT.check2 * (SEGMENT.custom1 >> 5)))); // max particles depends on intensity and rolling balls mode + size
if (SEGMENT.custom1 < 255)
PartSys->setParticleSize(SEGMENT.custom1); // set size globally
else
PartSys->perParticleSize = true;
*/
else {
PartSys->perParticleSize = true; // use random individual particle size (see below)
maxParticles *= 2; // use more particles if individual s ize is used as there is more space
}
PartSys->setUsedParticles(maxParticles); // reduce if using larger size and rolling balls mode
bool updateballs = false;
if (SEGENV.aux1 != SEGMENT.speed + SEGMENT.intensity + SEGMENT.check2 + SEGMENT.custom1 + PartSys->usedParticles) { // user settings change or more particles are available
SEGENV.step = SEGMENT.call; // reset delay
updateballs = true;
PartSys->sources[0].maxLife = SEGMENT.custom3 ? 5000 : 0xFFFF; // maximum lifetime in frames/2 (very long if not using gravity, this is enough to travel 4000 pixels at min speed)
PartSys->sources[0].maxLife = SEGMENT.custom3 ? 1000 : 0xFFFF; // maximum lifetime in frames/2 (very long if not using gravity, this is enough to travel 4000 pixels at min speed)
PartSys->sources[0].minLife = PartSys->sources[0].maxLife >> 1;
}
if (SEGMENT.check2) { //rolling balls
if (SEGMENT.check2) { // rolling balls
PartSys->setGravity(0);
PartSys->setWallHardness(255);
int speedsum = 0;
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
PartSys->particles[i].ttl = 260; // keep particles alive
if (updateballs) { //speed changed or particle is dead, set particle properties
PartSys->particles[i].ttl = 500; // keep particles alive
if (updateballs) { // speed changed or particle is dead, set particle properties
PartSys->particleFlags[i].collide = true;
if (PartSys->particles[i].x == 0) { // still at initial position (when not switching from a PS)
if (PartSys->particles[i].x == 0) { // still at initial position
PartSys->particles[i].x = hw_random16(PartSys->maxX); // random initial position for all particles
PartSys->particles[i].vx = (hw_random16() & 0x01) ? 1 : -1; // random initial direction
}
PartSys->particles[i].hue = hw_random8(); //set ball colors to random
PartSys->advPartProps[i].sat = 255;
PartSys->advPartProps[i].size = SEGMENT.custom1 < 255 ? SEGMENT.custom1 : hw_random8(); //set ball size
PartSys->advPartProps[i].size = hw_random8(); // set ball size for individual size mode
}
speedsum += abs(PartSys->particles[i].vx);
}
int32_t avgSpeed = speedsum / PartSys->usedParticles;
int32_t setSpeed = 2 + (SEGMENT.speed >> 3);
int32_t setSpeed = 2 + (SEGMENT.speed >> 2);
if (avgSpeed < setSpeed) { // if balls are slow, speed up some of them at random to keep the animation going
for (int i = 0; i < setSpeed - avgSpeed; i++) {
int idx = hw_random16(PartSys->usedParticles);
PartSys->particles[idx].vx += PartSys->particles[idx].vx >= 0 ? 1 : -1; // add 1, keep direction
if (abs(PartSys->particles[idx].vx) < PS_P_MAXSPEED)
PartSys->particles[idx].vx += PartSys->particles[idx].vx >= 0 ? 1 : -1; // add 1, keep direction
}
}
else if (avgSpeed > setSpeed + 8) // if avg speed is too high, apply friction to slow them down
PartSys->applyFriction(1);
}
else { //bouncing balls
else { // bouncing balls
PartSys->setWallHardness(220);
PartSys->sources[0].var = SEGMENT.speed >> 3;
int32_t newspeed = 2 + (SEGMENT.speed >> 1) - (SEGMENT.speed >> 3);
PartSys->sources[0].v = newspeed;
//check for balls that are 'laying on the ground' and remove them
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
if (PartSys->particles[i].vx == 0 && PartSys->particles[i].x < (PS_P_RADIUS_1D + SEGMENT.custom1))
PartSys->particles[i].ttl = 0;
if (PartSys->particles[i].ttl < 50) PartSys->particles[i].ttl = 0; // no dark particles
else if (PartSys->particles[i].vx == 0 && PartSys->particles[i].x < (PS_P_RADIUS_1D + SEGMENT.custom1))
PartSys->particles[i].ttl -= 50; // age fast
if (updateballs) {
PartSys->advPartProps[i].size = SEGMENT.custom1;
if (SEGMENT.custom3 == 0) //gravity off, update speed
if (SEGMENT.custom3 == 0) // gravity off, update speed
PartSys->particles[i].vx = PartSys->particles[i].vx > 0 ? newspeed : -newspeed; //keep the direction
}
}
@@ -9533,14 +9521,14 @@ uint16_t mode_particlePinball(void) {
SEGENV.step += interval + hw_random16(interval);
PartSys->sources[0].source.hue = hw_random16(); //set ball color
PartSys->sources[0].sat = 255;
PartSys->sources[0].size = SEGMENT.custom1 < 255 ? SEGMENT.custom1 : hw_random8(); //set ball size
PartSys->sources[0].size = hw_random8(); //set ball size
PartSys->sprayEmit(PartSys->sources[0]);
}
}
SEGENV.aux1 = SEGMENT.speed + SEGMENT.intensity + SEGMENT.check2 + SEGMENT.custom1 + PartSys->usedParticles;
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
PartSys->particleMoveUpdate(PartSys->particles[i], PartSys->particleFlags[i]); // double the speed
}
//for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
// PartSys->particleMoveUpdate(PartSys->particles[i], PartSys->particleFlags[i]); // double the speed note: this leads to bad collisions, also need to run collision detection before
//}
PartSys->update(); // update and render
return FRAMETIME;
@@ -9888,7 +9876,7 @@ uint16_t mode_particleHourglass(void) {
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, 32); // hardness value found by experimentation on different settings
PartSys->enableParticleCollisions(true, 64); // hardness value (found by experimentation on different settings)
uint32_t colormode = SEGMENT.custom1 >> 5; // 0-7
@@ -9902,6 +9890,12 @@ uint16_t mode_particleHourglass(void) {
SEGENV.aux0 = PartSys->usedParticles - 1; // initial state, start with highest number particle
}
// re-order particles in case heavy collisions flipped particles (highest number index particle is on the "bottom")
for (uint32_t 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);
}
}
// calculate target position depending on direction
auto calcTargetPos = [&](size_t i) {
return PartSys->particleFlags[i].reversegrav ?
@@ -9909,12 +9903,12 @@ uint16_t mode_particleHourglass(void) {
: (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
bool belowtarget = PartSys->particleFlags[i].reversegrav ? (PartSys->particles[i].x > targetposition) : (PartSys->particles[i].x < targetposition);
bool closeToTarget = abs(targetposition - PartSys->particles[i].x) < PS_P_RADIUS_1D;
if (belowtarget || closeToTarget) { // overshot target or close to target and slow speed
PartSys->particles[i].x = targetposition; // set exact position
PartSys->particleFlags[i].fixed = true; // pin particle
}
@@ -9928,25 +9922,17 @@ uint16_t mode_particleHourglass(void) {
case 0: PartSys->particles[i].hue = 120; break; // fixed at 120, if flip is activated, this can make red and green (use palette 34)
case 1: PartSys->particles[i].hue = basehue; break; // fixed selectable color
case 2: // 2 colors inverleaved (same code as 3)
case 3: PartSys->particles[i].hue = ((SEGMENT.custom1 & 0x1F) << 1) + (i % colormode)*74; break; // interleved colors (every 2 or 3 particles)
case 3: PartSys->particles[i].hue = ((SEGMENT.custom1 & 0x1F) << 1) + (i % 3)*74; break; // 3 interleved colors
case 4: PartSys->particles[i].hue = basehue + (i * 255) / PartSys->usedParticles; break; // gradient palette colors
case 5: PartSys->particles[i].hue = basehue + (i * 1024) / PartSys->usedParticles; break; // multi gradient palette colors
case 6: PartSys->particles[i].hue = i + (strip.now >> 3); break; // disco! moving color gradient
default: break;
default: break; // use color by position
}
}
if (SEGMENT.check1 && !PartSys->particleFlags[i].reversegrav) // flip color when fallen
PartSys->particles[i].hue += 120;
}
// re-order particles in case collisions flipped particles (highest number index particle is on the "bottom")
for (uint32_t 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;
@@ -9958,19 +9944,19 @@ uint16_t mode_particleHourglass(void) {
}
if (SEGENV.aux1 == 0) { // countdown passed, run
if (strip.now >= SEGENV.step) { // drop a particle, do not drop more often than every second frame or particles tangle up quite badly
if (strip.now >= SEGENV.step) { // drop a particle
// set next drop time
if (SEGMENT.check3 && *direction) // fast reset
SEGENV.step = strip.now + 100; // drop one particle every 100ms
else // normal interval
SEGENV.step = strip.now + max(20, SEGMENT.speed * 20); // map speed slider from 0.1s to 5s
SEGENV.step = strip.now + max(100, SEGMENT.speed * 100); // map speed slider from 0.1s to 25.5s
if (SEGENV.aux0 < PartSys->usedParticles) {
PartSys->particleFlags[SEGENV.aux0].reversegrav = *direction; // let this particle fall or rise
PartSys->particleFlags[SEGENV.aux0].fixed = false; // unpin
}
else { // overflow
*direction = !(*direction); // flip direction
SEGENV.aux1 = SEGMENT.virtualLength() + 100; // set countdown
SEGENV.aux1 = (SEGMENT.check2) * SEGMENT.vLength() + 100; // set restart countdown, make it short if auto start is unchecked
}
if (*direction == 0) // down, start dropping the highest number particle
SEGENV.aux0--; // next particle
@@ -9978,14 +9964,14 @@ uint16_t mode_particleHourglass(void) {
SEGENV.aux0++;
}
}
else if (SEGMENT.check2) // auto reset
else if (SEGMENT.check2) // auto start/reset
SEGENV.aux1--; // countdown
PartSys->update(); // update and render
return FRAMETIME;
}
static const char _data_FX_MODE_PS_HOURGLASS[] PROGMEM = "PS Hourglass@Interval,!,Color,Blur,Gravity,Colorflip,Start,Fast Reset;,!;!;1;pal=34,sx=50,ix=200,c1=140,c2=80,c3=4,o1=1,o2=1,o3=1";
static const char _data_FX_MODE_PS_HOURGLASS[] PROGMEM = "PS Hourglass@Interval,!,Color,Blur,Gravity,Colorflip,Start,Fast Reset;,!;!;1;pal=34,sx=5,ix=200,c1=140,c2=80,c3=4,o1=1,o2=1,o3=1";
/*
Particle based Spray effect (like a volcano, possible replacement for popcorn)
@@ -10102,7 +10088,7 @@ uint16_t mode_particleBalance(void) {
}
uint32_t randomindex = hw_random16(PartSys->usedParticles);
PartSys->particles[randomindex].vx = ((int32_t)PartSys->particles[randomindex].vx * 200) / 255; // apply friction to random particle to reduce clumping (without collisions)
PartSys->particles[randomindex].vx = ((int32_t)PartSys->particles[randomindex].vx * 200) / 255; // apply friction to random particle to reduce clumping
//if (SEGMENT.check2 && (SEGMENT.call & 0x07) == 0) // no walls, apply friction to smooth things out
if ((SEGMENT.call & 0x0F) == 0 && SEGMENT.custom3 > 4) // apply friction every 16th frame to smooth things out (except for low tilt)
@@ -10128,7 +10114,7 @@ by DedeHai (Damian Schneider)
uint16_t mode_particleChase(void) {
ParticleSystem1D *PartSys = nullptr;
if (SEGMENT.call == 0) { // initialization
if (!initParticleSystem1D(PartSys, 1, 255, 2, true)) // init
if (!initParticleSystem1D(PartSys, 1, 191, 2, true)) // init
return mode_static(); // allocation failed or is single pixel
SEGENV.aux0 = 0xFFFF; // invalidate
*PartSys->PSdataEnd = 1; // huedir
@@ -10142,15 +10128,17 @@ uint16_t mode_particleChase(void) {
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
PartSys->setColorByPosition(SEGMENT.check3);
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
uint32_t numParticles = 1 + map(SEGMENT.intensity, 0, 255, 0, PartSys->usedParticles / (1 + (SEGMENT.custom1 >> 5))); // 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;
if (SEGENV.aux0 != settingssum) { // settings changed changed, update
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
else {
SEGENV.step = (PartSys->maxX + (PS_P_RADIUS_1D << 6)) / numParticles; // spacing between particles
SEGENV.step = (SEGENV.step / PS_P_RADIUS_1D) * PS_P_RADIUS_1D; // round down to nearest multiple of particle subpixel unit to align to pixel grid (makes them move in union)
}
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)
@@ -10616,7 +10604,7 @@ 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
if (!initParticleSystem1D(PartSys, 1, 128, 0, true)) // init with advanced properties (used for spring forces)
return mode_static(); // allocation failed or is single pixel
SEGENV.aux0 = SEGENV.aux1 = 0xFFFF; // invalidate settings
}
@@ -10629,18 +10617,20 @@ uint16_t mode_particleSpringy(void) {
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.
//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->setParticleSize(SEGMENT.check2 ? 120 : 1); // large or small particles
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->particleFlags[i].collide = true; // enable collision for particles -> results in chaos, removed for now
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
//PartSys->advPartProps[i].size = SEGMENT.check2 ? 190 : 2; // set size, small or big -> use global size
}
SEGENV.aux0 = settingssum;
}
@@ -10732,7 +10722,6 @@ uint16_t mode_particleSpringy(void) {
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 {