// Package main stl.go - GOOS=js GOARCH=wasm go build -ldflags="-X main.stlFileName=STLFILE.stl" stl.go
package main
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"math"
"math/rand"
"reflect"
"runtime"
"strings"
"syscall/js"
"time"
"unsafe"
"github.com/go-gl/mathgl/mgl32"
"gitlab.com/russoj88/stl/stl"
)
var gl js.Value
var verticesNative = []float32{
-1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1,
-1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1,
-1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1,
1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1,
-1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1,
-1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1,
}
var colorsNative = []float32{
5, 3, 7, 5, 3, 7, 5, 3, 7, 5, 3, 7,
1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3,
0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
}
var indicesNative = []uint32{
0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7,
8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15,
16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23,
}
const vertShaderCode = `
attribute vec3 position;
uniform mat4 Pmatrix;
uniform mat4 Vmatrix;
uniform mat4 Mmatrix;
attribute vec3 color;
varying vec3 vColor;
void main(void) {
gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);
vColor = color;
}
`
const fragShaderCode = `
precision mediump float;
varying vec3 vColor;
void main(void) {
gl_FragColor = vec4(vColor, 1.);
}
`
var (
done chan struct{}
stlFileName string
originalHTML string
render Renderer
existingFooter js.Value
body js.Value
footer js.Value
speedSliderXValue js.Value
speedSliderYValue js.Value
speedSliderZValue js.Value
speedSliderZoomValue js.Value
canvasElement js.Value
currentZoom float32 = 3
)
func parseBase64File(input string) (output []byte, err error) {
searchString := "base64,"
index := strings.Index(input, searchString)
if index < 0 {
err = errors.New("Error opening file")
return
}
sBuffer := input[index+len(searchString):]
return base64.StdEncoding.DecodeString(sBuffer)
}
func uploaded(_ js.Value, args []js.Value) interface{} { // nolint
fmt.Println("Finished uploading")
result := args[0].Get("target").Get("result").String()
func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in upload", r)
js.Global().Call("alert", "Failed to parse file")
}
}()
uploadedFile, err := parseBase64File(result)
if err != nil {
panic(err)
}
stlSolid, err := NewSTL(uploadedFile)
if err != nil {
js.Global().Call("alert", "Could not parse file")
}
vert, colors, indices := stlSolid.GetModel()
modelSize := getMaxScalar(vert)
currentZoom = modelSize * 3
render.SetZoom(currentZoom)
render.SetModel(colors, vert, indices)
}()
return nil
}
func getMaxScalar(vertices []float32) float32 {
var max float32
for baseIndex := 0; baseIndex < len(vertices); baseIndex += 3 {
testScale := scalar(vertices[baseIndex], vertices[baseIndex], vertices[baseIndex])
if testScale > max {
max = testScale
}
}
return max
}
func scalar(x float32, y float32, z float32) float32 {
xy := math.Sqrt(float64(x*x + y*y))
return float32(math.Sqrt(xy*xy + float64(z*z)))
}
func main() {
time.Sleep(time.Second)
if strings.Contains(strings.ToLower(js.Global().Get("navigator").Get("userAgent").String()), "mobile") {
return
}
rawHTML := `
<datalist id="speeds">
<option>-1</option>
<option>0</option>
<option>1</option>
</datalist>
<table>
<tr>
<td>
<h2>3D Model</h2>
</td>
<td>
<p>Rotation X
<input id="speedSliderX" type="range" min="-1" max="1" step="0.01" list="speeds">
<text id="speedSliderXValue">0.0</text>
</p>
</td>
<td>
<p>Rotation Y
<input id="speedSliderY" type="range" min="-1" max="1" step="0.01" list="speeds">
<text id="speedSliderYValue">0.0</text>
</p>
</td>
<td>
<p>Rotation Z
<input id="speedSliderZ" type="range" min="-1" max="1" step="0.01" list="speeds">
<text id="speedSliderZValue">0.0</text>
</p>
</td>
<td>
<p>Zoom
<input id="speedSliderZoom" type="range" min="0" max="1000" step="1" list="speeds">
<text id="speedSliderZoomValue">0.0</text>
</p>
</td>
<td>
<p><button type="button" id="stop">Stop Rendering</button>
</p>
</td>
</tr>
</table>
`
//<canvas id='gocanvas'></canvas>
doc := js.Global().Get("document")
body = doc.Get("body")
// body.Set("innerHTML", rawHTML)
existingFooter = doc.Call("getElementsByTagName", "footer").Index(0)
if existingFooter.Truthy() {
originalHTML = existingFooter.Get("innerHTML").String()
footer = doc.Call("createElement", "footer")
footer.Set("innerHTML", originalHTML+rawHTML)
body.Call("replaceChild", footer, existingFooter)
} else {
footer = doc.Call("createElement", "footer")
footer.Set("innerHTML", rawHTML)
body.Call("appendChild", footer)
}
doc = js.Global().Get("document")
// canvasResizeCallback := js.FuncOf(canvasResize)
canvasElement = doc.Call("getElementById", "gocanvas")
// js.Global().Get("window").Call("addEventListener", "resize", canvasResizeCallback)
width := doc.Get("body").Get("clientWidth").Int()
height := doc.Get("body").Get("clientHeight").Int()
canvasElement.Set("width", width)
canvasElement.Set("height", height)
sliderSpeedXCallback := js.FuncOf(sliderChangeX)
speedSliderX := doc.Call("getElementById", "speedSliderX")
speedSliderX.Call("addEventListener", "input", sliderSpeedXCallback)
speedSliderXValue = doc.Call("getElementById", "speedSliderXValue")
sliderSpeedYCallback := js.FuncOf(sliderChangeY)
speedSliderY := doc.Call("getElementById", "speedSliderY")
speedSliderY.Call("addEventListener", "input", sliderSpeedYCallback)
speedSliderYValue = doc.Call("getElementById", "speedSliderYValue")
sliderSpeedZCallback := js.FuncOf(sliderChangeZ)
speedSliderZ := doc.Call("getElementById", "speedSliderZ")
speedSliderZ.Call("addEventListener", "input", sliderSpeedZCallback)
speedSliderZValue = doc.Call("getElementById", "speedSliderZValue")
sliderSpeedZoomCallback := js.FuncOf(sliderChangeZoom)
speedSliderZoom := doc.Call("getElementById", "speedSliderZoom")
speedSliderZoom.Call("addEventListener", "input", sliderSpeedZoomCallback)
speedSliderZoomValue = doc.Call("getElementById", "speedSliderZoomValue")
//zoomChangeCallback := js.FuncOf(zoomChange)
//js.Global().Get("window").Call("addEventListener", "wheel", zoomChangeCallback)
stopButtonCallback := js.FuncOf(stopApplication)
stopButton := doc.Call("getElementById", "stop")
stopButton.Call("addEventListener", "click", stopButtonCallback)
defer stopButtonCallback.Release()
response := js.Global().Call("fetch", "/stl/base64/"+stlFileName)
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 stereolithograph"
}))
promise.Call("then", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
result := p[0].String()
uploadedFile, _ := parseBase64File(result) // nolint
// uploadedFile, err := parseBase64File(result)
// if err != nil {
// return nil
// js.Global().Call("alert", "Could not parse fetched file")
// }
stlSolid, _ := NewSTL(uploadedFile) // nolint
// stlSolid, err := NewSTL(uploadedFile)
// if err != nil {
// return nil
// js.Global().Call("alert", "Could not parse fetched file")
// }
vert, colors, indices := stlSolid.GetModel()
modelSize := getMaxScalar(vert)
currentZoom := modelSize * 3
render.SetZoom(currentZoom)
render.SetModel(colors, vert, indices)
return nil
}))
gl = canvasElement.Call("getContext", "webgl")
if gl.IsUndefined() {
gl = canvasElement.Call("getContext", "experimental-webgl")
}
if gl.IsUndefined() {
js.Global().Call("alert", "browser might not support webgl")
return
}
config := InitialConfig{
Width: width,
Height: height,
SpeedX: 0,
SpeedY: 0,
SpeedZ: 0,
Colors: colorsNative,
Vertices: verticesNative,
Indices: indicesNative,
FragmentShaderCode: fragShaderCode,
VertexShaderCode: vertShaderCode,
}
var err error
render, err = NewRenderer(gl, config)
if err != nil {
js.Global().Call("alert", fmt.Sprintf("Cannot load webgl %v", err))
return
}
render.SetZoom(currentZoom)
defer render.Release()
x, y, z := render.GetSpeed()
speedSliderX.Set("value", fmt.Sprint(x))
speedSliderXValue.Set("innerHTML", fmt.Sprint(x))
speedSliderY.Set("value", fmt.Sprint(y))
speedSliderYValue.Set("innerHTML", fmt.Sprint(y))
speedSliderZ.Set("value", fmt.Sprint(z))
speedSliderZValue.Set("innerHTML", fmt.Sprint(z))
var renderFrame js.Func
renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
render.Render(this, args)
js.Global().Call("requestAnimationFrame", renderFrame)
return nil
})
js.Global().Call("requestAnimationFrame", renderFrame)
done := make(chan struct{})
<-done
}
func stopApplication(_ js.Value, _ []js.Value) interface{} {
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 canvasResize(_ js.Value, _ []js.Value) interface{} { // nolint
width := canvasElement.Get("clientWidth").Int()
height := canvasElement.Get("clientHeight").Int()
canvasElement.Set("width", width)
canvasElement.Set("height", height)
render.SetSize(height*2, width)
return nil
}
func sliderChangeX(this js.Value, _ []js.Value) interface{} {
var speed float32
sSpeed := this.Get("value").String()
fmt.Sscan(sSpeed, &speed) // nolint
render.SetSpeedX(speed)
speedSliderXValue.Set("innerHTML", sSpeed)
return nil
}
func sliderChangeY(this js.Value, _ []js.Value) interface{} {
var speed float32
sSpeed := this.Get("value").String()
fmt.Sscan(sSpeed, &speed) // nolint
render.SetSpeedY(speed)
speedSliderYValue.Set("innerHTML", sSpeed)
return nil
}
func sliderChangeZ(this js.Value, _ []js.Value) interface{} {
var speed float32
sSpeed := this.Get("value").String()
fmt.Sscan(sSpeed, &speed) // nolint
render.SetSpeedZ(speed)
speedSliderZValue.Set("innerHTML", sSpeed)
return nil
}
func sliderChangeZoom(this js.Value, _ []js.Value) interface{} {
var speed float32
sSpeed := this.Get("value").String()
fmt.Sscan(sSpeed, &speed) // nolint
speedSliderZoomValue.Set("innerHTML", sSpeed)
// deltaScale := 1 - (speed * 0.0001)
// currentZoom *= deltaScale
// zoomValue := this.Get("value").Float()
currentZoom = speed
render.SetZoom(currentZoom)
// speedSliderZoomValue.Set("innerHTML", fmt.Sprintf("%.2f", currentZoom))
return nil
}
func zoomChange(_ js.Value, args []js.Value) interface{} {
deltaY := args[0].Get("deltaY").Float()
deltaScale := 1 - (float32(deltaY) * 0.001)
currentZoom *= deltaScale
render.SetZoom(currentZoom)
speedSliderZoomValue.Set("innerHTML", fmt.Sprintf("%.2f", currentZoom))
return nil
}
// Model is an interface for a model
type Model interface {
GetModel() ([]float32, []float32, []uint16)
}
// NewSTL returns a new STL & errror
func NewSTL(buffer []byte) (output STL, err error) {
bufferReader := bytes.NewReader(buffer)
solid, err := stl.From(bufferReader)
if err != nil {
return
}
fmt.Printf("Parsed in %d Triangles\n", solid.TriangleCount)
numColors := (rand.Int() % 5) + 2 // nolint
colors := GenerateGradient(numColors, int(solid.TriangleCount))
var index uint32
for i, triangle := range solid.Triangles {
colorR := colors[i].Red
colorG := colors[i].Green
colorB := colors[i].Blue
output.vertices = append(output.vertices, triangle.Vertices[0].X)
output.vertices = append(output.vertices, triangle.Vertices[0].Y)
output.vertices = append(output.vertices, triangle.Vertices[0].Z)
output.indices = append(output.indices, index)
output.colors = append(output.colors, colorR)
output.colors = append(output.colors, colorG)
output.colors = append(output.colors, colorB)
index++
output.vertices = append(output.vertices, triangle.Vertices[1].X)
output.vertices = append(output.vertices, triangle.Vertices[1].Y)
output.vertices = append(output.vertices, triangle.Vertices[1].Z)
output.indices = append(output.indices, index)
output.colors = append(output.colors, colorR)
output.colors = append(output.colors, colorG)
output.colors = append(output.colors, colorB)
index++
output.vertices = append(output.vertices, triangle.Vertices[2].X)
output.vertices = append(output.vertices, triangle.Vertices[2].Y)
output.vertices = append(output.vertices, triangle.Vertices[2].Z)
output.indices = append(output.indices, index)
output.colors = append(output.colors, colorR)
output.colors = append(output.colors, colorG)
output.colors = append(output.colors, colorB)
index++
}
return output, err
}
// STL is a stereolithograph
type STL struct {
vertices []float32
colors []float32
indices []uint32
}
// GetModel gets the model
func (s STL) GetModel() ([]float32, []float32, []uint32) {
return s.vertices, s.colors, s.indices
}
// InitialConfig is the initial config
type InitialConfig struct {
Width int
Height int
SpeedX float32
SpeedY float32
SpeedZ float32
Colors []float32
Vertices []float32
Indices []uint32
FragmentShaderCode string
VertexShaderCode string
}
// Renderer is the renderer
type Renderer struct {
glContext js.Value
glTypes GLTypes
colors js.Value
vertices js.Value
indices js.Value
colorBuffer js.Value
vertexBuffer js.Value
indexBuffer js.Value
numIndices int
fragShader js.Value
vertShader js.Value
shaderProgram js.Value
tmark float32
rotationX float32
rotationY float32
rotationZ float32
movMatrix mgl32.Mat4
PositionMatrix js.Value
ViewMatrix js.Value
ModelMatrix js.Value
height int
width int
speedX float32
speedY float32
speedZ float32
}
// NewRenderer returns a new renderer & error
func NewRenderer(gl js.Value, config InitialConfig) (r Renderer, err error) {
// Get some WebGL bindings
r.glContext = gl
err = r.glTypes.New(r.glContext)
r.numIndices = len(config.Indices)
r.movMatrix = mgl32.Ident4()
r.width = config.Width
r.height = config.Height
r.speedX = config.SpeedX
r.speedY = config.SpeedY
r.speedZ = config.SpeedZ
// Convert buffers to JS TypedArrays
r.UpdateColorBuffer(config.Colors)
r.UpdateVerticesBuffer(config.Vertices)
r.UpdateIndicesBuffer(config.Indices)
r.UpdateFragmentShader(config.FragmentShaderCode)
r.UpdateVertexShader(config.VertexShaderCode)
r.updateShaderProgram()
r.attachShaderProgram()
r.setContextFlags()
r.createMatrixes()
r.EnableObject()
return
}
// SetModel sets a new model
func (r *Renderer) SetModel(Colors []float32, Vertices []float32, Indices []uint32) {
fmt.Println("Renderer.SetModel")
r.numIndices = len(Indices)
fmt.Println("Number of Indices:", len(Indices))
r.UpdateColorBuffer(Colors)
fmt.Println("Number of Colors:", len(Colors))
r.UpdateVerticesBuffer(Vertices)
fmt.Println("Number of Vertices:", len(Vertices))
r.UpdateIndicesBuffer(Indices)
r.EnableObject()
}
// Release releases the renderer
func (r *Renderer) Release() {
fmt.Println("Renderer.Release")
}
// EnableObject enables the object
func (r *Renderer) EnableObject() {
fmt.Println("Renderer.EnableObject")
r.glContext.Call("bindBuffer", r.glTypes.ElementArrayBuffer, r.indexBuffer)
}
// SetSpeedX set rotation x axis speed
func (r *Renderer) SetSpeedX(x float32) {
r.speedX = x
}
// SetSpeedY set rotation y axis speed
func (r *Renderer) SetSpeedY(y float32) {
r.speedY = y
}
// SetSpeedZ set rotation z axis speed
func (r *Renderer) SetSpeedZ(z float32) {
r.speedZ = z
}
// GetSpeed returns the rotation speeds
func (r *Renderer) GetSpeed() (x, y, z float32) {
return r.speedX, r.speedY, r.speedZ
}
// SetSize sets the size of the rendering
func (r *Renderer) SetSize(height, width int) {
r.height = height
r.width = width
fmt.Println("Size", r.width, r.height)
}
func (r *Renderer) createMatrixes() {
ratio := float32(r.width) / float32(r.height)
fmt.Println("Renderer.createMatrixes")
projMatrix := mgl32.Perspective(mgl32.DegToRad(45.0), ratio, 1, 100000.0)
projMatrixBuffer := (*[16]float32)(unsafe.Pointer(&projMatrix)) // nolint
typedProjMatrixBuffer := SliceToTypedArray([]float32((*projMatrixBuffer)[:]))
r.glContext.Call("uniformMatrix4fv", r.PositionMatrix, 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)[:]))
r.glContext.Call("uniformMatrix4fv", r.ViewMatrix, false, typedViewMatrixBuffer)
}
func (r *Renderer) setContextFlags() {
fmt.Println("Renderer.setContextFlags")
r.glContext.Call("clearColor", 0.0, 0.0, 0.0, 0.0) // Color the screen is cleared to
// r.glContext.Call("clearDepth", 1.0) // Z value that is set to the Depth buffer every frame
r.glContext.Call("viewport", 0, 0, r.width, r.height) // Viewport size
r.glContext.Call("depthFunc", r.glTypes.LEqual)
}
// UpdateFragmentShader Updates the Fragment Shader
func (r *Renderer) UpdateFragmentShader(shaderCode string) {
fmt.Println("Renderer.UpdateFragmentShader")
r.fragShader = r.glContext.Call("createShader", r.glTypes.FragmentShader)
r.glContext.Call("shaderSource", r.fragShader, shaderCode)
r.glContext.Call("compileShader", r.fragShader)
}
// UpdateVertexShader updates the vertex shader
func (r *Renderer) UpdateVertexShader(shaderCode string) {
fmt.Println("Renderer.UpdateVertexShader")
r.vertShader = r.glContext.Call("createShader", r.glTypes.VertexShader)
r.glContext.Call("shaderSource", r.vertShader, shaderCode)
r.glContext.Call("compileShader", r.vertShader)
}
func (r *Renderer) updateShaderProgram() {
fmt.Println("Renderer.updateShaderProgram")
if r.fragShader.IsUndefined() || r.vertShader.IsUndefined() {
return
}
r.shaderProgram = r.glContext.Call("createProgram")
r.glContext.Call("attachShader", r.shaderProgram, r.vertShader)
r.glContext.Call("attachShader", r.shaderProgram, r.fragShader)
r.glContext.Call("linkProgram", r.shaderProgram)
}
func (r *Renderer) attachShaderProgram() {
fmt.Println("Renderer.attachShaderProgram")
r.PositionMatrix = r.glContext.Call("getUniformLocation", r.shaderProgram, "Pmatrix")
r.ViewMatrix = r.glContext.Call("getUniformLocation", r.shaderProgram, "Vmatrix")
r.ModelMatrix = r.glContext.Call("getUniformLocation", r.shaderProgram, "Mmatrix")
r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.vertexBuffer)
position := r.glContext.Call("getAttribLocation", r.shaderProgram, "position")
r.glContext.Call("vertexAttribPointer", position, 3, r.glTypes.Float, false, 0, 0)
r.glContext.Call("enableVertexAttribArray", position)
r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.colorBuffer)
color := r.glContext.Call("getAttribLocation", r.shaderProgram, "color")
r.glContext.Call("vertexAttribPointer", color, 3, r.glTypes.Float, false, 0, 0)
r.glContext.Call("enableVertexAttribArray", color)
r.glContext.Call("useProgram", r.shaderProgram)
}
// UpdateColorBuffer Updates the ColorBuffer
func (r *Renderer) UpdateColorBuffer(buffer []float32) {
fmt.Println("Renderer.UpdateColorBuffer")
r.colors = SliceToTypedArray(buffer)
if r.colorBuffer.IsUndefined() {
r.colorBuffer = r.glContext.Call("createBuffer")
}
r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.colorBuffer)
r.glContext.Call("bufferData", r.glTypes.ArrayBuffer, r.colors, r.glTypes.StaticDraw)
}
// UpdateVerticesBuffer Updates the VerticesBuffer
func (r *Renderer) UpdateVerticesBuffer(buffer []float32) {
fmt.Println("Renderer.UpdateVerticesBuffer")
r.vertices = SliceToTypedArray(buffer)
if r.vertexBuffer.IsUndefined() {
r.vertexBuffer = r.glContext.Call("createBuffer")
}
r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.vertexBuffer)
r.glContext.Call("bufferData", r.glTypes.ArrayBuffer, r.vertices, r.glTypes.StaticDraw)
}
// UpdateIndicesBuffer Updates the IndicesBuffer
func (r *Renderer) UpdateIndicesBuffer(buffer []uint32) {
fmt.Println("Renderer.UpdateIndicesBuffer")
r.indices = SliceToTypedArray(buffer)
if r.indexBuffer.IsUndefined() {
r.indexBuffer = r.glContext.Call("createBuffer")
}
r.glContext.Call("bindBuffer", r.glTypes.ElementArrayBuffer, r.indexBuffer)
r.glContext.Call("bufferData", r.glTypes.ElementArrayBuffer, r.indices, r.glTypes.StaticDraw)
}
// Render renders
func (r *Renderer) Render(_ js.Value, args []js.Value) interface{} { // nolint
now := float32(args[0].Float())
tdiff := now - r.tmark
r.tmark = now
r.rotationX = r.rotationX + r.speedX*float32(tdiff)/500
r.rotationY = r.rotationY + r.speedY*float32(tdiff)/500
r.rotationZ = r.rotationZ + r.speedZ*float32(tdiff)/500
r.movMatrix = mgl32.HomogRotate3DX(r.rotationX)
r.movMatrix = r.movMatrix.Mul4(mgl32.HomogRotate3DY(r.rotationY))
r.movMatrix = r.movMatrix.Mul4(mgl32.HomogRotate3DZ(r.rotationZ))
modelMatrixBuffer := (*[16]float32)(unsafe.Pointer(&r.movMatrix)) // nolint
typedModelMatrixBuffer := SliceToTypedArray([]float32((*modelMatrixBuffer)[:]))
r.glContext.Call("uniformMatrix4fv", r.ModelMatrix, false, typedModelMatrixBuffer)
r.glContext.Call("enable", r.glTypes.DepthTest)
r.glContext.Call("clear", r.glTypes.ColorBufferBit)
r.glContext.Call("clear", r.glTypes.DepthBufferBit)
r.glContext.Call("drawElements", r.glTypes.Triangles, r.numIndices, r.glTypes.UnsignedInt, 0)
return nil
}
// SetZoom Sets the Zoom
func (r *Renderer) SetZoom(currentZoom float32) {
fmt.Println("Renderer.SetZoom")
viewMatrix := mgl32.LookAtV(mgl32.Vec3{currentZoom, currentZoom, currentZoom}, 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)[:]))
r.glContext.Call("uniformMatrix4fv", r.ViewMatrix, false, typedViewMatrixBuffer)
}
// NewColorInterpolation generates color interpolation
func NewColorInterpolation(a Color, b Color) ColorInterpolation {
return ColorInterpolation{
a,
b,
a.Subtract(b),
}
}
// ColorInterpolation is interpolated color
type ColorInterpolation struct {
startColor Color
endColor Color
deltaColor Color
}
// Interpolate interpolates
func (c ColorInterpolation) Interpolate(percent float32) Color {
scaled := c.deltaColor.MultiplyFloat(percent)
return c.startColor.Add(scaled)
}
// Color represents a color
type Color struct {
Red float32
Green float32
Blue float32
}
// NewRandomColor returns a New RandomColor
func NewRandomColor() Color {
return Color{rand.Float32(), rand.Float32(), rand.Float32()} // nolint
}
// Subtract Subtracts color
func (c Color) Subtract(d Color) Color {
return Color{
c.Red - d.Red,
c.Green - d.Green,
c.Blue - d.Blue,
}
}
// Add Adds color
func (c Color) Add(d Color) Color {
return Color{
c.Red + d.Red,
c.Green + d.Green,
c.Blue + d.Blue,
}
}
// MultiplyFloat Multiplies Float
func (c Color) MultiplyFloat(x float32) Color {
return Color{
c.Red * x,
c.Green * x,
c.Blue * x,
}
}
// GenerateGradient Generates Gradient
func GenerateGradient(numColors int, steps int) []Color {
distribution := distributeColors(numColors, steps)
colors := make([]Color, numColors)
for i := 0; i < numColors; i++ {
colors[i] = NewRandomColor()
}
outputBuffer := make([]Color, 0, steps)
for index := 0; index < numColors; index++ {
if index >= numColors-1 {
size := steps - distribution[index]
interpolation := NewColorInterpolation(colors[index-1], colors[index])
buffer := generateSingleGradient(interpolation, size)
outputBuffer = append(outputBuffer, buffer...)
break
}
currentStep := distribution[index]
nextStep := distribution[index+1]
size := nextStep - currentStep
interpolation := NewColorInterpolation(colors[index], colors[index+1])
buffer := generateSingleGradient(interpolation, size)
outputBuffer = append(outputBuffer, buffer...)
}
return outputBuffer
}
func distributeColors(numColors int, steps int) []int {
diff := int(math.Ceil(float64(steps) / float64(numColors)))
output := make([]int, numColors)
for i := 0; i < numColors; i++ {
output[i] = diff * i
}
return output
}
func generateSingleGradient(c ColorInterpolation, numSteps int) []Color {
output := make([]Color, numSteps)
for i := 0; i < numSteps; i++ {
percent := float32(i) / float32(numSteps)
output[i] = c.Interpolate(percent)
}
return output
}
// 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
UnsignedInt js.Value
LEqual js.Value
LineLoop js.Value
}
// New grabs the WebGL bindings from a GL context.
func (types *GLTypes) New(gl js.Value) error {
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")
enabled := gl.Call("getExtension", "OES_element_index_uint")
if !enabled.Truthy() {
return errors.New("missing extension: OES_element_index_uint")
}
types.UnsignedInt = gl.Get("UNSIGNED_INT")
return nil
}
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 converts Slice To TypedArray
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))
}
}