Bugfixes in PS, improvements to PS Fireworks 1D (#4673)

- fixed inconsitencies in size rendering
- fixed palette being wrapped in color by position and color by age modes
- Fixed bug in memory layout: for some unknown reason, if flags come before particles, last flag is sometimes overwritten, changing memory laout seems to fix that
- New color modes in PS Fireworks 1D:
 - custom3 slider < 16: lower saturation (check1: single color or multi-color explosions)
 - custom3 slider <= 23: full saturation (check1: single color or multi-color explosions)
 - custom3 slider > 23: color by speed (check 1 has not effect here)
 - custom slider = max: color by age or color by position (depends on check1)
This commit is contained in:
Damian Schneider 2025-05-01 13:39:23 +02:00 committed by GitHub
parent d10714d1c1
commit d9b086cbe9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 72 additions and 47 deletions

View File

@ -8127,7 +8127,7 @@ uint16_t mode_particlepit(void) {
PartSys->particles[i].sat = ((SEGMENT.custom3) << 3) + 7; PartSys->particles[i].sat = ((SEGMENT.custom3) << 3) + 7;
// set particle size // set particle size
if (SEGMENT.custom1 == 255) { if (SEGMENT.custom1 == 255) {
PartSys->setParticleSize(1); // set global size to 1 for advanced rendering PartSys->setParticleSize(1); // set global size to 1 for advanced rendering (no single pixel particles)
PartSys->advPartProps[i].size = hw_random16(SEGMENT.custom1); // set each particle to random size PartSys->advPartProps[i].size = hw_random16(SEGMENT.custom1); // set each particle to random size
} else { } else {
PartSys->setParticleSize(SEGMENT.custom1); // set global size PartSys->setParticleSize(SEGMENT.custom1); // set global size
@ -9085,7 +9085,6 @@ uint16_t mode_particlePinball(void) {
PartSys->sources[0].sourceFlags.collide = true; // seeded particles will collide (if enabled) 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->sources[0].source.x = PS_P_RADIUS_1D; //emit at bottom
PartSys->setKillOutOfBounds(true); // out of bounds particles dont return PartSys->setKillOutOfBounds(true); // out of bounds particles dont return
PartSys->setUsedParticles(255); // use all available particles for init
SEGENV.aux0 = 1; SEGENV.aux0 = 1;
SEGENV.aux1 = 5000; //set out of range to ensure uptate on first call SEGENV.aux1 = 5000; //set out of range to ensure uptate on first call
} }
@ -9308,6 +9307,7 @@ uint16_t mode_particleFireworks1D(void) {
uint8_t *forcecounter; uint8_t *forcecounter;
if (SEGMENT.call == 0) { // initialization if (SEGMENT.call == 0) { // initialization
if (!initParticleSystem1D(PartSys, 4, 150, 4, true)) // init advanced particle system
if (!initParticleSystem1D(PartSys, 4, 150, 4, true)) // init advanced particle system if (!initParticleSystem1D(PartSys, 4, 150, 4, true)) // init advanced particle system
return mode_static(); // allocation failed or is single pixel return mode_static(); // allocation failed or is single pixel
PartSys->setKillOutOfBounds(true); PartSys->setKillOutOfBounds(true);
@ -9321,9 +9321,7 @@ uint16_t mode_particleFireworks1D(void) {
// Particle System settings // Particle System settings
PartSys->updateSystem(); // update system properties (dimensions and data pointers) PartSys->updateSystem(); // update system properties (dimensions and data pointers)
forcecounter = PartSys->PSdataEnd; forcecounter = PartSys->PSdataEnd;
PartSys->setParticleSize(SEGMENT.check3); // 1 or 2 pixel rendering
PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur PartSys->setMotionBlur(SEGMENT.custom2); // anable motion blur
int32_t gravity = (1 + (SEGMENT.speed >> 3)); // gravity value used for rocket speed calculation int32_t gravity = (1 + (SEGMENT.speed >> 3)); // gravity value used for rocket speed calculation
PartSys->setGravity(SEGMENT.speed ? gravity : 0); // set gravity PartSys->setGravity(SEGMENT.speed ? gravity : 0); // set gravity
@ -9337,17 +9335,17 @@ uint16_t mode_particleFireworks1D(void) {
SEGENV.aux0 = 0; SEGENV.aux0 = 0;
PartSys->sources[0].sourceFlags.custom1 = 0; //flag used for rocket state PartSys->sources[0].sourceFlags.custom1 = 0; //flag used for rocket state
PartSys->sources[0].source.hue = hw_random16(); PartSys->sources[0].source.hue = hw_random16(); // different color for each launch
PartSys->sources[0].var = 10; // emit variation PartSys->sources[0].var = 10; // emit variation
PartSys->sources[0].v = -10; // emit speed PartSys->sources[0].v = -10; // emit speed
PartSys->sources[0].minLife = 30; PartSys->sources[0].minLife = 30;
PartSys->sources[0].maxLife = SEGMENT.check2 ? 400 : 40; PartSys->sources[0].maxLife = SEGMENT.check2 ? 400 : 60;
PartSys->sources[0].source.x = 0; // start from bottom PartSys->sources[0].source.x = 0; // start from bottom
uint32_t speed = sqrt((gravity * ((PartSys->maxX >> 2) + hw_random16(PartSys->maxX >> 1))) >> 4); // set speed such that rocket explods in frame uint32_t speed = sqrt((gravity * ((PartSys->maxX >> 2) + hw_random16(PartSys->maxX >> 1))) >> 4); // set speed such that rocket explods in frame
PartSys->sources[0].source.vx = min(speed, (uint32_t)127); PartSys->sources[0].source.vx = min(speed, (uint32_t)127);
PartSys->sources[0].source.ttl = 4000; PartSys->sources[0].source.ttl = 4000;
PartSys->sources[0].sat = 30; // low saturation exhaust PartSys->sources[0].sat = 30; // low saturation exhaust
PartSys->sources[0].size = 0; // default size PartSys->sources[0].size = SEGMENT.check3; // single or double pixel rendering
PartSys->sources[0].sourceFlags.reversegrav = false ; // normal gravity PartSys->sources[0].sourceFlags.reversegrav = false ; // normal gravity
if (SEGENV.aux0) { // inverted rockets launch from end if (SEGENV.aux0) { // inverted rockets launch from end
@ -9360,17 +9358,17 @@ uint16_t mode_particleFireworks1D(void) {
} }
else { // rocket is launched else { // rocket is launched
int32_t rocketgravity = -gravity; int32_t rocketgravity = -gravity;
int32_t speed = PartSys->sources[0].source.vx; int32_t currentspeed = PartSys->sources[0].source.vx;
if (SEGENV.aux0) { // negative speed rocket if (SEGENV.aux0) { // negative speed rocket
rocketgravity = -rocketgravity; rocketgravity = -rocketgravity;
speed = -speed; currentspeed = -currentspeed;
} }
PartSys->applyForce(PartSys->sources[0].source, rocketgravity, forcecounter[0]); PartSys->applyForce(PartSys->sources[0].source, rocketgravity, forcecounter[0]);
PartSys->particleMoveUpdate(PartSys->sources[0].source, PartSys->sources[0].sourceFlags); PartSys->particleMoveUpdate(PartSys->sources[0].source, PartSys->sources[0].sourceFlags);
PartSys->particleMoveUpdate(PartSys->sources[0].source, PartSys->sources[0].sourceFlags); // increase speed by calling the move function twice, also ages twice PartSys->particleMoveUpdate(PartSys->sources[0].source, PartSys->sources[0].sourceFlags); // increase rocket speed by calling the move function twice, also ages twice
uint32_t rocketheight = SEGENV.aux0 ? PartSys->maxX - PartSys->sources[0].source.x : PartSys->sources[0].source.x; uint32_t rocketheight = SEGENV.aux0 ? PartSys->maxX - PartSys->sources[0].source.x : PartSys->sources[0].source.x;
if (speed < 0 && PartSys->sources[0].source.ttl > 50) // reached apogee if (currentspeed < 0 && PartSys->sources[0].source.ttl > 50) // reached apogee
PartSys->sources[0].source.ttl = min((uint32_t)50, rocketheight >> (PS_P_RADIUS_SHIFT_1D + 3)); // alive for a few more frames PartSys->sources[0].source.ttl = min((uint32_t)50, rocketheight >> (PS_P_RADIUS_SHIFT_1D + 3)); // alive for a few more frames
if (PartSys->sources[0].source.ttl < 2) { // explode if (PartSys->sources[0].source.ttl < 2) { // explode
@ -9379,19 +9377,32 @@ uint16_t mode_particleFireworks1D(void) {
PartSys->sources[0].minLife = 600; PartSys->sources[0].minLife = 600;
PartSys->sources[0].maxLife = 1300; PartSys->sources[0].maxLife = 1300;
PartSys->sources[0].source.ttl = 100 + hw_random16(64 - (SEGMENT.speed >> 2)); // standby time til next launch PartSys->sources[0].source.ttl = 100 + hw_random16(64 - (SEGMENT.speed >> 2)); // standby time til next launch
PartSys->sources[0].sat = 7 + (SEGMENT.custom3 << 3); //color saturation TODO: replace saturation with something more useful? PartSys->sources[0].sat = SEGMENT.custom3 < 16 ? 10 + (SEGMENT.custom3 << 4) : 255; //color saturation
PartSys->sources[0].size = hw_random16(SEGMENT.intensity); // random particle size in explosion PartSys->sources[0].size = SEGMENT.check3 ? hw_random16(SEGMENT.intensity) : 0; // random particle size in explosion
uint32_t explosionsize = 8 + (PartSys->maxXpixel >> 2) + (PartSys->sources[0].source.x >> (PS_P_RADIUS_SHIFT_1D - 1)); uint32_t explosionsize = 8 + (PartSys->maxXpixel >> 2) + (PartSys->sources[0].source.x >> (PS_P_RADIUS_SHIFT_1D - 1));
explosionsize += hw_random16((explosionsize * SEGMENT.intensity) >> 8); explosionsize += hw_random16((explosionsize * SEGMENT.intensity) >> 8);
for (uint32_t e = 0; e < explosionsize; e++) { // emit explosion particles for (uint32_t e = 0; e < explosionsize; e++) { // emit explosion particles
int idx = PartSys->sprayEmit(PartSys->sources[0]); // emit a particle
if(SEGMENT.custom3 > 23) {
if(SEGMENT.custom3 == 31) { // highest slider value
PartSys->setColorByAge(SEGMENT.check1); // color by age if colorful mode is enabled
PartSys->setColorByPosition(!SEGMENT.check1); // color by position otherwise
}
else { // if custom3 is set to high value (but not highest), set particle color by initial speed
PartSys->particles[idx].hue = map(abs(PartSys->particles[idx].vx), 0, PartSys->sources[0].var, 0, 16 + hw_random16(200)); // set hue according to speed, use random amount of palette width
PartSys->particles[idx].hue += PartSys->sources[0].source.hue; // add hue offset of the rocket (random starting color)
}
}
else {
if (SEGMENT.check1) // colorful mode if (SEGMENT.check1) // colorful mode
PartSys->sources[0].source.hue = hw_random16(); //random color for each particle PartSys->sources[0].source.hue = hw_random16(); //random color for each particle
PartSys->sprayEmit(PartSys->sources[0]); // emit a particle
} }
} }
} }
if ((SEGMENT.call & 0x01) == 0 && PartSys->sources[0].sourceFlags.custom1 == false) // every second frame and not in standby }
if ((SEGMENT.call & 0x01) == 0 && PartSys->sources[0].sourceFlags.custom1 == false && PartSys->sources[0].source.ttl > 50) // every second frame and not in standby and not about to explode
PartSys->sprayEmit(PartSys->sources[0]); // emit exhaust particle PartSys->sprayEmit(PartSys->sources[0]); // emit exhaust particle
if ((SEGMENT.call & 0x03) == 0) // every fourth frame if ((SEGMENT.call & 0x03) == 0) // every fourth frame
PartSys->applyFriction(1); // apply friction to all particles PartSys->applyFriction(1); // apply friction to all particles
@ -9401,10 +9412,9 @@ uint16_t mode_particleFireworks1D(void) {
if (PartSys->particles[i].ttl > 10) PartSys->particles[i].ttl -= 10; //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan if (PartSys->particles[i].ttl > 10) PartSys->particles[i].ttl -= 10; //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan
else PartSys->particles[i].ttl = 0; else PartSys->particles[i].ttl = 0;
} }
return FRAMETIME; return FRAMETIME;
} }
static const char _data_FX_MODE_PS_FIREWORKS1D[] PROGMEM = "PS Fireworks 1D@Gravity,Explosion,Firing side,Blur,Saturation,Colorful,Trail,Smooth;,!;!;1;sx=150,c2=30,c3=31,o1=1,o2=1"; static const char _data_FX_MODE_PS_FIREWORKS1D[] PROGMEM = "PS Fireworks 1D@Gravity,Explosion,Firing side,Blur,Color,Colorful,Trail,Smooth;,!;!;1;c2=30,o1=1";
/* /*
Particle based Sparkle effect Particle based Sparkle effect
@ -9925,7 +9935,6 @@ uint16_t mode_particle1DGEQ(void) {
PartSys->sources[i].maxLife = 240 + SEGMENT.intensity; PartSys->sources[i].maxLife = 240 + SEGMENT.intensity;
PartSys->sources[i].sat = 255; PartSys->sources[i].sat = 255;
PartSys->sources[i].size = SEGMENT.custom1; PartSys->sources[i].size = SEGMENT.custom1;
PartSys->setParticleSize(SEGMENT.custom1);
PartSys->sources[i].source.x = (spacing >> 1) + spacing * i; //distribute evenly PartSys->sources[i].source.x = (spacing >> 1) + spacing * i; //distribute evenly
} }

View File

@ -48,6 +48,7 @@ ParticleSystem2D::ParticleSystem2D(uint32_t width, uint32_t height, uint32_t num
for (uint32_t i = 0; i < numSources; i++) { for (uint32_t i = 0; i < numSources; i++) {
sources[i].source.sat = 255; //set saturation to max by default sources[i].source.sat = 255; //set saturation to max by default
sources[i].source.ttl = 1; //set source alive sources[i].source.ttl = 1; //set source alive
sources[i].sourceFlags.asByte = 0; // all flags disabled
} }
} }
@ -559,6 +560,10 @@ void ParticleSystem2D::pointAttractor(const uint32_t particleindex, PSparticle &
void ParticleSystem2D::render() { void ParticleSystem2D::render() {
CRGB baseRGB; CRGB baseRGB;
uint32_t brightness; // particle brightness, fades if dying uint32_t brightness; // particle brightness, fades if dying
TBlendType blend = LINEARBLEND; // default color rendering: wrap palette
if (particlesettings.colorByAge) {
blend = LINEARBLEND_NOWRAP;
}
if (motionBlur) { // motion-blurring active if (motionBlur) { // motion-blurring active
for (int32_t y = 0; y <= maxYpixel; y++) { for (int32_t y = 0; y <= maxYpixel; y++) {
@ -581,11 +586,11 @@ void ParticleSystem2D::render() {
if (fireIntesity) { // fire mode if (fireIntesity) { // fire mode
brightness = (uint32_t)particles[i].ttl * (3 + (fireIntesity >> 5)) + 20; brightness = (uint32_t)particles[i].ttl * (3 + (fireIntesity >> 5)) + 20;
brightness = min(brightness, (uint32_t)255); brightness = min(brightness, (uint32_t)255);
baseRGB = ColorFromPaletteWLED(SEGPALETTE, brightness, 255); baseRGB = ColorFromPaletteWLED(SEGPALETTE, brightness, 255, LINEARBLEND_NOWRAP);
} }
else { else {
brightness = min((particles[i].ttl << 1), (int)255); brightness = min((particles[i].ttl << 1), (int)255);
baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255); baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255, blend);
if (particles[i].sat < 255) { if (particles[i].sat < 255) {
CHSV32 baseHSV; CHSV32 baseHSV;
rgb2hsv((uint32_t((byte(baseRGB.r) << 16) | (byte(baseRGB.g) << 8) | (byte(baseRGB.b)))), baseHSV); // convert to HSV rgb2hsv((uint32_t((byte(baseRGB.r) << 16) | (byte(baseRGB.g) << 8) | (byte(baseRGB.b)))), baseHSV); // convert to HSV
@ -598,6 +603,7 @@ void ParticleSystem2D::render() {
renderParticle(i, brightness, baseRGB, particlesettings.wrapX, particlesettings.wrapY); renderParticle(i, brightness, baseRGB, particlesettings.wrapX, particlesettings.wrapY);
} }
// apply global size rendering
if (particlesize > 1) { if (particlesize > 1) {
uint32_t passes = particlesize / 64 + 1; // number of blur passes, four passes max uint32_t passes = particlesize / 64 + 1; // number of blur passes, four passes max
uint32_t bluramount = particlesize; uint32_t bluramount = particlesize;
@ -626,7 +632,11 @@ void ParticleSystem2D::render() {
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer // calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
__attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY) { __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB& color, const bool wrapX, const bool wrapY) {
if (particlesize == 0) { // single pixel rendering uint32_t size = particlesize;
if (advPartProps && advPartProps[particleindex].size > 0) // use advanced size properties (0 means use global size including single pixel rendering)
size = advPartProps[particleindex].size;
if (size == 0) { // single pixel rendering
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT; uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT;
uint32_t y = particles[particleindex].y >> PS_P_RADIUS_SHIFT; uint32_t y = particles[particleindex].y >> PS_P_RADIUS_SHIFT;
if (x <= (uint32_t)maxXpixel && y <= (uint32_t)maxYpixel) { if (x <= (uint32_t)maxXpixel && y <= (uint32_t)maxYpixel) {
@ -667,7 +677,7 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint
pxlbrightness[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightness) >> PS_P_SURFACE pxlbrightness[2] = (dx * precal3) >> PS_P_SURFACE; // top right value equal to (dx * dy * brightness) >> PS_P_SURFACE
pxlbrightness[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightness) >> PS_P_SURFACE pxlbrightness[3] = (precal1 * precal3) >> PS_P_SURFACE; // top left value equal to ((PS_P_RADIUS-dx) * dy * brightness) >> PS_P_SURFACE
if (advPartProps && advPartProps[particleindex].size > 0) { //render particle to a bigger size if (advPartProps && advPartProps[particleindex].size > 1) { //render particle to a bigger size
CRGB renderbuffer[100]; // 10x10 pixel buffer CRGB renderbuffer[100]; // 10x10 pixel buffer
memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer
//particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10 //particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10
@ -962,15 +972,13 @@ void ParticleSystem2D::updateSystem(void) {
// FX handles the PSsources, need to tell this function how many there are // FX handles the PSsources, need to tell this function how many there are
void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) { void ParticleSystem2D::updatePSpointers(bool isadvanced, bool sizecontrol) {
PSPRINTLN("updatePSpointers"); PSPRINTLN("updatePSpointers");
// DEBUG_PRINT(F("*** PS pointers ***"));
// DEBUG_PRINTF_P(PSTR("this PS %p "), this);
// Note on memory alignment: // Note on memory alignment:
// a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment. // a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment.
// The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock. // The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock.
// by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes. // by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes.
particleFlags = reinterpret_cast<PSparticleFlags *>(this + 1); // pointer to particle flags particles = reinterpret_cast<PSparticle *>(this + 1); // pointer to particles
particles = reinterpret_cast<PSparticle *>(particleFlags + numParticles); // pointer to particles particleFlags = reinterpret_cast<PSparticleFlags *>(particles + numParticles); // pointer to particle flags
sources = reinterpret_cast<PSsource *>(particles + numParticles); // pointer to source(s) at data+sizeof(ParticleSystem2D) sources = reinterpret_cast<PSsource *>(particleFlags + numParticles); // pointer to source(s) at data+sizeof(ParticleSystem2D)
framebuffer = reinterpret_cast<CRGB *>(sources + numSources); // pointer to framebuffer framebuffer = reinterpret_cast<CRGB *>(sources + numSources); // pointer to framebuffer
// align pointer after framebuffer // align pointer after framebuffer
uintptr_t p = reinterpret_cast<uintptr_t>(framebuffer + (maxXpixel+1)*(maxYpixel+1)); uintptr_t p = reinterpret_cast<uintptr_t>(framebuffer + (maxXpixel+1)*(maxYpixel+1));
@ -1155,6 +1163,7 @@ ParticleSystem1D::ParticleSystem1D(uint32_t length, uint32_t numberofparticles,
// initialize some default non-zero values most FX use // initialize some default non-zero values most FX use
for (uint32_t i = 0; i < numSources; i++) { for (uint32_t i = 0; i < numSources; i++) {
sources[i].source.ttl = 1; //set source alive sources[i].source.ttl = 1; //set source alive
sources[i].sourceFlags.asByte = 0; // all flags disabled
} }
if (isadvanced) { if (isadvanced) {
@ -1269,7 +1278,7 @@ int32_t ParticleSystem1D::sprayEmit(const PSsource1D &emitter) {
particles[emitIndex].x = emitter.source.x; particles[emitIndex].x = emitter.source.x;
particles[emitIndex].hue = emitter.source.hue; particles[emitIndex].hue = emitter.source.hue;
particles[emitIndex].ttl = hw_random16(emitter.minLife, emitter.maxLife); particles[emitIndex].ttl = hw_random16(emitter.minLife, emitter.maxLife);
particleFlags[emitIndex].collide = emitter.sourceFlags.collide; particleFlags[emitIndex].collide = emitter.sourceFlags.collide; // TODO: could just set all flags (asByte) but need to check if that breaks any of the FX
particleFlags[emitIndex].reversegrav = emitter.sourceFlags.reversegrav; particleFlags[emitIndex].reversegrav = emitter.sourceFlags.reversegrav;
particleFlags[emitIndex].perpetual = emitter.sourceFlags.perpetual; particleFlags[emitIndex].perpetual = emitter.sourceFlags.perpetual;
if (advPartProps) { if (advPartProps) {
@ -1419,6 +1428,10 @@ void ParticleSystem1D::applyFriction(int32_t coefficient) {
void ParticleSystem1D::render() { void ParticleSystem1D::render() {
CRGB baseRGB; CRGB baseRGB;
uint32_t brightness; // particle brightness, fades if dying uint32_t brightness; // particle brightness, fades if dying
TBlendType blend = LINEARBLEND; // default color rendering: wrap palette
if (particlesettings.colorByAge || particlesettings.colorByPosition) {
blend = LINEARBLEND_NOWRAP;
}
#ifdef ESP8266 // no local buffer on ESP8266 #ifdef ESP8266 // no local buffer on ESP8266
if (motionBlur) if (motionBlur)
@ -1442,7 +1455,7 @@ void ParticleSystem1D::render() {
// generate RGB values for particle // generate RGB values for particle
brightness = min(particles[i].ttl << 1, (int)255); brightness = min(particles[i].ttl << 1, (int)255);
baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255); baseRGB = ColorFromPaletteWLED(SEGPALETTE, particles[i].hue, 255, blend);
if (advPartProps) { //saturation is advanced property in 1D system if (advPartProps) { //saturation is advanced property in 1D system
if (advPartProps[i].sat < 255) { if (advPartProps[i].sat < 255) {
@ -1489,9 +1502,9 @@ void ParticleSystem1D::render() {
// calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer // calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer
__attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap) { __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGB &color, const bool wrap) {
uint32_t size = particlesize; uint32_t size = particlesize;
if (advPartProps) { // use advanced size properties if (advPartProps) // use advanced size properties (1D system has no large size global rendering TODO: add large global rendering?)
size = advPartProps[particleindex].size; size = advPartProps[particleindex].size;
}
if (size == 0) { //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles (and updating it uses more code) if (size == 0) { //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles (and updating it uses more code)
uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D; uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D;
if (x <= (uint32_t)maxXpixel) { //by making x unsigned there is no need to check < 0 as it will overflow if (x <= (uint32_t)maxXpixel) { //by making x unsigned there is no need to check < 0 as it will overflow
@ -1736,30 +1749,32 @@ void ParticleSystem1D::updatePSpointers(bool isadvanced) {
// a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment. // a pointer MUST be 4 byte aligned. sizeof() in a struct/class is always aligned to the largest element. if it contains a 32bit, it will be padded to 4 bytes, 16bit is padded to 2byte alignment.
// The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock. // The PS is aligned to 4 bytes, a PSparticle is aligned to 2 and a struct containing only byte sized variables is not aligned at all and may need to be padded when dividing the memoryblock.
// by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes. // by making sure that the number of sources and particles is a multiple of 4, padding can be skipped here as alignent is ensured, independent of struct sizes.
particleFlags = reinterpret_cast<PSparticleFlags1D *>(this + 1); // pointer to particle flags particles = reinterpret_cast<PSparticle1D *>(this + 1); // pointer to particles
particles = reinterpret_cast<PSparticle1D *>(particleFlags + numParticles); // pointer to particles particleFlags = reinterpret_cast<PSparticleFlags1D *>(particles + numParticles); // pointer to particle flags
sources = reinterpret_cast<PSsource1D *>(particles + numParticles); // pointer to source(s) sources = reinterpret_cast<PSsource1D *>(particleFlags + numParticles); // pointer to source(s)
#ifdef ESP8266 // no local buffer on ESP8266 #ifdef ESP8266 // no local buffer on ESP8266
PSdataEnd = reinterpret_cast<uint8_t *>(sources + numSources); PSdataEnd = reinterpret_cast<uint8_t *>(sources + numSources);
#else #else
framebuffer = reinterpret_cast<CRGB *>(sources + numSources); // pointer to framebuffer framebuffer = reinterpret_cast<CRGB *>(sources + numSources); // pointer to framebuffer
// align pointer after framebuffer to 4bytes // align pointer after framebuffer to 4bytes
uintptr_t p = reinterpret_cast<uintptr_t>(framebuffer + (maxXpixel+1)); uintptr_t p = reinterpret_cast<uintptr_t>(framebuffer + (maxXpixel+1)); // maxXpixel is SEGMENT.virtualLength() - 1
p = (p + 3) & ~0x03; // align to 4-byte boundary p = (p + 3) & ~0x03; // align to 4-byte boundary
PSdataEnd = reinterpret_cast<uint8_t *>(p); // pointer to first available byte after the PS for FX additional data PSdataEnd = reinterpret_cast<uint8_t *>(p); // pointer to first available byte after the PS for FX additional data
#endif #endif
if (isadvanced) { if (isadvanced) {
advPartProps = reinterpret_cast<PSadvancedParticle1D *>(PSdataEnd); advPartProps = reinterpret_cast<PSadvancedParticle1D *>(PSdataEnd);
PSdataEnd = reinterpret_cast<uint8_t *>(advPartProps + numParticles); PSdataEnd = reinterpret_cast<uint8_t *>(advPartProps + numParticles); // since numParticles is a multiple of 4, this is always aligned to 4 bytes. No need to add padding bytes here
} }
#ifdef WLED_DEBUG_PS #ifdef WLED_DEBUG_PS
PSPRINTLN(" PS Pointers: "); PSPRINTLN(" PS Pointers: ");
PSPRINT(" PS : 0x"); PSPRINT(" PS : 0x");
Serial.println((uintptr_t)this, HEX); Serial.println((uintptr_t)this, HEX);
PSPRINT(" Sources : 0x"); PSPRINT(" Particleflags : 0x");
Serial.println((uintptr_t)sources, HEX); Serial.println((uintptr_t)particleFlags, HEX);
PSPRINT(" Particles : 0x"); PSPRINT(" Particles : 0x");
Serial.println((uintptr_t)particles, HEX); Serial.println((uintptr_t)particles, HEX);
PSPRINT(" Sources : 0x");
Serial.println((uintptr_t)sources, HEX);
#endif #endif
} }
@ -1780,6 +1795,7 @@ uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadva
numberofParticles = numberofParticles < 20 ? 20 : numberofParticles; // 20 minimum numberofParticles = numberofParticles < 20 ? 20 : numberofParticles; // 20 minimum
//make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes) //make sure it is a multiple of 4 for proper memory alignment (easier than using padding bytes)
numberofParticles = (numberofParticles+3) & ~0x03; // note: with a separate particle buffer, this is probably unnecessary numberofParticles = (numberofParticles+3) & ~0x03; // note: with a separate particle buffer, this is probably unnecessary
PSPRINTLN(" calc numparticles:" + String(numberofParticles))
return numberofParticles; return numberofParticles;
} }

View File

@ -96,7 +96,7 @@ typedef union {
// struct for additional particle settings (option) // struct for additional particle settings (option)
typedef struct { // 2 bytes typedef struct { // 2 bytes
uint8_t size; // particle size, 255 means 10 pixels in diameter uint8_t size; // particle size, 255 means 10 pixels in diameter, 0 means use global size (including single pixel rendering)
uint8_t forcecounter; // counter for applying forces to individual particles uint8_t forcecounter; // counter for applying forces to individual particles
} PSadvancedParticle; } PSadvancedParticle;
@ -127,7 +127,7 @@ typedef struct {
int8_t var; // variation of emitted speed (adds random(+/- var) to speed) int8_t var; // variation of emitted speed (adds random(+/- var) to speed)
int8_t vx; // emitting speed int8_t vx; // emitting speed
int8_t vy; int8_t vy;
uint8_t size; // particle size (advanced property) uint8_t size; // particle size (advanced property), global size is added on top to this size
} PSsource; } PSsource;
// class uses approximately 60 bytes // class uses approximately 60 bytes
@ -214,7 +214,7 @@ private:
uint8_t gforcecounter; // counter for global gravity uint8_t gforcecounter; // counter for global gravity
int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards) int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards)
// global particle properties for basic particles // 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 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)
uint8_t motionBlur; // motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0 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 smearBlur; // 2D smeared blurring of full frame
}; };
@ -289,7 +289,7 @@ typedef union {
// struct for additional particle settings (optional) // struct for additional particle settings (optional)
typedef struct { typedef struct {
uint8_t sat; //color saturation uint8_t sat; //color saturation
uint8_t size; // particle size, 255 means 10 pixels in diameter uint8_t size; // particle size, 255 means 10 pixels in diameter, this overrides global size setting
uint8_t forcecounter; uint8_t forcecounter;
} PSadvancedParticle1D; } PSadvancedParticle1D;
@ -333,7 +333,7 @@ public:
void setColorByPosition(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 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 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 setParticleSize(const uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled if advanced particle is used
void setGravity(int8_t force = 8); void setGravity(int8_t force = 8);
void enableParticleCollisions(bool enable, const uint8_t hardness = 255); void enableParticleCollisions(bool enable, const uint8_t hardness = 255);
@ -377,7 +377,7 @@ private:
uint8_t forcecounter; // counter for globally applied forces uint8_t forcecounter; // counter for globally applied forces
uint16_t collisionStartIdx; // particle array start index for collision detection uint16_t collisionStartIdx; // particle array start index for collision detection
//global particle properties for basic particles //global particle properties for basic particles
uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, is overruled by advanced particle size
uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations
uint8_t smearBlur; // smeared blurring of full frame uint8_t smearBlur; // smeared blurring of full frame
}; };