WLED/wled00/FXparticleSystem.h
Damian Schneider 02f14baad4
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
2025-04-15 19:07:21 +02:00

419 lines
25 KiB
C++

/*
FXparticleSystem.cpp
Particle system with functions for particle generation, particle movement and particle rendering to RGB matrix.
by DedeHai (Damian Schneider) 2013-2024
Copyright (c) 2024 Damian Schneider
Licensed under the EUPL v. 1.2 or later
*/
#ifdef WLED_DISABLE_2D
#define WLED_DISABLE_PARTICLESYSTEM2D
#endif
#if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) // not both disabled
#include <stdint.h>
#include "wled.h"
#define PS_P_MAXSPEED 120 // maximum speed a particle can have (vx/vy is int8)
#define MAX_MEMIDLE 10 // max idle time (in frames) before memory is deallocated (if deallocated during an effect, it will crash!)
//#define WLED_DEBUG_PS // note: enabling debug uses ~3k of flash
#ifdef WLED_DEBUG_PS
#define PSPRINT(x) Serial.print(x)
#define PSPRINTLN(x) Serial.println(x)
#else
#define PSPRINT(x)
#define PSPRINTLN(x)
#endif
// memory and transition manager
struct partMem {
void* particleMemPointer; // pointer to particle memory
uint32_t buffersize; // buffer size in bytes
uint8_t particleType; // type of particles currently in memory: 0 = none, particle struct size otherwise (required for 1D<->2D transitions)
uint8_t id; // ID of segment this memory belongs to
uint8_t watchdog; // counter to handle deallocation
uint8_t inTransition; // to track PS to PS FX transitions (is set to new FX ID during transitions), not set if not both FX are PS FX
uint8_t currentFX; // current FX ID, is set when transition is complete, used to detect back and forth transitions
bool finalTransfer; // used to update buffer in rendering function after transition has ended
bool transferParticles; // if set, particles in buffer are transferred to new FX
};
void* particleMemoryManager(const uint32_t requestedParticles, size_t structSize, uint32_t &availableToPS, uint32_t numParticlesUsed, const uint8_t effectID); // update particle memory pointer, handles memory transitions
void particleHandover(void *buffer, size_t structSize, int32_t numParticles);
void updateUsedParticles(const uint32_t allocated, const uint32_t available, const uint8_t percentage, uint32_t &used);
bool segmentIsOverlay(void); // check if segment is fully overlapping with at least one underlying segment
partMem* getPartMem(void); // returns pointer to memory struct for current segment or nullptr
void updateRenderingBuffer(uint32_t requiredpixels, bool isFramebuffer, bool initialize); // allocate CRGB rendering buffer, update size if needed
void transferBuffer(uint32_t width, uint32_t height, bool useAdditiveTransfer = false); // transfer the buffer to the segment (supports 1D and 2D)
void servicePSmem(); // increments watchdog, frees memory if idle too long
// limit speed of particles (used in 1D and 2D)
static inline int32_t limitSpeed(const int32_t speed) {
return speed > PS_P_MAXSPEED ? PS_P_MAXSPEED : (speed < -PS_P_MAXSPEED ? -PS_P_MAXSPEED : speed); // note: this is slightly faster than using min/max at the cost of 50bytes of flash
}
#endif
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
// memory allocation
#define ESP8266_MAXPARTICLES 300 // enough up to 20x20 pixels
#define ESP8266_MAXSOURCES 24
#define ESP32S2_MAXPARTICLES 1024 // enough up to 32x32 pixels
#define ESP32S2_MAXSOURCES 64
#define ESP32_MAXPARTICLES 2048 // enough up to 64x32 pixels
#define ESP32_MAXSOURCES 128
// particle dimensions (subpixel division)
#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement (must be a power of 2)
#define PS_P_HALFRADIUS (PS_P_RADIUS >> 1)
#define PS_P_RADIUS_SHIFT 6 // shift for RADIUS
#define PS_P_SURFACE 12 // shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2
#define PS_P_MINHARDRADIUS 64 // minimum hard surface radius for collisions
#define PS_P_MINSURFACEHARDNESS 128 // minimum hardness used in collision impulse calculation, below this hardness, particles become sticky
// struct for PS settings (shared for 1D and 2D class)
typedef union {
struct{ // one byte bit field for 2D settings
bool wrapX : 1;
bool wrapY : 1;
bool bounceX : 1;
bool bounceY : 1;
bool killoutofbounds : 1; // if set, out of bound particles are killed immediately
bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top
bool useCollisions : 1;
bool colorByAge : 1; // if set, particle hue is set by ttl value in render function
};
byte asByte; // access as a byte, order is: LSB is first entry in the list above
} PSsettings2D;
//struct for a single particle
typedef struct { // 10 bytes
int16_t x; // x position in particle system
int16_t y; // y position in particle system
uint16_t ttl; // time to live in frames
int8_t vx; // horizontal velocity
int8_t vy; // vertical velocity
uint8_t hue; // color hue
uint8_t sat; // particle color saturation
} PSparticle;
//struct for particle flags note: this is separate from the particle struct to save memory (ram alignment)
typedef union {
struct { // 1 byte
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 custom1 : 1; // unused custom flags, can be used by FX to track particle states
bool custom2 : 1;
bool custom3 : 1;
bool custom4 : 1;
bool custom5 : 1;
};
byte asByte; // access as a byte, order is: LSB is first entry in the list above
} PSparticleFlags;
// struct for additional particle settings (option)
typedef struct { // 2 bytes
uint8_t size; // particle size, 255 means 10 pixels in diameter
uint8_t forcecounter; // counter for applying forces to individual particles
} PSadvancedParticle;
// struct for advanced particle size control (option)
typedef struct { // 8 bytes
uint8_t asymmetry; // asymmetrical size (0=symmetrical, 255 fully asymmetric)
uint8_t asymdir; // direction of asymmetry, 64 is x, 192 is y (0 and 128 is symmetrical)
uint8_t maxsize; // target size for growing
uint8_t minsize; // target size for shrinking
uint8_t sizecounter : 4; // counters used for size contol (grow/shrink/wobble)
uint8_t wobblecounter : 4;
uint8_t growspeed : 4;
uint8_t shrinkspeed : 4;
uint8_t wobblespeed : 4;
bool grow : 1; // flags
bool shrink : 1;
bool pulsate : 1; // grows & shrinks & grows & ...
bool wobble : 1; // alternate x and y size
} PSsizeControl;
//struct for a particle source (20 bytes)
typedef struct {
uint16_t minLife; // minimum ttl of emittet particles
uint16_t maxLife; // maximum ttl of emitted particles
PSparticle source; // use a particle as the emitter source (speed, position, color)
PSparticleFlags sourceFlags; // flags for the source particle
int8_t var; // variation of emitted speed (adds random(+/- var) to speed)
int8_t vx; // emitting speed
int8_t vy;
uint8_t size; // particle size (advanced property)
} PSsource;
// class uses approximately 60 bytes
class ParticleSystem2D {
public:
ParticleSystem2D(const uint32_t width, const uint32_t height, const uint32_t numberofparticles, const uint32_t numberofsources, const bool isadvanced = false, const bool sizecontrol = false); // constructor
// note: memory is allcated in the FX function, no deconstructor needed
void update(void); //update the particles according to set options and render to the matrix
void updateFire(const uint8_t intensity, const bool renderonly); // update function for fire, if renderonly is set, particles are not updated (required to fix transitions with frameskips)
void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions
void particleMoveUpdate(PSparticle &part, PSparticleFlags &partFlags, PSsettings2D *options = NULL, PSadvancedParticle *advancedproperties = NULL); // move function
// particle emitters
int32_t sprayEmit(const PSsource &emitter);
void flameEmit(const PSsource &emitter);
int32_t angleEmit(PSsource& emitter, const uint16_t angle, const int32_t speed);
//particle physics
void applyGravity(PSparticle &part); // applies gravity to single particle (use this for sources)
[[gnu::hot]] void applyForce(PSparticle &part, const int8_t xforce, const int8_t yforce, uint8_t &counter);
[[gnu::hot]] void applyForce(const uint32_t particleindex, const int8_t xforce, const int8_t yforce); // use this for advanced property particles
void applyForce(const int8_t xforce, const int8_t yforce); // apply a force to all particles
void applyAngleForce(PSparticle &part, const int8_t force, const uint16_t angle, uint8_t &counter);
void applyAngleForce(const uint32_t particleindex, const int8_t force, const uint16_t angle); // use this for advanced property particles
void applyAngleForce(const int8_t force, const uint16_t angle); // apply angular force to all particles
void applyFriction(PSparticle &part, const int32_t coefficient); // apply friction to specific particle
void applyFriction(const int32_t coefficient); // apply friction to all used particles
void pointAttractor(const uint32_t particleindex, PSparticle &attractor, const uint8_t strength, const bool swallow);
// set options note: inlining the set function uses more flash so dont optimize
void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100%
inline uint32_t getAvailableParticles(void) { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init
void setCollisionHardness(const uint8_t hardness); // hardness for particle collisions (255 means full hard)
void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set
void setWallRoughness(const uint8_t roughness); // wall roughness randomizes wall collisions
void setMatrixSize(const uint32_t x, const uint32_t y);
void setWrapX(const bool enable);
void setWrapY(const bool enable);
void setBounceX(const bool enable);
void setBounceY(const bool enable);
void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die
void setSaturation(const uint8_t sat); // set global color saturation
void setColorByAge(const bool enable);
void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero
void setSmearBlur(const uint8_t bluramount); // enable 2D smeared blurring of full frame
void setParticleSize(const uint8_t size);
void setGravity(const int8_t force = 8);
void enableParticleCollisions(const bool enable, const uint8_t hardness = 255);
PSparticle *particles; // pointer to particle array
PSparticleFlags *particleFlags; // pointer to particle flags array
PSsource *sources; // pointer to sources
PSadvancedParticle *advPartProps; // pointer to advanced particle properties (can be NULL)
PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL)
uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data
int32_t maxX, maxY; // particle system size i.e. width-1 / height-1 in subpixels, Note: all "max" variables must be signed to compare to coordinates (which are signed)
int32_t maxXpixel, maxYpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 / height-1
uint32_t numSources; // number of sources
uint32_t usedParticles; // number of particles used in animation, is relative to 'numParticles'
//note: some variables are 32bit for speed and code size at the cost of ram
private:
//rendering functions
void ParticleSys_render();
[[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();
[[gnu::hot]] void collideParticles(PSparticle &particle1, PSparticle &particle2, const int32_t dx, const int32_t dy, const int32_t collDistSq);
void fireParticleupdate();
//utility functions
void updatePSpointers(const bool isadvanced, const bool sizecontrol); // update the data pointers to current segment data space
bool updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize);
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
// note: variables that are accessed often are 32bit for speed
PSsettings2D particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above
uint32_t numParticles; // total number of particles allocated by this system note: during transitions, less are available, use availableParticles
uint32_t availableParticles; // number of particles available for use (can be more or less than numParticles, assigned by memory manager)
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
int32_t collisionHardness;
uint32_t wallHardness;
uint32_t wallRoughness; // randomizes wall collisions
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed)
uint16_t collisionStartIdx; // particle array start index for collision detection
uint8_t fireIntesity = 0; // fire intensity, used for fire mode (flash use optimization, better than passing an argument to render function)
uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates
uint8_t forcecounter; // counter for globally applied forces
uint8_t gforcecounter; // counter for global gravity
int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards)
// global particle properties for basic particles
uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, 255 = 10 pixels (note: this is also added to individual sized particles)
uint8_t motionBlur; // motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0
uint8_t smearBlur; // 2D smeared blurring of full frame
uint8_t effectID; // ID of the effect that is using this particle system, used for transitions
uint32_t lastRender; // last time the particles were rendered, intermediate fix for speedup
};
void blur2D(CRGB *colorbuffer, const uint32_t xsize, uint32_t ysize, const uint32_t xblur, const uint32_t yblur, const uint32_t xstart = 0, uint32_t ystart = 0, const bool isparticle = false);
// initialization functions (not part of class)
bool initParticleSystem2D(ParticleSystem2D *&PartSys, const uint32_t requestedsources, const uint32_t additionalbytes = 0, const bool advanced = false, const bool sizecontrol = false);
uint32_t calculateNumberOfParticles2D(const uint32_t pixels, const bool advanced, const bool sizecontrol);
uint32_t calculateNumberOfSources2D(const uint32_t pixels, const uint32_t requestedsources);
bool allocateParticleSystemMemory2D(const uint32_t numparticles, const uint32_t numsources, const bool advanced, const bool sizecontrol, const uint32_t additionalbytes);
#endif // WLED_DISABLE_PARTICLESYSTEM2D
////////////////////////
// 1D Particle System //
////////////////////////
#ifndef WLED_DISABLE_PARTICLESYSTEM1D
// memory allocation
#define ESP8266_MAXPARTICLES_1D 450
#define ESP8266_MAXSOURCES_1D 16
#define ESP32S2_MAXPARTICLES_1D 1300
#define ESP32S2_MAXSOURCES_1D 32
#define ESP32_MAXPARTICLES_1D 2600
#define ESP32_MAXSOURCES_1D 64
// particle dimensions (subpixel division)
#define PS_P_RADIUS_1D 32 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines)
#define PS_P_HALFRADIUS_1D (PS_P_RADIUS_1D >> 1)
#define PS_P_RADIUS_SHIFT_1D 5 // 1 << PS_P_RADIUS_SHIFT = PS_P_RADIUS
#define PS_P_SURFACE_1D 5 // shift: 2^PS_P_SURFACE = PS_P_RADIUS_1D
#define PS_P_MINHARDRADIUS_1D 32 // minimum hard surface radius note: do not change or hourglass effect will be broken
#define PS_P_MINSURFACEHARDNESS_1D 120 // minimum hardness used in collision impulse calculation
// struct for PS settings (shared for 1D and 2D class)
typedef union {
struct{
// one byte bit field for 1D settings
bool wrap : 1;
bool bounce : 1;
bool killoutofbounds : 1; // if set, out of bound particles are killed immediately
bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top
bool useCollisions : 1;
bool colorByAge : 1; // if set, particle hue is set by ttl value in render function
bool colorByPosition : 1; // if set, particle hue is set by its position in the strip segment
bool unused : 1;
};
byte asByte; // access as a byte, order is: LSB is first entry in the list above
} PSsettings1D;
//struct for a single particle (8 bytes)
typedef struct {
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
} PSparticle1D;
//struct for particle flags
typedef union {
struct { // 1 byte
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 forcedirection : 1; // direction the force was applied, 1 is positive x-direction (used for collision stacking, similar to reversegrav) TODO: not used anymore, can be removed
bool fixed : 1; // if set, particle does not move (and collisions make other particles revert direction),
bool custom1 : 1; // unused custom flags, can be used by FX to track particle states
bool custom2 : 1;
};
byte asByte; // access as a byte, order is: LSB is first entry in the list above
} PSparticleFlags1D;
// struct for additional particle settings (optional)
typedef struct {
uint8_t sat; //color saturation
uint8_t size; // particle size, 255 means 10 pixels in diameter
uint8_t forcecounter;
} PSadvancedParticle1D;
//struct for a particle source (20 bytes)
typedef struct {
uint16_t minLife; // minimum ttl of emittet particles
uint16_t maxLife; // maximum ttl of emitted particles
PSparticle1D source; // use a particle as the emitter source (speed, position, color)
PSparticleFlags1D sourceFlags; // flags for the source particle
int8_t var; // variation of emitted speed (adds random(+/- var) to speed)
int8_t v; // emitting speed
uint8_t sat; // color saturation (advanced property)
uint8_t size; // particle size (advanced property)
// note: there is 3 bytes of padding added here
} PSsource1D;
class ParticleSystem1D
{
public:
ParticleSystem1D(const uint32_t length, const uint32_t numberofparticles, const uint32_t numberofsources, const bool isadvanced = false); // constructor
// note: memory is allcated in the FX function, no deconstructor needed
void update(void); //update the particles according to set options and render to the matrix
void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions
// particle emitters
int32_t sprayEmit(const PSsource1D &emitter);
void particleMoveUpdate(PSparticle1D &part, PSparticleFlags1D &partFlags, PSsettings1D *options = NULL, PSadvancedParticle1D *advancedproperties = NULL); // move function
//particle physics
[[gnu::hot]] void applyForce(PSparticle1D &part, const int8_t xforce, uint8_t &counter); //apply a force to a single particle
void applyForce(const int8_t xforce); // apply a force to all particles
void applyGravity(PSparticle1D &part, PSparticleFlags1D &partFlags); // applies gravity to single particle (use this for sources)
void applyFriction(const int32_t coefficient); // apply friction to all used particles
// set options
void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100%
inline uint32_t getAvailableParticles(void) { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init
void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set
void setSize(const uint32_t x); //set particle system size (= strip length)
void setWrap(const bool enable);
void setBounce(const bool enable);
void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die
// void setSaturation(uint8_t sat); // set global color saturation
void setColorByAge(const bool enable);
void setColorByPosition(const bool enable);
void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero
void setSmearBlur(const uint8_t bluramount); // enable 1D smeared blurring of full frame
void setParticleSize(const uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled by advanced particle size
void setGravity(int8_t force = 8);
void enableParticleCollisions(bool enable, const uint8_t hardness = 255);
PSparticle1D *particles; // pointer to particle array
PSparticleFlags1D *particleFlags; // pointer to particle flags array
PSsource1D *sources; // pointer to sources
PSadvancedParticle1D *advPartProps; // pointer to advanced particle properties (can be NULL)
//PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL)
uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data
int32_t maxX; // particle system size i.e. width-1, Note: all "max" variables must be signed to compare to coordinates (which are signed)
int32_t maxXpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1
uint32_t numSources; // number of sources
uint32_t usedParticles; // number of particles used in animation, is relative to 'numParticles'
private:
//rendering functions
void ParticleSys_render(void);
[[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, 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
//void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
// note: variables that are accessed often are 32bit for speed
PSsettings1D particlesettings; // settings used when updating particles
uint32_t numParticles; // total number of particles allocated by this system note: never use more than this, even if more are available (only this many advanced particles are allocated)
uint32_t availableParticles; // number of particles available for use (can be more or less than numParticles, assigned by memory manager)
uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
int32_t collisionHardness;
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection
uint32_t wallHardness;
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
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
uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations
uint8_t smearBlur; // smeared blurring of full frame
uint8_t effectID; // ID of the effect that is using this particle system, used for transitions
uint32_t lastRender; // last time the particles were rendered, intermediate fix for speedup
};
bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedsources, const uint8_t fractionofparticles = 255, const uint32_t additionalbytes = 0, const bool advanced = false);
uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced);
uint32_t calculateNumberOfSources1D(const uint32_t requestedsources);
bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t numsources, const bool isadvanced, const uint32_t additionalbytes);
void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start);
#endif // WLED_DISABLE_PARTICLESYSTEM1D