Skip to content

Commit

Permalink
WIP: fixed 1D system for over 1000 pixels, added collision binning fo…
Browse files Browse the repository at this point in the history
…r 1D

- making x coordinate 32bit allows for larger strips but uses a lot of ram due to struct memory alignment (12bytes instead of 8 bytes), this needs some more work to properly fix.
- adding collision binning significantly speeds things up, about a factor of 2 on most FX using collision
- there are still some bugs in FX or 1D memory handling (or both) on large setups
  • Loading branch information
DedeHai committed Jan 8, 2025
1 parent ffb17c3 commit 5d0daa7
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 42 deletions.
4 changes: 2 additions & 2 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10155,10 +10155,10 @@ uint16_t mode_particle1Dsonicstream(void) {

// Particle System settings
PartSys->updateSystem(); // update system properties (dimensions and data pointers)
PartSys->setMotionBlur(20 + SEGMENT.custom2>>1); // anable motion blur
PartSys->setMotionBlur(20 + (SEGMENT.custom2 >> 1)); // anable motion blur
PartSys->setSmearBlur(200); // smooth out the edges

PartSys->sources[0].v = 5 + SEGMENT.speed >> 2;
PartSys->sources[0].v = 5 + (SEGMENT.speed >> 2);

// FFT processing
um_data_t *um_data;
Expand Down
87 changes: 52 additions & 35 deletions wled00/FXparticleSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -852,18 +852,15 @@ void ParticleSystem2D::handleCollisions() {
uint32_t numBins = (maxX + (BIN_WIDTH -1)) / BIN_WIDTH; // number of bins in x direction
uint16_t binIndices[maxBinParticles]; // creat array on stack for indices, 2kB max for 1024 particles (ESP32_MAXPARTICLES/2)
uint32_t binParticleCount; // number of particles in the current bin
uint32_t nextFrameStartIdx = 0; // index of the first particle in the next frame (set if bin overflow)
uint16_t nextFrameStartIdx = 0; // index of the first particle in the next frame (set if bin overflow)

// Loop through each bin
for (uint32_t bin = 0; bin < numBins; bin++) {
binParticleCount = 0; // Reset particle count for this bin
binParticleCount = 0; // reset for this bin
int32_t binStart = bin * BIN_WIDTH;
int32_t binEnd = binStart + BIN_WIDTH;

// Compute bin bounds
uint32_t binStart = bin * BIN_WIDTH;
uint32_t binEnd = binStart + BIN_WIDTH;

// Fill the binIndices array for this bin
for (uint32_t i = collisionStartIdx; i < usedParticles; ++i) {
// fill the binIndices array for this bin
for (uint32_t i = collisionStartIdx; i < usedParticles; i++) {
if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) { // colliding particle
if (particles[i].x >= binStart && particles[i].x <= binEnd) { // >= and <= to include particles on the edge of the bin (overlap to ensure boarder particles collide with adjacent bins)
if (binParticleCount >= maxBinParticles) { // bin is full, more particles in this bin so do the rest next frame
Expand Down Expand Up @@ -1245,7 +1242,7 @@ void ParticleSystem1D::update(void) {
if (particlesettings.colorByPosition) {
uint32_t scale = (255 << 16) / maxX; // speed improvement: multiplication is faster than division
for (uint32_t i = 0; i < usedParticles; i++) {
particles[i].hue = (scale * (uint32_t)particles[i].x) >> 16;
particles[i].hue = (scale * particles[i].x) >> 16; // note: x is > 0 if not out of bounds
}
}

Expand Down Expand Up @@ -1426,7 +1423,7 @@ void ParticleSystem1D::particleMoveUpdate(PSparticle1D &part, PSsettings1D *opti
}

if (!part.fixed)
part.x = (int16_t)newX; // set new position
part.x = newX; // set new position
else
part.vx = 0; // set speed to zero. note: particle can get speed in collisions, if unfixed, it should not speed away
}
Expand Down Expand Up @@ -1663,26 +1660,47 @@ void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint32

// detect collisions in an array of particles and handle them
void ParticleSystem1D::handleCollisions() {
uint32_t i, j;
int32_t collisiondistance = PS_P_MINHARDRADIUS_1D;

for (i = 0; i < usedParticles; i++) {
// go though all 'higher number' particles and see if any of those are in close proximity and if they are, make them collide
if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) { // if particle is alive and does collide and is not out of view
int32_t dx; // distance to other particles
for (j = i + 1; j < usedParticles; j++) { // check against higher number particles
if (particles[j].ttl > 0 && particles[j].collide) { // if target particle is alive and collides
if (advPartProps) { // use advanced size properties
collisiondistance = PS_P_MINHARDRADIUS_1D + (((uint32_t)advPartProps[i].size + (uint32_t)advPartProps[j].size) >> 1);
}
dx = particles[j].x - particles[i].x;
int32_t dv = (int32_t)particles[j].vx - (int32_t)particles[i].vx;
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) { // check if close
collideParticles(&particles[i], &particles[j], dx, dv, collisiondistance);
constexpr uint32_t BIN_WIDTH = 32 * PS_P_RADIUS_1D; // width of each bin, 32 pixels gives good results, smaller is slower, larger also gets slower, 48 is also still ok
uint32_t maxBinParticles = (usedParticles + 1) / 4; // assume no more than 1/4 of the particles are in the same bin
uint32_t numBins = (maxX + 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
uint16_t nextFrameStartIdx = 0; // index of the first particle in the next frame (set if bin overflow)

for (uint32_t bin = 0; bin < numBins; bin++) {
binParticleCount = 0; // reset for this bin
int32_t binStart = bin * BIN_WIDTH;
int32_t binEnd = binStart + BIN_WIDTH;

// fill the binIndices array for this bin
for (uint32_t i = collisionStartIdx; i < usedParticles; i++) {
if (particles[i].ttl > 0 && particles[i].outofbounds == 0 && particles[i].collide) { // colliding particle
if (particles[i].x >= binStart && particles[i].x <= binEnd) { // >= and <= to include particles on the edge of the bin (overlap to ensure boarder particles collide with adjacent bins)
if (binParticleCount >= maxBinParticles) { // bin is full, more particles in this bin so do the rest next frame
nextFrameStartIdx = i; // bin overflow can only happen once as bin size is at least half of the particles (or half +1)
break;
}
binIndices[binParticleCount++] = i;
}
}
}

for (uint32_t i = 0; i < binParticleCount; i++) { // go though all 'higher number' particles and see if any of those are in close proximity and if they are, make them collide
uint32_t idx_i = binIndices[i];
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 + (((uint32_t)advPartProps[idx_i].size + (uint32_t)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) { // check if close
collideParticles(&particles[idx_i], &particles[idx_j], dx, dv, collisiondistance);
}
}
}
Expand Down Expand Up @@ -2084,6 +2102,9 @@ void* particleMemoryManager(const uint32_t requestedParticles, size_t structSize
int32_t totransfer = maxParticles - availableToPS; // transfer all remaining particles
if(totransfer < 0) totransfer = 0; // safety check
particleHandover(buffer, structSize, totransfer);

//TODO: there is a bug here, in 1D system, this does not really work right. maybe an alignment problem??? (2D seems to work fine)
// -> bug seems magically fixed?
if(maxParticles / numParticlesUsed > 3) { // FX uses less than 25%: move the already existing particles to the beginning of the buffer
uint32_t usedbytes = availableToPS * structSize;
uint32_t bufferoffset = (maxParticles - 1) - availableToPS; // offset to existing particles (see above)
Expand Down Expand Up @@ -2125,12 +2146,12 @@ void* particleMemoryManager(const uint32_t requestedParticles, size_t structSize

// (re)initialize particles in the particle buffer for use in the new FX
void particleHandover(void *buffer, size_t structSize, int32_t numToTransfer) {
if (pmem->particleType != structSize) { // check if we are being handed over from a different system (1D<->2D), clear buffer if so
memset(buffer, 0, numToTransfer * structSize); // clear buffer
}
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
if (structSize == sizeof(PSparticle)) { // 2D particle
PSparticle *particles = (PSparticle *)buffer;
if (pmem->particleType != sizeof(PSparticle)) { // check if we are being handed over from a 1D system, clear buffer if so
memset(buffer, 0, numToTransfer * sizeof(PSparticle)); // clear buffer
}
for (int32_t i = 0; i < numToTransfer; i++) {
particles[i].perpetual = false; // particle ages
if (particles[i].outofbounds)
Expand All @@ -2146,10 +2167,6 @@ void particleHandover(void *buffer, size_t structSize, int32_t numToTransfer) {
{
#ifndef WLED_DISABLE_PARTICLESYSTEM1D
PSparticle1D *particles = (PSparticle1D *)buffer;
// check if we are being handed over from a 2D system, clear buffer if so
if (pmem->particleType != sizeof(PSparticle1D)) {
memset(buffer, 0, numToTransfer * sizeof(PSparticle1D)); // clear buffer
}
for (int32_t i = 0; i < numToTransfer; i++) {
particles[i].perpetual = false; // particle ages
particles[i].fixed = false; // unfix all particles
Expand Down
10 changes: 5 additions & 5 deletions wled00/FXparticleSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ class ParticleSystem2D {
int32_t collisionHardness;
uint32_t wallHardness;
uint32_t wallRoughness; // randomizes wall collisions
uint32_t collisionStartIdx; // particle array start index for collision detection
uint16_t collisionStartIdx; // particle array start index for collision detection
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed)
uint8_t forcecounter; // counter for globally applied forces
uint8_t gforcecounter; // counter for global gravity
Expand Down Expand Up @@ -269,19 +269,19 @@ typedef union {

//struct for a single particle (8 bytes)
typedef struct {
int16_t x; // x position in particle system
int32_t x; // x position in particle system
uint16_t ttl; // time to live in frames
int8_t vx; // horizontal velocity
uint8_t hue; // color hue
// two byte bit field:
//uint16_t ttl : 11; // time to live, 11 bit or 2047 max (which is 25s at 80FPS)
uint16_t ttl; // time to live in frames
bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area
bool collide : 1; // if set, particle takes part in collisions
bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds)
bool reversegrav : 1; // if set, gravity is reversed on this particle
bool fixed : 1; // if set, particle does not move (and collisions make other particles revert direction),
// note: there is on byte of padding added here, making TTL a 16bit variable saves 500bytes of flash so much faster than a bit field
// TODO: can this be optimized? wastes a lot of ram...
// TODO: can this be optimized? wastes a lot of ram... -> yes, TODO: make the flags a seperate struct array and handle it everywhere.
} PSparticle1D;

// struct for additional particle settings (optional)
Expand Down Expand Up @@ -371,7 +371,7 @@ class ParticleSystem1D
uint8_t gforcecounter; // counter for global gravity
int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards)
uint8_t forcecounter; // counter for globally applied forces
//uint8_t collisioncounter; // counter to handle collisions TODO: could use the SEGMENT.call? -> currently unused
uint16_t collisionStartIdx; // particle array start index for collision detection
//global particle properties for basic particles
uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, larger sizez TBD (TODO: need larger sizes?)
uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations
Expand Down

0 comments on commit 5d0daa7

Please sign in to comment.