// Package main b.go - GOOS=js GOARCH=wasm go build b.go
package main
import (
"fmt"
"math"
"math/rand"
"reflect"
"runtime"
"strings"
"sync"
"syscall/js"
"time"
"unsafe"
"github.com/go-gl/mathgl/mgl32"
)
var glTypes GLTypes
var mobileversion string
var linuxversion string
var wg sync.WaitGroup
func generateSphereVertices(radius float32, stacks, slices int) ([]float32, []uint16) {
var vertices []float32
var indices []uint16
for i := 0; i <= stacks; i++ {
phi := float32(i) * float32(math.Pi) / float32(stacks)
for j := 0; j <= slices; j++ {
theta := float32(j) * 2.0 * float32(math.Pi) / float32(slices)
x := radius * float32(math.Sin(float64(phi))) * float32(math.Cos(float64(theta)))
y := radius * float32(math.Sin(float64(phi))) * float32(math.Sin(float64(theta)))
z := radius * float32(math.Cos(float64(phi)))
vertices = append(vertices, x, y, z)
}
}
for i := 0; i < stacks; i++ {
for j := 0; j <= slices; j++ {
indices = append(indices, uint16(i*(slices+1)+j), uint16((i+1)*(slices+1)+j))
}
}
return vertices, indices
}
/*
func randomRotationMatrix() mgl32.Mat4 {
rand.Seed(int64(rand.Uint32())) // Initialize the random number generator
return mgl32.HomogRotate3D(
mgl32.DegToRad(rand.Float32() * 360),
mgl32.Vec3{rand.Float32(), rand.Float32(), rand.Float32()}.Normalize(),
)
}
*/
func randomRotationMatrix() mgl32.Mat4 {
return mgl32.HomogRotate3D(mgl32.DegToRad(rand.Float32()*360.0), mgl32.Vec3{rand.Float32(), rand.Float32(), rand.Float32()}.Normalize()) //nolint
}
func prependChild(newElement, parent js.Value) {
firstChild := parent.Get("firstChild")
if firstChild.IsNull() {
parent.Call("appendChild", newElement)
} else {
parent.Call("insertBefore", newElement, firstChild)
}
}
func stopApplication(_ js.Value, _ []js.Value) interface{} {
doc := js.Global().Get("document")
bh := doc.Call("getElementById", "middletd")
if bh.Truthy() {
bh.Set("innerHTML", " ")
}
/*
speedSliderZoomValue.Set("innerHTML", float32(10000))
currentZoom = float32(10000)
render.SetZoom(float32(10000))
footer.Set("innerHTML", originalHTML)
js.Global().Call("setTimeout", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
close(done)
done <- struct{}{}
return nil
}), time.Duration(5*time.Second).Milliseconds())
*/
return nil
}
func main() {
time.Sleep(time.Second)
time.Sleep(time.Second)
time.Sleep(time.Second)
time.Sleep(time.Second)
time.Sleep(time.Second)
doc := js.Global().Get("document")
/*
bh := doc.Call("getElementById", "middletd")
if bh.Truthy() {
bh.Set("innerHTML", "<button type='button' id='stop'>Stop Rendering</button>")
}
stopButtonCallback := js.FuncOf(stopApplication)
stopButton := doc.Call("getElementById", "stop")
stopButton.Call("addEventListener", "click", stopButtonCallback)
defer stopButtonCallback.Release()
*/
canvasEl := doc.Call("getElementById", "gocanvas")
width := doc.Get("body").Get("clientWidth").Int()
height := doc.Get("body").Get("clientHeight").Int()
canvasEl.Set("width", width)
canvasEl.Set("height", height)
ismobile := strings.Contains(strings.ToLower(js.Global().Get("navigator").Get("userAgent").String()), "mobile")
islinux := strings.Contains(strings.ToLower(js.Global().Get("navigator").Get("userAgent").String()), "linux")
if !ismobile && islinux && !strings.Contains(strings.ToLower(js.Global().Get("navigator").Get("userAgent").String()), "firefox") {
wg.Add(1)
logoimg := js.Global().Get("document").Call("getElementById", "logo")
if !logoimg.IsNull() {
logoimg.Call("remove")
}
var htmllogo string
htmllogo = "logolarge.html"
if ismobile {
htmllogo = "mobilelogo.html"
}
response := js.Global().Call("fetch", htmllogo)
promise := response.Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
if p[0].Get("ok").Bool() {
return p[0].Call("text")
}
return "Error fetching ASCII art"
}))
promise.Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
asciiArt := p[0].String()
doc := js.Global().Get("document")
// body := doc.Get("body")
htmlanimationdiv := doc.Call("getElementById", "htmlanimation")
//if ismobile {
// htmlanimationdiv.Set("innerHTML", "<pre style='margin: 0; padding: 0; font-size: 1px;'>"+asciiArt+"</pre>")
// return nil
//}
var lines []string
if !ismobile && islinux {
lines = strings.Split(asciiArt, "\n")
}
div1 := doc.Call("createElement", "div")
div1.Get("style").Set("margin", "0")
div1.Get("style").Set("padding", "0")
div2 := doc.Call("createElement", "div")
div2.Get("style").Set("margin", "0")
div2.Get("style").Set("padding", "0")
htmlanimationdiv.Call("appendChild", div1)
htmlanimationdiv.Call("appendChild", div2)
if !ismobile && islinux {
index := 0
index1 := len(lines) - 1
// index := (len(lines) - 1)/2 // Start from the last line
// index1 := index - 1
// Define a recursive function to append lines with delay
var appendLineWithDelay func()
appendLineWithDelay = func() {
if index1 >= 0 && index < len(lines) && index < index1 {
// if index1 >= 0 && index < len(lines) {
pre1 := doc.Call("createElement", "pre")
//if ismobile {
// pre1.Get("style").Set("font-size", "3px")
//} else {
pre1.Get("style").Set("font-size", "1px")
//}
pre1.Get("style").Set("text-align", "center")
pre1.Get("style").Set("margin", "0")
pre1.Get("style").Set("padding", "0")
// pre1.Set("innerHTML", lines[index1]+"\n")
pre1.Set("innerHTML", lines[index]+"\n")
pre2 := doc.Call("createElement", "pre")
//if ismobile {
// pre2.Get("style").Set("font-size", "3px")
//} else {
pre2.Get("style").Set("font-size", "1px")
//}
pre2.Get("style").Set("text-align", "center")
pre2.Get("style").Set("margin", "0")
pre2.Get("style").Set("padding", "0")
// pre2.Set("innerHTML", lines[index]+"\n")
pre2.Set("innerHTML", lines[index1]+"\n")
// div2.Call("appendChild", pre2)
// prependChild(pre1, div1)
div1.Call("appendChild", pre1)
prependChild(pre2, div2)
index++
index1--
js.Global().Call("setTimeout", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
appendLineWithDelay()
return nil
}), 0)
} else {
wg.Done()
}
}
appendLineWithDelay()
} else {
htmlanimationdiv.Set("innerHTML", "<pre style='margin: 0; padding: 0; font-size: 1px;'>"+asciiArt+"</pre>")
}
return nil
}))
wg.Wait()
htmlanimationdiv := doc.Call("getElementById", "htmlanimation")
htmlanimationdiv.Get("style").Set("margin", "0")
htmlanimationdiv.Get("style").Set("position", "absolute")
htmlanimationdiv.Get("style").Set("top", "50%")
htmlanimationdiv.Get("style").Set("-ms-transform", "translateY(-50%)")
htmlanimationdiv.Get("style").Set("transform", "translate(50%, -50%)")
htmlanimationdiv.Get("style").Set("z-index", "-1")
}
time.Sleep(time.Second)
gl := canvasEl.Call("getContext", "webgl")
if gl.IsUndefined() {
gl = canvasEl.Call("getContext", "experimental-webgl")
}
if gl.IsUndefined() {
js.Global().Call("alert", "browser might not support webgl")
return
}
glTypes.New(gl)
fragShaderCode := `
precision mediump float;
uniform vec3 uBaseColor; // Color value at the base
uniform vec3 uTopColor; // Color value at the top
varying vec3 vPosition; // Interpolated vertex position
void main(void) {
float t = (vPosition.y + 1.0) * 0.5; // Normalize the y-coordinate to [0, 1]
vec3 rainbowColor = mix(uBaseColor, uTopColor, t);
gl_FragColor = vec4(rainbowColor, 1.0);
}
`
vertShaderCode := `
attribute vec3 position;
uniform mat4 Pmatrix;
uniform mat4 Vmatrix;
uniform mat4 Mmatrix;
varying vec3 vPosition; // Pass vertex position to fragment shader
void main(void) {
gl_Position = Pmatrix * Vmatrix * Mmatrix * vec4(position, 1.0);
vPosition = position; // Pass vertex position to fragment shader
}
`
radius := float32(1.0)
stacks := 30
slices := 30
vertices, _ := generateSphereVertices(radius, stacks, slices)
var verticesTyped = SliceToTypedArray(vertices)
vertexBuffer := gl.Call("createBuffer")
gl.Call("bindBuffer", glTypes.ArrayBuffer, vertexBuffer)
gl.Call("bufferData", glTypes.ArrayBuffer, verticesTyped, glTypes.StaticDraw)
vertShader := gl.Call("createShader", glTypes.VertexShader)
gl.Call("shaderSource", vertShader, vertShaderCode)
gl.Call("compileShader", vertShader)
fragShader := gl.Call("createShader", glTypes.FragmentShader)
gl.Call("shaderSource", fragShader, fragShaderCode)
gl.Call("compileShader", fragShader)
shaderProgram := gl.Call("createProgram")
gl.Call("attachShader", shaderProgram, vertShader)
gl.Call("attachShader", shaderProgram, fragShader)
gl.Call("linkProgram", shaderProgram)
gl.Call("bindBuffer", glTypes.ArrayBuffer, vertexBuffer)
position := gl.Call("getAttribLocation", shaderProgram, "position")
gl.Call("vertexAttribPointer", position, 3, glTypes.Float, false, 0, 0)
gl.Call("enableVertexAttribArray", position)
gl.Call("useProgram", shaderProgram)
uBaseColor := gl.Call("getUniformLocation", shaderProgram, "uBaseColor")
uTopColor := gl.Call("getUniformLocation", shaderProgram, "uTopColor")
uColor := gl.Call("getUniformLocation", shaderProgram, "uColor")
gl.Call("uniform3f", uBaseColor, 1.0, 0.0, 0.0)
gl.Call("uniform3f", uTopColor, 0.0, 0.0, 1.0)
gl.Call("uniform3f", uColor, 1.0, 1.0, 1.0)
gl.Call("clearColor", 0, 0, 0, 0)
gl.Call("clearDepth", 1.0)
gl.Call("viewport", 0, 0, width, height)
gl.Call("depthFunc", glTypes.LEqual)
projMatrix := mgl32.Perspective(mgl32.DegToRad(45.0), float32(width)/float32(height), 1, 100.0)
projMatrixBuffer := (*[16]float32)(unsafe.Pointer(&projMatrix)) // nolint
typedProjMatrixBuffer := SliceToTypedArray([]float32((*projMatrixBuffer)[:]))
gl.Call("uniformMatrix4fv", gl.Call("getUniformLocation", shaderProgram, "Pmatrix"), false, typedProjMatrixBuffer)
viewMatrix := mgl32.LookAtV(mgl32.Vec3{3.0, 3.0, 3.0}, mgl32.Vec3{0.0, 0.0, 0.0}, mgl32.Vec3{0.0, 1.0, 0.0})
viewMatrixBuffer := (*[16]float32)(unsafe.Pointer(&viewMatrix)) // nolint
typedViewMatrixBuffer := SliceToTypedArray([]float32((*viewMatrixBuffer)[:]))
gl.Call("uniformMatrix4fv", gl.Call("getUniformLocation", shaderProgram, "Vmatrix"), false, typedViewMatrixBuffer)
movMatrix := randomRotationMatrix()
var renderFrame js.Func
var tmark float32
var rotation = float32(0)
renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if tmark == 0 {
tmark = float32(args[0].Float())
rotation = rand.Float32() * 360.0 // nolint
}
now := float32(args[0].Float())
tdiff := now - tmark
tmark = now
rotation = rotation + float32(tdiff)/5000
movMatrix = mgl32.HomogRotate3DX(0.5 * rotation)
movMatrix = movMatrix.Mul4(mgl32.HomogRotate3DY(0.3 * rotation))
movMatrix = movMatrix.Mul4(mgl32.HomogRotate3DZ(0.2 * rotation))
modelMatrixBuffer := (*[16]float32)(unsafe.Pointer(&movMatrix)) // nolint
typedModelMatrixBuffer := SliceToTypedArray([]float32((*modelMatrixBuffer)[:]))
gl.Call("uniformMatrix4fv", gl.Call("getUniformLocation", shaderProgram, "Mmatrix"), false, typedModelMatrixBuffer)
gl.Call("enable", glTypes.DepthTest)
gl.Call("clear", glTypes.ColorBufferBit)
gl.Call("clear", glTypes.DepthBufferBit)
indices := []uint16{}
for i := 0; i < stacks; i++ {
for j := 0; j <= slices; j++ {
indices = append(indices, uint16(i*(slices+1)+j), uint16((i+1)*(slices+1)+j))
}
}
indicesTyped := SliceToTypedArray(indices)
indexBuffer := gl.Call("createBuffer")
gl.Call("bindBuffer", glTypes.ElementArrayBuffer, indexBuffer)
gl.Call("bufferData", glTypes.ElementArrayBuffer, indicesTyped, glTypes.StaticDraw)
gl.Call("drawElements", glTypes.Line, len(indices), glTypes.UnsignedShort, 0)
gl.Call("drawArrays", glTypes.LineLoop, 0, len(vertices)/3)
js.Global().Call("requestAnimationFrame", renderFrame)
return nil
})
defer renderFrame.Release()
// <div id='anchor'></div>"
// // Calculate the position of the anchor element
// anchorEl := doc.Call("getElementById", "anchor")
// anchorRect := anchorEl.Call("getBoundingClientRect")
// anchorX := anchorRect.Get("left").Float() + anchorRect.Get("width").Float()/2
// anchorY := anchorRect.Get("top").Float() + anchorRect.Get("height").Float()/2
// // Adjust the position of the animation container based on the anchor position
// gocanvasContainer := doc.Call("getElementById", "gocanvas-container")
// gocanvasContainer.Get("style").Set("left", fmt.Sprintf("%fpx", anchorX))
// gocanvasContainer.Get("style").Set("top", fmt.Sprintf("%fpx", anchorY))
js.Global().Call("requestAnimationFrame", renderFrame)
done := make(chan struct{})
<-done
}
// GLTypes provides WebGL bindings.
type GLTypes struct {
StaticDraw js.Value
ArrayBuffer js.Value
ElementArrayBuffer js.Value
VertexShader js.Value
FragmentShader js.Value
Float js.Value
DepthTest js.Value
ColorBufferBit js.Value
DepthBufferBit js.Value
Triangles js.Value
UnsignedShort js.Value
LEqual js.Value
LineLoop js.Value
Line js.Value
}
// New returns new webgl bindings
func (types *GLTypes) New(gl js.Value) {
types.StaticDraw = gl.Get("STATIC_DRAW")
types.ArrayBuffer = gl.Get("ARRAY_BUFFER")
types.ElementArrayBuffer = gl.Get("ELEMENT_ARRAY_BUFFER")
types.VertexShader = gl.Get("VERTEX_SHADER")
types.FragmentShader = gl.Get("FRAGMENT_SHADER")
types.Float = gl.Get("FLOAT")
types.DepthTest = gl.Get("DEPTH_TEST")
types.ColorBufferBit = gl.Get("COLOR_BUFFER_BIT")
types.Triangles = gl.Get("TRIANGLES")
types.UnsignedShort = gl.Get("UNSIGNED_SHORT")
types.LEqual = gl.Get("LEQUAL")
types.DepthBufferBit = gl.Get("DEPTH_BUFFER_BIT")
types.LineLoop = gl.Get("LINE_LOOP")
types.Line = gl.Get("LINES")
}
func sliceToByteSlice(s interface{}) []byte {
switch s := s.(type) {
case []int8:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) // nolint
return *(*[]byte)(unsafe.Pointer(h)) // nolint
case []int16:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) // nolint
h.Len *= 2
h.Cap *= 2
return *(*[]byte)(unsafe.Pointer(h)) // nolint
case []int32:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) // nolint
h.Len *= 4
h.Cap *= 4
return *(*[]byte)(unsafe.Pointer(h)) // nolint
case []int64:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) // nolint
h.Len *= 8
h.Cap *= 8
return *(*[]byte)(unsafe.Pointer(h)) // nolint
case []uint8:
return s
case []uint16:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) // nolint
h.Len *= 2
h.Cap *= 2
return *(*[]byte)(unsafe.Pointer(h)) // nolint
case []uint32:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) // nolint
h.Len *= 4
h.Cap *= 4
return *(*[]byte)(unsafe.Pointer(h)) // nolint
case []uint64:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) // nolint
h.Len *= 8
h.Cap *= 8
return *(*[]byte)(unsafe.Pointer(h)) // nolint
case []float32:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) // nolint
h.Len *= 4
h.Cap *= 4
return *(*[]byte)(unsafe.Pointer(h)) // nolint
case []float64:
h := (*reflect.SliceHeader)(unsafe.Pointer(&s)) // nolint
h.Len *= 8
h.Cap *= 8
return *(*[]byte)(unsafe.Pointer(h)) // nolint
default:
panic(fmt.Sprintf("jsutil: unexpected value at sliceToBytesSlice: %T", s))
}
}
// SliceToTypedArray convert Slice To Typed Array
func SliceToTypedArray(s interface{}) js.Value {
switch s := s.(type) {
case []int8:
a := js.Global().Get("Uint8Array").New(len(s))
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Int8Array").New(buf, a.Get("byteOffset"), a.Get("byteLength"))
case []int16:
a := js.Global().Get("Uint8Array").New(len(s) * 2)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Int16Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/2)
case []int32:
a := js.Global().Get("Uint8Array").New(len(s) * 4)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Int32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4)
case []uint8:
a := js.Global().Get("Uint8Array").New(len(s))
js.CopyBytesToJS(a, s)
runtime.KeepAlive(s)
return a
case []uint16:
a := js.Global().Get("Uint8Array").New(len(s) * 2)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Uint16Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/2)
case []uint32:
a := js.Global().Get("Uint8Array").New(len(s) * 4)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Uint32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4)
case []float32:
a := js.Global().Get("Uint8Array").New(len(s) * 4)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Float32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4)
case []float64:
a := js.Global().Get("Uint8Array").New(len(s) * 8)
js.CopyBytesToJS(a, sliceToByteSlice(s))
runtime.KeepAlive(s)
buf := a.Get("buffer")
return js.Global().Get("Float64Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/8)
default:
panic(fmt.Sprintf("jsutil: unexpected value at SliceToTypedArray: %T", s))
}
}