166 lines
3.8 KiB
Go
166 lines
3.8 KiB
Go
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),
|
|
)
|
|
}
|
|
}
|