Skip to content
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

Ant and PacMan effects #4536

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
# ------------------------------------------------------------------------------

# CI/release binaries
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover
;default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved

src_dir = ./wled00
data_dir = ./wled00/data
build_cache_dir = ~/.buildcache
extra_configs =
platformio_override.ini

default_envs = esp32dev

BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
[common]
# ------------------------------------------------------------------------------
# PLATFORM:
Expand Down Expand Up @@ -125,7 +127,7 @@ framework = arduino
board_build.flash_mode = dout
monitor_speed = 115200
# slow upload speed but most compatible (use platformio_override.ini to use faster speed)
upload_speed = 115200
upload_speed = 921600
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved

# ------------------------------------------------------------------------------
# LIBRARIES: required dependencies
Expand Down
283 changes: 281 additions & 2 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3020,9 +3020,9 @@ static const char _data_FX_MODE_BOUNCINGBALLS[] PROGMEM = "Bouncing Balls@Gravit
* https://github.com/Aircoookie/WLED/pull/1039
*/
// modified for balltrack mode
typedef struct RollingBall {
typedef struct RollingBall { // used for rolling_balls and Ants modes
unsigned long lastBounceUpdate;
float mass; // could fix this to be = 1. if memory is an issue
float mass; // could fix this to be = 1. if memory is an issue // mass not used in ants mode
float velocity;
float height;
} rball_t;
Expand Down Expand Up @@ -3115,6 +3115,280 @@ static uint16_t rolling_balls(void) {
static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of balls,,,,Collisions,Overlay,Trails;!,!,!;!;1;m12=1"; //bar


/*
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler - January 2025
* bouncing balls on a track track Effect modified from Aircoookie's bouncing balls
* Courtesy of pjhatch (https://github.com/pjhatch)
* https://github.com/Aircoookie/WLED/pull/1039
*
* First slider is for the ants' speed.
* Second slider is for the # of ants (1 to 16).
* Third slider is for the Ants' size (1 to x leds)
* Fourth slider is for the background color (black, green, yellow, brown, cyan or white)
* 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)
*/
static uint16_t mode_ants(void) {
//allocate segment data
uint32_t bgcolor = BLACK;
uint8_t antSize = 1;
const uint16_t maxNumAnts = 32; // 255/16 + 1 ( * 2 ?????)
uint16_t dataSize = sizeof(rball_t) * maxNumAnts;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed

int confusedAnt; // the first random ant to go backwards
rball_t *ants = reinterpret_cast<rball_t *>(SEGENV.data);

// number of ants based on intensity setting to max of 16 (32?)
uint8_t numAnts = SEGMENT.intensity/16 + 1;
if (SEGLEN > 127)
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
numAnts *= 2; // double the number of ants for longer strips/segments

antSize = map(SEGMENT.custom1, 0, 255, 1, 5); // the size/length of each ant is user selectable (1 to 5 pixels) with a slider
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved

uint8_t bgColorIdx = map(SEGMENT.custom2, 0, 255, 0, 5); // background color based on the what the user selects from the Background Color slider
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
switch (bgColorIdx) {
case 0: bgcolor = BLACK; break; // black
case 1: bgcolor = 0x007700; break; // med-dark green
case 2: bgcolor = 0x9c7a00; break; // dark yellow
case 3: bgcolor = 0x845000; break; // brown
case 4: bgcolor = 0x00aaaa; break; // cyan-ish
case 5: bgcolor = 0x999999; break; // light gray (whitish)
}

if (SEGENV.call == 0) {
confusedAnt = random(0,numAnts-1);
for (int i = 0; i < maxNumAnts; i++) {
ants[i].lastBounceUpdate = strip.now;
ants[i].velocity = 10.0f * float(random16(1000, 5000))/5000.0f; // Random number from 1 to 5
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
if (i == confusedAnt) // make ant[i] go in the opposite direction
ants[i].velocity = -ants[i].velocity;
ants[i].height = (float(random16(0, 10000)) / 10000.0f); // Random number from 0 to 1 (used for the position of the ant on the strip)
}
}

float cfac = float(scale8(8, 255-SEGMENT.speed) +1)*20000.0f; // this uses the Aircoookie conversion factor for scaling time using speed slider

if (!SEGMENT.check2) SEGMENT.fill(bgcolor); // fill all LEDs with background color
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved

for (int i = 0; i < numAnts; i++) { // for each Ant, do this...
float timeSinceLastUpdate = float((strip.now - ants[i].lastBounceUpdate))/cfac;
float thisHeight = ants[i].height + ants[i].velocity * timeSinceLastUpdate; // this method keeps higher resolution
// test if intensity level was increased and some ants are way off the track then put them back
if (thisHeight < -0.5f || thisHeight > 1.5f) {
thisHeight = ants[i].height = (float(random16(0, 10000)) / 10000.0f); // from 0.0 to 1.0
ants[i].lastBounceUpdate = strip.now;
}
// check if reached past the beginning of the strip. If so, wrap around.
if (thisHeight <= 0.0f && ants[i].velocity < 0.0f) {
thisHeight = 1.0f;
ants[i].lastBounceUpdate = strip.now;
ants[i].height = thisHeight;
}
// check if reached past the end of the strip. If so, wrap around.
if (thisHeight >= 1.0f && ants[i].velocity > 0.0f) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general, this effect may also be done with my particle system, the code would be much simpler as the system will handle bounce between ants and wrap around, just need to handle the confused ants "manually". you can take a look at the "PS pinball" effect to see if you like that look.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks for the suggestion. I'll look at that effect and code.

thisHeight = 0.0f;
ants[i].lastBounceUpdate = strip.now;
ants[i].height = thisHeight;
}
// check for "passing by" or "bumping into"
if (!SEGMENT.check3) { // Ants bump into each other and reverse direction if checkbox #3 is not "checked"; they pass each other if "checked"
for (int j = i+1; j < numAnts; j++) {
if (ants[j].velocity != ants[i].velocity) {
// tcollided + ants[j].lastBounceUpdate is acutal time of collision (this keeps precision with long to float conversions)
float tcollided = (cfac*(ants[i].height - ants[j].height) +
ants[i].velocity*float(ants[j].lastBounceUpdate - ants[i].lastBounceUpdate))/(ants[j].velocity - ants[i].velocity);

if ((tcollided > 2.0f) && (tcollided < float(strip.now - ants[j].lastBounceUpdate))) { // 2ms minimum to avoid duplicate bounces
ants[i].height = ants[i].height + ants[i].velocity*(tcollided + float(ants[j].lastBounceUpdate - ants[i].lastBounceUpdate))/cfac;
ants[j].height = ants[i].height;
ants[i].lastBounceUpdate = (unsigned long)(tcollided + 0.5f) + ants[j].lastBounceUpdate;
ants[j].lastBounceUpdate = ants[i].lastBounceUpdate;

if (ants[i].velocity > ants[j].velocity)
ants[i].velocity = -ants[i].velocity;
else
ants[j].velocity = -ants[j].velocity;
thisHeight = ants[i].height + ants[i].velocity*(strip.now - ants[i].lastBounceUpdate)/cfac;
}
}
}
}

uint32_t color = SEGCOLOR(0);
if (SEGMENT.palette) {
color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP, 0);
} else {
color = SEGCOLOR(i % NUM_COLORS);
}

if (thisHeight < 0.0f) thisHeight = 0.0f;
if (thisHeight > 1.0f) thisHeight = 1.0f;
uint16_t pos = round(thisHeight * (SEGLEN - 1));

for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 5 pixels)
SEGMENT.setPixelColor(pos, color);
pos = pos + 1;
}

ants[i].lastBounceUpdate = strip.now;
ants[i].height = thisHeight;
}

return FRAMETIME;
}
static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,Background color,,,Overlay,Pass by;1,2,3;!;1;sx=192,ix=255,c1=80,c2=0,pal=0,m12=1";


typedef struct PacManChars {
uint16_t pos;
uint8_t size;
uint32_t color;
} pacmancharacters_t;

#define ORANGEYELLOW (uint32_t)0xFF8800
#define WHITEISH (uint32_t)0x999999

/*
/ Pac-Man (created by making modifications to the Ants effect which was a
* modification of the Rolling Balls effect) - Bob Loeffler - January 2025
*
* The first slider is for speed.
* Checkbox1 is for displaying white dots that PacMan eats. Enabled will show white dots. Disabled will not show any white dots (all leds will be black).
* Checkbox2 not used.
* Checkbox3 not implemented yet. Will be for switching the direction of travel. Enabled will start them going from LED 0 to SEGLEN; disabled will start them going from SEGLEN to 0.
*
*/
static uint16_t mode_pacman(void) {
//allocate segment data
const uint16_t numGhosts = 4;
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
uint16_t dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot
if (!SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed
pacmancharacters_t *character = reinterpret_cast<pacmancharacters_t *>(SEGENV.data);

unsigned long blinkingPowerdotTimer = strip.now - SEGENV.step; // timer for the blinking power dot
uint8_t startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking

if (SEGLEN > 150)
startBlinkingGhostsLED = SEGLEN/4; // For longer strips, start blinking the ghosts when there is only 1/4th of the LEDs left
else
startBlinkingGhostsLED = SEGLEN/3; // for short strips, start blinking the ghosts when there is 1/3rd of the LEDs left

if (SEGENV.call == 0) {
SEGENV.aux0 = 1; // direction of movement for all characters. 0=ghosts chasing pacman; 1=pacman chasing ghosts
SEGENV.aux1 = 0; // are the ghosts blue? 0=No; 1=Yes

SEGMENT.fill(WHITEISH); // fill all LEDs with white "dots" (but not bright WHITE dots)

for (int i = 0; i < 6; i++) { // make all 6 characters (4 ghosts + 1 Pac-Man + 1 Power dot) the same size
character[i].size = 1; // Not used now, but maybe a different size (2 pixels?) in the future?
}

character[0].color = YELLOW; // Pac-man character
character[0].pos = 10;

character[1].color = RED; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color)
character[1].pos = 6;

character[2].color = PURPLE; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color)
character[2].pos = 4;

character[3].color = CYAN; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color)
character[3].pos = 2;

character[4].color = ORANGE; // Ghost character (turns blue when the power dot is eaten; blinks just before it turns back to normal color)
character[4].pos = 0;

character[5].color = ORANGEYELLOW; // orange-ish powerdot (always blinks until it is eaten)
character[5].pos = SEGLEN-1;
SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led

SEGENV.aux1 = 0; // ghosts are initially not blue
}

// blink the orange-ish powerdot pixel
if (blinkingPowerdotTimer > 250) { // every 250 milliseconds (1/4th of a sec)
if (character[5].color == ORANGEYELLOW)
character[5].color = BLACK;
else
character[5].color = ORANGEYELLOW;
SEGENV.step = strip.now;
}

// PacMan is eating the power dot!
if (character[0].pos >= SEGLEN) {
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
SEGENV.aux0 = 0; // reverse direction for all characters
for (int i=1; i<5; i++) { // For all 4 ghosts...
character[i].color = BLUE; // change their color to blue
}
for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish powerdot
character[i].pos = character[i].pos - 1;
}
SEGENV.aux1 = 1; // ghosts need to be blue now = true/yes
}

// When the ghosts are blue, and the last ghost is within the first 25% (or 33%) of LEDs in the segment...
if (SEGENV.aux1 == 1 && character[0].pos <= startBlinkingGhostsLED) {
for (int i=1; i<5; i++) { // For all 4 ghosts...
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
if (character[i].color == BLUE)
character[i].color = BLACK;
else
character[i].color = BLUE;
}
SEGENV.aux1 = 1; // ghosts still blue = true/yes
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
}

// when the ghosts are blue and PacMan gets to the beginning of the segment...
if (SEGENV.aux1 == 1 && character[0].pos <= 0) {
for (int i = character[0].pos; i < SEGLEN-1; i++) { // set up the white dots (or not) so PacMan can start eating them again; start at the dot just ahead of PacMan
if (SEGMENT.check1)
SEGMENT.setPixelColor(i, WHITEISH); // dim white color dots
else
SEGMENT.setPixelColor(i, BLACK); // black (no dots)
}
SEGMENT.setPixelColor(SEGLEN-1, ORANGEYELLOW); // draw the orange-ish powerdot in the last led

SEGENV.aux0 = 1; // reverse direction for all characters (back to normal direction)
character[1].color = RED; // change ghost 1 color back to red
character[2].color = PURPLE; // change ghost 2 color back to purple
character[3].color = CYAN; // change ghost 3 color back to cyan
character[4].color = ORANGE; // change ghost 4 color back to orange
SEGENV.aux1 = 0; // ghosts should not be blue anymore, so set to false
}

// display everything
if (SEGENV.aux0 == 1) { // going forward from the beginning
SEGMENT.setPixelColor(character[0].pos, character[0].color); // draw PacMan
SEGMENT.setPixelColor(character[0].pos-1, BLACK);
character[0].pos = character[0].pos+1;

for (int i = 1; i < 5; i++) { // draw the 4 ghosts (and black dots surrounding each ghost)
SEGMENT.setPixelColor(character[i].pos, character[i].color);
SEGMENT.setPixelColor(character[i].pos-1, BLACK);
character[i].pos = character[i].pos+1;
}

SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the orange-ish powerdot
}
else { // going backward (after PacMan ate the power dot)
SEGMENT.setPixelColor(character[0].pos+1, BLACK);
SEGMENT.setPixelColor(character[0].pos, character[0].color);
SEGMENT.setPixelColor(character[0].pos-1, BLACK);
character[0].pos = character[0].pos-1;

for (int i = 1; i < 5; i++) { // draw the 4 ghosts (and black dots surrounding each ghost)
SEGMENT.setPixelColor(character[i].pos+1, BLACK);
SEGMENT.setPixelColor(character[i].pos, character[i].color);
SEGMENT.setPixelColor(character[i].pos-1, BLACK);
character[i].pos = character[i].pos-1;
} // do not draw the power dot since PacMan ate it
}

return 20 + ((22 * (uint32_t)(255 - SEGMENT.speed)) / SEGLEN); // FRAMETIME and FRAMETIME_FIXED were too fast
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
}
static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White Dots,,Reverse;,,;!;1;m12=1,o1=1";


/*
* Sinelon stolen from FASTLED examples
*/
Expand Down Expand Up @@ -7822,6 +8096,11 @@ void WS2812FX::setupEffectData() {
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);

// --- Bob L's 1D effects ---
addEffect(FX_MODE_ANTS, &mode_ants, _data_FX_MODE_ANTS);
addEffect(FX_MODE_PACMAN, &mode_pacman, _data_FX_MODE_PACMAN);
//addEffect(FX_MODE_RACERS, &mode_racers, _data_FX_MODE_RACERS);

// --- 1D audio effects ---
addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS);
addEffect(FX_MODE_PIXELWAVE, &mode_pixelwave, _data_FX_MODE_PIXELWAVE);
Expand Down
5 changes: 4 additions & 1 deletion wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,11 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define FX_MODE_WAVESINS 184
#define FX_MODE_ROCKTAVES 185
#define FX_MODE_2DAKEMI 186
#define FX_MODE_ANTS 187
BobLoeffler68 marked this conversation as resolved.
Show resolved Hide resolved
#define FX_MODE_PACMAN 188
//#define FX_MODE_RACERS 189

#define MODE_COUNT 187
#define MODE_COUNT 189


#define BLEND_STYLE_FADE 0x00 // universal
Expand Down