GCC Code Coverage Report


Directory: ./
File: overlays/wled/FXparticleSystem.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 2 2 100.0%
Functions: 1 1 100.0%
Branches: 2 2 100.0%

Line Branch Exec Source
1 /*
2 FXparticleSystem.cpp
3
4 Particle system with functions for particle generation, particle movement and particle rendering to RGB matrix.
5 by DedeHai (Damian Schneider) 2013-2024
6
7 Copyright (c) 2024 Damian Schneider
8 Licensed under the EUPL v. 1.2 or later
9 */
10
11 #ifdef WLED_DISABLE_2D
12 #define WLED_DISABLE_PARTICLESYSTEM2D
13 #endif
14
15 #if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) // not both disabled
16
17 #include <stdint.h>
18 #include "wled.h"
19
20 #define PS_P_MAXSPEED 120 // maximum speed a particle can have (vx/vy is int8)
21 #define MAX_MEMIDLE 10 // max idle time (in frames) before memory is deallocated (if deallocated during an effect, it will crash!)
22
23 //#define WLED_DEBUG_PS // note: enabling debug uses ~3k of flash
24
25 #ifdef WLED_DEBUG_PS
26 #define PSPRINT(x) Serial.print(x)
27 #define PSPRINTLN(x) Serial.println(x)
28 #else
29 #define PSPRINT(x)
30 #define PSPRINTLN(x)
31 #endif
32
33 // limit speed of particles (used in 1D and 2D)
34 53050 static inline int32_t limitSpeed(const int32_t speed) {
35
2/2
✓ Branch 0 taken 53046 times.
✓ Branch 1 taken 4 times.
53050 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
36 }
37 #endif
38
39 #ifndef WLED_DISABLE_PARTICLESYSTEM2D
40 // memory allocation
41 #define ESP8266_MAXPARTICLES 256 // enough up to 16x16 pixels
42 #define ESP8266_MAXSOURCES 24
43 #define ESP32S2_MAXPARTICLES 1024 // enough up to 32x32 pixels
44 #define ESP32S2_MAXSOURCES 64
45 #define ESP32_MAXPARTICLES 2048 // enough up to 64x32 pixels
46 #define ESP32_MAXSOURCES 128
47
48 // particle dimensions (subpixel division)
49 #define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement (must be a power of 2)
50 #define PS_P_HALFRADIUS (PS_P_RADIUS >> 1)
51 #define PS_P_RADIUS_SHIFT 6 // shift for RADIUS
52 #define PS_P_SURFACE 12 // shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2
53 #define PS_P_MINHARDRADIUS 64 // minimum hard surface radius for collisions
54 #define PS_P_MINSURFACEHARDNESS 128 // minimum hardness used in collision impulse calculation, below this hardness, particles become sticky
55
56 // struct for PS settings (shared for 1D and 2D class)
57 typedef union {
58 struct{ // one byte bit field for 2D settings
59 bool wrapX : 1;
60 bool wrapY : 1;
61 bool bounceX : 1;
62 bool bounceY : 1;
63 bool killoutofbounds : 1; // if set, out of bound particles are killed immediately
64 bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top
65 bool useCollisions : 1;
66 bool colorByAge : 1; // if set, particle hue is set by ttl value in render function
67 };
68 byte asByte; // access as a byte, order is: LSB is first entry in the list above
69 } PSsettings2D;
70
71 //struct for a single particle
72 typedef struct { // 10 bytes
73 int16_t x; // x position in particle system
74 int16_t y; // y position in particle system
75 uint16_t ttl; // time to live in frames
76 int8_t vx; // horizontal velocity
77 int8_t vy; // vertical velocity
78 uint8_t hue; // color hue
79 uint8_t sat; // particle color saturation
80 } PSparticle;
81
82 //struct for particle flags note: this is separate from the particle struct to save memory (ram alignment)
83 typedef union {
84 struct { // 1 byte
85 bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area
86 bool collide : 1; // if set, particle takes part in collisions
87 bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds)
88 bool custom1 : 1; // unused custom flags, can be used by FX to track particle states
89 bool custom2 : 1;
90 bool custom3 : 1;
91 bool custom4 : 1;
92 bool custom5 : 1;
93 };
94 byte asByte; // access as a byte, order is: LSB is first entry in the list above
95 } PSparticleFlags;
96
97 // struct for additional particle settings (option)
98 typedef struct { // 2 bytes
99 uint8_t size; // particle size, 255 means 10 pixels in diameter, 0 means use global size (including single pixel rendering)
100 uint8_t forcecounter; // counter for applying forces to individual particles
101 } PSadvancedParticle;
102
103 // struct for advanced particle size control (option)
104 typedef struct { // 8 bytes
105 uint8_t asymmetry; // asymmetrical size (0=symmetrical, 255 fully asymmetric)
106 uint8_t asymdir; // direction of asymmetry, 64 is x, 192 is y (0 and 128 is symmetrical)
107 uint8_t maxsize; // target size for growing
108 uint8_t minsize; // target size for shrinking
109 uint8_t sizecounter : 4; // counters used for size contol (grow/shrink/wobble)
110 uint8_t wobblecounter : 4;
111 uint8_t growspeed : 4;
112 uint8_t shrinkspeed : 4;
113 uint8_t wobblespeed : 4;
114 bool grow : 1; // flags
115 bool shrink : 1;
116 bool pulsate : 1; // grows & shrinks & grows & ...
117 bool wobble : 1; // alternate x and y size
118 } PSsizeControl;
119
120
121 //struct for a particle source (20 bytes)
122 typedef struct {
123 uint16_t minLife; // minimum ttl of emittet particles
124 uint16_t maxLife; // maximum ttl of emitted particles
125 PSparticle source; // use a particle as the emitter source (speed, position, color)
126 PSparticleFlags sourceFlags; // flags for the source particle
127 int8_t var; // variation of emitted speed (adds random(+/- var) to speed)
128 int8_t vx; // emitting speed
129 int8_t vy;
130 uint8_t size; // particle size (advanced property), global size is added on top to this size
131 } PSsource;
132
133 // class uses approximately 60 bytes
134 class ParticleSystem2D {
135 public:
136 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
137 // note: memory is allcated in the FX function, no deconstructor needed
138 void update(void); //update the particles according to set options and render to the matrix
139 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)
140 void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions
141 void particleMoveUpdate(PSparticle &part, PSparticleFlags &partFlags, PSsettings2D *options = NULL, PSadvancedParticle *advancedproperties = NULL); // move function
142 // particle emitters
143 int32_t sprayEmit(const PSsource &emitter);
144 void flameEmit(const PSsource &emitter);
145 int32_t angleEmit(PSsource& emitter, const uint16_t angle, const int32_t speed);
146 //particle physics
147 void applyGravity(PSparticle &part); // applies gravity to single particle (use this for sources)
148 [[gnu::hot]] void applyForce(PSparticle &part, const int8_t xforce, const int8_t yforce, uint8_t &counter);
149 [[gnu::hot]] void applyForce(const uint32_t particleindex, const int8_t xforce, const int8_t yforce); // use this for advanced property particles
150 void applyForce(const int8_t xforce, const int8_t yforce); // apply a force to all particles
151 void applyAngleForce(PSparticle &part, const int8_t force, const uint16_t angle, uint8_t &counter);
152 void applyAngleForce(const uint32_t particleindex, const int8_t force, const uint16_t angle); // use this for advanced property particles
153 void applyAngleForce(const int8_t force, const uint16_t angle); // apply angular force to all particles
154 void applyFriction(PSparticle &part, const int32_t coefficient); // apply friction to specific particle
155 void applyFriction(const int32_t coefficient); // apply friction to all used particles
156 void pointAttractor(const uint32_t particleindex, PSparticle &attractor, const uint8_t strength, const bool swallow);
157 // set options note: inlining the set function uses more flash so dont optimize
158 void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100%
159 void setCollisionHardness(const uint8_t hardness); // hardness for particle collisions (255 means full hard)
160 void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set
161 void setWallRoughness(const uint8_t roughness); // wall roughness randomizes wall collisions
162 void setMatrixSize(const uint32_t x, const uint32_t y);
163 void setWrapX(const bool enable);
164 void setWrapY(const bool enable);
165 void setBounceX(const bool enable);
166 void setBounceY(const bool enable);
167 void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die
168 void setSaturation(const uint8_t sat); // set global color saturation
169 void setColorByAge(const bool enable);
170 void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero
171 void setSmearBlur(const uint8_t bluramount); // enable 2D smeared blurring of full frame
172 void setParticleSize(const uint8_t size);
173 void setGravity(const int8_t force = 8);
174 void enableParticleCollisions(const bool enable, const uint8_t hardness = 255);
175
176 PSparticle *particles; // pointer to particle array
177 PSparticleFlags *particleFlags; // pointer to particle flags array
178 PSsource *sources; // pointer to sources
179 PSadvancedParticle *advPartProps; // pointer to advanced particle properties (can be NULL)
180 PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL)
181 uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data
182 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)
183 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
184 uint32_t numSources; // number of sources
185 uint32_t usedParticles; // number of particles used in animation, is relative to 'numParticles'
186 //note: some variables are 32bit for speed and code size at the cost of ram
187
188 private:
189 //rendering functions
190 void render();
191 [[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY);
192 //paricle physics applied by system if flags are set
193 void applyGravity(); // applies gravity to all particles
194 void handleCollisions();
195 [[gnu::hot]] void collideParticles(PSparticle &particle1, PSparticle &particle2, const int32_t dx, const int32_t dy, const uint32_t collDistSq);
196 void fireParticleupdate();
197 //utility functions
198 void updatePSpointers(const bool isadvanced, const bool sizecontrol); // update the data pointers to current segment data space
199 bool updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
200 void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize);
201 [[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
202 // note: variables that are accessed often are 32bit for speed
203 CRGB *framebuffer; // local frame buffer for rendering
204 PSsettings2D particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above
205 uint32_t numParticles; // total number of particles allocated by this system
206 uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
207 int32_t collisionHardness;
208 uint32_t wallHardness;
209 uint32_t wallRoughness; // randomizes wall collisions
210 uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed)
211 uint16_t collisionStartIdx; // particle array start index for collision detection
212 uint8_t fireIntesity = 0; // fire intensity, used for fire mode (flash use optimization, better than passing an argument to render function)
213 uint8_t forcecounter; // counter for globally applied forces
214 uint8_t gforcecounter; // counter for global gravity
215 int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards)
216 // global particle properties for basic particles
217 uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, 255 = 10 pixels (note: this is also added to individual sized particles, set to 0 or 1 for standard advanced particle rendering)
218 uint8_t motionBlur; // motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0
219 uint8_t smearBlur; // 2D smeared blurring of full frame
220 };
221
222 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);
223 // initialization functions (not part of class)
224 bool initParticleSystem2D(ParticleSystem2D *&PartSys, const uint32_t requestedsources, const uint32_t additionalbytes = 0, const bool advanced = false, const bool sizecontrol = false);
225 uint32_t calculateNumberOfParticles2D(const uint32_t pixels, const bool advanced, const bool sizecontrol);
226 uint32_t calculateNumberOfSources2D(const uint32_t pixels, const uint32_t requestedsources);
227 bool allocateParticleSystemMemory2D(const uint32_t numparticles, const uint32_t numsources, const bool advanced, const bool sizecontrol, const uint32_t additionalbytes);
228 #endif // WLED_DISABLE_PARTICLESYSTEM2D
229
230 ////////////////////////
231 // 1D Particle System //
232 ////////////////////////
233 #ifndef WLED_DISABLE_PARTICLESYSTEM1D
234 // memory allocation
235 #define ESP8266_MAXPARTICLES_1D 320
236 #define ESP8266_MAXSOURCES_1D 16
237 #define ESP32S2_MAXPARTICLES_1D 1300
238 #define ESP32S2_MAXSOURCES_1D 32
239 #define ESP32_MAXPARTICLES_1D 2600
240 #define ESP32_MAXSOURCES_1D 64
241
242 // particle dimensions (subpixel division)
243 #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)
244 #define PS_P_HALFRADIUS_1D (PS_P_RADIUS_1D >> 1)
245 #define PS_P_RADIUS_SHIFT_1D 5 // 1 << PS_P_RADIUS_SHIFT = PS_P_RADIUS
246 #define PS_P_SURFACE_1D 5 // shift: 2^PS_P_SURFACE = PS_P_RADIUS_1D
247 #define PS_P_MINHARDRADIUS_1D 32 // minimum hard surface radius note: do not change or hourglass effect will be broken
248 #define PS_P_MINSURFACEHARDNESS_1D 120 // minimum hardness used in collision impulse calculation
249
250 // struct for PS settings (shared for 1D and 2D class)
251 typedef union {
252 struct{
253 // one byte bit field for 1D settings
254 bool wrap : 1;
255 bool bounce : 1;
256 bool killoutofbounds : 1; // if set, out of bound particles are killed immediately
257 bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top
258 bool useCollisions : 1;
259 bool colorByAge : 1; // if set, particle hue is set by ttl value in render function
260 bool colorByPosition : 1; // if set, particle hue is set by its position in the strip segment
261 bool unused : 1;
262 };
263 byte asByte; // access as a byte, order is: LSB is first entry in the list above
264 } PSsettings1D;
265
266 //struct for a single particle (8 bytes)
267 typedef struct {
268 int32_t x; // x position in particle system
269 uint16_t ttl; // time to live in frames
270 int8_t vx; // horizontal velocity
271 uint8_t hue; // color hue
272 } PSparticle1D;
273
274 //struct for particle flags
275 typedef union {
276 struct { // 1 byte
277 bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area
278 bool collide : 1; // if set, particle takes part in collisions
279 bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds)
280 bool reversegrav : 1; // if set, gravity is reversed on this particle
281 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
282 bool fixed : 1; // if set, particle does not move (and collisions make other particles revert direction),
283 bool custom1 : 1; // unused custom flags, can be used by FX to track particle states
284 bool custom2 : 1;
285 };
286 byte asByte; // access as a byte, order is: LSB is first entry in the list above
287 } PSparticleFlags1D;
288
289 // struct for additional particle settings (optional)
290 typedef struct {
291 uint8_t sat; //color saturation
292 uint8_t size; // particle size, 255 means 10 pixels in diameter, this overrides global size setting
293 uint8_t forcecounter;
294 } PSadvancedParticle1D;
295
296 //struct for a particle source (20 bytes)
297 typedef struct {
298 uint16_t minLife; // minimum ttl of emittet particles
299 uint16_t maxLife; // maximum ttl of emitted particles
300 PSparticle1D source; // use a particle as the emitter source (speed, position, color)
301 PSparticleFlags1D sourceFlags; // flags for the source particle
302 int8_t var; // variation of emitted speed (adds random(+/- var) to speed)
303 int8_t v; // emitting speed
304 uint8_t sat; // color saturation (advanced property)
305 uint8_t size; // particle size (advanced property)
306 // note: there is 3 bytes of padding added here
307 } PSsource1D;
308
309 class ParticleSystem1D
310 {
311 public:
312 ParticleSystem1D(const uint32_t length, const uint32_t numberofparticles, const uint32_t numberofsources, const bool isadvanced = false); // constructor
313 // note: memory is allcated in the FX function, no deconstructor needed
314 void update(void); //update the particles according to set options and render to the matrix
315 void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions
316 // particle emitters
317 int32_t sprayEmit(const PSsource1D &emitter);
318 void particleMoveUpdate(PSparticle1D &part, PSparticleFlags1D &partFlags, PSsettings1D *options = NULL, PSadvancedParticle1D *advancedproperties = NULL); // move function
319 //particle physics
320 [[gnu::hot]] void applyForce(PSparticle1D &part, const int8_t xforce, uint8_t &counter); //apply a force to a single particle
321 void applyForce(const int8_t xforce); // apply a force to all particles
322 void applyGravity(PSparticle1D &part, PSparticleFlags1D &partFlags); // applies gravity to single particle (use this for sources)
323 void applyFriction(const int32_t coefficient); // apply friction to all used particles
324 // set options
325 void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100%
326 void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set
327 void setSize(const uint32_t x); //set particle system size (= strip length)
328 void setWrap(const bool enable);
329 void setBounce(const bool enable);
330 void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die
331 // void setSaturation(uint8_t sat); // set global color saturation
332 void setColorByAge(const bool enable);
333 void setColorByPosition(const bool enable);
334 void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero
335 void setSmearBlur(const uint8_t bluramount); // enable 1D smeared blurring of full frame
336 void setParticleSize(const uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled if advanced particle is used
337 void setGravity(int8_t force = 8);
338 void enableParticleCollisions(bool enable, const uint8_t hardness = 255);
339
340 PSparticle1D *particles; // pointer to particle array
341 PSparticleFlags1D *particleFlags; // pointer to particle flags array
342 PSsource1D *sources; // pointer to sources
343 PSadvancedParticle1D *advPartProps; // pointer to advanced particle properties (can be NULL)
344 //PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL)
345 uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data
346 int32_t maxX; // particle system size i.e. width-1, Note: all "max" variables must be signed to compare to coordinates (which are signed)
347 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
348 uint32_t numSources; // number of sources
349 uint32_t usedParticles; // number of particles used in animation, is relative to 'numParticles'
350
351 private:
352 //rendering functions
353 void render(void);
354 [[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap);
355
356 //paricle physics applied by system if flags are set
357 void applyGravity(); // applies gravity to all particles
358 void handleCollisions();
359 [[gnu::hot]] void collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, const int32_t dx, const uint32_t dx_abs, const uint32_t collisiondistance);
360
361 //utility functions
362 void updatePSpointers(const bool isadvanced); // update the data pointers to current segment data space
363 //void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
364 [[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
365 // note: variables that are accessed often are 32bit for speed
366 #ifndef ESP8266
367 CRGB *framebuffer; // local frame buffer for rendering
368 #endif
369 PSsettings1D particlesettings; // settings used when updating particles
370 uint32_t numParticles; // total number of particles allocated by this system
371 uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
372 int32_t collisionHardness;
373 uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection
374 uint32_t wallHardness;
375 uint8_t gforcecounter; // counter for global gravity
376 int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards)
377 uint8_t forcecounter; // counter for globally applied forces
378 uint16_t collisionStartIdx; // particle array start index for collision detection
379 //global particle properties for basic particles
380 uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, is overruled by advanced particle size
381 uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations
382 uint8_t smearBlur; // smeared blurring of full frame
383 };
384
385 bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedsources, const uint8_t fractionofparticles = 255, const uint32_t additionalbytes = 0, const bool advanced = false);
386 uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced);
387 uint32_t calculateNumberOfSources1D(const uint32_t requestedsources);
388 bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t numsources, const bool isadvanced, const uint32_t additionalbytes);
389 void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start);
390 #endif // WLED_DISABLE_PARTICLESYSTEM1D
391