From 34d0f17597f31af1f29e994c4dec0eaddc0b924e Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 3 Feb 2025 00:44:25 -0700 Subject: [PATCH 1/9] Initial commit. Added Ant and PacMan modes --- package-lock.json | 4 +- platformio.ini | 6 +- wled00/FX.cpp | 283 +++++++++++++++++++++++++++++++++++++++++++++- wled00/FX.h | 5 +- 4 files changed, 291 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0afeeaafd8..c81072cfc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.16.0-dev", + "version": "0.16.0-alpha", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.16.0-dev", + "version": "0.16.0-alpha", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", diff --git a/platformio.ini b/platformio.ini index e775c61f52..967ec6b6c6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # 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 src_dir = ./wled00 data_dir = ./wled00/data @@ -18,6 +18,8 @@ build_cache_dir = ~/.buildcache extra_configs = platformio_override.ini +default_envs = esp32dev + [common] # ------------------------------------------------------------------------------ # PLATFORM: @@ -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 # ------------------------------------------------------------------------------ # LIBRARIES: required dependencies diff --git a/wled00/FX.cpp b/wled00/FX.cpp index b9ead1412c..6d30129c49 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -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; @@ -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 +/* +/ 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(SEGENV.data); + + // number of ants based on intensity setting to max of 16 (32?) + uint8_t numAnts = SEGMENT.intensity/16 + 1; + if (SEGLEN > 127) + 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 + + uint8_t bgColorIdx = map(SEGMENT.custom2, 0, 255, 0, 5); // background color based on the what the user selects from the Background Color slider + 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 + 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 + + 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) { + 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; + 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(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) { + 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... + if (character[i].color == BLUE) + character[i].color = BLACK; + else + character[i].color = BLUE; + } + SEGENV.aux1 = 1; // ghosts still blue = true/yes + } + + // 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 +} +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White Dots,,Reverse;,,;!;1;m12=1,o1=1"; + + /* * Sinelon stolen from FASTLED examples */ @@ -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); diff --git a/wled00/FX.h b/wled00/FX.h index 3b1f8f8f15..70e3f6ba0b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -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 +#define FX_MODE_PACMAN 188 +//#define FX_MODE_RACERS 189 -#define MODE_COUNT 187 +#define MODE_COUNT 189 #define BLEND_STYLE_FADE 0x00 // universal From 3b22eda795370cfb6bb5a75a67dc6db18ad2e877 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 9 Feb 2025 21:10:18 -0700 Subject: [PATCH 2/9] Lots of things changed in Ants and PacMan, and reverted platformio.ini Ants ---- - Made the number of ants more dynamically user-selectable. - The size of ants can now be up to 20 pixels wide. - Changed random() and random16() to hw_random() and hw_random16(). - Fixed a few suggestions by CodeRabbit TODO: - Foreground and background color selection needs to be improved. The current way of setting the background color is very clunky (with slider #4). PacMan ------ - constexpr instead of const - return mode_static() on short segments (15 or less pixels) - made use of numGhosts intead of hard-coded integers - now using aux0 for binary flags (DIRECTION_FLAG and GHOSTSBLUE_FLAG) - now using aux1 for the timer tick count - reworked timing to draw moving characters and power dot - now returning FRAMETIME correctly TODO: - Speed adjustments? - Transition from previous FX is not looking good. It is supposed to wipe out any pixels by making them black. Then draw whitish dots every other pixel if the White Dots checkbox is enabled. But there are usually colored pixels still displayed from the previous FX. --- platformio.ini | 6 +- wled00/FX.cpp | 176 ++++++++++++++++++++++++++----------------------- 2 files changed, 95 insertions(+), 87 deletions(-) diff --git a/platformio.ini b/platformio.ini index 967ec6b6c6..e775c61f52 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ # ------------------------------------------------------------------------------ # 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 src_dir = ./wled00 data_dir = ./wled00/data @@ -18,8 +18,6 @@ build_cache_dir = ~/.buildcache extra_configs = platformio_override.ini -default_envs = esp32dev - [common] # ------------------------------------------------------------------------------ # PLATFORM: @@ -127,7 +125,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 = 921600 +upload_speed = 115200 # ------------------------------------------------------------------------------ # LIBRARIES: required dependencies diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 6d30129c49..c9955d554c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3122,29 +3122,29 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * 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) +* Second slider is for the # of ants. +* Third slider is for the Ants' size. +* * 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; + static constexpr uint16_t MAX_ANTS = 32; // Maximum number of ants + static constexpr uint8_t DEFAULT_ANT_SIZE = 1; + uint8_t antSize = DEFAULT_ANT_SIZE; + uint16_t dataSize = sizeof(rball_t) * MAX_ANTS; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed int confusedAnt; // the first random ant to go backwards rball_t *ants = reinterpret_cast(SEGENV.data); - // number of ants based on intensity setting to max of 16 (32?) - uint8_t numAnts = SEGMENT.intensity/16 + 1; - if (SEGLEN > 127) - 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 + // number of ants based on intensity setting to max of 32 + uint8_t numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); + if (numAnts > 32) numAnts = MAX_ANTS; + + antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 5 pixels) with a slider uint8_t bgColorIdx = map(SEGMENT.custom2, 0, 255, 0, 5); // background color based on the what the user selects from the Background Color slider switch (bgColorIdx) { @@ -3157,13 +3157,13 @@ static uint16_t mode_ants(void) { } if (SEGENV.call == 0) { - confusedAnt = random(0,numAnts-1); - for (int i = 0; i < maxNumAnts; i++) { + confusedAnt = hw_random(0,numAnts-1); + for (int i = 0; i < MAX_ANTS; i++) { ants[i].lastBounceUpdate = strip.now; - ants[i].velocity = 10.0f * float(random16(1000, 5000))/5000.0f; // Random number from 1 to 5 + ants[i].velocity = 10.0f * float(hw_random16(1000, 5000))/5000.0f; // Random number from 1 to 5 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) + ants[i].height = (float(hw_random16(0, 10000)) / 10000.0f); // Random number from 0 to 1 (used for the position of the ant on the strip) } } @@ -3173,10 +3173,10 @@ static uint16_t mode_ants(void) { 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 + 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 + thisHeight = ants[i].height = (float(hw_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. @@ -3193,7 +3193,7 @@ static uint16_t mode_ants(void) { } // 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++) { + 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) + @@ -3248,25 +3248,25 @@ typedef struct PacManChars { #define ORANGEYELLOW (uint32_t)0xFF8800 #define WHITEISH (uint32_t)0x999999 +#define DIRECTION_FLAG 0b01 +#define GHOSTSBLUE_FLAG 0b10 /* / Pac-Man (created by making modifications to the Ants effect which was a -* modification of the Rolling Balls effect) - Bob Loeffler - January 2025 +* modification of the Rolling Balls effect) - Bob Loeffler - Jan-Feb 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. -* +* aux0 is for boolean flags: bit 1 is for the direction of character movement (0=ghosts chasing pacman; 1=pacman chasing ghosts); bit 2 is for whether the ghosts should be blue color (1=yes; 0=no) +* aux1 is the main counter for timing */ static uint16_t mode_pacman(void) { //allocate segment data - const uint16_t numGhosts = 4; - 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 + constexpr uint16_t numGhosts = 4; + uint16_t dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot + if (SEGLEN <= 15 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed pacmancharacters_t *character = reinterpret_cast(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) @@ -3275,10 +3275,9 @@ static uint16_t mode_pacman(void) { 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 + SEGENV.aux0 = DIRECTION_FLAG; // initial direction of movement for all characters. 1=pacman chasing ghosts - SEGMENT.fill(WHITEISH); // fill all LEDs with white "dots" (but not bright WHITE dots) + SEGMENT.fill(BLACK); // black out all LEDs on the segment 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? @@ -3299,94 +3298,105 @@ static uint16_t mode_pacman(void) { 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].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) character[5].pos = SEGLEN-1; + + if (SEGMENT.check1) { // set up the white dots (or not) so PacMan can start eating them; start at the dot just ahead of PacMan + for (int i = character[0].pos; i < SEGLEN-1; i++) { + SEGMENT.setPixelColor(i, WHITEISH); // dim white color dots + i++; // skip a dot so the dots are not next to each other + } + } + 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 + + SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // the ghosts are not blue yet, so set this to false (0) } - // blink the orange-ish powerdot pixel - if (blinkingPowerdotTimer > 250) { // every 250 milliseconds (1/4th of a sec) + if (strip.now > SEGENV.step) { + SEGENV.step = strip.now; // "+ 100" creates a very jerky movement as the characters jump ahead several pixels each time they move + SEGENV.aux1++; + } + + // blink the orange-ish power dot pixel + if (SEGENV.aux1 % 10 == 0) { // blink every 10 ticks of the ticker timer if (character[5].color == ORANGEYELLOW) character[5].color = BLACK; else character[5].color = ORANGEYELLOW; - SEGENV.step = strip.now; + if (SEGENV.aux0 & DIRECTION_FLAG) + SEGMENT.setPixelColor(character[5].pos, character[5].color); } - // PacMan is eating the power dot! + // PacMan ate the power dot! Chase the ghosts! if (character[0].pos >= SEGLEN) { - 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 + SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing pacman + for (int i = 1; i < numGhosts + 1; 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 + for (int i = 0; i < 5; i++) { // move each character back one pixel, but not the orange-ish power dot 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... - if (character[i].color == BLUE) - character[i].color = BLACK; - else - character[i].color = BLUE; - } - SEGENV.aux1 = 1; // ghosts still blue = true/yes + SEGENV.aux0 = SEGENV.aux0 | GHOSTSBLUE_FLAG; // ghosts need to be blue now = true/yes } // when the ghosts are blue and PacMan gets to the beginning of the segment... - if (SEGENV.aux1 == 1 && character[0].pos <= 0) { + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && 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) + i++; // skip a dot so the dots are not next to each other } - SEGMENT.setPixelColor(SEGLEN-1, ORANGEYELLOW); // draw the orange-ish powerdot in the last led + SEGMENT.setPixelColor(character[5].pos, character[5].color); // ...draw the orange-ish power dot in the last led - SEGENV.aux0 = 1; // reverse direction for all characters (back to normal direction) + SEGENV.aux0 = SEGENV.aux0 | DIRECTION_FLAG; // reverse direction for all characters (back to normal direction; 1=pacman chasing ghosts) 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 + SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // 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; + if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts + if (SEGENV.aux0 & DIRECTION_FLAG) { // 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 < numGhosts + 1; 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; + } + } + else { // Going backward (after PacMan ate the power dot)... + SEGMENT.setPixelColor(character[0].pos+1, BLACK); + 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 < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[0].pos <= startBlinkingGhostsLED) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts + if (character[i].color == BLUE) + character[i].color = BLACK; + else + character[i].color = BLUE; + } + + 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; + } } - - 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 + return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White Dots,,Reverse;,,;!;1;m12=1,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White Dots,,;,,;!;1;m12=1,o1=1"; /* From 92fe943a1a6af8dd4b40e9a65d9e442e5b2b8664 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 10 Feb 2025 19:28:32 -0700 Subject: [PATCH 3/9] Removed changes in package-lock.json file --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c81072cfc6..0afeeaafd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.16.0-alpha", + "version": "0.16.0-dev", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.16.0-alpha", + "version": "0.16.0-dev", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", From 655ee452cb13889f1eb70a7cc474987e49a8cf31 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 10 Feb 2025 19:58:29 -0700 Subject: [PATCH 4/9] Re-used 2 old effect IDs. --- wled00/FX.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 70e3f6ba0b..b74b074cd2 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -209,6 +209,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_LAKE 75 #define FX_MODE_METEOR 76 //#define FX_MODE_METEOR_SMOOTH 77 // merged with meteor +#define FX_MODE_ANTS 77 // gap fill #define FX_MODE_RAILWAY 78 #define FX_MODE_RIPPLE 79 #define FX_MODE_TWINKLEFOX 80 @@ -287,6 +288,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_2DFIRENOISE 149 #define FX_MODE_2DSQUAREDSWIRL 150 // #define FX_MODE_2DFIRE2012 151 +#define FX_MODE_PACMAN 151 // gap fill - not SR #define FX_MODE_2DDNA 152 #define FX_MODE_2DMATRIX 153 #define FX_MODE_2DMETABALLS 154 @@ -322,11 +324,9 @@ 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 -#define FX_MODE_PACMAN 188 -//#define FX_MODE_RACERS 189 +//#define FX_MODE_RACERS 187 -#define MODE_COUNT 189 +#define MODE_COUNT 187 #define BLEND_STYLE_FADE 0x00 // universal From 697b70d75fc39097753cf3cc223541b18d04b677 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Mon, 10 Feb 2025 23:10:17 -0700 Subject: [PATCH 5/9] Changed int types in Ants and PacMan --- wled00/FX.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c9955d554c..c59960ddf6 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3131,17 +3131,17 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b static uint16_t mode_ants(void) { //allocate segment data uint32_t bgcolor = BLACK; - static constexpr uint16_t MAX_ANTS = 32; // Maximum number of ants - static constexpr uint8_t DEFAULT_ANT_SIZE = 1; - uint8_t antSize = DEFAULT_ANT_SIZE; - uint16_t dataSize = sizeof(rball_t) * MAX_ANTS; + static constexpr unsigned MAX_ANTS = 32; // Maximum number of ants + static constexpr unsigned DEFAULT_ANT_SIZE = 1; + unsigned antSize = DEFAULT_ANT_SIZE; + unsigned dataSize = sizeof(rball_t) * MAX_ANTS; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed int confusedAnt; // the first random ant to go backwards rball_t *ants = reinterpret_cast(SEGENV.data); // number of ants based on intensity setting to max of 32 - uint8_t numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); + unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); if (numAnts > 32) numAnts = MAX_ANTS; antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 5 pixels) with a slider @@ -3224,7 +3224,7 @@ static uint16_t mode_ants(void) { if (thisHeight < 0.0f) thisHeight = 0.0f; if (thisHeight > 1.0f) thisHeight = 1.0f; - uint16_t pos = round(thisHeight * (SEGLEN - 1)); + unsigned 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); @@ -3241,8 +3241,8 @@ static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant s typedef struct PacManChars { - uint16_t pos; - uint8_t size; + unsigned pos; + unsigned size; uint32_t color; } pacmancharacters_t; @@ -3262,12 +3262,12 @@ typedef struct PacManChars { */ static uint16_t mode_pacman(void) { //allocate segment data - constexpr uint16_t numGhosts = 4; - uint16_t dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot + constexpr unsigned numGhosts = 4; + unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + 1 + 1); // 4 ghosts + 1 Pac-Man + 1 Power dot if (SEGLEN <= 15 || !SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed pacmancharacters_t *character = reinterpret_cast(SEGENV.data); - uint8_t startBlinkingGhostsLED; // the first LED when the blue ghosts will start blinking + unsigned 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 From bf4e5eb3702624c4dc9b7a29f2aad6abe50d4a71 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Fri, 14 Feb 2025 00:20:51 -0700 Subject: [PATCH 6/9] Ant effect: color selection is much better --- wled00/FX.cpp | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c59960ddf6..30fd94b0c3 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3124,38 +3124,27 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * First slider is for the ants' speed. * Second slider is for the # of ants. * Third slider is for the Ants' size. -* +* Checkbox1 is for using the palettes (enabled) or the color slots (disabled) * 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; static constexpr unsigned MAX_ANTS = 32; // Maximum number of ants static constexpr unsigned DEFAULT_ANT_SIZE = 1; unsigned antSize = DEFAULT_ANT_SIZE; unsigned dataSize = sizeof(rball_t) * MAX_ANTS; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed int confusedAnt; // the first random ant to go backwards rball_t *ants = reinterpret_cast(SEGENV.data); - // number of ants based on intensity setting to max of 32 + // number of ants based on intensity setting (max of 32) unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); if (numAnts > 32) numAnts = MAX_ANTS; - antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 5 pixels) with a slider + antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 20 pixels) with a slider - uint8_t bgColorIdx = map(SEGMENT.custom2, 0, 255, 0, 5); // background color based on the what the user selects from the Background Color slider - 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 = hw_random(0,numAnts-1); for (int i = 0; i < MAX_ANTS; i++) { @@ -3169,8 +3158,8 @@ static uint16_t mode_ants(void) { 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 - + if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); // fill all LEDs with background color (Bg) if the user didn't select Overlay checkbox + 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 @@ -3215,18 +3204,23 @@ static uint16_t mode_ants(void) { } } - 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); + uint32_t color; + if (SEGMENT.check1) { // if the Palette checkbox is selected, use palette colors + color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP,255); + } + else { // otherwise, use the 2 selectable color slots (Fx and Cs); not Bg as that's the background color + unsigned coloridx = i % 3; + if (coloridx == 1) + color = SEGCOLOR(0); // color Fx + else + color = SEGCOLOR(2); // color Cs } if (thisHeight < 0.0f) thisHeight = 0.0f; if (thisHeight > 1.0f) thisHeight = 1.0f; unsigned pos = round(thisHeight * (SEGLEN - 1)); - for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 5 pixels) + for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 20 pixels) SEGMENT.setPixelColor(pos, color); pos = pos + 1; } @@ -3237,7 +3231,7 @@ static uint16_t mode_ants(void) { 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"; +static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,Palettes,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32"; typedef struct PacManChars { From 9107fae686e20d116d1a8225ce84a7dc5e76cbb6 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Fri, 14 Feb 2025 01:11:57 -0700 Subject: [PATCH 7/9] Ant effect: final change for color selection --- wled00/FX.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 30fd94b0c3..0be615b8e3 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3124,7 +3124,7 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b * First slider is for the ants' speed. * Second slider is for the # of ants. * Third slider is for the Ants' size. -* Checkbox1 is for using the palettes (enabled) or the color slots (disabled) +* * 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) */ @@ -3205,10 +3205,10 @@ static uint16_t mode_ants(void) { } uint32_t color; - if (SEGMENT.check1) { // if the Palette checkbox is selected, use palette colors + if (SEGMENT.palette != 0 ) { // if a Palette is selected (besides the Default palette), use the palette's colors color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP,255); } - else { // otherwise, use the 2 selectable color slots (Fx and Cs); not Bg as that's the background color + else { // otherwise, Default palette selected; use the 2 selectable color slots (Fx and Cs) unsigned coloridx = i % 3; if (coloridx == 1) color = SEGCOLOR(0); // color Fx @@ -3231,7 +3231,7 @@ static uint16_t mode_ants(void) { return FRAMETIME; } -static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,Palettes,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32"; +static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32"; typedef struct PacManChars { From 9ce00b66053e2de11044be0b4349cbbcbc430f2e Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sat, 15 Feb 2025 20:56:05 -0700 Subject: [PATCH 8/9] Ant effect: new option to gather food --- wled00/FX.cpp | 115 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 86 insertions(+), 29 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0be615b8e3..98c7a55ead 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -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 { // used for rolling_balls and Ants modes +typedef struct RollingBall { unsigned long lastBounceUpdate; - float mass; // could fix this to be = 1. if memory is an issue // mass not used in ants mode + float mass; // could fix this to be = 1. if memory is an issue float velocity; float height; } rball_t; @@ -3115,36 +3115,48 @@ 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 +typedef struct Ants { + unsigned long lastBounceUpdate; + bool hasFood; + float velocity; + float height; +} ant_t; /* / 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 +* bouncing balls on a track track Effect modified from Aircoookie's bouncing ballsccccccccccccccccccccccccccc * 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. * Third slider is for the Ants' size. -* +* 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 they have food, we will enable the Pass By option so they can drop off their food easier. * 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; static constexpr unsigned MAX_ANTS = 32; // Maximum number of ants static constexpr unsigned DEFAULT_ANT_SIZE = 1; unsigned antSize = DEFAULT_ANT_SIZE; - unsigned dataSize = sizeof(rball_t) * MAX_ANTS; + unsigned dataSize = sizeof(ant_t) * MAX_ANTS; if (!SEGENV.allocateData(dataSize)) return mode_static(); // allocation failed int confusedAnt; // the first random ant to go backwards - rball_t *ants = reinterpret_cast(SEGENV.data); + ant_t *ants = reinterpret_cast(SEGENV.data); - // number of ants based on intensity setting (max of 32) - unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); - if (numAnts > 32) numAnts = MAX_ANTS; + unsigned numAnts = 1 + (SEGLEN * SEGMENT.intensity >> 12); // number of ants based on intensity setting + if (numAnts > 32) numAnts = MAX_ANTS; // max of 32 ants - antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 20 pixels) with a slider + bool passBy = SEGMENT.check3; // see if the user wants the ants to pass by each other without colliding with them + antSize = map(SEGMENT.custom1, 0, 255, 1, 20); // the size/length of each ant is user selectable (1 to 20 pixels) with a slider + if (SEGMENT.check1) // if checkbox 1 (Gather food) is enabled, add one pixel to the ant size to make it look like food is in it's mouth. + antSize += 1; + if (SEGENV.call == 0) { confusedAnt = hw_random(0,numAnts-1); for (int i = 0; i < MAX_ANTS; i++) { @@ -3158,30 +3170,59 @@ static uint16_t mode_ants(void) { 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(SEGCOLOR(1)); // fill all LEDs with background color (Bg) if the user didn't select Overlay checkbox + if (!SEGMENT.check2) { + bgcolor = SEGCOLOR(1); + SEGMENT.fill(bgcolor); // fill all LEDs with background color (Bg) if the user didn't select Overlay checkbox + } - for (int i = 0; i < numAnts; i++) { // for each Ant, do this... + 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 + 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(hw_random16(0, 10000)) / 10000.0f); // from 0.0 to 1.0 + thisHeight = ants[i].height = (float(hw_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. + + // check if reached past the beginning of the strip. if (thisHeight <= 0.0f && ants[i].velocity < 0.0f) { - thisHeight = 1.0f; - ants[i].lastBounceUpdate = strip.now; - ants[i].height = thisHeight; + if (SEGMENT.check1) { // if looking for food, stop and go back the other way + thisHeight = 0.0f; + ants[i].velocity = -ants[i].velocity; // reverse direction + ants[i].lastBounceUpdate = strip.now; + ants[i].height = thisHeight; + ants[i].hasFood = true; // found food + passBy = true; // when looking for food, pass by other ants without bumping into them + SEGMENT.check3 = true; // when looking for food, pass by other ants without bumping into them + } + else { // If not looking for food, wrap around + 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. + + // check if reached past the end of the strip. if (thisHeight >= 1.0f && ants[i].velocity > 0.0f) { - thisHeight = 0.0f; - ants[i].lastBounceUpdate = strip.now; - ants[i].height = thisHeight; + if (SEGMENT.check1) { // if looking for food, stop and go back the other way + thisHeight = 1.0f; + ants[i].velocity = -ants[i].velocity; // reverse direction + ants[i].lastBounceUpdate = strip.now; + ants[i].height = thisHeight; + ants[i].hasFood = false; // dropped off the food, now going back for more + passBy = true; // when looking for food, pass by other ants without bumping into them + SEGMENT.check3 = true; // when looking for food, pass by other ants without bumping into them + } + else { // If not looking for food, wrap around + 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" + + // check for "passing by" or "bumping into" other ants + if (!passBy) { // Ants bump into each other and reverse direction if checkbox #3 (Pass by) 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) @@ -3208,7 +3249,7 @@ static uint16_t mode_ants(void) { if (SEGMENT.palette != 0 ) { // if a Palette is selected (besides the Default palette), use the palette's colors color = SEGMENT.color_from_palette(i*255/numAnts, false, PALETTE_SOLID_WRAP,255); } - else { // otherwise, Default palette selected; use the 2 selectable color slots (Fx and Cs) + else { // ...otherwise, Default palette selected; use the 2 selectable color slots (Fx and Cs) unsigned coloridx = i % 3; if (coloridx == 1) color = SEGCOLOR(0); // color Fx @@ -3220,8 +3261,24 @@ static uint16_t mode_ants(void) { if (thisHeight > 1.0f) thisHeight = 1.0f; unsigned pos = round(thisHeight * (SEGLEN - 1)); - for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 20 pixels) - SEGMENT.setPixelColor(pos, color); + for (int z = 0; z < antSize; z++) { // make each ant the selected size (between 1 and 20 pixels) + if (ants[i].velocity < 0) { + if (z == 0 && SEGMENT.check1 && ants[i].hasFood) { // make the food pixel white, but if the ant is white, make the food pixel yellow; + if (color == WHITE) // ...but if the bg is yellow, make the food pixel gray. If the bg is white, make it yellow. + color = bgcolor==YELLOW?GRAY:YELLOW; + else + color = bgcolor==WHITE?YELLOW:WHITE; + } + } + else { // velocity > 0 + if (z == antSize-1 && SEGMENT.check1 && ants[i].hasFood) { // make the food pixel white, but if the ant is white, make the food pixel yellow; + if (color == WHITE) // ...but if the bg is yellow, make the food pixel gray. If the bg is white, make it yellow. + color = bgcolor==YELLOW?GRAY:YELLOW; + else + color = bgcolor==WHITE?YELLOW:WHITE; + } + } + SEGMENT.setPixelColor(pos, color); // draw the pixel with the correct color pos = pos + 1; } @@ -3231,12 +3288,12 @@ static uint16_t mode_ants(void) { return FRAMETIME; } -static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32"; +static const char _data_FX_MODE_ANTS[] PROGMEM = "Ants@Ant speed,# of ants,Ant size,,,Gathering food,Overlay,Pass by;!,!,!;!;1;sx=192,ix=255,c1=32"; typedef struct PacManChars { unsigned pos; - unsigned size; + unsigned size; uint32_t color; } pacmancharacters_t; From 63cc5c0618474b8f7eff1b5e30f95d5f6e3abef7 Mon Sep 17 00:00:00 2001 From: Bob Loeffler Date: Sun, 16 Feb 2025 14:04:46 -0700 Subject: [PATCH 9/9] PacMan effect: Transition from previous effect is fixed --- wled00/FX.cpp | 77 +++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 98c7a55ead..d9834bc1be 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3122,7 +3122,7 @@ typedef struct Ants { float height; } ant_t; /* -/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler - January 2025 +/ Ants (created by making modifications to the Rolling Balls code) - Bob Loeffler - Jan-Feb 2025 * bouncing balls on a track track Effect modified from Aircoookie's bouncing ballsccccccccccccccccccccccccccc * Courtesy of pjhatch (https://github.com/pjhatch) * https://github.com/Aircoookie/WLED/pull/1039 @@ -3132,7 +3132,7 @@ typedef struct Ants { * Third slider is for the Ants' size. * 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 they have food, we will enable the Pass By option so they can drop off their food easier. +* When they have food, we will enable the Pass By option 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) */ @@ -3301,7 +3301,7 @@ typedef struct PacManChars { #define WHITEISH (uint32_t)0x999999 #define DIRECTION_FLAG 0b01 #define GHOSTSBLUE_FLAG 0b10 - +#define PACMAN 0 // the PacMan character is character[0] /* / Pac-Man (created by making modifications to the Ants effect which was a * modification of the Rolling Balls effect) - Bob Loeffler - Jan-Feb 2025 @@ -3328,14 +3328,12 @@ static uint16_t mode_pacman(void) { if (SEGENV.call == 0) { SEGENV.aux0 = DIRECTION_FLAG; // initial direction of movement for all characters. 1=pacman chasing ghosts - SEGMENT.fill(BLACK); // black out all LEDs on the segment - 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[PACMAN].color = YELLOW; // Pac-man character[0] + character[PACMAN].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; @@ -3352,13 +3350,6 @@ static uint16_t mode_pacman(void) { character[5].color = ORANGEYELLOW; // orange-ish power dot (always blinks until it is eaten) character[5].pos = SEGLEN-1; - if (SEGMENT.check1) { // set up the white dots (or not) so PacMan can start eating them; start at the dot just ahead of PacMan - for (int i = character[0].pos; i < SEGLEN-1; i++) { - SEGMENT.setPixelColor(i, WHITEISH); // dim white color dots - i++; // skip a dot so the dots are not next to each other - } - } - SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the power dot in the last pixel/led SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // the ghosts are not blue yet, so set this to false (0) @@ -3369,21 +3360,42 @@ static uint16_t mode_pacman(void) { SEGENV.aux1++; } + // draw white dots (or black LEDs) so PacMan can start eating them + if (SEGENV.aux0 & DIRECTION_FLAG && character[PACMAN].pos > 0) { + for (int i = character[4].pos; i > 0; i--) { // black out LEDs behind the last ghost (character[4]) in case they are on (transition from previous effect) + SEGMENT.setPixelColor(i-2, BLACK); + } + + if (SEGMENT.check1) { // White Dots option is selected, so draw white dots (and black LEDs between them) in front of PacMan + for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character + SEGMENT.setPixelColor(i, WHITEISH); // white dots + SEGMENT.setPixelColor(i-1, BLACK); // black LEDS between each white dot + i--; // skip a dot so the dots are not next to each other + } + } + else { // White Dots option is NOT selected, so draw black LEDs in front of PacMan + for (int i = SEGLEN-2; i > character[PACMAN].pos; i--) { // start at the end of the segment (but not the last one because it's the orange power dot) and draw to the PacMan character + SEGMENT.setPixelColor(i, BLACK); // black LEDS only + } + }; + } + // blink the orange-ish power dot pixel if (SEGENV.aux1 % 10 == 0) { // blink every 10 ticks of the ticker timer if (character[5].color == ORANGEYELLOW) character[5].color = BLACK; else character[5].color = ORANGEYELLOW; - if (SEGENV.aux0 & DIRECTION_FLAG) - SEGMENT.setPixelColor(character[5].pos, character[5].color); + + if (SEGENV.aux0 & DIRECTION_FLAG) + SEGMENT.setPixelColor(character[5].pos, character[5].color); } // PacMan ate the power dot! Chase the ghosts! - if (character[0].pos >= SEGLEN) { + if (character[PACMAN].pos >= SEGLEN) { SEGENV.aux0 = SEGENV.aux0 & ~DIRECTION_FLAG; // reverse direction for all characters; 0=ghosts chasing pacman for (int i = 1; i < numGhosts + 1; i++) { // For all 4 ghosts... - character[i].color = BLUE; // change their color to blue + 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 power dot character[i].pos = character[i].pos - 1; @@ -3392,15 +3404,8 @@ static uint16_t mode_pacman(void) { } // when the ghosts are blue and PacMan gets to the beginning of the segment... - if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && 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) - i++; // skip a dot so the dots are not next to each other - } - SEGMENT.setPixelColor(character[5].pos, character[5].color); // ...draw the orange-ish power dot in the last led + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[PACMAN].pos <= 0) { + SEGMENT.setPixelColor(character[5].pos, character[5].color); // draw the orange-ish power dot in the last led SEGENV.aux0 = SEGENV.aux0 | DIRECTION_FLAG; // reverse direction for all characters (back to normal direction; 1=pacman chasing ghosts) character[1].color = RED; // change ghost 1 color back to red @@ -3410,12 +3415,12 @@ static uint16_t mode_pacman(void) { SEGENV.aux0 = SEGENV.aux0 & ~GHOSTSBLUE_FLAG; // ghosts should not be blue anymore, so set to false } - // display everything + // display the characters if (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 20, 1) == 0) { // User-selectable speed of PacMan and the Ghosts if (SEGENV.aux0 & DIRECTION_FLAG) { // 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; + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan + SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); + character[0].pos = character[PACMAN].pos+1; for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) SEGMENT.setPixelColor(character[i].pos, character[i].color); @@ -3424,13 +3429,13 @@ static uint16_t mode_pacman(void) { } } else { // Going backward (after PacMan ate the power dot)... - SEGMENT.setPixelColor(character[0].pos+1, BLACK); - SEGMENT.setPixelColor(character[0].pos, character[0].color); // ...draw PacMan - SEGMENT.setPixelColor(character[0].pos-1, BLACK); + SEGMENT.setPixelColor(character[PACMAN].pos+1, BLACK); + SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color); // ...draw PacMan + SEGMENT.setPixelColor(character[PACMAN].pos-1, BLACK); character[0].pos = character[0].pos-1; for (int i = 1; i < numGhosts + 1; i++) { // ...draw the 4 ghosts (and black dots surrounding each ghost) - if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[0].pos <= startBlinkingGhostsLED) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts + if ((SEGENV.aux0 & GHOSTSBLUE_FLAG) && character[PACMAN].pos <= startBlinkingGhostsLED) { // if the ghost is blue and nearing the beginning of the strip, blink the ghosts if (character[i].color == BLUE) character[i].color = BLACK; else @@ -3447,7 +3452,7 @@ static uint16_t mode_pacman(void) { return FRAMETIME; } -static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White Dots,,;,,;!;1;m12=1,o1=1"; +static const char _data_FX_MODE_PACMAN[] PROGMEM = "PacMan@Speed,,,,,White dots,,;,,;!;1;m12=1,o1=1"; /*