Skip to content

Commit

Permalink
added preliminary functions and FX
Browse files Browse the repository at this point in the history
  • Loading branch information
DedeHai committed Feb 6, 2024
1 parent 3b82219 commit 820d8dd
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 30 deletions.
168 changes: 160 additions & 8 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8103,7 +8103,7 @@ uint16_t mode_particlefireworks(void)
{
if (rockets[i].source.ttl)
{
Particle_Move_update(&rockets[i].source); // move the rocket, age the rocket (ttl--)
Particle_Move_update(&rockets[i].source); // move the rocket, age the rocket (ttl--)
}
else if (rockets[i].source.vy > 0)
{ // rocket has died and is moving up. stop it so it will explode (is handled in the code above)
Expand All @@ -8114,7 +8114,7 @@ uint16_t mode_particlefireworks(void)
rockets[i].source.ttl = random8((255 - SEGMENT.speed))+10; // standby time til next launch (in frames at 42fps, max of 265 is about 6 seconds
rockets[i].vx = 0; // emitting speed
rockets[i].vy = 0; // emitting speed
rockets[i].var = 30; // speed variation around vx,vy (+/- var/2)
rockets[i].var = (SEGMENT.intensity >> 2) + 10; // speed variation around vx,vy (+/- var/2)
}
else if (rockets[i].source.vy < 0) // rocket is exploded and time is up (ttl=0 and negative speed), relaunch it
{
Expand All @@ -8125,7 +8125,7 @@ uint16_t mode_particlefireworks(void)
rockets[i].source.vx = random8(5) - 2;
rockets[i].source.hue = 30; // rocket exhaust = orange (if using rainbow palette)
rockets[i].source.ttl = random8(SEGMENT.custom1) + (SEGMENT.custom1>>1); // sets explosion height (rockets explode at the top if set too high as paticle update set speed to zero if moving out of matrix)
rockets[i].maxLife = 40; //exhaust particle life
rockets[i].maxLife = 30; //exhaust particle life
rockets[i].minLife = 10;
rockets[i].vx = 0; // emitting speed
rockets[i].vy = 0; // emitting speed
Expand All @@ -8139,7 +8139,7 @@ uint16_t mode_particlefireworks(void)

return FRAMETIME;
}
static const char _data_FX_MODE_PARTICLEFIREWORKS[] PROGMEM = "Particle Fireworks@Launches,Explosion Size,Height,Bounce Strength,Rockts,Wrap X,Bounce X,Bounce Y;;!;012;pal=11,sx=100,ix=50,c1=64,c2=128,c3=10,o1=0,o2=0,o3=1";
static const char _data_FX_MODE_PARTICLEFIREWORKS[] PROGMEM = "Particle Fireworks@Launches,Explosion Size,Height,Bounce,Rockets,Wrap X,Bounce X,Bounce Y;;!;012;pal=11,sx=100,ix=50,c1=64,c2=128,c3=10,o1=0,o2=0,o3=1";

/*
* Particle gravity spray
Expand Down Expand Up @@ -8563,7 +8563,7 @@ uint16_t mode_particlefall(void)

return FRAMETIME;
}
static const char _data_FX_MODE_PARTICLEFALL[] PROGMEM = "Falling Particles@Speed,Intensity,Randomness,Collision hardness,Saturation,Wrap X,Side bounce,Ground bounce;;!;012;pal=11,sx=100,ix=200,c1=31,c2=0,c3=10,o1=0,o2=0,o3=1";
static const char _data_FX_MODE_PARTICLEFALL[] PROGMEM = "Falling Particles@Speed,Intensity,Randomness,Collision hardness,Saturation,Wrap X,Side bounce,Ground bounce;;!;012;pal=11,sx=100,ix=200,c1=31,c2=0,c3=20,o1=0,o2=0,o3=1";

/*
* Particle pile up test
Expand All @@ -8573,7 +8573,7 @@ static const char _data_FX_MODE_PARTICLEFALL[] PROGMEM = "Falling Particles@Spee

uint16_t mode_particlepile(void)
{

/*
if (SEGLEN == 1)
return mode_static();

Expand Down Expand Up @@ -8720,9 +8720,161 @@ uint16_t mode_particlepile(void)
// render the particles
ParticleSys_render(particles, numParticles, 255, SEGMENT.check1, false);

return FRAMETIME;*/

/*
* Particle smashing down like meteorites, maybe call it particle impact or something like that
* by DedeHai (Damian Schneider)
*/


if (SEGLEN == 1)
return mode_static();

const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1;
const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength();

// particle system box dimensions
const uint16_t PS_MAX_X(cols * PS_P_RADIUS - 1);
const uint16_t PS_MAX_Y(rows * PS_P_RADIUS - 1);

#ifdef ESP8266
const uint16_t numParticles = 250;
const uint8_t MaxNumRockets = 4;
#else
const uint16_t numParticles = 650;
const uint8_t MaxNumRockets = 8;
#endif

PSparticle *particles;
PSpointsource *rockets;

// allocate memory and divide it into proper pointers, max is 32k for all segments.
uint32_t dataSize = sizeof(PSparticle) * numParticles;
dataSize += sizeof(PSpointsource) * (MaxNumRockets);
if (!SEGENV.allocateData(dataSize))
return mode_static(); // allocation failed; //allocation failed

// DEBUG_PRINT(F("particle datasize = "));
// DEBUG_PRINTLN(dataSize);

rockets = reinterpret_cast<PSpointsource *>(SEGENV.data);
// calculate the end of the spray data and assign it as the data pointer for the particles:
particles = reinterpret_cast<PSparticle *>(rockets + MaxNumRockets); // cast the data array into a particle pointer

uint16_t i = 0;
uint16_t j = 0;
uint8_t numRockets = map(SEGMENT.custom3, 0, 31, 1, MaxNumRockets); //number of rockets to use for animation

if (SEGMENT.call == 0) // initialization
{
for (i = 0; i < numParticles; i++)
{
particles[i].ttl = 0;
}
for (i = 0; i < numRockets; i++)
{
rockets[i].source.ttl = random8(20 * i); // set initial delay for rockets
rockets[i].source.vy = 10; // at positive speeds, no particles are emitted and if particle dies, it will be relaunched
}
}

// update particles, create particles

// check each rocket's state and emit particles according to its state: moving up = emit exhaust, at top = explode; falling down = standby time
uint16_t emitparticles; // number of particles to emit for each rocket's state
i = 0;
for (j = 0; j < numRockets; j++)
{
// determine rocket state by its speed:
if (rockets[j].source.vy < 0) // moving down, emit sparks
{
emitparticles = 2;
}
else if (rockets[j].source.vy > 0) // moving up means standby
{
emitparticles = 0;
}
else // speed is zero, explode!
{
rockets[j].source.vy = 2; // set source speed positive so it goes into timeout and launches again
emitparticles = random8(SEGMENT.intensity >> 1) + 10; // defines the size of the explosion
}

for (i; i < numParticles; i++)
{
if (particles[i].ttl == 0)
{ // particle is dead
if (emitparticles > 0)
{
Emitter_Fountain_emit(&rockets[j], &particles[i]);
emitparticles--;
}
else
break; // done emitting for this rocket
}
}
}

// update particles
for (i = 0; i < numParticles; i++)
{
if (particles[i].ttl)
{
Particle_Gravity_update(&particles[i], SEGMENT.check1, SEGMENT.check2, SEGMENT.check3, SEGMENT.custom2);
}
}

// update the meteors, set the speed state
for (i = 0; i < numRockets; i++)
{
Serial.print(rockets[i].source.vy);
Serial.print(" ");
if (rockets[i].source.ttl)
{
Particle_Move_update(&rockets[i].source); // move the rocket, age the rocket (ttl--)
// if source reaches the bottom, set speed to 0 so it will explode on next function call (handled above)
if ((rockets[i].source.y < PS_P_RADIUS) && (rockets[i].source.vy < 0)) // reached the bottom pixel on its way down
{
rockets[i].source.vy = 0; // set speed zero so it will explode
rockets[i].source.vx = 0;
rockets[i].source.y = 5; // offset from ground so explosion happens not out of frame (if moving fast, this can happen)
rockets[i].source.hue = random8(); // random color of explosion
rockets[i].maxLife = 200;
rockets[i].minLife = 50;
rockets[i].source.ttl = random8((255 - SEGMENT.speed)) + 10; // standby time til next launch (in frames at 42fps, max of 265 is about 6 seconds
rockets[i].vx = 0; // emitting speed x
rockets[i].vy = 8; // emitting speed y
rockets[i].var = (SEGMENT.custom1 >> 1); // speed variation around vx,vy (+/- var/2)
}
}
else if (rockets[i].source.vy > 0) // rocket is exploded and time is up (ttl==0 and positive speed), relaunch it
{
// reinitialize rocket
rockets[i].source.y = PS_MAX_Y-2; // start from top
rockets[i].source.x = random16(PS_MAX_X);
rockets[i].source.vy = -30 -random(30)-10; //TODO: need to make this user selectable?
rockets[i].source.vx = random8(30) - 15;
rockets[i].source.hue = 220; // rocket exhaust = orange (if using fire palette)
rockets[i].source.ttl = 1000; //long live, will explode at bottom
rockets[i].maxLife = 60; // spark particle life
rockets[i].minLife = 20;
rockets[i].vx = 0; // emitting speed
rockets[i].vy = -10; // emitting speed
rockets[i].var = 5; // speed variation around vx,vy (+/- var/2)
}
}
SEGMENT.fill(BLACK); // clear the matrix
// render the particles
ParticleSys_render(particles, numParticles, 255, false, false);

return FRAMETIME;

}
static const char _data_FX_MODE_PARTICLEPILE[] PROGMEM = "Particle Pile Test@Moving Speed,Intensity,Particle Speed,Spray Angle,Bouncyness,Wrap X,Bounce X,Ground bounce;;!;012;pal=11,sx=100,ix=200,c1=190,c2=128,c3=28,o1=0,o2=0,o3=1";

static const char _data_FX_MODE_PARTICLEPILE[] PROGMEM = "Particle Pile Test@Launches,Explosion Size,Explosion Force,Bounce,Rockets,Wrap X,Bounce X,Bounce Y;;!;012;pal=11,sx=100,ix=50,c1=64,c2=128,c3=10,o1=0,o2=0,o3=1";

//static const char _data_FX_MODE_PARTICLEPILE[] PROGMEM = "Particle Pile Test@Moving Speed,Intensity,Particle Speed,Spray Angle,Bouncyness,Wrap X,Bounce X,Ground bounce;;!;012;pal=11,sx=100,ix=200,c1=190,c2=128,c3=28,o1=0,o2=0,o3=1";

/*
Particle Box, applies gravity to particles in either a random direction or in a rocking motion
Expand Down Expand Up @@ -8875,7 +9027,7 @@ uint16_t mode_particlebox(void)
applyFriction(&particles[i], 1);
}
// Particle_Gravity_update(&particles[i], SEGMENT.check1, SEGMENT.check2, SEGMENT.check3, (uint8_t)200);
Particle_Bounce_update(&particles[i], (uint8_t)200);
Particle_Bounce_update(&particles[i], hardness);
}

SEGMENT.fill(BLACK); // clear the matrix
Expand Down
110 changes: 90 additions & 20 deletions wled00/FXparticleSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,81 @@ void Emitter_Fountain_emit(PSpointsource *emitter, PSparticle *part)
part->hue = emitter->source.hue;
}

// TODO: could solve all update functions in a single function with parameters and handle gravity acceleration in a separte function (uses more cpu time but that is not a huge issue)
//attracts a particle to an attractor particle using the inverse square-law
void Particle_attractor(PSparticle *particle, PSparticle *attractor, uint8_t* counter, uint8_t strength) //todo: add a parameter 'swallow' so the attractor can 'suck up' particles that are very close
{
// Calculate the distance between the particle and the attractor
int16_t dx = attractor->x - particle->x;
int16_t dy = attractor->y - particle->y;

// Calculate the force based on inverse square law
int32_t distanceSquared = dx * dx + dy * dy;
//check if distance is small enough to even cause a force (for that strength<<10 must be bigger than the distance squared
int32_t shiftedstrength = (int32_t)strength << 16;
if (shiftedstrength > distanceSquared) //if too far away, no force is applied (force < 1)
{
int32_t force = shiftedstrength / distanceSquared;
int32_t xforce = (force * dx)>>12; //scale so that at force starts to increase at about 10 pixels away
int32_t yforce = (force * dy)>>12;

uint8_t xcounter = *counter & 0xF; //lower four bits
uint8_t ycounter = *counter>>4; // upper four bits
*counter = 0; //reset counter, is set back to correct values below

//man muss die jetzt noch schlau shiften, dazu ein excel machen
//for small forces, need to use a delay timer (counter)
if(xforce < 16)
{
xcounter += force;
if (xcounter > 16)
{
xcounter -= 16;
*counter |= xcounter; // write lower four bits
// apply force in x direction
if (dx < 0)
{
particle->vx -= 1;
}
else
{
particle->vx += 1;
}
}

}
else{
particle->vx += xforce/16;
}

if(yforce < 16)
{
ycounter += yforce;
if(ycounter > 16)
{
ycounter -= 16;
*counter |= (ycounter<<4); //write upper four bits

if (dy < 0)
{
particle->vy -= 1;
}
else
{
particle->vy += 1;
}
}

void Particle_Move_update(PSparticle *part) // particle moves, decays and dies (age or out of matrix)
}
else{
particle->vy += yforce / 16;
}

}
}

// TODO: could solve all update functions in a single function with parameters and handle gravity acceleration in a separte function (uses more cpu time but that is not a huge issue) or maybe not, like this, different preferences can be set

void Particle_Move_update(PSparticle *part) // particle moves, decays and dies
{
// Matrix dimension
const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1;
Expand All @@ -67,26 +139,24 @@ void Particle_Move_update(PSparticle *part) // particle moves, decays and dies (

if (part->ttl > 0)
{
// age
part->ttl--;
// age
part->ttl--;

// apply velocity
part->x += (int16_t)part->vx;
part->y += (int16_t)part->vy;

// apply velocity
part->x += (int16_t)part->vx;
part->y += (int16_t)part->vy;
part->outofbounds = 0; // reset out of bounds (in case particle was created outside the matrix and is now moving into view)

// check if particle is out of bounds
if ((part->y <= 0) || (part->y >= PS_MAX_Y))
{
part->ttl = 0;
}
if ((part->x <= 0) || (part->x >= PS_MAX_X))
{
part->ttl = 0;
}
if (part->vx == 0 && part->vy == 0)
{
part->ttl = 0;
}
// check if particle is out of bounds
if ((part->y <= 0) || (part->y >= PS_MAX_Y))
{
part->outofbounds = 1;
}
if ((part->x <= 0) || (part->x >= PS_MAX_X))
{
part->outofbounds = 1;
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions wled00/FXparticleSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@
#define PS_P_SURFACE 12 //shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2


//struct for a single particle with gravity (12 bytes)
//struct for a single particle
typedef struct {
int16_t x; //x position in particle system
int16_t y; //y position in particle system
uint16_t ttl; //time to live
uint8_t outofbounds; //set to 1 if outside of matrix
uint8_t hue; //hue
uint8_t hue; //color hue
uint8_t sat; //color saturation
int8_t vx; //horizontal velocity
int8_t vy; //vertical velocity
} PSparticle;
Expand Down

0 comments on commit 820d8dd

Please sign in to comment.