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