-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Introduce comet effect usermod with fire particle system #5347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
|
Comment on lines
+54
to
+55
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When 🛠️ Proposed fix- uint16_t cometFrequencyDelay = 2040 - (SEGMENT.intensity << 3);
- nextCometCreationTime = strip.now + cometFrequencyDelay + hw_random16(cometFrequencyDelay);
+ uint16_t cometFrequencyDelay = max(1U, (unsigned)(2040U - (SEGMENT.intensity << 3)));
+ nextCometCreationTime = strip.now + cometFrequencyDelay + hw_random16(cometFrequencyDelay);🤖 Prompt for AI Agents |
||
| } | ||
| 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; | ||
|
Comment on lines
+57
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When no comet will spawn ( 🛠️ Proposed fix uint8_t canLargeCometSpawn =
SEGMENT.custom1 > hw_random8(254)
+ && chosenIndex != NULL_INDEX
&& chosenIndex != 0
&& chosenIndex != numComets - 1;🤖 Prompt for AI Agents |
||
| uint8_t fallingSpeed = 1 + (SEGMENT.speed >> 2); | ||
|
|
||
| // Update the comets | ||
| for (i = 0; i < numComets; i++) { | ||
| auto& source = PartSys->sources[i]; | ||
gustebeast marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| auto& sourceParticle = source.source; | ||
|
|
||
| if (!has_fallen_off_screen(i)) { | ||
| // Active comets fall downwards and emit flames | ||
| sourceParticle.y -= fallingSpeed; | ||
gustebeast marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "name": "PS Comet", | ||
| "build": { "libArchive": false } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nextCometCreationTimeis a file-scope global — breaks multi-segment usageAll segments running this effect share the same spawn timer. When one segment creates a comet it sets the timer far into the future, silencing comet creation in every other segment for that duration.
Store the next-creation timestamp per segment, e.g. in
SEGENV.aux0/aux1(packed as auint32_t) or inside a small context struct held inSEGENV.dataalongside theParticleSystem2Dpointer.🤖 Prompt for AI Agents