242 lines
5.9 KiB
Go
242 lines
5.9 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"math"
|
||
|
|
||
|
"syscall/js"
|
||
|
)
|
||
|
|
||
|
type selection struct {
|
||
|
startX int
|
||
|
startY int
|
||
|
endX int
|
||
|
endY int
|
||
|
}
|
||
|
|
||
|
type mbRender struct {
|
||
|
doc js.Value
|
||
|
ctx js.Value
|
||
|
|
||
|
backCtx []js.Value
|
||
|
curCtx int
|
||
|
|
||
|
width float64
|
||
|
height float64
|
||
|
minX float64
|
||
|
minY float64
|
||
|
maxX float64
|
||
|
maxY float64
|
||
|
|
||
|
colorMode string
|
||
|
|
||
|
imageData js.Value
|
||
|
mandelbrot chan Mandelbrot
|
||
|
mandelbrotValue Mandelbrot
|
||
|
mandelbrotProgress chan float64
|
||
|
mandelbrotProgressValue float64
|
||
|
|
||
|
mouseDown bool
|
||
|
hasSelection bool
|
||
|
selection selection
|
||
|
}
|
||
|
|
||
|
type Mandelbrot []byte
|
||
|
|
||
|
func (mb *mbRender) getMandelbrot(maxIterations uint) {
|
||
|
go func() {
|
||
|
mb.mandelbrot <- mb.CalculateMandelbrot(maxIterations)
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
func (mb *mbRender) start() {
|
||
|
mb.doc = js.Global().Get("document")
|
||
|
canvasEl := mb.doc.Call("getElementById", "canvas")
|
||
|
|
||
|
//mb.width = mb.doc.Get("body").Get("clientWidth").Float()
|
||
|
//mb.height = mb.doc.Get("body").Get("clientHeight").Float()
|
||
|
mb.width = 1000.0
|
||
|
mb.height = 1000.0
|
||
|
|
||
|
mb.minX = -2.0
|
||
|
mb.minY = -2.0
|
||
|
mb.maxX = 2
|
||
|
mb.maxY = 2
|
||
|
|
||
|
canvasEl.Set("width", mb.width)
|
||
|
canvasEl.Set("height", mb.height)
|
||
|
|
||
|
mb.ctx = canvasEl.Call("getContext", "2d")
|
||
|
mb.imageData = mb.ctx.Call("getImageData", 0, 0, mb.width, mb.height)
|
||
|
mb.mandelbrot = make(chan Mandelbrot)
|
||
|
mb.getMandelbrot(1000)
|
||
|
|
||
|
mouseDownEvt := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||
|
evt := args[0]
|
||
|
mb.mouseDown = true
|
||
|
mb.hasSelection = true
|
||
|
mb.selection = selection{
|
||
|
startX: evt.Get("clientX").Int() - canvasEl.Get("offsetLeft").Int(),
|
||
|
startY: evt.Get("clientY").Int() - canvasEl.Get("offsetTop").Int(),
|
||
|
endX: evt.Get("clientX").Int() - canvasEl.Get("offsetLeft").Int(),
|
||
|
endY: evt.Get("clientY").Int() - canvasEl.Get("offsetTop").Int(),
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
defer mouseDownEvt.Release()
|
||
|
|
||
|
mouseMoveEvt := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||
|
evt := args[0]
|
||
|
if mb.mouseDown {
|
||
|
posX := evt.Get("clientX").Int() - canvasEl.Get("offsetLeft").Int()
|
||
|
posY := evt.Get("clientY").Int() - canvasEl.Get("offsetTop").Int()
|
||
|
dx := posX - mb.selection.startX
|
||
|
dy := posY - mb.selection.startY
|
||
|
if math.Abs(float64(dx)) > math.Abs(float64(dy)) {
|
||
|
mb.selection.endX = mb.selection.startX + dx
|
||
|
mb.selection.endY = mb.selection.startY + dx
|
||
|
} else {
|
||
|
mb.selection.endX = mb.selection.startX + dy
|
||
|
mb.selection.endY = mb.selection.startY + dy
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
})
|
||
|
defer mouseMoveEvt.Release()
|
||
|
|
||
|
mouseUpEvt := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||
|
mb.mouseDown = false
|
||
|
mb.hasSelection = false
|
||
|
|
||
|
newMinX := CalcReal(float64(mb.selection.startX), mb.width, mb.minX, mb.maxX)
|
||
|
newMinY := CalcReal(float64(mb.selection.startY), mb.height, mb.minY, mb.maxY)
|
||
|
newMaxX := CalcReal(float64(mb.selection.endX), mb.width, mb.minX, mb.maxX)
|
||
|
newMaxY := CalcReal(float64(mb.selection.endY), mb.height, mb.minY, mb.maxY)
|
||
|
|
||
|
mb.minX = newMinX
|
||
|
mb.minY = newMinY
|
||
|
mb.maxX = newMaxX
|
||
|
mb.maxY = newMaxY
|
||
|
|
||
|
mb.getMandelbrot(uint(math.Min(1000*1/math.Sqrt((mb.maxX-mb.minX)), 100000)))
|
||
|
|
||
|
return nil
|
||
|
})
|
||
|
defer mouseUpEvt.Release()
|
||
|
|
||
|
canvasEl.Call("addEventListener", "mousedown", mouseDownEvt)
|
||
|
canvasEl.Call("addEventListener", "mousemove", mouseMoveEvt)
|
||
|
canvasEl.Call("addEventListener", "mouseup", mouseUpEvt)
|
||
|
|
||
|
var renderFrame js.Func
|
||
|
|
||
|
renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||
|
// mb.ctx.Call("clearRect", 0, 0, mb.width, mb.height)
|
||
|
|
||
|
select {
|
||
|
case x, ok := <-mb.mandelbrot:
|
||
|
if ok {
|
||
|
mb.mandelbrotValue = x
|
||
|
}
|
||
|
default:
|
||
|
}
|
||
|
select {
|
||
|
case x, ok := <-mb.mandelbrotProgress:
|
||
|
if ok {
|
||
|
mb.mandelbrotProgressValue = x
|
||
|
}
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
value := js.Global().Get("Uint8Array").New(len(mb.mandelbrotValue))
|
||
|
js.CopyBytesToJS(value, mb.mandelbrotValue)
|
||
|
mb.imageData.Get("data").Call("set", value)
|
||
|
mb.ctx.Call("putImageData", mb.imageData, 0, 0)
|
||
|
|
||
|
if mb.hasSelection {
|
||
|
mb.ctx.Set("fillStyle", "rgba(0, 0, 255, 0.8)")
|
||
|
mb.ctx.Call("fillRect", mb.selection.startX, mb.selection.startY, mb.selection.endX-mb.selection.startX, mb.selection.endY-mb.selection.startY)
|
||
|
}
|
||
|
|
||
|
js.Global().Call("requestAnimationFrame", renderFrame)
|
||
|
return nil
|
||
|
})
|
||
|
|
||
|
done := make(chan struct{}, 0)
|
||
|
|
||
|
js.Global().Call("requestAnimationFrame", renderFrame)
|
||
|
<-done
|
||
|
}
|
||
|
|
||
|
func (m Mandelbrot) SetPixel(width int, x int, y int, color []byte) {
|
||
|
start := y*int(width)*4 + x*4
|
||
|
for i := 0; i < 4; i++ {
|
||
|
m[start+i] = color[i]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (mb mbRender) GetColor(iteration uint, maxIterations uint) []byte {
|
||
|
|
||
|
intensity := (math.Pi / 2.0) * float64(iteration) / float64(maxIterations)
|
||
|
|
||
|
return []byte{
|
||
|
byte(math.Sin(intensity) * 256),
|
||
|
byte(math.Sin(intensity*2) * 256),
|
||
|
byte(math.Cos(intensity) * 256),
|
||
|
255,
|
||
|
}
|
||
|
|
||
|
// HSL - red
|
||
|
/*c := colorful.Hsl(float64(iteration)/256, 1.0, float64(iteration)/(float64(iteration)+8.0))
|
||
|
return []byte{
|
||
|
byte(c.R * 255),
|
||
|
byte(c.G * 255),
|
||
|
byte(c.B * 255),
|
||
|
255,
|
||
|
}*/
|
||
|
}
|
||
|
|
||
|
func CalcReal(x, width, minX, maxX float64) float64 {
|
||
|
return (x*(maxX-minX))/width + minX
|
||
|
}
|
||
|
func CalcImaginary(y, height, minY, maxY float64) float64 {
|
||
|
return (y*(maxY-minY))/height + minY
|
||
|
}
|
||
|
|
||
|
func (mb *mbRender) CalculateMandelbrot(maxIterations uint) Mandelbrot {
|
||
|
fmt.Println("max iterations", maxIterations)
|
||
|
mandelbrot := make(Mandelbrot, int(mb.width*mb.height*4))
|
||
|
mb.mandelbrotProgress = make(chan float64)
|
||
|
|
||
|
for row := 0; row < int(mb.height); row++ {
|
||
|
for col := 0; col < int(mb.width); col++ {
|
||
|
cRe := CalcReal(float64(col), mb.width, mb.minX, mb.maxX)
|
||
|
cIm := CalcImaginary(float64(row), mb.height, mb.minY, mb.maxY)
|
||
|
|
||
|
var x float64
|
||
|
var y float64
|
||
|
|
||
|
var iteration uint
|
||
|
for ; x*x+y*y <= 4 && iteration < maxIterations; iteration++ {
|
||
|
newX := x*x - y*y + cRe
|
||
|
y = 2*x*y + cIm
|
||
|
x = newX
|
||
|
}
|
||
|
|
||
|
mandelbrot.SetPixel(int(mb.width), col, row, mb.GetColor(iteration, maxIterations))
|
||
|
}
|
||
|
go func() {
|
||
|
mb.mandelbrotProgress <- float64(row) / mb.height
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
return mandelbrot
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
fmt.Println("Hallo fögel")
|
||
|
|
||
|
mb := mbRender{}
|
||
|
mb.start()
|
||
|
}
|