🎲
Ebitengineでカオス曲線描いてみた
タイトルの通り、Ebitengineでカオス曲線を描画してみました。
以下の動画では、HTMLのCanvasにカオス曲線を描いています。Ebitengineで書き換えました。
動画内で紹介されている記事も載せておきます。
遊び方
localで遊ぶ
git clone https://github.com/hamao0820/chaos.git
cd chaos
go run main.go
ブラウザで遊ぶ
使い方
基本的に生成されたカオス曲線を眺めるだけです。
Enterキーを押すことで、新しいカオス曲線を生成することができます。
コード
図形の表示に関しては次のコードを参考にしました。
動画では、ステップごとにCanvasに描画していましたが、先に全ての点を計算しておいて、offscreenPix []byte
で全てのピクセル情報を管理し、offscreen *ebiten.Image
にWritePixels
メソッドで画像を生成します。その後, Update
メソッド内では、screen.DrawImage(offsetscree, nil)
で画像全体を描画しました。
コード全文
main.go
package main
import (
"fmt"
"image/color"
"log"
"math"
"math/rand"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
const (
screenWidth = 640
screenHeight = 640
plotCount = int(1e6)
)
var (
a, b, c, d float32
countMap [screenHeight + 1][screenWidth + 1]int
)
func init() {
a = rand.Float32()*6 - 3
b = rand.Float32()*6 - 3
c = rand.Float32()*6 - 3
d = rand.Float32()*6 - 3
}
type Game struct {
offscreen *ebiten.Image
offscreenPix []byte
}
func NewGame() *Game {
g := &Game{
offscreen: ebiten.NewImage(screenWidth, screenHeight),
offscreenPix: make([]byte, screenWidth*screenHeight*4),
}
g.updateOffscreen()
return g
}
func (g *Game) updateOffscreen() {
var x, y float32
for i := 0; i < plotCount; i++ {
nx := math.Sin(float64(a)*float64(y)) - math.Cos(float64(b)*float64(x))
ny := math.Sin(float64(c)*float64(x)) - math.Cos(float64(d)*float64(y))
px := math.Floor(float64((nx + 2) / 4 * screenWidth))
py := math.Floor(float64((ny + 2) / 4 * screenHeight))
if px < 0 || px >= screenWidth || py < 0 || py >= screenHeight {
continue
}
countMap[int(py)][int(px)]++
x = float32(nx)
y = float32(ny)
}
for y := 0; y <= screenHeight; y++ {
for x := 0; x <= screenWidth; x++ {
if countMap[y][x] == 0 {
continue
}
ratio := float64(countMap[y][x]) / float64(plotCount) * screenWidth
if ratio > 1 {
ratio = 0.99
}
k := 1 - math.Pow(1-ratio, 50)
off := y*screenWidth*4 + x*4
g.offscreenPix[off] = 0xff
g.offscreenPix[off+1] = byte(0xff * k)
g.offscreenPix[off+2] = 0xff
g.offscreenPix[off+3] = byte(0xff * k)
}
}
g.offscreen.WritePixels(g.offscreenPix)
}
func (g *Game) Update() error {
switch {
case inpututil.IsKeyJustPressed(ebiten.KeyEnter):
a = rand.Float32()*6 - 3
b = rand.Float32()*6 - 3
c = rand.Float32()*6 - 3
d = rand.Float32()*6 - 3
g.offscreen.Dispose()
g.offscreen.Fill(color.Black)
g.offscreen = ebiten.NewImage(screenWidth, screenHeight)
g.offscreenPix = make([]byte, screenWidth*screenHeight*4)
countMap = [screenHeight + 1][screenWidth + 1]int{}
g.updateOffscreen()
}
return nil
}
func (g *Game) Draw(screen *ebiten.Image) {
screen.DrawImage(g.offscreen, nil)
ebitenutil.DebugPrint(screen, "Press Enter to generate a new image")
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("a: %f, b: %f, c: %f, d: %f", a, b, c, d), 0, 20)
}
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
return screenWidth, screenHeight
}
func main() {
ebiten.SetWindowSize(screenWidth, screenHeight)
ebiten.SetWindowTitle("Mandelbrot (Ebitengine Demo)")
if err := ebiten.RunGame(NewGame()); err != nil {
log.Fatal(err)
}
}
例
上手くいくと、このような綺麗なカオス曲線が生成されます。
初期値によっては、次のように収束してしまうこともあるので、何回か試してみてください。
まとめ
始めてEbitengineを使ってみました。
サンプルは豊富なので、それらを参考にしながら少しずつ勉強していこうと思います。
Discussion