Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
72 changes: 36 additions & 36 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3150,25 +3150,25 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b
* aux1 is the main counter for timing.
*/
typedef struct PacManChars {
signed pos;
signed topPos; // LED position of farthest PacMan has moved
int pos;
int topPos; // LED position of farthest PacMan has moved
uint32_t color;
bool direction; // true = moving away from first LED
bool blue; // used for ghosts only
bool eaten; // used for power dots only
} pacmancharacters_t;

static void mode_pacman(void) {
constexpr unsigned ORANGEYELLOW = 0xFFCC00;
constexpr unsigned PURPLEISH = 0xB000B0;
constexpr unsigned ORANGEISH = 0xFF8800;
constexpr unsigned WHITEISH = 0x999999;
constexpr unsigned PACMAN = 0; // PacMan is character[0]
constexpr uint32_t ORANGEYELLOW = 0xFFCC00;
constexpr uint32_t PURPLEISH = 0xB000B0;
constexpr uint32_t ORANGEISH = 0xFF8800;
constexpr uint32_t WHITEISH = 0x999999;
constexpr uint32_t PACMAN = 0; // PacMan is character[0]
constexpr uint32_t ghostColors[] = {RED, PURPLEISH, CYAN, ORANGEISH};

unsigned maxPowerDots = min(SEGLEN / 10U, 255U); // cap the max so packed state fits in 8 bits
unsigned numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots);
unsigned numGhosts = map(SEGMENT.custom3, 0, 31, 2, 8);
uint32_t maxPowerDots = min(SEGLEN / 10U, 255U); // cap the max so packed state fits in 8 bits
uint32_t numPowerDots = map(SEGMENT.intensity, 0, 255, 1, maxPowerDots);
uint32_t numGhosts = map(SEGMENT.custom3, 0, 31, 2, 8);
bool smearMode = SEGMENT.check2;

// Pack two 8-bit values into one 16-bit field (stored in SEGENV.aux0)
Expand All @@ -3177,15 +3177,15 @@ static void mode_pacman(void) {
SEGENV.aux0 = combined_value;

// Allocate segment data
unsigned dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); // +1 is the PacMan character
uint32_t dataSize = sizeof(pacmancharacters_t) * (numGhosts + maxPowerDots + 1); // +1 is the PacMan character
if (SEGLEN <= 16 + (2*numGhosts) || !SEGENV.allocateData(dataSize)) FX_FALLBACK_STATIC;
pacmancharacters_t *character = reinterpret_cast<pacmancharacters_t *>(SEGENV.data);

// Calculate when blue ghosts start blinking.
// On first call (or after settings change), `topPos` is not known yet, so fall back to the full segment length in that case.
int maxBlinkPos = (SEGENV.call == 0) ? (int)SEGLEN - 1 : character[PACMAN].topPos;
if (maxBlinkPos < 20) maxBlinkPos = 20;
int startBlinkingGhostsLED = (SEGLEN < 64)
int startBlinkingGhostsLED = (SEGLEN < 64U)
? (int)SEGLEN / 3
: map(SEGMENT.custom1, 0, 255, 20, maxBlinkPos);

Expand All @@ -3199,19 +3199,19 @@ static void mode_pacman(void) {
character[PACMAN].blue = false;

// Initialize ghosts with alternating colors
for (int i = 1; i <= numGhosts; i++) {
for (uint32_t i = 1; i <= numGhosts; i++) {
character[i].color = ghostColors[(i-1) % 4];
character[i].pos = -2 * (i + 1);
character[i].pos = -2 * int32_t(i + 1);
character[i].direction = true;
character[i].blue = false;
}

// Initialize power dots
for (int i = 0; i < numPowerDots; i++) {
for (uint32_t i = 0; i < numPowerDots; i++) {
character[i + numGhosts + 1].color = ORANGEYELLOW;
character[i + numGhosts + 1].eaten = false;
}
character[numGhosts + 1].pos = SEGLEN - 1; // Last power dot at end
character[numGhosts + 1].pos = int32_t(SEGLEN - 1); // Last power dot at end
}

if (strip.now > SEGENV.step) {
Expand All @@ -3225,50 +3225,50 @@ static void mode_pacman(void) {
// Draw white dots in front of PacMan if option selected
if (SEGMENT.check1) {
int step = SEGMENT.check3 ? 1 : 2; // Compact or spaced dots
for (int i = SEGLEN - 1; i > character[PACMAN].topPos; i -= step) {
for (int i = (int32_t)(SEGLEN - 1); i > character[PACMAN].topPos; i -= step) {
SEGMENT.setPixelColor(i, WHITEISH);
}
}

// Update power dot positions dynamically
uint32_t everyXLeds = (((uint32_t)SEGLEN - 10U) << 8) / numPowerDots; // Fixed-point spacing for power dots: use 32-bit math to avoid overflow on long segments.
for (int i = 1; i < numPowerDots; i++) {
character[i + numGhosts + 1].pos = 10 + ((i * everyXLeds) >> 8);
for (uint32_t i = 1; i < numPowerDots; i++) {
character[i + numGhosts + 1].pos = int32_t(10 + ((i * everyXLeds) >> 8));
}

// Blink power dots every 10 ticks
if (SEGENV.aux1 % 10 == 0) {
if (SEGENV.aux1 % 10U == 0) {
uint32_t dotColor = (character[numGhosts + 1].color == ORANGEYELLOW) ? BLACK : ORANGEYELLOW;
for (int i = 0; i < numPowerDots; i++) {
for (uint32_t i = 0; i < numPowerDots; i++) {
character[i + numGhosts + 1].color = dotColor;
}
}

// Blink blue ghosts when nearing start
if (SEGENV.aux1 % 15 == 0 && character[1].blue && character[PACMAN].pos <= startBlinkingGhostsLED) {
if (SEGENV.aux1 % 15U == 0 && character[1].blue && character[PACMAN].pos <= startBlinkingGhostsLED) {
uint32_t ghostColor = (character[1].color == BLUE) ? WHITEISH : BLUE;
for (int i = 1; i <= numGhosts; i++) {
for (uint32_t i = 1; i <= numGhosts; i++) {
character[i].color = ghostColor;
}
}

// Draw uneaten power dots
for (int i = 0; i < numPowerDots; i++) {
if (!character[i + numGhosts + 1].eaten && (unsigned)character[i + numGhosts + 1].pos < SEGLEN) {
for (uint32_t i = 0; i < numPowerDots; i++) {
if (!character[i + numGhosts + 1].eaten && (uint32_t)character[i + numGhosts + 1].pos < SEGLEN) {
SEGMENT.setPixelColor(character[i + numGhosts + 1].pos, character[i + numGhosts + 1].color);
}
}

// Check if PacMan ate a power dot
for (int j = 0; j < numPowerDots; j++) {
for (uint32_t j = 0; j < numPowerDots; j++) {
auto &dot = character[j + numGhosts + 1];
if (character[PACMAN].pos == dot.pos && !dot.eaten) {
// Reverse all characters - PacMan now chases ghosts
for (int i = 0; i <= numGhosts; i++) {
for (uint32_t i = 0; i <= numGhosts; i++) {
character[i].direction = false;
}
// Turn ghosts blue
for (int i = 1; i <= numGhosts; i++) {
for (uint32_t i = 1; i <= numGhosts; i++) {
character[i].color = BLUE;
character[i].blue = true;
}
Expand All @@ -3280,42 +3280,42 @@ static void mode_pacman(void) {
// Reset when PacMan reaches start with blue ghosts
if (character[1].blue && character[PACMAN].pos <= 0) {
// Reverse direction back
for (int i = 0; i <= numGhosts; i++) {
for (uint32_t i = 0; i <= numGhosts; i++) {
character[i].direction = true;
}
// Reset ghost colors
for (int i = 1; i <= numGhosts; i++) {
for (uint32_t i = 1; i <= numGhosts; i++) {
character[i].color = ghostColors[(i-1) % 4];
character[i].blue = false;
}
// Reset power dots if last one was eaten
if (character[numGhosts + 1].eaten) {
for (int i = 0; i < numPowerDots; i++) {
for (uint32_t i = 0; i < numPowerDots; i++) {
character[i + numGhosts + 1].eaten = false;
}
character[PACMAN].topPos = 0; // set the top position of PacMan to LED 0 (beginning of the segment)
}
}

// Update and draw characters based on speed setting
bool updatePositions = (SEGENV.aux1 % map(SEGMENT.speed, 0, 255, 15, 1) == 0);
bool updatePositions = (SEGENV.aux1 % uint32_t(map(SEGMENT.speed, 0, 255, 15, 1)) == 0);

// update positions of characters if it's time to do so
if (updatePositions) {
character[PACMAN].pos += character[PACMAN].direction ? 1 : -1;
for (int i = 1; i <= numGhosts; i++) {
for (uint32_t i = 1; i <= numGhosts; i++) {
character[i].pos += character[i].direction ? 1 : -1;
}
}

// Draw PacMan
if ((unsigned)character[PACMAN].pos < SEGLEN) {
if ((uint32_t)character[PACMAN].pos < SEGLEN) {
SEGMENT.setPixelColor(character[PACMAN].pos, character[PACMAN].color);
}

// Draw ghosts
for (int i = 1; i <= numGhosts; i++) {
if ((unsigned)character[i].pos < SEGLEN) {
for (uint32_t i = 1; i <= numGhosts; i++) {
if ((uint32_t)character[i].pos < SEGLEN) {
SEGMENT.setPixelColor(character[i].pos, character[i].color);
}
}
Expand Down
6 changes: 3 additions & 3 deletions wled00/data/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,10 @@ function connectWs(onOpen) {
// start: start pixel index
// len: number of pixels to send
// colors: Uint8Array with RGB values (3*len bytes)
function sendDDP(ws, start, len, colors) {
function sendDDP(ws, start, len, colors, isESP8266=false) {
if (!colors || colors.length < len * 3) return false; // not enough color data
let maxDDPpx = 472; // must fit into one WebSocket frame of 1428 bytes, DDP header is 10+1 bytes -> 472 RGB pixels
//let maxDDPpx = 172; // ESP8266: must fit into one WebSocket frame of 528 bytes -> 172 RGB pixels TODO: add support for ESP8266?
// data must fit into one WebSocket frame of 1428 bytes, DDP header is 10+1 bytes -> 472 RGB pixels (ESP8266: 528 bytes -> 172 RGB pixels)
let maxDDPpx = isESP8266 ? 172 : 472;
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
// send in chunks of maxDDPpx
for (let i = 0; i < len; i += maxDDPpx) {
Expand Down
25 changes: 20 additions & 5 deletions wled00/data/pixelforge/pixelforge.htm
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@
</div>

<div id="iTab" class="tabc active">
<div id="iTabContent">
<h3 style="margin-top:20px;">Target Segment</h3>
<select id="seg"></select>

Expand Down Expand Up @@ -320,6 +321,8 @@ <h3 style="margin-top:0;padding-top:0;border-top:0">Crop & Adjust Image</h3>
</div>
</div>
<button class="btn" id="up">Convert & Upload to WLED</button>
</div>
<div id="iTab8266" style="display:none;">Not available on ESP8266</div>
</div>
</div>

Expand Down Expand Up @@ -424,6 +427,7 @@ <h3>Custom Fonts</h3>
const classics=['console_font_4x6.wbf','console_font_5x12.wbf','console_font_5x8.wbf','console_font_6x8.wbf','console_font_7x9.wbf']; // classic WLED fonts list
let pT = []; // local tools list from JSON
let wv = [0, 0]; // wled version [major, minor], updated in fsMem(), used to check tool compatibility
let is8266 = false; // restrictions apply for ESP8266, set when getting the info
const remoteURL = 'https://wled.github.io/wled-web-tools/pftools.json'; // tools list
const toolsjson = 'pftools.json';
// note: the pftools.json must use major.minor for tool versions (e.g. 0.95 or 1.1), otherwise the update check won't work
Expand All @@ -434,23 +438,25 @@ <h3>Custom Fonts</h3>
const s = document.createElement('script');
s.src = 'common.js';
s.onerror = () => setTimeout(loadFiles, 100);
s.onload = () => {
loadResources(['style.css','omggif.js'], init); // load omggif.js then call init()
s.onload = async () => {
getLoc(); // set up loc/locip for getURL() before any fetch (file mode / reverse proxy)
await fsMem(); // update & show file system memory info, also updates wled version (wv) and is8266
const resources = ['style.css'];
if (!is8266) resources.push('omggif.js'); // omggif is not available on ESP8266
loadResources(resources, init); // load omggif.js then call init()
};
document.head.appendChild(s);
})();

/* init */
async function init() {
getLoc();
// create off screen canvas
rv = cE('canvas');
rvc = rv.getContext('2d',{willReadFrequently:true});
rv.width = cv.width; rv.height = cv.height;
await flU(); // update file list
tabSw(localStorage.tab||'img'); // switch to last open tab or image tab by default
await segLoad(); // load available segments
await fsMem(); // update & show file system memory info, also updates wled version (wv)
await loadTools(); // load additional tools list from pftools.json
}

Expand Down Expand Up @@ -707,6 +713,7 @@ <h3>${esc(t.name)} <small style="font-size:10px">v${esc(t.ver)}</small></h3>
const m = info.ver.match(/\d+/g); // extract all numbers from version string (e.g. "16.1.0-beta" → [16, 1])
wv = [parseInt(m[0]) || 0, parseInt(m[1]) || 0];
}
if (info.arch === 'esp8266') is8266 = true;
}
}catch(e){console.error(e);}
}
Expand Down Expand Up @@ -1263,7 +1270,15 @@ <h3>${esc(t.name)} <small style="font-size:10px">v${esc(t.ver)}</small></h3>
getId(id).classList.toggle('active', tab===['img','txt','oth'][i%3]);
});
localStorage.tab=tab;
({txt:()=>{txtSegLoad(); scanFonts();}, img:imgLoad}[tab]||(()=>{}))(); // on tab switch, load images and available fonts
if (tab === 'img') {
getId('iTab8266').style.display = is8266 ? 'block' : 'none'; // show "not available" on ESP8266
getId('iTabContent').style.display = is8266 ? 'none' : ''; // show normal image tool on ESP32
if (!is8266) imgLoad();
}
if (tab === 'txt') {
txtSegLoad();
scanFonts();
}
}
'Img,Txt,Oth'.split(',').forEach((s,i)=>{
getId('t'+s).onclick=()=>tabSw(['img','txt','oth'][i]);
Expand Down
8 changes: 7 additions & 1 deletion wled00/wled_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
#include "html_settings.h"
#include "html_other.h"
#include "js_iro.h"
#include "js_omggif.h"
#ifdef WLED_ENABLE_GIF
#include "js_omggif.h"
#endif
#ifdef WLED_ENABLE_PIXART
#include "html_pixart.h"
#endif
Expand Down Expand Up @@ -42,7 +44,9 @@ static const char s_no_store[] PROGMEM = "no-store";
static const char s_expires[] PROGMEM = "Expires";
static const char _common_js[] PROGMEM = "/common.js";
static const char _iro_js[] PROGMEM = "/iro.js";
#ifdef WLED_ENABLE_GIF
static const char _omggif_js[] PROGMEM = "/omggif.js";
#endif

//Is this an IP?
static bool isIp(const String &str) {
Expand Down Expand Up @@ -362,9 +366,11 @@ void initServer()
handleStaticContent(request, FPSTR(_iro_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_iro, JS_iro_length);
});

#ifdef WLED_ENABLE_GIF
server.on(_omggif_js, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_omggif_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_omggif, JS_omggif_length);
});
#endif

//settings page
server.on(F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){
Expand Down
Loading