Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a6111fb
PacMan effect added with colors fixed after gamma correction was fixe…
BobLoeffler68 Aug 30, 2025
d6e8623
A few modifications to PacMan suggested by CodeRabbit
BobLoeffler68 Aug 31, 2025
fc72aa7
A few more suggestions by CodeRabbit
BobLoeffler68 Aug 31, 2025
01c7ea5
Comments cleanup and StartBlinkingGhostsLED change
BobLoeffler68 Oct 28, 2025
f50d171
Merge branch 'main' of https://github.com/Aircoookie/WLED into pr-pac…
BobLoeffler68 Oct 28, 2025
eadc874
Change to startBlinkingGhostsLED
BobLoeffler68 Oct 31, 2025
f0789b5
Merge branch 'main' of https://github.com/Aircoookie/WLED into pr-pac…
BobLoeffler68 Nov 22, 2025
42256f3
User can now select number of ghosts (between 2 and 8)
BobLoeffler68 Nov 23, 2025
223df3b
PacMan code cleanup by claude.ai
BobLoeffler68 Nov 23, 2025
89d8e05
Small change that was strongly recommended by CodeRabbitAI regarding …
BobLoeffler68 Nov 23, 2025
69dc9a6
PacMan: Removed extra whitespace and added back a couple comments tha…
BobLoeffler68 Nov 23, 2025
d395249
Optimized Ants effect (ran through claude.ai)
BobLoeffler68 Nov 24, 2025
8c4520a
Two small changes recommended by CodeRabbitAI
BobLoeffler68 Nov 24, 2025
7d5defd
Ants: fixed indentation/tab issue found by coderabbitai
BobLoeffler68 Nov 24, 2025
7f6d632
Change from round() to roundf() suggested by coderabbitai
BobLoeffler68 Nov 24, 2025
3c40814
Ants: removed const from several local variables
BobLoeffler68 Nov 26, 2025
1fe14d7
Ants: removed 3 lines of comments from beginning of effect code
BobLoeffler68 Nov 26, 2025
f146eba
Ants: minor change to confusedAntIndex
BobLoeffler68 Nov 26, 2025
b0f0cbf
Ants: 2 changes in collision code suggested by coderabbitai
BobLoeffler68 Nov 26, 2025
f35a334
Ants: changed 2 comments to clarify Checkbox1 behavior
BobLoeffler68 Nov 26, 2025
1c25650
Ants: changed a comment
BobLoeffler68 Nov 27, 2025
10b7860
Ants: added the Blur option
BobLoeffler68 Nov 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3158,6 +3158,194 @@ static uint16_t rolling_balls(void) {
static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collide,Overlay,Trails;!,!,!;!;1;m12=1"; //bar
#endif // WLED_PS_DONT_REPLACE_FX


/*
/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler 2025
* First slider is for the ants' speed.
* Second slider is for the # of ants.
* Third slider is for the Ants' size.
* Fourth slider (custom2) is for blurring the LEDs in the segment.
* Checkbox1 is for Gathering food (enabled if you want the ants to gather food, disabled if they are just walking).
* We will switch directions when they get to the beginning or end of the segment when gathering food.
* When gathering food, the Pass By option will automatically be enabled so they can drop off their food easier (and look for more food).
* Checkbox2 is for Overlay mode (enabled is Overlay, disabled is no overlay)
* Checkbox3 is for whether the ants will bump into each other (disabled) or just pass by each other (enabled)
*/
// Ant structure representing each ant's state
struct Ant {
unsigned long lastBumpUpdate; // the last time the ant bumped into another ant
bool hasFood;
float velocity;
float position; // (0.0 to 1.0 range)
};

constexpr unsigned MAX_ANTS = 32;
constexpr float MIN_COLLISION_TIME_MS = 2.0f;
constexpr float VELOCITY_MIN = 2.0f;
constexpr float VELOCITY_MAX = 10.0f;

// Helper function to get food pixel color based on ant and background colors
static uint32_t getFoodColor(uint32_t antColor, uint32_t backgroundColor) {
if (antColor == WHITE)
return (backgroundColor == YELLOW) ? GRAY : YELLOW;
return (backgroundColor == WHITE) ? YELLOW : WHITE;
}

// Helper function to handle ant boundary wrapping or bouncing
static void handleBoundary(Ant& ant, float& position, bool gatherFood, bool atStart, unsigned long currentTime) {
if (gatherFood) {
// Bounce mode: reverse direction and update food status
position = atStart ? 0.0f : 1.0f;
ant.velocity = -ant.velocity;
ant.lastBumpUpdate = currentTime;
ant.position = position;
ant.hasFood = atStart; // Has food when leaving start, drops it at end
} else {
// Wrap mode: teleport to opposite end
position = atStart ? 1.0f : 0.0f;
ant.lastBumpUpdate = currentTime;
ant.position = position;
}
}

// Helper function to calculate ant color
static uint32_t getAntColor(int antIndex, int numAnts, bool usePalette) {
if (usePalette)
return SEGMENT.color_from_palette(antIndex * 255 / numAnts, false, PALETTE_SOLID_WRAP, 255);
// Alternate between two colors for default palette
return (antIndex % 3 == 1) ? SEGCOLOR(0) : SEGCOLOR(2);
}

// Helper function to render a single ant pixel with food handling
static void renderAntPixel(int pixelIndex, int pixelOffset, int antSize, const Ant& ant, uint32_t antColor, uint32_t backgroundColor, bool gatherFood) {
bool isMovingBackward = (ant.velocity < 0);
bool isFoodPixel = gatherFood && ant.hasFood && ((isMovingBackward && pixelOffset == 0) || (!isMovingBackward && pixelOffset == antSize - 1));
if (isFoodPixel) {
SEGMENT.setPixelColor(pixelIndex, getFoodColor(antColor, backgroundColor));
} else {
SEGMENT.setPixelColor(pixelIndex, antColor);
}
}

static uint16_t mode_ants(void) {
if (SEGLEN <= 1) return mode_static();

// Allocate memory for ant data
uint32_t backgroundColor = SEGCOLOR(1);
unsigned dataSize = sizeof(Ant) * MAX_ANTS;
if (!SEGENV.allocateData(dataSize)) return mode_static(); // Allocation failed

Ant* ants = reinterpret_cast<Ant*>(SEGENV.data);

// Extract configuration from segment settings
unsigned numAnts = min(1 + (SEGLEN * SEGMENT.intensity >> 12), MAX_ANTS);
bool gatherFood = SEGMENT.check1;
bool overlayMode = SEGMENT.check2;
bool passBy = SEGMENT.check3 || gatherFood; // global no‑collision when gathering food is enabled
unsigned antSize = map(SEGMENT.custom1, 0, 255, 1, 20) + (gatherFood ? 1 : 0);

// Initialize ants on first call
if (SEGENV.call == 0) {
int confusedAntIndex = hw_random(0, numAnts); // the first random ant to go backwards

for (int i = 0; i < MAX_ANTS; i++) {
ants[i].lastBumpUpdate = strip.now;

// Random velocity
float velocity = VELOCITY_MIN + (VELOCITY_MAX - VELOCITY_MIN) * hw_random16(1000, 5000) / 5000.0f;
// One random ant moves in opposite direction
ants[i].velocity = (i == confusedAntIndex) ? -velocity : velocity;
// Random starting position (0.0 to 1.0)
ants[i].position = hw_random16(0, 10000) / 10000.0f;
// Ants don't have food yet
ants[i].hasFood = false;
}
}

// Calculate time conversion factor based on speed slider
float timeConversionFactor = float(scale8(8, 255 - SEGMENT.speed) + 1) * 20000.0f;

// Clear background if not in overlay mode
if (!overlayMode) SEGMENT.fill(backgroundColor);

// Update and render each ant
for (int i = 0; i < numAnts; i++) {
float timeSinceLastUpdate = float(strip.now - ants[i].lastBumpUpdate) / timeConversionFactor;
float newPosition = ants[i].position + ants[i].velocity * timeSinceLastUpdate;

// Reset ants that wandered too far off-track (e.g., after intensity change)
if (newPosition < -0.5f || newPosition > 1.5f) {
newPosition = ants[i].position = hw_random16(0, 10000) / 10000.0f;
ants[i].lastBumpUpdate = strip.now;
}

// Handle boundary conditions (bounce or wrap)
if (newPosition <= 0.0f && ants[i].velocity < 0.0f) {
handleBoundary(ants[i], newPosition, gatherFood, true, strip.now);
} else if (newPosition >= 1.0f && ants[i].velocity > 0.0f) {
handleBoundary(ants[i], newPosition, gatherFood, false, strip.now);
}

// Handle collisions between ants (if not passing by)
if (!passBy) {
for (int j = i + 1; j < numAnts; j++) {
if (fabsf(ants[j].velocity - ants[i].velocity) < 0.001f) continue; // Moving in same direction at same speed; avoids tiny denominators

// Calculate collision time using physics
float timeOffset = float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate);
float collisionTime = (timeConversionFactor * (ants[i].position - ants[j].position) + ants[i].velocity * timeOffset) / (ants[j].velocity - ants[i].velocity);

// Check if collision occurred in valid time window
float timeSinceJ = float(strip.now - ants[j].lastBumpUpdate);
if (collisionTime > MIN_COLLISION_TIME_MS && collisionTime < timeSinceJ) {
// Update positions to collision point
float adjustedTime = (collisionTime + float(ants[j].lastBumpUpdate - ants[i].lastBumpUpdate)) / timeConversionFactor;
ants[i].position += ants[i].velocity * adjustedTime;
ants[j].position = ants[i].position;

// Update collision time
unsigned long collisionMoment = static_cast<unsigned long>(collisionTime + 0.5f) + ants[j].lastBumpUpdate;
ants[i].lastBumpUpdate = collisionMoment;
ants[j].lastBumpUpdate = collisionMoment;

// Reverse the ant with greater speed magnitude
if (fabsf(ants[i].velocity) > fabsf(ants[j].velocity)) {
ants[i].velocity = -ants[i].velocity;
} else {
ants[j].velocity = -ants[j].velocity;
}

// Recalculate position after collision
newPosition = ants[i].position + ants[i].velocity * (strip.now - ants[i].lastBumpUpdate) / timeConversionFactor;
}
}
}

// Clamp position to valid range
newPosition = constrain(newPosition, 0.0f, 1.0f);
unsigned pixelPosition = roundf(newPosition * (SEGLEN - 1));

// Determine ant color
uint32_t antColor = getAntColor(i, numAnts, SEGMENT.palette != 0);

// Render ant pixels
for (int pixelOffset = 0; pixelOffset < antSize; pixelOffset++) {
unsigned currentPixel = pixelPosition + pixelOffset;
if (currentPixel >= SEGLEN) break;
renderAntPixel(currentPixel, pixelOffset, antSize, ants[i], antColor, backgroundColor, gatherFood);
}

// Update ant state
ants[i].lastBumpUpdate = strip.now;
ants[i].position = newPosition;
}

SEGMENT.blur(SEGMENT.custom2>>1);
return FRAMETIME;
}
static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,Blur,,Gathering food,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32,c2=0,o1=1,o3=1";


/*
* Sinelon stolen from FASTLED examples
*/
Expand Down Expand Up @@ -10907,6 +11095,7 @@ void WS2812FX::setupEffectData() {
addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS);
addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR);
addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH);
addEffect(FX_MODE_ANTS, &mode_ants, _data_FX_MODE_ANTS);

// --- 1D audio effects ---
addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS);
Expand Down
3 changes: 2 additions & 1 deletion wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,8 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define FX_MODE_PS1DSONICBOOM 215
#define FX_MODE_PS1DSPRINGY 216
#define FX_MODE_PARTICLEGALAXY 217
#define MODE_COUNT 218
#define FX_MODE_ANTS 218
#define MODE_COUNT 219


#define BLEND_STYLE_FADE 0x00 // universal
Expand Down