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