package main import ( "github.com/firefly-zero/firefly-go/firefly" "github.com/orsinium-labs/tinymath" ) const ( screenW = 240 screenH = 160 pixSize = 3 projDist = 10000.0 scale = 0.45 rotSpeed = 0.0015 ) // Custom palette for the ninja sprite (cyan gradient + accents) var paletteRGB = [16][3]uint8{ {0x00, 0xBC, 0xD5}, // 1: darkest cyan {0x15, 0xC3, 0xD8}, // 2: dark cyan {0x30, 0xC9, 0xDC}, // 3: medium-dark cyan {0x50, 0xD1, 0xE1}, // 4: medium cyan {0x70, 0xD8, 0xE5}, // 5: medium-light cyan {0x90, 0xE0, 0xEA}, // 6: light cyan {0xA8, 0xE5, 0xED}, // 7: pale cyan {0xC0, 0xEB, 0xF0}, // 8: very pale cyan {0xD8, 0xF1, 0xF4}, // 9: ice cyan {0xE8, 0xF5, 0xF6}, // 10: near-white cyan {0xF2, 0xF7, 0xF8}, // 11: almost white {0xF9, 0xF9, 0xF9}, // 12: white {0xFF, 0xFB, 0xFA}, // 13: warm white {0xF0, 0xC1, 0xD8}, // 14: pink accent {0xB0, 0xC0, 0xD7}, // 15: blue-gray {0xC0, 0xC1, 0xD7}, // 16: light blue-gray } type pixel3D struct { x, y, z float32 color firefly.Color screenX int screenY int screenZ float32 pixScale float32 } var ( pixels [numPixels]pixel3D angleX float32 angleY float32 ) func init() { firefly.Boot = boot firefly.Update = update firefly.Render = render } func boot() { // Set custom palette var pal [16]firefly.RGB for i := 0; i < 16; i++ { pal[i] = firefly.NewRGB(paletteRGB[i][0], paletteRGB[i][1], paletteRGB[i][2]) } firefly.SetPalette(pal) // Initialize pixels with coordinates, random Z, and nearest palette color for i := 0; i < numPixels; i++ { pixels[i].x = float32(imgCoord[i][0]) * scale pixels[i].y = float32(imgCoord[i][1]) * scale // Random Z between -45 and 45 (scaled from original -100..100) rnd := firefly.GetRandom() pixels[i].z = (float32(rnd%2000) - 1000.0) / 1000.0 * 45.0 pixels[i].color = nearestPaletteColor(imgColorRGB[i][0], imgColorRGB[i][1], imgColorRGB[i][2]) } } func nearestPaletteColor(r, g, b uint8) firefly.Color { bestDist := int32(1 << 30) bestIdx := firefly.Color(1) for i := 0; i < 16; i++ { dr := int32(r) - int32(paletteRGB[i][0]) dg := int32(g) - int32(paletteRGB[i][1]) db := int32(b) - int32(paletteRGB[i][2]) dist := dr*dr + dg*dg + db*db if dist < bestDist { bestDist = dist bestIdx = firefly.Color(i + 1) } } return bestIdx } func update() { pad, _ := firefly.ReadPad(firefly.Combined) // Map pad position (-1000..1000) to rotation angle angleX += float32(pad.X) * rotSpeed angleY += float32(pad.Y) * rotSpeed // Project all pixels to 2D radX := angleX * (tinymath.Pi / 180.0) radY := angleY * (tinymath.Pi / 180.0) sinX, cosX := tinymath.SinCos(radX) sinY, cosY := tinymath.SinCos(radY) for i := 0; i < numPixels; i++ { px := pixels[i].x py := pixels[i].y pz := pixels[i].z // Rotate around Y axis (horizontal mouse/pad movement) rx := px*cosX - pz*sinX rz1 := px*sinX + pz*cosX // Rotate around X axis (vertical mouse/pad movement) ry := py*cosY - rz1*sinY rz := py*sinY + rz1*cosY // Perspective projection d := projDist + rz if d < 100 { d = 100 } sx := projDist * rx / d sy := projDist * ry / d // Scale factor based on depth s := 1.0 + (pixels[i].z-rz)*0.004 if s < 0.3 { s = 0.3 } if s > 2.5 { s = 2.5 } pixels[i].screenX = int(sx) + screenW/2 pixels[i].screenY = int(sy) + screenH/2 pixels[i].screenZ = rz pixels[i].pixScale = s } } func render() { firefly.ClearScreen(firefly.ColorWhite) // Draw all pixels as small rectangles for i := 0; i < numPixels; i++ { p := &pixels[i] sz := int(float32(pixSize) * p.pixScale) if sz < 1 { sz = 1 } x := p.screenX - sz/2 y := p.screenY - sz/2 // Skip if off-screen if x+sz < 0 || x >= screenW || y+sz < 0 || y >= screenH { continue } firefly.DrawRect( firefly.Point{X: x, Y: y}, firefly.Size{W: sz, H: sz}, firefly.Solid(p.color), ) } }