Files
wehub-resource-sync ead81af521
Deploy to GitHub Pages / deploy (push) Failing after 0s
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

94 lines
2.4 KiB
Go

package ui
import (
"math"
"strings"
)
// renderButterfly draws a symmetric Rorschach/butterfly pattern using Braille
// dots. The spectrum bands are mirrored horizontally from the center, and
// organic variation is added via sine wobble and scatterHash to create
// ink-blot-like shapes that pulse with the music.
func (v *Visualizer) renderButterfly(bands []float64) string {
height := v.Rows
dotRows := height * 4
dotCols := PanelWidth * 2
centerX := dotCols / 2
bandCount := len(bands)
grid := make([]bool, dotRows*dotCols)
for dy := range dotRows {
// Map vertical position to a band index.
bandF := float64(dy) / float64(max(1, dotRows-1)) * float64(bandCount-1)
bi := int(bandF)
frac := bandF - float64(bi)
var energy float64
if bi >= bandCount-1 {
energy = bands[bandCount-1]
} else {
energy = bands[bi]*(1-frac) + bands[bi+1]*frac
}
// Wing width: how far from center the pattern extends.
t := float64(v.frame)*0.08 + float64(dy)*0.3
wobble := math.Sin(t) * 0.15
wingWidth := int(float64(centerX) * (energy + wobble) * 0.9)
for dx := range wingWidth {
// Distance from center normalized to wing width.
norm := float64(dx) / float64(max(1, wingWidth))
// Organic edge: denser near center, sparser at edges.
threshold := (1.0 - norm*norm) * energy
// Add frame-based flicker at the edges.
if norm > 0.6 {
threshold *= 0.5 + 0.5*math.Sin(float64(v.frame)*0.1+float64(dy)*0.5+float64(dx)*0.3)
}
if scatterHash(bi, dy, dx, v.frame/3) < threshold {
// Right wing.
rx := centerX + dx
if rx < dotCols {
grid[dy*dotCols+rx] = true
}
// Left wing (mirror).
lx := centerX - 1 - dx
if lx >= 0 {
grid[dy*dotCols+lx] = true
}
}
}
// Central spine — always drawn.
if energy > 0.05 {
grid[dy*dotCols+centerX] = true
if centerX > 0 {
grid[dy*dotCols+centerX-1] = true
}
}
}
// Render braille with row-based coloring.
lines := make([]string, height)
for row := range height {
var content strings.Builder
for ch := range PanelWidth {
var braille rune = '\u2800'
for dr := range 4 {
for dc := range 2 {
if grid[(row*4+dr)*dotCols+ch*2+dc] {
braille |= brailleBit[dr][dc]
}
}
}
content.WriteRune(braille)
}
// Color gradient from top to bottom.
norm := float64(row) / float64(max(1, height-1))
lines[row] = specWrap(norm, content.String())
}
return strings.Join(lines, "\n")
}