Files
2026-02-26 22:03:45 +01:00

878 lines
23 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "animation.h"
#include <math.h>
#include <pgmspace.h>
#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);
}