fire-pixel/main.go

167 lines
3.8 KiB
Go
Raw Normal View History

2026-02-08 16:39:20 +00:00
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),
)
}
}