Introduce comet effect usermod with fire particle system (#5347)

* Introduce comet effect usermod with fire particle system

* Introduce comet effect usermod with fire particle system
This commit is contained in:
gustebeast
2026-03-02 10:10:31 -08:00
committed by GitHub
parent 7fa15761ae
commit 5790309371
3 changed files with 152 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
#include "wled.h"
#include "FXparticleSystem.h"
unsigned long nextCometCreationTime = 0;
#define FX_FALLBACK_STATIC { SEGMENT.fill(SEGCOLOR(0)); return; }
// Use UINT32_MAX - 1 for the "no comet" case so we can add 1 later and not have it overflow
#define NULL_INDEX UINT32_MAX - 1
///////////////////////
// Effect Function //
///////////////////////
void mode_pscomet() {
ParticleSystem2D *PartSys = nullptr;
uint32_t i;
if (SEGMENT.call == 0) { // Initialization
// Try to allocate one comet for every column
if (!initParticleSystem2D(PartSys, SEGMENT.vWidth())) {
FX_FALLBACK_STATIC; // Allocation failed or not 2D
}
PartSys->setMotionBlur(170); // Enable motion blur
PartSys->setParticleSize(0); // Allow small comets to be a single pixel wide
}
else {
PartSys = reinterpret_cast<ParticleSystem2D *>(SEGENV.data); // If not first call, use existing data
}
if (PartSys == nullptr || SEGMENT.vHeight() < 2 || SEGMENT.vWidth() < 2) {
FX_FALLBACK_STATIC;
}
PartSys->updateSystem(); // Update system properties (dimensions and data pointers)
auto has_fallen_off_screen = [PartSys](uint32_t particleIndex) {
return particleIndex < PartSys->numSources
? PartSys->sources[particleIndex].source.y < PartSys->maxY * -1
: true;
};
// This will be SEGMENT.vWidth() unless the particle system had insufficient memory
uint32_t numComets = PartSys->numSources;
// Pick a random column for a new comet to spawn, but reset it to null if it's not time yet or there's already a
// comet nearby
uint32_t chosenIndex = hw_random(numComets);
if (
strip.now < nextCometCreationTime
|| !has_fallen_off_screen(chosenIndex - 1)
|| !has_fallen_off_screen(chosenIndex)
|| !has_fallen_off_screen(chosenIndex + 1)
) {
chosenIndex = NULL_INDEX;
} else {
uint16_t cometFrequencyDelay = 2040 - (SEGMENT.intensity << 3);
nextCometCreationTime = strip.now + cometFrequencyDelay + hw_random16(cometFrequencyDelay);
}
uint8_t canLargeCometSpawn =
// Slider 3 determines % of large comets with extra particle sources on their sides
SEGMENT.custom1 > hw_random8(254)
&& chosenIndex != 0
&& chosenIndex != numComets - 1;
uint8_t fallingSpeed = 1 + (SEGMENT.speed >> 2);
// Update the comets
for (i = 0; i < numComets; i++) {
auto& source = PartSys->sources[i];
auto& sourceParticle = source.source;
if (!has_fallen_off_screen(i)) {
// Active comets fall downwards and emit flames
sourceParticle.y -= fallingSpeed;
source.vy = (SEGMENT.speed >> 5) - fallingSpeed; // Emitting speed (upwards)
PartSys->flameEmit(PartSys->sources[i]);
continue;
}
bool isChosenComet = i == chosenIndex;
bool isChosenSideComet =
canLargeCometSpawn &&
(i == chosenIndex - 1 || i == chosenIndex + 1);
// Chosen comets respawn at the top
if (isChosenComet || isChosenSideComet) {
// Map the comet index into an output pixel index
sourceParticle.x = i * PartSys->maxX / (SEGMENT.vWidth() - 1);
// Spawn a bit above the top to avoid popping into view
sourceParticle.y = PartSys->maxY + (2 * fallingSpeed);
if (isChosenComet) {
// Slider 4 controls comet length via particle lifetime and fire intensity adjustments
source.maxLife = 16 + (SEGMENT.custom2 >> 2);
source.minLife = source.maxLife >> 1;
sourceParticle.ttl = 16 - (SEGMENT.custom2 >> 4);
} else {
// Side comets have fixed length
source.maxLife = 18;
source.minLife = 14;
sourceParticle.ttl = 16;
// Shift side comets up by 1 pixel
sourceParticle.y += 2 * PartSys->maxY / (SEGMENT.vHeight() - 1);
}
}
}
// Slider 4 controls comet length via particle lifetime and fire intensity adjustments
PartSys->updateFire(max(255U - SEGMENT.custom2, 45U));
}
static const char _data_FX_MODE_PSCOMET[] PROGMEM = "PS Comet@Falling Speed,Comet Frequency,Large Comet Probability,Comet Length;;!;2;pal=35,sx=128,ix=255,c1=32,c2=128";
/////////////////////
// UserMod Class //
/////////////////////
class PSCometUsermod : public Usermod {
public:
void setup() override {
strip.addEffect(255, &mode_pscomet, _data_FX_MODE_PSCOMET);
}
void loop() override {}
};
static PSCometUsermod ps_comet;
REGISTER_USERMOD(ps_comet);

View File

@@ -0,0 +1,25 @@
## Description
A 2D falling comet effect similar to "Matrix" but with a fire particle simulation to enhance the comet trail visuals. Works with custom color palettes, defaulting to "Fire". Supports "small" and "large" comets which are 1px and 3px wide respectively.
Demo: [https://imgur.com/a/i1v5WAy](https://imgur.com/a/i1v5WAy)
## Installation
To activate the usermod, add the following line to your platformio_override.ini
```ini
custom_usermods = ps_comet
```
Or if you are already using a usermod, append ps_comet to the list
```ini
custom_usermods = audioreactive ps_comet
```
You should now see "PS Comet" appear in your effect list.
## Parameters
1. **Falling Speed** sets how fast the comets fall
2. **Comet Frequency** determines how many comets are on screen at a time
3. **Large Comet Probability** determines how often large 3px wide comets spawn
4. **Comet Length** sets how far comet trails stretch vertically

View File

@@ -0,0 +1,4 @@
{
"name": "PS Comet",
"build": { "libArchive": false }
}