package splashmenu import ( "bytes" "cosmos/diego/groovy" _ "embed" "image" "image/color" _ "image/jpeg" _ "image/png" "log" "math" "math/rand" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/inpututil" ) const ( magicRatio = 12 maxGlitchOffset = 25 minGlitchInterval = 60 maxGlitchTimeOffset = 15 ) var ( //go:embed assets/bsoft.png mImage_png []byte imgDimensions = groovy.Area{Width: 827, Height: 628} //go:embed assets/title.png bImage_png []byte titlePosition = groovy.Area{Width: 580, Height: 490} imgToBlur *ebiten.Image bImage *ebiten.Image glitchTimer uint ) func init() { // Decode an image from the image file's byte slice. img, _, err := image.Decode(bytes.NewReader(mImage_png)) if err != nil { log.Fatal(err) } imgToBlur = ebiten.NewImageFromImage(img) // Decode an image from the image file's byte slice. img, _, err = image.Decode(bytes.NewReader(bImage_png)) if err != nil { log.Fatal(err) } bImage = ebiten.NewImageFromImage(img) glitchTimer = 15 } type Bsoft struct { Dimensions groovy.Area events map[groovy.SceneEvent]func() bgcolor color.RGBA increment int renderTarget *ebiten.Image mosaicRatio uint countDown uint } func NewBsoft() Bsoft { return Bsoft{ bgcolor: backgroundBaseColor, events: make(map[groovy.SceneEvent]func()), mosaicRatio: 16, increment: 0, } } func (b *Bsoft) Draw(screen *ebiten.Image) { b.DrawGlitchLogo(screen) b.DrawGlitchLogoText(screen) } func (b *Bsoft) DrawGlitchLogo(screen *ebiten.Image) { //summation of sinusoids with different frequencies for 'step' response on pixel-glithcy-ness var sum float64 = 0 for i := 0; i < 10; i++ { w := math.Sin(float64(b.increment) / (math.Pi * float64(i+1))) sum = sum + w } ratio := sum + magicRatio // Shrink the image once. op := &ebiten.DrawImageOptions{} op.GeoM.Scale(1/ratio, 1/ratio) b.renderTarget.DrawImage(imgToBlur, op) // Enlarge the shrunk image. // The filter is the nearest filter, so the result will be mosaic. op = &ebiten.DrawImageOptions{} op.GeoM.Scale(ratio, ratio) op.GeoM.Translate(float64(b.Dimensions.Width)/2-float64(imgDimensions.Width)/2, float64(b.Dimensions.Height)/2-float64(imgDimensions.Height)/2-35) screen.DrawImage(b.renderTarget, op) } func (b *Bsoft) DrawGlitchLogoText(screen *ebiten.Image) { //glitchy twitch title behaviour op := &ebiten.DrawImageOptions{} var glitchX int = 0 var glitchY int = 0 X := minGlitchInterval Y := maxGlitchTimeOffset //glithiness active until countdown resolved, randomize the delta offset //creates temporary glitch effect on the logo text, this is achieved as follows: // 1. if the glitch frame counter is active, glitch offset is in effect and computed // 2. otherwise, we engage the glitch after X frames have passed with a random Y frame offset if b.countDown > 0 { glitchX = rand.Intn(maxGlitchOffset) * getRandomDirection() glitchY = rand.Intn(maxGlitchOffset) * getRandomDirection() b.countDown-- } else if b.increment%(X+rand.Intn(Y)) == 0 { b.countDown = glitchTimer //set glitch timer, in frames } op.GeoM.Translate(float64(titlePosition.Width+glitchX), float64(titlePosition.Height+glitchY)) screen.DrawImage(bImage, op) } // returns +1 or -1 as an integer func getRandomDirection() int { if rand.Intn(2) == 1 { return -1 } return 1 } func (b *Bsoft) Update() error { b.increment++ var keysPressed []ebiten.Key keysPressed = inpututil.AppendPressedKeys(keysPressed[:0]) for _, k := range keysPressed { switch k { case ebiten.KeyUp: b.mosaicRatio = b.mosaicRatio + 1 case ebiten.KeyDown: if b.mosaicRatio > 1 { b.mosaicRatio = b.mosaicRatio - 1 } case ebiten.KeyQ: if b.events[groovy.COMPLETED] != nil { b.events[groovy.COMPLETED]() } default: } } return nil } // sets sene dimensions func (b *Bsoft) SetDimensions(a groovy.Area) { b.Dimensions = a b.renderTarget = ebiten.NewImage(imgDimensions.Width, imgDimensions.Height) } func (b *Bsoft) SetEventHandler(event groovy.SceneEvent, f func()) { b.events[event] = f }