| 319 | } |
| 320 | |
| 321 | void PerlinParticlePunch::renderMeteor(const MeteorParticle &m) { |
| 322 | int center = int(m.position); |
| 323 | |
| 324 | // --- Head: 5-pixel gaussian kernel --- |
| 325 | static const float kGaussian[] = {0.15f, 0.60f, 1.0f, 0.60f, 0.15f}; |
| 326 | for (int offset = -2; offset <= 2; ++offset) { |
| 327 | int idx = center + offset; |
| 328 | if (idx < 0 || idx >= mNumLeds) |
| 329 | continue; |
| 330 | float weight = kGaussian[offset + 2]; |
| 331 | u8 bri = clamp_u8(255.0f * m.intensity * weight); |
| 332 | // Re-entry sparkle: random brightness jitter per pixel per frame |
| 333 | bri = scale8(bri, random8(153, 255)); |
| 334 | CRGB headColor = mMeteorHeadColor; |
| 335 | headColor.nscale8(bri); |
| 336 | writeMax(mTrailBuffer[idx], headColor); |
| 337 | } |
| 338 | |
| 339 | // --- Tail: gradient behind the head --- |
| 340 | float tailLen = m.tailLength(); |
| 341 | int tailPixels = int(tailLen); |
| 342 | for (int i = 1; i <= tailPixels; ++i) { |
| 343 | int idx = center - i; |
| 344 | if (idx < 0) |
| 345 | break; |
| 346 | if (idx >= mNumLeds) |
| 347 | continue; |
| 348 | |
| 349 | // t: 0.0 at head, 1.0 at tail tip |
| 350 | float t = float(i) / float(tailPixels); |
| 351 | fract8 blendAmt = clamp_u8(t * 255.0f); |
| 352 | CRGB tailColor = blend(mMeteorMidColor, mMeteorTailColor, blendAmt); |
| 353 | // Quadratic brightness falloff along tail |
| 354 | float bri = (1.0f - t * t) * m.intensity; |
| 355 | tailColor.nscale8(clamp_u8(bri * 255.0f)); |
| 356 | writeMax(mTrailBuffer[idx], tailColor); |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | void PerlinParticlePunch::renderDebris(const DebrisParticle &d) { |
| 361 | int idx = int(d.position); |
nothing calls this directly
no test coverage detected