#include "animation.h" #include #include #define WIDTH 20 #define HEIGHT 20 #define SIN_TABLE_SIZE 256 #define TWO_PI 6.28318530717958647692f #define INV_TWO_PI (1.0f / TWO_PI) const float sinTable[SIN_TABLE_SIZE] PROGMEM = { #include "sin_table_256.inc" }; // Pumpkin pixel art 8x8 (simplified) // 0 = empty, 1 = pumpkin body, 2 = eye const uint8_t pumpkinSprite[8][8] = { {0,1,1,1,1,1,1,0}, {1,1,1,1,1,1,1,1}, {1,2,0,1,1,0,2,1}, {1,1,1,1,1,1,1,1}, {1,1,1,0,0,1,1,1}, {1,1,0,1,1,0,1,1}, {0,1,1,1,1,1,1,0}, {0,0,1,1,1,1,0,0} }; const uint8_t imageManuBude[20][20] = { {0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,1,0,0,0,0,1,1,1,1,1,1,1,1,1}, {0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0}, {0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} }; // Colors uint32_t pumpkinColor = Adafruit_NeoPixel::Color(255,140,0); // orange uint32_t eyeBaseColor = Adafruit_NeoPixel::Color(255,60,0); // fiery red-orange static inline float sinPreCalc(float value) { // Scale factor (precompute once as constant) const float scale = SIN_TABLE_SIZE / TWO_PI; // Convert radians → table index (float) float index = value * scale; // Convert to integer index int i0 = (int)index; // Fractional part for interpolation float frac = index - i0; // Wrap index using power-of-two mask (256 -> 0xFF) i0 &= (SIN_TABLE_SIZE - 1); int i1 = (i0 + 1) & (SIN_TABLE_SIZE - 1); // Read from flash float s0 = pgm_read_float(&sinTable[i0]); float s1 = pgm_read_float(&sinTable[i1]); // Linear interpolation return s0 + frac * (s1 - s0); } // Bubble animation function uint32_t bubbles(uint8_t x, uint8_t y, float timeMs) { float colorR = 0, colorG = 0, colorB = 0; // Generate several bubbles per column for (uint8_t i = 0; i < 5; i++) { // Each bubble has its own horizontal position and speed float speed = 0.02f + 0.01f * (hash8(i*3, x)/255.0f); // pixels/ms float phase = hash8(i*7, x)/255.0f * HEIGHT; // starting row float bubbleY = fmod((timeMs*speed + phase), HEIGHT); // Distance from this bubble to pixel float dist = fabs(bubbleY - y); if (dist < 1.5) { // bubble radius // Brightness fades at edges float brightness = 1.0f - (dist/1.5f); colorR += 100 * brightness; colorG += 180 * brightness; colorB += 255 * brightness; } } // Clamp colors if (colorR > 255) colorR = 255; if (colorG > 255) colorG = 255; if (colorB > 255) colorB = 255; return Adafruit_NeoPixel::Color((uint8_t)colorR, (uint8_t)colorG, (uint8_t)colorB); } // Pseudo-random flicker float flicker(float timeMs, uint8_t seed) { return 0.6f + 0.4f * sinPreCalc(timeMs*0.01f + seed*3.14f); // 0.6..1.0 } // Pumpkin pixel function uint32_t minecraftPumpkin(uint8_t x, uint8_t y, float timeMs) { int spriteWidth = 8; int spriteHeight = 8; int offsetX = WIDTH/2 - spriteWidth/2; int offsetY = HEIGHT/2 - spriteHeight/2; int localX = x - offsetX; int localY = y - offsetY; if (localX >= 0 && localX < spriteWidth && localY >= 0 && localY < spriteHeight) { uint8_t val = pumpkinSprite[localY][localX]; if (val == 1) return pumpkinColor; if (val == 2) { // Flickering fire eyes float f = flicker(timeMs, localX*10 + localY); uint8_t r = (uint8_t)(255 * f); uint8_t g = (uint8_t)(60 * f); uint8_t b = 0; return Adafruit_NeoPixel::Color(r,g,b); } } return Adafruit_NeoPixel::Color(0,0,0); // background black } // Among Us pixel art 7x7 (simplified) // 0 = empty, 1 = body, 2 = visor const uint8_t amongUsSprite[8][8] = { {0,0,1,1,1,1,0,0}, {0,1,1,3,3,3,3,0}, {1,1,3,2,2,2,2,3}, {1,1,3,2,2,2,2,3}, {1,1,1,3,3,3,3,0}, {1,1,1,1,1,1,1,0}, {0,1,1,1,1,1,1,0}, {0,1,1,0,0,1,1,0} }; // Colors uint32_t bodyColor = Adafruit_NeoPixel::Color(255,0,0); // red crewmate uint32_t visorColor = Adafruit_NeoPixel::Color(150,200,255); // visor uint32_t visorColorInner = Adafruit_NeoPixel::Color(0,0,255); // visor // Function to get pixel color of Among Us crewmate uint32_t amongUsPixel(uint8_t x, uint8_t y, float timeMs) { // Move crewmate horizontally across screen int spriteWidth = 8; int spriteHeight = 8; int posX = (int)(fmod(timeMs * 0.01f, WIDTH + spriteWidth)) - spriteWidth; int posY = HEIGHT / 2 - spriteHeight / 2; int localX = x - posX; int localY = y - posY; if (localX >= 0 && localX < spriteWidth && localY >= 0 && localY < spriteHeight) { uint8_t val = amongUsSprite[localY][localX]; if (val == 1) return bodyColor; if (val == 2) return visorColorInner; if (val == 3) return visorColor; } return Adafruit_NeoPixel::Color(0,0,0); // background black } // Fast pseudo-random hash function (per pixel) uint8_t hash8(uint16_t x, uint16_t y) { uint32_t h = x * 374761393 + y * 668265263; // large primes h = (h ^ (h >> 13)) * 1274126177; return (h >> 16) & 0xFF; } // Purple rain waterfall color function uint32_t purpleRain(uint8_t x, uint8_t y, float timeMs) { // Each column has its own speed float speed = 0.03f + 0.01f * (hash8(x, 0) / 255.0f); // pixels/ms float t = timeMs * speed; // Determine the head of the falling stream int headRow = (int)t % HEIGHT; // Distance from head int dist = (y + HEIGHT - headRow) % HEIGHT; // Brightness fades with distance uint8_t brightness = 0; if (dist == 0) brightness = 255; // brightest head else if (dist < 6) brightness = 255 - dist * 60; // fading tail else brightness = 0; // Purple color: R and B nonzero, G low uint8_t r = brightness; uint8_t g = brightness / 2; // small green tint uint8_t b = brightness; return Adafruit_NeoPixel::Color(r, g, b); } // Starry sky function uint32_t starrySky(uint8_t x, uint8_t y, float timeMs) { uint8_t seed = hash8(x, y); // Only ~5% of pixels have stars if (seed < 20) { // 0..12 // Twinkle: small brightness variation float phase = (timeMs * 0.002f + seed) * 0.1f; float blink = 0.7f + 0.8f * sinPreCalc(phase * 6.2831f); // 0.4..1.0 uint8_t brightness = (uint8_t)(150 * blink + 50); // 50..200 return Adafruit_NeoPixel::Color(brightness, brightness, brightness); } // Shooting star: rare (~0.5%) moving diagonally float t = fmod(timeMs * 0.01f, WIDTH + HEIGHT); if (seed > 250 && fabs(x - t) < 1 && fabs(y - t) < 1) { return Adafruit_NeoPixel::Color(255, 200, 150); } // Otherwise empty sky return Adafruit_NeoPixel::Color(0, 0, 0); } uint32_t spinningDavidStarPixel(uint8_t x, uint8_t y, float timeMs) { float t = timeMs * 0.001f; // seconds // Center of the matrix float cx = 10.0f; float cy = 10.0f; // Translate to center float dx = x - cx; float dy = y - cy; // Rotation angle float angle = t * 2.0f; // radians, spins over time float cosA = sinPreCalc(TWO_PI/4 - angle); float sinA = sinPreCalc(angle); float rx = dx * cosA - dy * sinA; float ry = dx * sinA + dy * cosA; // Star of David = two overlapping equilateral triangles // Approximate with slopes: y = ±sqrt(3)*x for the triangles bool inStar = false; // Upper triangle if (ry >= -rx * 1.7f && ry >= rx * 1.7f && ry <= 0) inStar = true; // Lower triangle if (ry <= -rx * 1.7f && ry <= rx * 1.7f && ry >= 0) inStar = true; // Draw the star if (inStar) { // Optional: pulsating color uint8_t pulse = 150 + 105 * sinPreCalc(t * 3.0f); return NeoPixel.Color(pulse, pulse, 255); } // Background return NeoPixel.Color(0, 0, 20); } uint32_t jumpingJackPixel(uint8_t x, uint8_t y, float timeMs) { float t = timeMs * 0.001f; // seconds // Center of the stick figure int cx = 10; int cy = 12; // Oscillation amplitudes float armSpread = 4.0f * sinPreCalc(t * 2.0f); // arms float legSpread = 4.0f * sinPreCalc(t * 2.0f); // legs float jump = sinPreCalc(t * 2.0f) * -3.0f; // up/down jump // Background uint32_t bg = NeoPixel.Color(0, 0, 0); // ----------------- // Head (3x3) // ----------------- int headY = cy - 5 + (int)jump; if ((x >= cx - 1 && x <= cx + 1) && (y >= headY && y <= headY + 2)) { return NeoPixel.Color(255, 255, 255); } // ----------------- // Body (vertical) // ----------------- int bodyTop = cy - 2 + (int)jump; int bodyBottom = cy + 2 + (int)jump; if (x == cx && y >= bodyTop && y <= bodyBottom) { return NeoPixel.Color(255, 255, 255); } // ----------------- // Arms (diagonal) // ----------------- int leftArmX = cx - 1 - (int)armSpread; int rightArmX = cx + 1 + (int)armSpread; int armY = cy - 1 + (int)jump; if ((x == leftArmX && y == armY - 1) || (x == leftArmX + 1 && y == armY) || (x == rightArmX && y == armY - 1) || (x == rightArmX - 1 && y == armY)) { return NeoPixel.Color(255, 255, 255); } // ----------------- // Legs (diagonal) // ----------------- int leftLegX = cx - 1 - (int)legSpread; int rightLegX = cx + 1 + (int)legSpread; int legY = cy + 3 + (int)jump; if ((x == leftLegX && y == legY) || (x == leftLegX + 1 && y == legY + 1) || (x == rightLegX && y == legY) || (x == rightLegX - 1 && y == legY + 1)) { return NeoPixel.Color(255, 255, 255); } return bg; } uint32_t bonfireMoonPixel(uint8_t x, uint8_t y, float timeMs) { float t = timeMs * 0.001f; // seconds // ------------------------ // Quarter moon (crescent) // ------------------------ int moonX = 15; int moonY = 4; int moonR = 3; float mdx = x - moonX; float mdy = y - moonY; float mdist = sqrt(mdx * mdx + mdy * mdy); bool inMoon = (mdist <= moonR); // Cut-out circle to form crescent float cutX = moonX + 2; // shift right to carve shadow float cdx = x - cutX; float cdist = sqrt(cdx * cdx + mdy * mdy); bool inShadow = (cdist <= moonR); if (inMoon && !inShadow) { uint8_t glow = 140 + 40 * sinPreCalc(t * 0.5f); return NeoPixel.Color(glow, glow, glow + 20); } // ------------ // Big bonfire // ------------ int baseX = 10; int baseY = 16; bool log1 = (y == baseY && x >= 4 && x <= 15); bool log2 = (y == baseY - 1 && x >= 5 && x <= 14); bool log3 = (y == baseY - 2 && x >= 7 && x <= 12); if (log1 || log2 || log3) { return NeoPixel.Color(100, 50, 15); } int dx = x - baseX; int dy = baseY - y; if (dy >= 0 && dy <= 10 && abs(dx) <= (5 - dy / 2)) { float flicker = sinPreCalc(t * 9.0f + x * 2.1f + y * 1.4f) * 0.5f + 0.5f; uint8_t r = 170 + 85 * flicker; uint8_t g = 70 + 160 * flicker - dy * 8; uint8_t b = 0; return NeoPixel.Color(constrain(r, 150, 255), constrain(g, 40, 220), b); } if (dy == 0 && abs(dx) <= 6) { uint8_t glow = 90 + 50 * sinPreCalc(t * 7.0f + x * 0.8f); return NeoPixel.Color(glow, glow / 2, 0); } // ---------------- // Night sky bg // ---------------- uint8_t stars = 4 + 6 * sinPreCalc(t * 0.3f + x * 0.6f + y * 0.4f); return NeoPixel.Color(0, 0, stars); } uint32_t nyanCatPixel(uint8_t x, uint8_t y, float timeMs) { float t = timeMs * 0.0015f; // seconds // Cat position (flies left -> right, loops) float catXf = fmod(t * 6.0f, 30.0f) - 6.0f; // offscreen -> onscreen int catX = (int)catXf; int catY = 10 + (int)(sinPreCalc(t * 2.0f) * 2.0f); // Background space uint8_t stars = 5 + 10 * sinPreCalc(t * 0.7f + x * 0.9f + y * 0.4f); uint32_t bg = NeoPixel.Color(0, 0, stars); // Rainbow trail (behind cat) if (x < catX && x > catX - 8 && abs(y - catY) <= 1) { float phase = t * 6.0f + x * 0.5f; uint8_t r = 127 + 127 * sinPreCalc(phase + 0.0f); uint8_t g = 127 + 127 * sinPreCalc(phase + 2.1f); uint8_t b = 127 + 127 * sinPreCalc(phase + 4.2f); return NeoPixel.Color(r, g, b); } // Simple cat sprite (5x4) bool cat = (x == catX && y == catY) || (x == catX + 1 && y == catY) || (x == catX + 2 && y == catY) || (x == catX + 3 && y == catY) || (x == catX && y == catY - 1) || (x == catX + 1 && y == catY - 1) || (x == catX + 2 && y == catY - 1) || (x == catX + 3 && y == catY - 1) || (x == catX + 1 && y == catY - 2) || (x == catX + 2 && y == catY - 2) || (x == catX + 1 && y == catY + 1) || (x == catX + 2 && y == catY + 1); if (cat) { return NeoPixel.Color(200, 180, 160); // cat body } // Face pixels (eyes) if (x == catX + 1 && y == catY - 1) return NeoPixel.Color(0, 0, 0); if (x == catX + 2 && y == catY - 1) return NeoPixel.Color(0, 0, 0); return bg; } uint32_t xwingDeathStarPixel(uint8_t x, uint8_t y, float timeMs) { // Convert ms to seconds for smooth trig float t = timeMs * 0.001f; // Scene timing (ms) float flyTime = 2000.0f; float shootTime = 1200.0f; float explodeTime = 2000.0f; float total = flyTime + shootTime + explodeTime; float local = fmod(timeMs, total); // Positions int xwingX = (int)(-5 + (local / flyTime) * 30); // flies left -> right int xwingY = 10 + (int)(sinPreCalc(t * 2.0f) * 2.0f); int dsX = 15; int dsY = 10; int dsR = 3; // Background stars uint8_t bg = 10 + 10 * sinPreCalc(t * 0.5f + x * 0.7f + y * 0.3f); uint32_t color = NeoPixel.Color(0, 0, bg); // Death Star body float dx = x - dsX; float dy = y - dsY; float dist = sqrt(dx * dx + dy * dy); bool onDeathStar = dist <= dsR; // Phase 1: Fly-by if (local < flyTime) { if (onDeathStar) { return NeoPixel.Color(120, 120, 120); // gray Death Star } } // Phase 2: Shooting if (local >= flyTime && local < flyTime + shootTime) { if (onDeathStar) { return NeoPixel.Color(150, 150, 150); } // Laser beam int laserY = xwingY; if (y == laserY && x > xwingX && x < dsX) { return NeoPixel.Color(255, 0, 0); } } // Phase 3: Explosion if (local >= flyTime + shootTime) { float e = local - flyTime - shootTime; float radius = e * 0.005f; if (dist < radius) { uint8_t r = 255; uint8_t g = max(0, 200 - (int)(e * 0.1f)); uint8_t b = 0; return NeoPixel.Color(r, g, b); } } // X-Wing sprite (tiny cross) bool xwing = (x == xwingX && y == xwingY) || (x == xwingX - 1 && y == xwingY) || (x == xwingX + 1 && y == xwingY) || (x == xwingX && y == xwingY - 1) || (x == xwingX && y == xwingY + 1); if (xwing) { return NeoPixel.Color(200, 200, 200); } return color; } uint32_t flappyBirdPixel(uint8_t x, uint8_t y, float t) { const int W = 20; const int H = 20; float time = 0.0; time = t / 400; // Bird position (fixed X, smooth Y motion) int birdX = 5; float birdY = 10.0f + sinPreCalc(time * 3.0f) * 3.0f; // flap motion // Pipe movement (smooth scrolling) float pipePos = 19.0f - fmod(time * 4.0f, 25.0f); int pipeX = (int)pipePos; // Moving gap float gapCenter = 10.0f + sinPreCalc(time * 0.7f) * 4.0f; int gapSize = 5; // Background (sky) uint8_t bgR = 0, bgG = 0, bgB = 20; // Pipes bool isPipe = (x == pipeX || x == pipeX - 1); bool inGap = (y >= gapCenter - gapSize / 2.0f && y <= gapCenter + gapSize / 2.0f); if (isPipe && !inGap) { return NeoPixel.Color(0, 160, 0); } // Bird (2x2) bool isBird = (x == birdX || x == birdX + 1) && (y == (int)birdY || y == (int)(birdY + 1)); if (isBird) { return NeoPixel.Color(255, 220, 0); } return NeoPixel.Color(bgR, bgG, bgB); } uint32_t creeper(uint8_t x, uint8_t y, uint32_t timestep) { int phase = (timestep / 40) % 120; // animation cycle int cx = 10; int cy = 10; // Base color = off uint8_t r = 0, g = 0, b = 0; // Creeper face mask bool face = false; bool eyeL = (x >= 5 && x <= 7 && y >= 5 && y <= 7); bool eyeR = (x >= 12 && x <= 14 && y >= 5 && y <= 7); bool mouth = ((x >= 7 && x <= 12 && y >= 11 && y <= 13) || (x >= 8 && x <= 9 && y >= 9 && y <= 11) || (x >= 10 && x <= 11 && y >= 9 && y <= 11)); bool head = (x >= 3 && x <= 16 && y >= 3 && y <= 16); // Phase 1 – Normal creeper if (phase < 50) { if (head) { if (eyeL || eyeR || mouth) { return NeoPixel.Color(0, 0, 0); } else { return NeoPixel.Color(0, 180, 0); } } return NeoPixel.Color(0, 0, 0); } // Phase 2 – White flash (about to explode) if (phase < 65) { if (head) { uint8_t flash = 200 + 55 * sinPreCalc(phase * 0.8); return NeoPixel.Color(flash, flash, flash); } return NeoPixel.Color(0, 0, 0); } // Phase 3 – Explosion int t = phase - 65; float dx = x - cx; float dy = y - cy; float dist = sqrt(dx * dx + dy * dy); // Expanding explosion radius float radius = t * 0.4; if (dist > radius && dist < radius + 2) { // fiery ring uint8_t rr = 255; uint8_t gg = random(80, 160); return NeoPixel.Color(rr, gg, 0); } if (dist < radius) { // fading embers uint8_t fade = max(0, 200 - t * 6); return NeoPixel.Color(fade, fade / 2, 0); } return NeoPixel.Color(0, 0, 0); } uint32_t breathe(uint8_t x, uint8_t y, float t) { float cx = 9.5, cy = 9.5; float dx = x - cx; float dy = y - cy; float dist = sqrt(dx * dx + dy * dy); float wave = sinPreCalc(dist * 0.6 - t * 0.05 / 200.0); float breath = (sinPreCalc(t * 0.02 / 200.0) + 1) * 0.5; uint8_t r = 50 + 200 * breath; uint8_t g = 30 + 100 * wave * breath; uint8_t b = 150 + 100 * (1 - breath); return NeoPixel.Color(r, g, b); } uint32_t galaxy(uint8_t x, uint8_t y, float t) { float cx = 9.5, cy = 9.5; float dx = x - cx; float dy = y - cy; float angle = atan2(dy, dx); float dist = sqrt(dx * dx + dy * dy); float spin = angle + dist * 0.3 - t / 10 * 0.02; float v = (sinPreCalc(spin * 3) + 1) * 0.5; uint8_t r = 80 + 175 * v; uint8_t g = 0; uint8_t b = 120 + 135 * (1 - v); return NeoPixel.Color(r, g, b); } float distToSegment(float px, float py, float x1, float y1, float x2, float y2) { float vx = x2 - x1; float vy = y2 - y1; float wx = px - x1; float wy = py - y1; float c1 = vx * wx + vy * wy; if (c1 <= 0) return sqrt((px - x1)*(px - x1) + (py - y1)*(py - y1)); float c2 = vx * vx + vy * vy; if (c2 <= c1) return sqrt((px - x2)*(px - x2) + (py - y2)*(py - y2)); float b = c1 / c2; float bx = x1 + b * vx; float by = y1 + b * vy; return sqrt((px - bx)*(px - bx) + (py - by)*(py - by)); } uint32_t flyingWireCubeHard(uint8_t x, uint8_t y, float t) { float cx = 9.5; float cy = 9.5; float time = t * 0.0015 / 2.0; float cube[8][3] = { {-1,-1,-1}, {1,-1,-1}, {1,1,-1}, {-1,1,-1}, {-1,-1, 1}, {1,-1, 1}, {1,1, 1}, {-1,1, 1} }; const uint8_t edges[12][2] = { {0,1},{1,2},{2,3},{3,0}, {4,5},{5,6},{6,7},{7,4}, {0,4},{1,5},{2,6},{3,7} }; float zpos = fmod(time * 3.0, 6.0) + 2.0; float scale = 7.0; float rx = time * 1.3; float ry = time * 0.9; float rz = time * 0.7; float px = x; float py = y; float proj[8][2]; for (int i = 0; i < 8; i++) { float X = cube[i][0]; float Y = cube[i][1]; float Z = cube[i][2]; float y1 = Y * sinPreCalc(TWO_PI/4 - rx) - Z * sinPreCalc(rx); float z1 = Y * sinPreCalc(rx) + Z * sinPreCalc(TWO_PI/4 - rx); Y = y1; Z = z1; float x2 = X * sinPreCalc(TWO_PI/4 - ry) + Z * sinPreCalc(ry); float z2 = -X * sinPreCalc(ry) + Z * sinPreCalc(TWO_PI/4 - ry); X = x2; Z = z2; float x3 = X * sinPreCalc(TWO_PI/4 - rz) - Y * sinPreCalc(rz); float y3 = X * sinPreCalc(rz) + Y * sinPreCalc(TWO_PI/4 - rz); X = x3; Y = y3; proj[i][0] = cx + X * scale; proj[i][1] = cy + Y * scale; } bool onEdge = false; const float thickness = 0.6; // edge thickness in pixels for (int e = 0; e < 12; e++) { int a = edges[e][0]; int b = edges[e][1]; float d = distToSegment(px, py, proj[a][0], proj[a][1], proj[b][0], proj[b][1]); if (d < thickness) { onEdge = true; break; } } if (onEdge) { return NeoPixel.Color(0, 0, 255); // sharp neon wireframe } else { return NeoPixel.Color(0, 0, 0); // background off } } bool isPacmanPixel(int px, int py, float cx, float cy, float r, float mouthOpen) { float dx = px - cx; float dy = py - cy; float dist2 = dx*dx + dy*dy; if (dist2 > r*r) return false; float angle = atan2(dy, dx); // -PI..PI // Mouth opening angle float mouth = mouthOpen * 0.8; // max opening if (angle > -mouth && angle < mouth) return false; return true; } uint32_t pacman(uint8_t x, uint8_t y, uint32_t t) { // Movement float time = t * 0.005 / 3.0; float cx = fmod(time * 6.0, 26.0) - 3.0; // move across screen float cy = 10.0; float r = 4.0; // Mouth animation (chomp) float mouth = (sinPreCalc(time * 6.0) + 1.0) * 0.5; // 0..1 // Optional pellets int pelletX = ((int)(time * 6.0)) % 20; bool pellet = (x == pelletX && y == 10); if (isPacmanPixel(x, y, cx, cy, r, mouth)) { return NeoPixel.Color(255, 220, 0); // Pac-Man yellow } if (pellet) { return NeoPixel.Color(200, 200, 200); // pellet } return NeoPixel.Color(0, 0, 0); } uint32_t coolEffect(uint8_t x, uint8_t y, float t) { // Center of the grid float cx = 9.5; float cy = 9.5; float time = 0; time = t / 100.0; // Distance from center float dx = x - cx; float dy = y - cy; float dist = sqrt(dx * dx + dy * dy); // Animated wave float wave = sinPreCalc(dist * 0.6 - time * 0.15); // Rainbow hue shifts over time and space float hue = dist * 20 + time * 3; // Breathing brightness float brightness = (sinPreCalc(time * 0.08) + 1.0) * 0.5; // 0..1 // Convert HSV → RGB (simple version) uint8_t r, g, b; float h = fmod(hue, 360.0); int i = int(h / 60.0) % 6; float f = (h / 60.0) - i; float v = brightness * (0.5 + 0.5 * wave); if (v < 0) v = 0; if (v > 1) v = 1; float p = 0; float q = v * (1 - f); float s = v * f; float R, G, B; switch (i) { case 0: R = v; G = s; B = p; break; case 1: R = q; G = v; B = p; break; case 2: R = p; G = v; B = s; break; case 3: R = p; G = q; B = v; break; case 4: R = s; G = p; B = v; break; default: R = v; G = p; B = q; break; } r = (uint8_t)(R * 255); g = (uint8_t)(G * 255); b = (uint8_t)(B * 255); return NeoPixel.Color(r, g, b); }