1// ===== stl2.go =====
2// Package main stl2.go
3package main
4
5import (
6 "bytes"
7 "crypto/rand"
8 "encoding/base64"
9 "encoding/binary"
10 "log"
11 m "math"
12 r "reflect"
13 "runtime"
14 "strconv"
15 "strings"
16 "syscall/js"
17 u "unsafe"
18
19 "github.com/go-gl/mathgl/mgl32"
20 "gitlab.com/russoj88/stl/stl"
21)
22
23var (
24 wasmName string
25 running = true
26 done chan struct{}
27 stlFileName, originalHTML string
28 rr Renderer
29 existingFooter, body, footer, sXV, sYV, sZV, sZoomV, cEl, gl js.Value
30 currentZoom float32 = 3
31)
32
33const gebi = "getElementById"
34const ael = "addEventListener"
35const ih = "innerHTML"
36
37func main() {
38 ready := make(chan struct{})
39
40 document := js.Global().Get("document")
41 readyState := document.Get("readyState").String()
42 if readyState == "interactive" || readyState == "complete" {
43 log.Println(wasmName+":", "WASM: DOM already fully loaded")
44 close(ready)
45 } else {
46 cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
47 log.Println(wasmName+":", "WASM: DOM fully loaded and parsed")
48 close(ready)
49 return nil
50 })
51 defer cb.Release()
52
53 document.Call(ael, "DOMContentLoaded", cb)
54 log.Println(wasmName+":", "WASM: waiting for DOM to load")
55 }
56
57 // Wait until the DOM is ready
58 <-ready
59
60 cEl = document.Call(gebi, "gocanvas")
61 if cEl.IsUndefined() || cEl.IsNull() {
62 return
63 }
64
65 window := js.Global().Get("window")
66 location := window.Get("location")
67 pathname := location.Get("pathname").String()
68 parts := strings.SplitN(pathname, "/", 3)
69 if len(parts) > 1 {
70 parts[1] = "/" + parts[1]
71 } else {
72 parts = append(parts, "/")
73 }
74 switch parts[1] {
75 case "/cat":
76 log.Println(wasmName+":", "nothing to animate ; return")
77 return
78 case "/p":
79 log.Println(wasmName+":", "fetching stereolithograph")
80 preElement := js.Global().Get("document").Call("getElementById", "package-type")
81 if preElement.IsUndefined() || preElement.IsNull() {
82 log.Println(wasmName+":", "Element with ID 'package-type' not found. Exiting.")
83 return // Exit the WebAssembly here
84 }
85
86 packageText := preElement.Get("textContent").String()
87 packageType := packageText[len("Package Type: "):]
88 stlFileName = packageType + ".stl"
89
90 // Fetch the STL file
91 response := js.Global().Call("fetch", "/i/stl/base64/"+stlFileName)
92 promise := response.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
93 resp := args[0]
94 if !resp.Get("ok").Bool() {
95 log.Printf("HTTP Error: %s (status: %d)", resp.Get("statusText").String(), resp.Get("status").Int())
96 log.Println(wasmName+":", "Failed to fetch the STL file "+stlFileName+". Exiting.")
97 return nil // Exit the WebAssembly here
98 }
99 return resp.Call("text")
100 }))
101
102 // Process the fetched data
103 promise.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
104 if len(args) > 0 {
105 result := args[0].String()
106 uploadedFile, err1 := parseBase64File(result)
107 if !err1.IsNull() {
108 log.Println(wasmName+":", "Error parsing the base64 file:", err1)
109 return nil
110 }
111
112 stlSolid, err2 := NewSTL(uploadedFile)
113 if err2 != nil {
114 log.Println(wasmName+":", "Error creating STL object:", err2)
115 return nil
116 }
117
118 vert, colors, indices := stlSolid.GetModel()
119 modelSize := getMaxScalar(vert)
120 currentZoom := modelSize * 3
121 rr.SetZoom(currentZoom)
122 rr.SetModel(colors, vert, indices)
123 }
124 return nil
125 }))
126
127 // Ensure this function does not execute until the fetch of the STL file succeeds
128 promise.Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
129 log.Println(wasmName+":", "An error occurred during the fetch or processing. Exiting.")
130 return nil
131 }))
132 niam()
133 default:
134 niam()
135 }
136}
137func niam() {
138 mgl32.DisableMemoryPooling()
139
140 //stlFileName = "TO-247.stl"
141 tdata := struct {
142 XRange, ZMin, ZMax string
143 XStep, ZoomStep string
144 }{
145 XRange: "1", ZMin: "0", ZMax: "50",
146 XStep: "0.01", ZoomStep: "0.1",
147 }
148 if stlFileName != ".stl" && stlFileName != "" {
149 tdata.ZMin = "10"
150 tdata.ZMax = "1000"
151 }
152
153 var controlsHTML = `
154 <datalist id="speeds">
155 <option>-` + tdata.XRange + `</option><option>0</option><option>` + tdata.XRange + `</option></datalist>
156 <table class="🌐">
157 <tr><td><p><button type="button" id="stop">Stop Rendering</button></p></td><td><p>X<input id="X" type="range" min="-` + tdata.XRange + `" max="` + tdata.XRange + `" step="` + tdata.XStep + `" list="speeds"><text id="XV">00.00</text></p></td>
158 <td><p>Y<input id="Y" type="range" min="-` + tdata.XRange + `" max="` + tdata.XRange + `" step="` + tdata.XStep + `" list="speeds"><text id="YV">00.00</text></p></td>
159 <td><p>Z<input id="Z" type="range" min="-` + tdata.XRange + `" max="` + tdata.XRange + `" step="` + tdata.XStep + `" list="speeds"><text id="ZV">00.00</text></p></td>
160 <td><p>Zoom<input id="Zoom" type="range" min="` + tdata.ZMin + `" max="` + tdata.ZMax + `" step="` + tdata.ZoomStep + `" list="speeds"><text id="ZoomV">0000.00</text></p></td>
161 </table>
162 `
163 doc := js.Global().Get("document")
164 body = doc.Get("body")
165 existingFooter = doc.Call("getElementsByTagName", "footer").Index(0)
166 if existingFooter.Truthy() {
167 originalHTML = existingFooter.Get(ih).String()
168 footer = doc.Call("createElement", "footer")
169 footer.Set(ih, originalHTML+controlsHTML)
170 body.Call("replaceChild", footer, existingFooter)
171 } else {
172 footer = doc.Call("createElement", "footer")
173 footer.Set(ih, controlsHTML)
174 body.Call("appendChild", footer)
175 }
176
177 cEl = doc.Call(gebi, "gocanvas")
178 width := doc.Get("body").Get("clientWidth").Int()
179 height := doc.Get("body").Get("clientHeight").Int()
180
181 cEl.Set("width", width)
182 cEl.Set("height", height)
183 sXc := js.FuncOf(sCX)
184 sX := doc.Call(gebi, "X")
185 sX.Call(ael, "input", sXc)
186 sXV = doc.Call(gebi, "XV")
187
188 sYc := js.FuncOf(sCY)
189 sY := doc.Call(gebi, "Y")
190 sY.Call(ael, "input", sYc)
191 sYV = doc.Call(gebi, "YV")
192
193 sZc := js.FuncOf(sCZ)
194 sZ := doc.Call(gebi, "Z")
195 sZ.Call(ael, "input", sZc)
196 sZV = doc.Call(gebi, "ZV")
197
198 sZoomc := js.FuncOf(sCZoom)
199 sZoom := doc.Call(gebi, "Zoom")
200 sZoom.Call(ael, "input", sZoomc)
201 sZoomV = doc.Call(gebi, "ZoomV")
202
203 sBc := js.FuncOf(stopApplication)
204 sB := doc.Call(gebi, "stop")
205 sB.Call(ael, "click", sBc)
206 defer sBc.Release()
207
208 gl = cEl.Call("getContext", "webgl")
209 if gl.IsUndefined() {
210 gl = cEl.Call("getContext", "experimental-webgl")
211 }
212 if gl.IsUndefined() {
213 js.Global().Call("alert", "WASM: browser might not support webgl")
214 return
215 }
216
217 config := InitialConfig{
218 W: width,
219 H: height,
220 X: 0,
221 Y: 0,
222 Z: 0,
223 Vertices: verticesNative,
224 Indices: indicesNative,
225 Colors: colorsNative,
226 FSC: fragShaderCode,
227 VSC: vertShaderCode,
228 }
229
230 config.X = cryptoRandFloat32() / 20
231 config.Y = cryptoRandFloat32() / 20
232 config.Z = cryptoRandFloat32() / 20
233 config.Vertices, config.Indices = generateSphereVertices(float32(1.0), 30, 30)
234 if stlFileName == ".stl" || stlFileName == "" {
235 config.FSC, config.VSC = fragShaderCode1, vertShaderCode1
236 }
237 var jsErr js.Value
238 rr, jsErr = NewRenderer(gl, config)
239 if !jsErr.IsNull() {
240 js.Global().Call("alert", "WASM: Cannot load webgl ")
241 return
242 }
243 rr.SetZoom(currentZoom)
244 defer rr.Release()
245
246 x, y, z := rr.GetSpeed()
247 sX.Set("value", f32(x, 'f', -1, 64))
248 if x > 0 {
249 sXV.Set(ih, "+"+f32(x, 'f', 2, 64))
250 }
251 if x == 0 {
252 sXV.Set(ih, " "+f32(x, 'f', 2, 64))
253 }
254 if x < 0 {
255 sXV.Set(ih, f32(x, 'f', 2, 64))
256 }
257 sY.Set("value", f32(y, 'f', -1, 64))
258 if y > 0 {
259 sYV.Set(ih, "+"+f32(y, 'f', 2, 64))
260 }
261 if y == 0 {
262 sYV.Set(ih, "0"+f32(y, 'f', 2, 64))
263 }
264 if y < 0 {
265 sYV.Set(ih, f32(y, 'f', 2, 64))
266 }
267 sZ.Set("value", f32(z, 'f', -1, 64))
268 if z > 0 {
269 sZV.Set(ih, "+"+f32(z, 'f', 2, 64))
270 }
271 if z == 0 {
272 sZV.Set(ih, "0"+f32(z, 'f', 2, 64))
273 }
274 if z < 0 {
275 sZV.Set(ih, f32(z, 'f', 2, 64))
276 }
277
278 var renderFrame js.Func
279 renderFrame = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
280 rr.Render(this, args)
281 js.Global().Call("requestAnimationFrame", renderFrame)
282 return nil
283 })
284 js.Global().Call("requestAnimationFrame", renderFrame)
285
286 done = make(chan struct{})
287
288 <-done
289}
290
291func parseBase64File(input string) (output []byte, err js.Value) {
292 searchString := "base64,"
293 searchLength := len(searchString)
294 var index = -1
295 for i := 0; i <= len(input)-searchLength; i++ {
296 if input[i:i+searchLength] == searchString {
297 index = i
298 break
299 }
300 }
301 if index < 0 {
302 err = js.Global().Get("Error").New("Error opening file")
303 return
304 }
305 sBuffer := input[index+searchLength:]
306 output, decodeErr := base64.StdEncoding.DecodeString(sBuffer)
307 if decodeErr != nil {
308 err = js.Global().Get("Error").New(decodeErr.Error())
309 return
310 }
311 return output, js.Null()
312}
313
314func getMaxScalar(vertices []float32) float32 {
315 var max float32
316 for baseIndex := 0; baseIndex < len(vertices); baseIndex += 3 {
317 testScale := scalar(vertices[baseIndex], vertices[baseIndex], vertices[baseIndex])
318 if testScale > max {
319 max = testScale
320 }
321 }
322 return max
323}
324
325func scalar(x float32, y float32, z float32) float32 {
326 xy := m.Sqrt(float64(x*x + y*y))
327 return float32(m.Sqrt(xy*xy + float64(z*z)))
328}
329
330func cryptoRandFloat32() float32 {
331 b := make([]byte, 4)
332 _, err := rand.Read(b)
333 if err != nil {
334 panic("crypto/rand read failed: " + err.Error())
335 }
336 u := uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
337 return float32(u) / float32(m.MaxUint32)
338}
339
340func stopApplication(_ js.Value, _ []js.Value) interface{} {
341 running = false
342 sZoomV.Set(ih, float32(0))
343 currentZoom = float32(0)
344 rr.SetZoom(float32(0))
345 footer.Set(ih, originalHTML)
346
347 if callUpdateCartDisplay := js.Global().Get("callUpdateCartDisplay"); !callUpdateCartDisplay.IsUndefined() && !callUpdateCartDisplay.IsNull() {
348 callUpdateCartDisplay.Invoke()
349 } else {
350 js.Global().Call("console.warn", "callUpdateCartDisplay is undefined or null")
351 }
352
353 js.Global().Call("setTimeout", js.FuncOf(func(this js.Value, p []js.Value) interface{} {
354 close(done)
355 // done <- struct{}{}
356 return nil
357 }), 5000)
358 return nil
359}
360
361func sCX(this js.Value, _ []js.Value) interface{} {
362 sSpeed := this.Get("value").String()
363 s, _ := strconv.ParseFloat(sSpeed, 64)
364 rr.SetX(float32(s))
365 if s > 0 {
366 sXV.Set(ih, "+"+f64(s, 'f', 2, 32))
367 }
368 if s == 0 {
369 sXV.Set(ih, "0"+f64(s, 'f', 2, 32))
370 }
371 if s < 0 {
372 sXV.Set(ih, f64(s, 'f', 2, 32))
373 }
374 return nil
375}
376
377func sCY(this js.Value, _ []js.Value) interface{} {
378 sS := this.Get("value").String()
379 s, _ := strconv.ParseFloat(sS, 64)
380 rr.SetY(float32(s))
381 if s > 0 {
382 sYV.Set(ih, "+"+f64(s, 'f', 2, 32))
383 }
384 if s == 0 {
385 sYV.Set(ih, "0"+f64(s, 'f', 2, 32))
386 }
387 if s < 0 {
388 sYV.Set(ih, f64(s, 'f', 2, 32))
389 }
390 return nil
391}
392
393func sCZ(this js.Value, _ []js.Value) interface{} {
394 sS := this.Get("value").String()
395 s, _ := strconv.ParseFloat(sS, 64)
396 rr.SetZ(float32(s))
397 if s > 0 {
398 sZV.Set(ih, "+"+f64(s, 'f', 2, 32))
399 }
400 if s == 0 {
401 sZV.Set(ih, "0"+f64(s, 'f', 2, 32))
402 }
403 if s < 0 {
404 sZV.Set(ih, f64(s, 'f', 2, 32))
405 }
406 return nil
407}
408
409func sCZoom(this js.Value, _ []js.Value) interface{} {
410 sS := this.Get("value").String()
411 s, _ := strconv.ParseFloat(sS, 64)
412 if s < 10 {
413 sZoomV.Set(ih, "000"+f64(s, 'f', 2, 32))
414 } else if s < 100 {
415 sZoomV.Set(ih, "00"+f64(s, 'f', 2, 32))
416 } else if s < 1000 {
417 sZoomV.Set(ih, "0"+f64(s, 'f', 2, 32))
418 } else {
419 sZoomV.Set(ih, f64(s, 'f', 2, 32))
420 }
421 currentZoom = float32(s)
422 rr.SetZoom(currentZoom)
423 return nil
424}
425
426// Model is an interface for a model
427type Model interface {
428 GetModel() ([]float32, []float32, []uint16)
429}
430
431func cryptoRandIntn(max int) (int, js.Value) {
432 if max <= 0 {
433 return 0, js.Global().Get("Error").New("max must be a positive integer")
434 }
435 numBytes := (max + 7) / 8
436 maxBytes := 1 << (numBytes * 8)
437 randBytes := make([]byte, numBytes)
438 randNum := 0
439 for {
440 _, err := rand.Read(randBytes)
441 if err != nil {
442 return 0, js.Global().Get("Error").New("error generating random number")
443 }
444 for _, b := range randBytes {
445 randNum = (randNum << 8) | int(b)
446 }
447 if randNum < maxBytes-maxBytes%max {
448 break
449 }
450 }
451 return randNum % max, js.Null()
452}
453
454// NewSTL returns a new STL & error
455func NewSTL(buffer []byte) (o STL, err error) {
456 bufferReader := bytes.NewReader(buffer)
457 solid, err := stl.From(bufferReader)
458 if err != nil {
459 return
460 }
461
462 // Generate random rotation matrix
463 rotationMatrix := randomRotationMatrix()
464
465 // Generate colors
466 numColors, _ := cryptoRandIntn(5)
467 numColors += 2 // Random number between 2 and 6
468 colors := GenerateGradient(numColors, int(solid.TriangleCount))
469
470 var index uint32
471 for i, triangle := range solid.Triangles {
472 colorR := colors[i].Red
473 colorG := colors[i].Green
474 colorB := colors[i].Blue
475
476 // Convert each triangle's vertices to custom Vertex type and apply rotation
477 v0 := Vertex{X: float32(triangle.Vertices[0].X), Y: float32(triangle.Vertices[0].Y), Z: float32(triangle.Vertices[0].Z)}
478 v1 := Vertex{X: float32(triangle.Vertices[1].X), Y: float32(triangle.Vertices[1].Y), Z: float32(triangle.Vertices[1].Z)}
479 v2 := Vertex{X: float32(triangle.Vertices[2].X), Y: float32(triangle.Vertices[2].Y), Z: float32(triangle.Vertices[2].Z)}
480
481 // Rotate and add vertices
482 o.addRotatedVertex(&index, v0, rotationMatrix, colorR, colorG, colorB)
483 o.addRotatedVertex(&index, v1, rotationMatrix, colorR, colorG, colorB)
484 o.addRotatedVertex(&index, v2, rotationMatrix, colorR, colorG, colorB)
485 }
486
487 return o, err
488}
489
490// Add a rotated vertex to the STL structure
491func (s *STL) addRotatedVertex(index *uint32, vertex Vertex, rotation mgl32.Mat4, r, g, b float32) {
492 // Apply rotation
493 rotatedVertex := rotation.Mul4x1(mgl32.Vec3{vertex.X, vertex.Y, vertex.Z}.Vec4(1.0))
494
495 // Add rotated vertex to the STL struct
496 s.v = append(s.v, rotatedVertex[0], rotatedVertex[1], rotatedVertex[2])
497 s.i = append(s.i, *index)
498 s.c = append(s.c, r, g, b)
499 (*index)++
500}
501
502// STL is a stereolithograph
503type STL struct {
504 v []float32
505 c []float32
506 i []uint32
507}
508
509// Define a custom Vertex type for storing the vertex data.
510type Vertex struct {
511 X, Y, Z float32
512}
513
514// GetModel gets the model
515func (s STL) GetModel() ([]float32, []float32, []uint32) {
516 return s.v, s.c, s.i
517}
518
519// InitialConfig is the initial config
520type InitialConfig struct {
521 W int
522 H int
523 X float32
524 Y float32
525 Z float32
526 Colors []float32
527 Vertices []float32
528 Indices []uint32
529 FSC string
530 VSC string
531}
532
533// Renderer is the renderer
534type Renderer struct {
535 glContext js.Value
536 glTypes GLTypes
537 colors js.Value
538 v js.Value
539 i js.Value
540 colorBuffer js.Value
541 vertexBuffer js.Value
542 indexBuffer js.Value
543 numIndices int
544 numVertices int
545 fragShader js.Value
546 vertShader js.Value
547 shaderProgram js.Value
548 tmark float32
549 rX float32 //rotation X
550 rY float32
551 rZ float32
552 movMatrix mgl32.Mat4
553 PositionMatrix js.Value
554 ViewMatrix js.Value
555 ModelMatrix js.Value
556 height int
557 width int
558 sX float32
559 sY float32
560 sZ float32
561}
562
563// NewRenderer returns a new renderer & error
564func NewRenderer(gl js.Value, config InitialConfig) (r Renderer, err js.Value) {
565 // Get some WebGL bindings
566 r.glContext = gl
567 err = r.glTypes.New(r.glContext)
568 r.numIndices = len(config.Indices)
569 r.numVertices = len(config.Vertices)
570 r.movMatrix = mgl32.Ident4()
571 r.width = config.W
572 r.height = config.H
573
574 r.sX = config.X
575 r.sY = config.Y
576 r.sZ = config.Z
577
578 // Convert buffers to JS TypedArrays
579 r.UpdateColorBuffer(config.Colors)
580 r.UpdateVerticesBuffer(config.Vertices)
581 r.UpdateIndicesBuffer(config.Indices)
582
583 r.UpdateFragmentShader(config.FSC)
584 r.UpdateVertexShader(config.VSC)
585 r.updateShaderProgram()
586 r.attachShaderProgram()
587
588 r.setContextFlags()
589
590 r.createMatrixes()
591 r.EnableObject()
592 return
593}
594
595// SetModel sets a new model
596func (r *Renderer) SetModel(Colors []float32, Vertices []float32, Indices []uint32) {
597 r.numIndices = len(Indices)
598 r.UpdateColorBuffer(Colors)
599 r.UpdateVerticesBuffer(Vertices)
600 r.UpdateIndicesBuffer(Indices)
601 r.EnableObject()
602}
603
604// Release releases the renderer
605func (r *Renderer) Release() {
606 return
607}
608
609// EnableObject enables the object
610func (r *Renderer) EnableObject() {
611 r.glContext.Call("bindBuffer", r.glTypes.ElementArrayBuffer, r.indexBuffer)
612}
613
614// SetX set rotation x axis speed
615func (r *Renderer) SetX(x float32) {
616 r.sX = x
617}
618
619// SetY set rotation y axis speed
620func (r *Renderer) SetY(y float32) {
621 r.sY = y
622}
623
624// SetZ set rotation z axis speed
625func (r *Renderer) SetZ(z float32) {
626 r.sZ = z
627}
628
629// GetSpeed returns the rotation speeds
630func (r *Renderer) GetSpeed() (x, y, z float32) {
631 return r.sX, r.sY, r.sZ
632}
633
634// SetSize sets the size of the rendering
635func (r *Renderer) SetSize(height, width int) {
636 r.height = height
637 r.width = width
638}
639
640func (r *Renderer) createMatrixes() {
641 ratio := float32(r.width) / float32(r.height)
642 // fmt.Println("Renderer.createMatrixes")
643 projMatrix := mgl32.Perspective(mgl32.DegToRad(45.0), ratio, 1, 100000.0)
644 projMatrixBuffer := (*[16]float32)(u.Pointer(&projMatrix)) // nolint
645 typedProjMatrixBuffer := S2TA([]float32((*projMatrixBuffer)[:]))
646 r.glContext.Call("uniformMatrix4fv", r.PositionMatrix, false, typedProjMatrixBuffer)
647
648 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})
649 viewMatrixBuffer := (*[16]float32)(u.Pointer(&viewMatrix)) // nolint
650 typedViewMatrixBuffer := S2TA([]float32((*viewMatrixBuffer)[:]))
651 r.glContext.Call("uniformMatrix4fv", r.ViewMatrix, false, typedViewMatrixBuffer)
652}
653
654func (r *Renderer) setContextFlags() {
655 r.glContext.Call("clearColor", 0.0, 0.0, 0.0, 0.0) // Color the screen is cleared to
656 r.glContext.Call("viewport", 0, 0, r.width, r.height) // Viewport size
657 r.glContext.Call("depthFunc", r.glTypes.LEqual)
658}
659
660// UpdateFragmentShader Updates the Fragment Shader
661func (r *Renderer) UpdateFragmentShader(shaderCode string) {
662 r.fragShader = r.glContext.Call("createShader", r.glTypes.FragmentShader)
663 r.glContext.Call("shaderSource", r.fragShader, shaderCode)
664 r.glContext.Call("compileShader", r.fragShader)
665}
666
667// UpdateVertexShader updates the vertex shader
668func (r *Renderer) UpdateVertexShader(shaderCode string) {
669 r.vertShader = r.glContext.Call("createShader", r.glTypes.VertexShader)
670 r.glContext.Call("shaderSource", r.vertShader, shaderCode)
671 r.glContext.Call("compileShader", r.vertShader)
672}
673
674func (r *Renderer) updateShaderProgram() {
675 if r.fragShader.IsUndefined() || r.vertShader.IsUndefined() {
676 return
677 }
678 r.shaderProgram = r.glContext.Call("createProgram")
679 r.glContext.Call("attachShader", r.shaderProgram, r.vertShader)
680 r.glContext.Call("attachShader", r.shaderProgram, r.fragShader)
681 r.glContext.Call("linkProgram", r.shaderProgram)
682}
683
684const gul = "getUniformLocation"
685
686func (r *Renderer) attachShaderProgram() {
687 r.PositionMatrix = r.glContext.Call(gul, r.shaderProgram, "Pmatrix")
688 r.ViewMatrix = r.glContext.Call(gul, r.shaderProgram, "Vmatrix")
689 r.ModelMatrix = r.glContext.Call(gul, r.shaderProgram, "Mmatrix")
690
691 r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.vertexBuffer)
692 position := r.glContext.Call("getAttribLocation", r.shaderProgram, "position")
693 r.glContext.Call("vertexAttribPointer", position, 3, r.glTypes.Float, false, 0, 0)
694 r.glContext.Call("enableVertexAttribArray", position)
695
696 r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.colorBuffer)
697 color := r.glContext.Call("getAttribLocation", r.shaderProgram, "color")
698 r.glContext.Call("vertexAttribPointer", color, 3, r.glTypes.Float, false, 0, 0)
699 r.glContext.Call("enableVertexAttribArray", color)
700
701 r.glContext.Call("useProgram", r.shaderProgram)
702 if stlFileName == ".stl" || stlFileName == "" {
703
704 uBaseColor := r.glContext.Call(gul, r.shaderProgram, "uBaseColor")
705 uTopColor := r.glContext.Call(gul, r.shaderProgram, "uTopColor")
706 uColor := r.glContext.Call(gul, r.shaderProgram, "uColor")
707 r.glContext.Call("uniform3f", uBaseColor, 1.0, 0.0, 0.0)
708 r.glContext.Call("uniform3f", uTopColor, 0.0, 0.0, 1.0)
709 r.glContext.Call("uniform3f", uColor, 1.0, 1.0, 1.0)
710 }
711}
712
713// UpdateColorBuffer Updates the ColorBuffer
714func (r *Renderer) UpdateColorBuffer(buffer []float32) {
715 r.colors = S2TA(buffer)
716 if r.colorBuffer.IsUndefined() {
717 r.colorBuffer = r.glContext.Call("createBuffer")
718 }
719 r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.colorBuffer)
720 r.glContext.Call("bufferData", r.glTypes.ArrayBuffer, r.colors, r.glTypes.StaticDraw)
721}
722
723// UpdateVerticesBuffer Updates the VerticesBuffer
724func (r *Renderer) UpdateVerticesBuffer(buffer []float32) {
725 r.v = S2TA(buffer)
726 if r.vertexBuffer.IsUndefined() {
727 r.vertexBuffer = r.glContext.Call("createBuffer")
728 }
729 r.glContext.Call("bindBuffer", r.glTypes.ArrayBuffer, r.vertexBuffer)
730 r.glContext.Call("bufferData", r.glTypes.ArrayBuffer, r.v, r.glTypes.StaticDraw)
731}
732
733// UpdateIndicesBuffer Updates the IndicesBuffer
734func (r *Renderer) UpdateIndicesBuffer(buffer []uint32) {
735 r.i = S2TA(buffer)
736 if r.indexBuffer.IsUndefined() {
737 r.indexBuffer = r.glContext.Call("createBuffer")
738 }
739 r.glContext.Call("bindBuffer", r.glTypes.ElementArrayBuffer, r.indexBuffer)
740 r.glContext.Call("bufferData", r.glTypes.ElementArrayBuffer, r.i, r.glTypes.StaticDraw)
741}
742
743// Render renders
744func (r *Renderer) Render(_ js.Value, args []js.Value) interface{} { // nolint
745 now := float32(args[0].Float())
746 tdiff := now - r.tmark
747 r.tmark = now
748 r.rX = r.rX + r.sX*float32(tdiff)/500
749 r.rY = r.rY + r.sY*float32(tdiff)/500
750 r.rZ = r.rZ + r.sZ*float32(tdiff)/500
751
752 r.movMatrix = mgl32.HomogRotate3DX(r.rX)
753 r.movMatrix = r.movMatrix.Mul4(mgl32.HomogRotate3DY(r.rY))
754 r.movMatrix = r.movMatrix.Mul4(mgl32.HomogRotate3DZ(r.rZ))
755
756 modelMatrixBuffer := (*[16]float32)(u.Pointer(&r.movMatrix)) // nolint
757 typedModelMatrixBuffer := S2TA([]float32((*modelMatrixBuffer)[:]))
758
759 r.glContext.Call("uniformMatrix4fv", r.ModelMatrix, false, typedModelMatrixBuffer)
760
761 r.glContext.Call("enable", r.glTypes.DepthTest)
762 r.glContext.Call("clear", r.glTypes.ColorBufferBit)
763 r.glContext.Call("clear", r.glTypes.DepthBufferBit)
764 usegltype := r.glTypes.Triangles
765 if stlFileName == ".stl" || stlFileName == "" {
766 usegltype = r.glTypes.Line
767 r.glContext.Call("drawArrays", r.glTypes.LineLoop, 0, r.numVertices/3)
768 }
769 r.glContext.Call("drawElements", usegltype, r.numIndices, r.glTypes.UnsignedInt, 0)
770
771 return nil
772}
773
774// SetZoom Sets the Zoom
775func (r *Renderer) SetZoom(currentZoom float32) {
776 viewMatrix := mgl32.LookAtV(mgl32.Vec3{currentZoom, currentZoom, currentZoom}, mgl32.Vec3{0.0, 0.0, 0.0}, mgl32.Vec3{0.0, 1.0, 0.0})
777 viewMatrixBuffer := (*[16]float32)(u.Pointer(&viewMatrix)) // nolint
778 typedViewMatrixBuffer := S2TA([]float32((*viewMatrixBuffer)[:]))
779 r.glContext.Call("uniformMatrix4fv", r.ViewMatrix, false, typedViewMatrixBuffer)
780}
781
782// NewColorInterpolation generates color interpolation
783func NewColorInterpolation(a Color, b Color) ColorInterpolation {
784 return ColorInterpolation{
785 a,
786 b,
787 a.Subtract(b),
788 }
789}
790
791// ColorInterpolation is interpolated color
792type ColorInterpolation struct {
793 startColor Color
794 endColor Color
795 deltaColor Color
796}
797
798// Interpolate interpolates
799func (c ColorInterpolation) Interpolate(percent float32) Color {
800 scaled := c.deltaColor.MultiplyFloat(percent)
801 return c.startColor.Add(scaled)
802}
803
804// Color represents a color
805type Color struct {
806 Red float32
807 Green float32
808 Blue float32
809}
810
811// NewRandomColor returns a New RandomColor
812func NewRandomColor() Color {
813 const maxRGB = 255
814 var r, g, b float64
815 buf := make([]byte, 3)
816 rand.Read(buf)
817 r = float64(buf[0]) / 256
818 g = float64(buf[1]) / 256
819 b = float64(buf[2]) / 256
820 r = r * maxRGB
821 g = g * maxRGB
822 b = b * maxRGB
823 return Color{float32(r), float32(g), float32(b)}
824}
825
826// Subtract Subtracts color
827func (c Color) Subtract(d Color) Color {
828 return Color{
829 c.Red - d.Red,
830 c.Green - d.Green,
831 c.Blue - d.Blue,
832 }
833}
834
835// Add Adds color
836func (c Color) Add(d Color) Color {
837 return Color{
838 c.Red + d.Red,
839 c.Green + d.Green,
840 c.Blue + d.Blue,
841 }
842}
843
844// MultiplyFloat Multiplies Float
845func (c Color) MultiplyFloat(x float32) Color {
846 return Color{
847 c.Red * x,
848 c.Green * x,
849 c.Blue * x,
850 }
851}
852
853// GenerateGradient Generates Gradient
854func GenerateGradient(numColors int, steps int) []Color {
855 distribution := distributeColors(numColors, steps)
856 colors := make([]Color, numColors)
857 for i := 0; i < numColors; i++ {
858 colors[i] = NewRandomColor()
859 }
860 outputBuffer := make([]Color, 0, steps)
861 for index := 0; index < numColors; index++ {
862 if index >= numColors-1 {
863 size := steps - distribution[index]
864 interpolation := NewColorInterpolation(colors[index-1], colors[index])
865 buffer := generateSingleGradient(interpolation, size)
866 outputBuffer = append(outputBuffer, buffer...)
867 break
868 }
869 currentStep := distribution[index]
870 nextStep := distribution[index+1]
871 size := nextStep - currentStep
872 interpolation := NewColorInterpolation(colors[index], colors[index+1])
873 buffer := generateSingleGradient(interpolation, size)
874 outputBuffer = append(outputBuffer, buffer...)
875 }
876 return outputBuffer
877}
878
879func distributeColors(numColors int, steps int) []int {
880 diff := int(m.Ceil(float64(steps) / float64(numColors)))
881 output := make([]int, numColors)
882 for i := 0; i < numColors; i++ {
883 output[i] = diff * i
884 }
885 return output
886}
887
888func generateSingleGradient(c ColorInterpolation, numSteps int) []Color {
889 output := make([]Color, numSteps)
890 for i := 0; i < numSteps; i++ {
891 percent := float32(i) / float32(numSteps)
892 output[i] = c.Interpolate(percent)
893 }
894 return output
895}
896
897func f32(f float32, g byte, prec, bitSize int) string {
898 return strconv.FormatFloat(float64(f), g, prec, bitSize)
899}
900func f64(f float64, g byte, prec, bitSize int) string {
901 return strconv.FormatFloat(f, g, prec, bitSize)
902}
903
904func generateSphereVertices(radius float32, stacks, slices int) ([]float32, []uint32) {
905 var vertices []float32
906 var indices []uint32
907
908 // Generate random initial rotation
909 rotationMatrix := randomRotationMatrix()
910
911 // Generate sphere vertices
912 for i := 0; i <= stacks; i++ {
913 phi := float32(i) * float32(m.Pi) / float32(stacks)
914 for j := 0; j <= slices; j++ {
915 theta := float32(j) * 2.0 * float32(m.Pi) / float32(slices)
916 x := radius * float32(m.Sin(float64(phi))) * float32(m.Cos(float64(theta)))
917 y := radius * float32(m.Sin(float64(phi))) * float32(m.Sin(float64(theta)))
918 z := radius * float32(m.Cos(float64(phi)))
919
920 // Apply rotation to the vertex
921 vertex := mgl32.Vec3{z, y, x}
922 rotatedVertex := rotationMatrix.Mul4x1(vertex.Vec4(1.0))
923
924 // Append rotated vertex
925 vertices = append(vertices, rotatedVertex[0], rotatedVertex[1], rotatedVertex[2])
926 }
927 }
928
929 // Generate sphere indices
930 for i := 0; i < stacks; i++ {
931 for j := 0; j <= slices; j++ {
932 indices = append(indices, uint32(i*(slices+1)+j), uint32((i+1)*(slices+1)+j))
933 }
934 }
935
936 return vertices, indices
937}
938
939func randomRotationMatrix() mgl32.Mat4 {
940 // Generate random rotation angles (in radians) using crypto/rand
941 rotX := randomFloat32() * 2 * float32(m.Pi)
942 rotY := randomFloat32() * 2 * float32(m.Pi)
943 rotZ := randomFloat32() * 2 * float32(m.Pi)
944
945 // Create rotation matrices for each axis
946 rotMatrixX := mgl32.HomogRotate3DX(rotX)
947 rotMatrixY := mgl32.HomogRotate3DY(rotY)
948 rotMatrixZ := mgl32.HomogRotate3DZ(rotZ)
949
950 // Combine rotations (Z * Y * X)
951 return rotMatrixZ.Mul4(rotMatrixY).Mul4(rotMatrixX)
952}
953
954func randomFloat32() float32 {
955 var randomValue uint32
956 err := binary.Read(rand.Reader, binary.BigEndian, &randomValue)
957 if err != nil {
958 log.Fatalf("Failed to read random value: %v", err)
959 }
960 return float32(randomValue) / float32(0xFFFFFFFF)
961}
962
963var verticesNative = []float32{
964 -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1,
965 -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1,
966 -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1,
967 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1,
968 -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1,
969 -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1,
970}
971var colorsNative = []float32{
972 5, 3, 7, 5, 3, 7, 5, 3, 7, 5, 3, 7,
973 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3,
974 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
975 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
976 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
977 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
978}
979
980var indicesNative = []uint32{
981 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7,
982 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15,
983 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23,
984}
985
986const vertShaderCode = `
987attribute vec3 position;
988uniform mat4 Pmatrix;
989uniform mat4 Vmatrix;
990uniform mat4 Mmatrix;
991attribute vec3 color;
992varying vec3 vColor;
993
994void main(void) {
995 gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);
996 vColor = color;
997}
998`
999const fragShaderCode = `
1000precision mediump float;
1001varying vec3 vColor;
1002void main(void) {
1003 gl_FragColor = vec4(vColor, 1.);
1004}
1005`
1006const fragShaderCode1 = `
1007precision mediump float;
1008uniform vec3 uBaseColor; // Color value at the base
1009uniform vec3 uTopColor; // Color value at the top
1010varying vec3 vPosition; // Interpolated vertex position
1011void main(void) {
1012 float t = (vPosition.y + 1.0) * 0.5; // Normalize the y-coordinate to [0, 1]
1013 vec3 rainbowColor = mix(uBaseColor, uTopColor, t);
1014 gl_FragColor = vec4(rainbowColor, 1.0);
1015}
1016`
1017const vertShaderCode1 = `
1018 attribute vec3 position;
1019 uniform mat4 Pmatrix;
1020 uniform mat4 Vmatrix;
1021 uniform mat4 Mmatrix;
1022 varying vec3 vPosition; // Pass vertex position to fragment shader
1023 void main(void) {
1024 gl_Position = Pmatrix * Vmatrix * Mmatrix * vec4(position, 1.0);
1025 vPosition = position; // Pass vertex position to fragment shader
1026 }
1027 `
1028
1029// GLTypes provides WebGL bindings.
1030type GLTypes struct {
1031 StaticDraw js.Value
1032 ArrayBuffer js.Value
1033 ElementArrayBuffer js.Value
1034 VertexShader js.Value
1035 FragmentShader js.Value
1036 Float js.Value
1037 DepthTest js.Value
1038 ColorBufferBit js.Value
1039 DepthBufferBit js.Value
1040 Triangles js.Value
1041 UnsignedShort js.Value
1042 UnsignedInt js.Value
1043 LEqual js.Value
1044 LineLoop js.Value
1045 Line js.Value
1046}
1047
1048// New grabs the WebGL bindings from a GL context.
1049func (types *GLTypes) New(gl js.Value) js.Value {
1050 types.StaticDraw = gl.Get("STATIC_DRAW")
1051 types.ArrayBuffer = gl.Get("ARRAY_BUFFER")
1052 types.ElementArrayBuffer = gl.Get("ELEMENT_ARRAY_BUFFER")
1053 types.VertexShader = gl.Get("VERTEX_SHADER")
1054 types.FragmentShader = gl.Get("FRAGMENT_SHADER")
1055 types.Float = gl.Get("FLOAT")
1056 types.DepthTest = gl.Get("DEPTH_TEST")
1057 types.ColorBufferBit = gl.Get("COLOR_BUFFER_BIT")
1058 types.Triangles = gl.Get("TRIANGLES")
1059 types.UnsignedShort = gl.Get("UNSIGNED_SHORT")
1060 types.LEqual = gl.Get("LEQUAL")
1061 types.DepthBufferBit = gl.Get("DEPTH_BUFFER_BIT")
1062 types.LineLoop = gl.Get("LINE_LOOP")
1063 types.Line = gl.Get("LINES")
1064 enabled := gl.Call("getExtension", "OES_element_index_uint")
1065 if !enabled.Truthy() {
1066 return js.Global().Get("Error").New("missing extension: OES_element_index_uint")
1067 }
1068 types.UnsignedInt = gl.Get("UNSIGNED_INT")
1069 return js.Null()
1070}
1071
1072func sliceToByteSlice(s interface{}) []byte {
1073 switch s := s.(type) {
1074 case []int8:
1075 h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1076 return *(*[]byte)(u.Pointer(h)) // nolint
1077 case []int16:
1078 h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1079 h.Len *= 2
1080 h.Cap *= 2
1081 return *(*[]byte)(u.Pointer(h)) // nolint
1082 case []int32:
1083 h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1084 h.Len *= 4
1085 h.Cap *= 4
1086 return *(*[]byte)(u.Pointer(h)) // nolint
1087 case []int64:
1088 h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1089 h.Len *= 8
1090 h.Cap *= 8
1091 return *(*[]byte)(u.Pointer(h)) // nolint
1092 case []uint8:
1093 return s
1094 case []uint16:
1095 h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1096 h.Len *= 2
1097 h.Cap *= 2
1098 return *(*[]byte)(u.Pointer(h)) // nolint
1099 case []uint32:
1100 h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1101 h.Len *= 4
1102 h.Cap *= 4
1103 return *(*[]byte)(u.Pointer(h)) // nolint
1104 case []uint64:
1105 h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1106 h.Len *= 8
1107 h.Cap *= 8
1108 return *(*[]byte)(u.Pointer(h)) // nolint
1109 case []float32:
1110 h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1111 h.Len *= 4
1112 h.Cap *= 4
1113 return *(*[]byte)(u.Pointer(h)) // nolint
1114 case []float64:
1115 h := (*r.SliceHeader)(u.Pointer(&s)) // nolint
1116 h.Len *= 8
1117 h.Cap *= 8
1118 return *(*[]byte)(u.Pointer(h)) // nolint
1119 default:
1120 // panic("jsutil: unexpected value at sliceToBytesSlice: " + r.TypeOf(s).String())
1121 panic("jsutil: unexpected value at sliceToBytesSlice: ")
1122 }
1123}
1124
1125const bo = "byteOffset"
1126const bl = "byteLength"
1127const b = "buffer"
1128const u8a = "Uint8Array"
1129
1130// S2TA converts Slice To TypedArray
1131func S2TA(s interface{}) js.Value {
1132 switch s := s.(type) {
1133 case []int8:
1134 a := js.Global().Get(u8a).New(len(s))
1135 js.CopyBytesToJS(a, sliceToByteSlice(s))
1136 runtime.KeepAlive(s)
1137 buf := a.Get(b)
1138 return js.Global().Get("Int8Array").New(buf, a.Get(bo), a.Get(bl))
1139 case []int16:
1140 a := js.Global().Get(u8a).New(len(s) * 2)
1141 js.CopyBytesToJS(a, sliceToByteSlice(s))
1142 runtime.KeepAlive(s)
1143 buf := a.Get(b)
1144 return js.Global().Get("Int16Array").New(buf, a.Get(bo), a.Get(bl).Int()/2)
1145 case []int32:
1146 a := js.Global().Get(u8a).New(len(s) * 4)
1147 js.CopyBytesToJS(a, sliceToByteSlice(s))
1148 runtime.KeepAlive(s)
1149 buf := a.Get(b)
1150 return js.Global().Get("Int32Array").New(buf, a.Get(bo), a.Get(bl).Int()/4)
1151 case []uint8:
1152 a := js.Global().Get(u8a).New(len(s))
1153 js.CopyBytesToJS(a, s)
1154 runtime.KeepAlive(s)
1155 return a
1156 case []uint16:
1157 a := js.Global().Get(u8a).New(len(s) * 2)
1158 js.CopyBytesToJS(a, sliceToByteSlice(s))
1159 runtime.KeepAlive(s)
1160 buf := a.Get(b)
1161 return js.Global().Get("Uint16Array").New(buf, a.Get(bo), a.Get(bl).Int()/2)
1162 case []uint32:
1163 a := js.Global().Get(u8a).New(len(s) * 4)
1164 js.CopyBytesToJS(a, sliceToByteSlice(s))
1165 runtime.KeepAlive(s)
1166 buf := a.Get(b)
1167 return js.Global().Get("Uint32Array").New(buf, a.Get(bo), a.Get(bl).Int()/4)
1168 case []float32:
1169 a := js.Global().Get(u8a).New(len(s) * 4)
1170 js.CopyBytesToJS(a, sliceToByteSlice(s))
1171 runtime.KeepAlive(s)
1172 buf := a.Get(b)
1173 return js.Global().Get("Float32Array").New(buf, a.Get(bo), a.Get(bl).Int()/4)
1174 case []float64:
1175 a := js.Global().Get(u8a).New(len(s) * 8)
1176 js.CopyBytesToJS(a, sliceToByteSlice(s))
1177 runtime.KeepAlive(s)
1178 buf := a.Get(b)
1179 return js.Global().Get("Float64Array").New(buf, a.Get(bo), a.Get(bl).Int()/8)
1180 default:
1181 // panic("jsutil: unexpected value at S2TA: " + r.TypeOf(s).String())
1182 panic("jsutil: unexpected value at S2TA: ")
1183 }
1184}
1185
1186