| 411 | // --------------------------------------------------------------------------- |
| 412 | |
| 413 | void PerlinParticlePunch::draw(DrawContext context) { |
| 414 | fl::span<CRGB> leds = context.leds; |
| 415 | if (leds.empty() || mNumLeds == 0) { |
| 416 | return; |
| 417 | } |
| 418 | u32 now = context.now; |
| 419 | |
| 420 | // --- Layer 1: Perlin noise background (fresh each frame) --- |
| 421 | noiseCircleDraw(now, leds); |
| 422 | |
| 423 | // --- Trail buffer decay --- |
| 424 | // Use the larger of the two trail intensities for the shared buffer. |
| 425 | // Higher intensity value = less fade per frame = longer trails. |
| 426 | u8 trailIntensity = mAmbientTrailIntensity > mMeteorTrailIntensity |
| 427 | ? mAmbientTrailIntensity |
| 428 | : mMeteorTrailIntensity; |
| 429 | u8 decayRate = 255 - trailIntensity; |
| 430 | fadeToBlackBy(mTrailBuffer.data(), mNumLeds, decayRate); |
| 431 | |
| 432 | // --- Layer 2: Ambient particles --- |
| 433 | for (u16 i = 0; i < (u16)mAmbientParticles.size(); ++i) { |
| 434 | AmbientParticle &p = mAmbientParticles[i]; |
| 435 | if (!p.alive) |
| 436 | continue; |
| 437 | // Physics update |
| 438 | p.velocity *= mDrag; |
| 439 | p.position += p.velocity; |
| 440 | p.brightness *= mAmbientBrightnessDecay; |
| 441 | if (p.position >= float(mNumLeds) || p.position < 0.0f || |
| 442 | p.brightness < 8.0f || p.velocity < mMinVelocity) { |
| 443 | p.alive = false; |
| 444 | continue; |
| 445 | } |
| 446 | renderAmbient(p); |
| 447 | } |
| 448 | |
| 449 | // --- Layer 3: Meteors --- |
| 450 | for (u16 i = 0; i < (u16)mMeteorParticles.size(); ++i) { |
| 451 | MeteorParticle &m = mMeteorParticles[i]; |
| 452 | if (!m.alive) |
| 453 | continue; |
| 454 | // Debris spawn check before physics update |
| 455 | if (m.shouldSpawnDebris()) { |
| 456 | spawnDebrisFromMeteor(m, now); |
| 457 | } |
| 458 | // Physics update |
| 459 | m.velocity *= mMeteorDrag; |
| 460 | m.position += m.velocity; |
| 461 | m.frameCounter++; |
| 462 | if (m.position >= float(mNumLeds) || m.velocity < mMinVelocity) { |
| 463 | m.alive = false; |
| 464 | continue; |
| 465 | } |
| 466 | renderMeteor(m); |
| 467 | } |
| 468 | |
| 469 | // --- Debris --- |
| 470 | for (u16 i = 0; i < (u16)mDebrisParticles.size(); ++i) { |
nothing calls this directly
no test coverage detected