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:
		| @@ -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 | ||||||
|         if (SEGMENT.check1) // colorful mode |         int idx = PartSys->sprayEmit(PartSys->sources[0]); // emit a particle | ||||||
|           PartSys->sources[0].source.hue = hw_random16(); //random color for each particle |         if(SEGMENT.custom3 > 23) { | ||||||
|         PartSys->sprayEmit(PartSys->sources[0]); // emit a particle |           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 | ||||||
|  |             PartSys->sources[0].source.hue = hw_random16(); //random color for each 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 | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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; | ||||||
| @@ -605,7 +611,7 @@ void ParticleSystem2D::render() { | |||||||
|     for (uint32_t i = 0; i < passes; i++) { |     for (uint32_t i = 0; i < passes; i++) { | ||||||
|       if (i == 2) // for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) |       if (i == 2) // for the last two passes, use higher amount of blur (results in a nicer brightness gradient with soft edges) | ||||||
|         bitshift = 1; |         bitshift = 1; | ||||||
|         blur2D(framebuffer, maxXpixel + 1, maxYpixel + 1, bluramount << bitshift, bluramount << bitshift); |       blur2D(framebuffer, maxXpixel + 1, maxYpixel + 1, bluramount << bitshift, bluramount << bitshift); | ||||||
|       bluramount -= 64; |       bluramount -= 64; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -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; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Damian Schneider
					Damian Schneider