Quellcode durchsuchen

Merge branch 'master' into i177-fullscreen

Daniel Salvadori vor 4 Jahren
Ursprung
Commit
30af58ec69
91 geänderte Dateien mit 1118 neuen und 532 gelöschten Zeilen
  1. 1 1
      .gitattributes
  2. 9 3
      README.md
  3. 4 3
      app/app-desktop.go
  4. 17 33
      audio/al/al.go
  5. 3 2
      audio/audio_file.go
  6. 11 12
      audio/ov/vorbisfile.go
  7. 4 3
      audio/player.go
  8. 10 8
      audio/vorbis/vorbis.go
  9. 13 4
      camera/camera.go
  10. 2 1
      camera/orbit_control.go
  11. 6 1
      core/node.go
  12. 1 1
      experimental/collision/collision.go
  13. 9 9
      experimental/collision/matrix_test.go
  14. 2 1
      experimental/collision/raycaster.go
  15. 0 1
      experimental/collision/shape/capsule.go
  16. 0 1
      experimental/collision/shape/cone.go
  17. 7 10
      experimental/collision/shape/convexhull.go
  18. 0 1
      experimental/collision/shape/cylinder.go
  19. 0 1
      experimental/collision/shape/heightfield.go
  20. 1 1
      experimental/collision/shape/plane.go
  21. 2 2
      experimental/collision/shape/sphere.go
  22. 2 2
      experimental/physics/broadphase.go
  23. 1 1
      experimental/physics/constraint/conetwist.go
  24. 2 2
      experimental/physics/constraint/distance.go
  25. 2 2
      experimental/physics/constraint/hinge.go
  26. 1 1
      experimental/physics/constraint/lock.go
  27. 5 5
      experimental/physics/constraint/pointtopoint.go
  28. 3 3
      experimental/physics/equation/cone.go
  29. 7 7
      experimental/physics/equation/contact.go
  30. 7 7
      experimental/physics/equation/equation.go
  31. 3 3
      experimental/physics/equation/friction.go
  32. 1 1
      experimental/physics/equation/rotational.go
  33. 1 1
      experimental/physics/equation/rotationalmotor.go
  34. 2 2
      experimental/physics/forcefield.go
  35. 116 118
      experimental/physics/narrowphase.go
  36. 18 18
      experimental/physics/object/body.go
  37. 6 6
      experimental/physics/solver/gs.go
  38. 2 1
      geometry/cone-cylinder.go
  39. 2 1
      geometry/disk.go
  40. 3 2
      geometry/morph.go
  41. 2 1
      geometry/sphere.go
  42. 2 1
      geometry/torus.go
  43. 39 38
      geometry/tube.go
  44. 1 1
      gls/build.go
  45. 24 17
      gls/gls-browser.go
  46. 81 0
      gls/gls-desktop.go
  47. 2 1
      gls/gls.go
  48. 1 1
      gls/shaderdefines.go
  49. 4 4
      go.mod
  50. 8 7
      go.sum
  51. 1 1
      graphic/graphic.go
  52. 2 1
      graphic/rigged_mesh.go
  53. 1 1
      graphic/skeleton.go
  54. 11 2
      graphic/sprite.go
  55. 9 9
      gui/assets/data.go
  56. 75 75
      gui/assets/icon/icodes.go
  57. 2 1
      gui/chart.go
  58. 3 2
      gui/edit.go
  59. 2 1
      gui/image.go
  60. 2 1
      gui/itemscroller.go
  61. 2 1
      loader/collada/animation.go
  62. 3 2
      loader/collada/collada.go
  63. 179 3
      loader/collada/geometry.go
  64. 42 0
      loader/collada/library_geometries.go
  65. 3 2
      loader/collada/material.go
  66. 2 1
      loader/collada/scene.go
  67. 6 5
      loader/gltf/gltf.go
  68. 3 3
      loader/gltf/material_pbr.go
  69. 2 5
      loader/obj/obj.go
  70. 6 0
      material/material.go
  71. 10 0
      material/standard.go
  72. 2 2
      math32/array.go
  73. 22 25
      math32/curves.go
  74. 20 0
      math32/matrix4.go
  75. 5 5
      math32/quaternion.go
  76. 24 0
      math32/vector2.go
  77. 13 8
      math32/vector3.go
  78. 18 1
      math32/vector4.go
  79. 2 1
      renderer/renderer.go
  80. 23 3
      renderer/shaders/include/phong_model.glsl
  81. 23 3
      renderer/shaders/sources.go
  82. 2 1
      renderer/shaman.go
  83. 2 1
      text/atlas.go
  84. 5 4
      text/font.go
  85. 2 1
      texture/animator.go
  86. 8 1
      texture/texture2D.go
  87. 1 1
      tools/g3nshaders/main.go
  88. 2 1
      util/stats/stats.go
  89. 122 0
      util/wasm/wasm.go
  90. 3 2
      window/canvas.go
  91. 3 3
      window/glfw.go

+ 1 - 1
.gitattributes

@@ -1 +1 @@
-audio/windows/* linguist-vendored
+audio/windows/** linguist-vendored

+ 9 - 3
README.md

@@ -2,7 +2,9 @@
 <p align="center"><img width="150" src="https://github.com/g3n/g3nd/blob/master/data/images/g3n_logo.png" alt="G3N Banner"/></p>
 <p align="center">
   <a href="https://godoc.org/github.com/g3n/engine"><img src="https://godoc.org/github.com/g3n/engine?status.svg" alt="Godoc"></img></a>
-  <a href="https://goreportcard.com/report/github.com/g3n/engine"><img src="https://goreportcard.com/badge/github.com/g3n/engine"  alt="Go Report Card"/></a>
+  <a href="https://goreportcard.com/report/github.com/g3n/engine"><img src="https://goreportcard.com/badge/github.com/g3n/engine" alt="Go Report Card"/></a>
+  <a href="https://discord.gg/Dq9FpV4c"><img src="https://img.shields.io/badge/Discord-G3N-blue" alt="Discord"/></a>
+  
 </p>
 <p><h1 align="center">G3N - Go 3D Game Engine</h1></p>
 
@@ -78,7 +80,7 @@ go install ./...
 * Geometry generators: box, sphere, cylinder, torus, etc...
 * Geometries support morph targets and multimaterials
 * Support for animated sprites based on sprite sheets
-* Perspective and ortographic cameras
+* Perspective and orthographic cameras
 * Text image generation and support for TrueType fonts
 * Image textures can be loaded from GIF, PNG or JPEG files
 * Animation framework for position, rotation, and scale of objects
@@ -204,4 +206,8 @@ If you find a bug or create a new feature you are encouraged to send pull reques
 
 ## Community
 
-Join our [channel](https://gophers.slack.com/messages/g3n) on Gophers Slack ([Click here to register for Gophers Slack](https://invite.slack.golangbridge.org/)). It's a great way to have your questions answered quickly by the G3N community.
+Join our [Discord channel](https://discord.gg/NfaeVr8zDg). It's the best way to have your questions answered quickly by the G3N community.
+
+## Stargazers over time
+
+[![Stargazers over time](https://starchart.cc/g3n/engine.svg)](https://starchart.cc/g3n/engine)

+ 4 - 3
app/app-desktop.go

@@ -8,11 +8,12 @@ package app
 
 import (
 	"fmt"
+	"time"
+
 	"github.com/g3n/engine/audio/al"
 	"github.com/g3n/engine/audio/vorbis"
 	"github.com/g3n/engine/renderer"
 	"github.com/g3n/engine/window"
-	"time"
 )
 
 // Desktop application defaults
@@ -67,7 +68,7 @@ func (a *Application) Run(update func(rend *renderer.Renderer, deltaTime time.Du
 	a.frameStart = time.Now()
 
 	// Set up recurring calls to user's update function
-	for true {
+	for {
 		// If Exit() was called or there was an attempt to close the window dispatch OnExit event for subscribers.
 		// If no subscriber cancelled the event, terminate the application.
 		if a.IWindow.(*window.GlfwWindow).ShouldClose() {
@@ -121,7 +122,7 @@ func (a *Application) KeyState() *window.KeyState {
 // RunTime returns the elapsed duration since the call to Run().
 func (a *Application) RunTime() time.Duration {
 
-	return time.Now().Sub(a.startTime)
+	return time.Since(a.startTime)
 }
 
 // openDefaultAudioDevice opens the default audio device setting it to the current context

+ 17 - 33
audio/al/al.go

@@ -6,14 +6,16 @@
 // The OpenAL documentation can be accessed at https://openal.org/documentation/
 package al
 
-// #cgo darwin   CFLAGS:  -DGO_DARWIN  -I/usr/local/opt/openal-soft/include/AL -I/usr/include/AL
-// #cgo freebsd  CFLAGS:  -DGO_FREEBSD -I/usr/local/include/AL
-// #cgo linux    CFLAGS:  -DGO_LINUX   -I/usr/include/AL
-// #cgo windows  CFLAGS:  -DGO_WINDOWS -I${SRCDIR}/../windows/openal-soft-1.18.2/include/AL
-// #cgo darwin   LDFLAGS: -L/usr/local/opt/openal-soft/lib -lopenal
-// #cgo freebsd  LDFLAGS: -L/usr/local/lib -lopenal
-// #cgo linux    LDFLAGS: -lopenal
-// #cgo windows  LDFLAGS: -L${SRCDIR}/../windows/bin -lOpenAL32
+// #cgo darwin,amd64  CFLAGS:  -DGO_DARWIN  -I/usr/local/opt/openal-soft/include/AL -I/usr/include/AL
+// #cgo darwin,arm64  CFLAGS:  -DGO_DARWIN  -I/opt/homebrew/opt/openal-soft/include/AL
+// #cgo freebsd       CFLAGS:  -DGO_FREEBSD -I/usr/local/include/AL
+// #cgo linux         CFLAGS:  -DGO_LINUX   -I/usr/include/AL
+// #cgo windows       CFLAGS:  -DGO_WINDOWS -I${SRCDIR}/../windows/openal-soft-1.18.2/include/AL
+// #cgo darwin,amd64  LDFLAGS: -L/usr/local/opt/openal-soft/lib -lopenal
+// #cgo darwin,arm64  LDFLAGS: -L/opt/homebrew/opt/openal-soft/lib -lopenal
+// #cgo freebsd       LDFLAGS: -L/usr/local/lib -lopenal
+// #cgo linux         LDFLAGS: -lopenal
+// #cgo windows       LDFLAGS: -L${SRCDIR}/../windows/bin -lOpenAL32
 // #include <stdlib.h>
 // #include "al.h"
 // #include "alc.h"
@@ -375,10 +377,7 @@ func CtxIsExtensionPresent(dev *Device, extname string) bool {
 	cname := (*C.ALCchar)(C.CString(extname))
 	defer C.free(unsafe.Pointer(cname))
 	cres := C.alcIsExtensionPresent(dev.cdev, cname)
-	if cres == C.AL_TRUE {
-		return true
-	}
-	return false
+	return cres == C.AL_TRUE
 }
 
 func CtxGetEnumValue(dev *Device, enumName string) uint32 {
@@ -457,10 +456,7 @@ func Disable(capability uint) {
 func IsEnabled(capability uint) bool {
 
 	cres := C.alIsEnabled(C.ALenum(capability))
-	if cres == C.AL_TRUE {
-		return true
-	}
-	return false
+	return cres == C.AL_TRUE
 }
 
 func GetString(param uint32) string {
@@ -500,10 +496,7 @@ func GetDoublev(param uint32, values []float64) {
 func GetBoolean(param uint32) bool {
 
 	cres := C.alGetBoolean(C.ALenum(param))
-	if cres == C.AL_TRUE {
-		return true
-	}
-	return false
+	return cres == C.AL_TRUE
 }
 
 func GetInteger(param uint32) int32 {
@@ -538,10 +531,7 @@ func IsExtensionPresent(extName string) bool {
 	cstr := (*C.ALchar)(C.CString(extName))
 	defer C.free(unsafe.Pointer(cstr))
 	cres := C.alIsExtensionPresent(cstr)
-	if cres == 0 {
-		return false
-	}
-	return true
+	return cres != 0
 }
 
 func GetEnumValue(enam string) uint32 {
@@ -598,7 +588,7 @@ func GetListener3f(param uint32) (float32, float32, float32) {
 	return float32(cval1), float32(cval2), float32(cval3)
 }
 
-func GetListenerfv(param uint32, values []uint32) {
+func GetListenerfv(param uint32, values []float32) {
 
 	C.alGetListenerfv(C.ALenum(param), (*C.ALfloat)(unsafe.Pointer(&values[0])))
 }
@@ -656,10 +646,7 @@ func DeleteSources(sources []uint32) {
 func IsSource(source uint32) bool {
 
 	cres := C.alIsSource(C.ALuint(source))
-	if cres == C.AL_TRUE {
-		return true
-	}
-	return false
+	return cres == C.AL_TRUE
 }
 
 func Sourcef(source uint32, param uint32, value float32) {
@@ -812,10 +799,7 @@ func DeleteBuffers(buffers []uint32) {
 func IsBuffer(buffer uint32) bool {
 
 	cres := C.alIsBuffer(C.ALuint(buffer))
-	if cres == C.AL_TRUE {
-		return true
-	}
-	return false
+	return cres == C.AL_TRUE
 }
 
 func BufferData(buffer uint32, format uint32, data unsafe.Pointer, size uint32, freq uint32) {

+ 3 - 2
audio/audio_file.go

@@ -8,11 +8,12 @@ package audio
 
 import (
 	"fmt"
-	"github.com/g3n/engine/audio/al"
-	"github.com/g3n/engine/audio/ov"
 	"io"
 	"os"
 	"unsafe"
+
+	"github.com/g3n/engine/audio/al"
+	"github.com/g3n/engine/audio/ov"
 )
 
 const (

+ 11 - 12
audio/ov/vorbisfile.go

@@ -6,14 +6,16 @@
 // The libvorbisfile C API reference is at: https://xiph.org/vorbis/doc/vorbisfile/reference.html
 package ov
 
-// #cgo darwin   CFLAGS:  -DGO_DARWIN  -I/usr/include/vorbis -I/usr/local/include/vorbis
-// #cgo freebsd  CFLAGS:  -DGO_FREEBSD -I/usr/include/vorbis -I/usr/local/include/vorbis
-// #cgo linux    CFLAGS:  -DGO_LINUX   -I/usr/include/vorbis
-// #cgo windows  CFLAGS:  -DGO_WINDOWS -I${SRCDIR}/../windows/libvorbis-1.3.5/include/vorbis -I${SRCDIR}/../windows/libogg-1.3.3/include
-// #cgo darwin   LDFLAGS: -L/usr/lib -L/usr/local/lib -lvorbisfile
-// #cgo freebsd  LDFLAGS: -L/usr/lib -L/usr/local/lib -lvorbisfile
-// #cgo linux    LDFLAGS: -lvorbisfile
-// #cgo windows  LDFLAGS: -L${SRCDIR}/../windows/bin -llibvorbisfile
+// #cgo darwin,amd64  CFLAGS:  -DGO_DARWIN  -I/usr/include/vorbis -I/usr/local/include/vorbis
+// #cgo darwin,arm64  CFLAGS:  -DGO_DARWIN  -I/opt/homebrew/include -I/opt/homebrew/include/vorbis
+// #cgo freebsd       CFLAGS:  -DGO_FREEBSD -I/usr/include/vorbis -I/usr/local/include/vorbis
+// #cgo linux         CFLAGS:  -DGO_LINUX   -I/usr/include/vorbis
+// #cgo windows       CFLAGS:  -DGO_WINDOWS -I${SRCDIR}/../windows/libvorbis-1.3.5/include/vorbis -I${SRCDIR}/../windows/libogg-1.3.3/include
+// #cgo darwin,amd64  LDFLAGS: -L/usr/lib -L/usr/local/lib -lvorbisfile
+// #cgo darwin,arm64  LDFLAGS: -L/opt/homebrew/lib -lvorbisfile
+// #cgo freebsd       LDFLAGS: -L/usr/lib -L/usr/local/lib -lvorbisfile
+// #cgo linux         LDFLAGS: -lvorbisfile
+// #cgo windows       LDFLAGS: -L${SRCDIR}/../windows/bin -llibvorbisfile
 // #include <stdlib.h>
 // #include "vorbisfile.h"
 import "C"
@@ -138,10 +140,7 @@ func Info(f *File, link int, info *VorbisInfo) error {
 func Seekable(f *File) bool {
 
 	cres := C.ov_seekable(f.vf)
-	if cres == 0 {
-		return false
-	}
-	return true
+	return cres != 0
 }
 
 // Seek seeks to the offset specified (in number pcm samples) within the physical bitstream.

+ 4 - 3
audio/player.go

@@ -10,13 +10,14 @@ package audio
 import "C"
 
 import (
+	"io"
+	"time"
+	"unsafe"
+
 	"github.com/g3n/engine/audio/al"
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
-	"io"
-	"time"
-	"unsafe"
 )
 
 const (

+ 10 - 8
audio/vorbis/vorbis.go

@@ -6,14 +6,16 @@
 // See API reference at: https://xiph.org/vorbis/doc/libvorbis/reference.html
 package vorbis
 
-// #cgo darwin   CFLAGS:  -DGO_DARWIN  -I/usr/include/vorbis -I/usr/local/include/vorbis
-// #cgo freebsd  CFLAGS:  -DGO_FREEBSD -I/usr/local/include/vorbis
-// #cgo linux    CFLAGS:  -DGO_LINUX   -I/usr/include/vorbis
-// #cgo windows  CFLAGS:  -DGO_WINDOWS -I${SRCDIR}/../windows/libvorbis-1.3.5/include/vorbis -I${SRCDIR}/../windows/libogg-1.3.3/include
-// #cgo darwin   LDFLAGS: -L/usr/lib -L/usr/local/lib -lvorbis
-// #cgo freebsd  LDFLAGS: -L/usr/local/lib -lvorbis
-// #cgo linux    LDFLAGS: -lvorbis
-// #cgo windows  LDFLAGS: -L${SRCDIR}/../windows/bin -llibvorbis
+// #cgo darwin,amd64  CFLAGS:  -DGO_DARWIN  -I/usr/include/vorbis -I/usr/local/include/vorbis
+// #cgo darwin,arm64  CFLAGS:  -DGO_DARWIN  -I/opt/homebrew/include -I/opt/homebrew/include/vorbis
+// #cgo freebsd       CFLAGS:  -DGO_FREEBSD -I/usr/local/include/vorbis
+// #cgo linux         CFLAGS:  -DGO_LINUX   -I/usr/include/vorbis
+// #cgo windows       CFLAGS:  -DGO_WINDOWS -I${SRCDIR}/../windows/libvorbis-1.3.5/include/vorbis -I${SRCDIR}/../windows/libogg-1.3.3/include
+// #cgo darwin,amd64  LDFLAGS: -L/usr/lib -L/usr/local/lib -lvorbis
+// #cgo darwin,arm64  LDFLAGS: -L/opt/homebrew/lib -lvorbis
+// #cgo freebsd       LDFLAGS: -L/usr/local/lib -lvorbis
+// #cgo linux         LDFLAGS: -lvorbis
+// #cgo windows       LDFLAGS: -L${SRCDIR}/../windows/bin -llibvorbis
 // #include "codec.h"
 import "C"
 

+ 13 - 4
camera/camera.go

@@ -242,11 +242,20 @@ func (c *Camera) ProjMatrix(m *math32.Matrix4) {
 	if c.projChanged {
 		switch c.proj {
 		case Perspective:
-			fov := c.fov
-			if c.axis == Horizontal {
-				fov *= c.aspect
+			t := c.near * math32.Tan(math32.DegToRad(c.fov*0.5))
+			ymax := t
+			ymin := -t
+			xmax := t
+			xmin := -t
+			switch c.axis {
+			case Vertical:
+				xmax *= c.aspect
+				xmin *= c.aspect
+			case Horizontal:
+				ymax /= c.aspect
+				ymin /= c.aspect
 			}
-			c.projMatrix.MakePerspective(c.fov, c.aspect, c.near, c.far)
+			c.projMatrix.MakeFrustum(xmin, xmax, ymin, ymax, c.near, c.far)
 		case Orthographic:
 			s := c.size / 2
 			var h, w float32

+ 2 - 1
camera/orbit_control.go

@@ -5,11 +5,12 @@
 package camera
 
 import (
+	"math"
+
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/gui"
 	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/window"
-	"math"
 )
 
 // OrbitEnabled specifies which control types are enabled.

+ 6 - 1
core/node.go

@@ -132,7 +132,11 @@ func (n *Node) BoundingBox() math32.Box3 {
 func (n *Node) Render(gs *gls.GLS) {}
 
 // Dispose satisfies the INode interface.
-func (n *Node) Dispose() {}
+func (n *Node) Dispose() {
+	for _, child := range n.children {
+		child.Dispose()
+	}
+}
 
 // Clone clones the Node and satisfies the INode interface.
 func (n *Node) Clone() INode {
@@ -142,6 +146,7 @@ func (n *Node) Clone() INode {
 	// TODO clone Dispatcher?
 	clone.Dispatcher.Initialize()
 
+	clone.inode = clone
 	clone.parent = n.parent
 	clone.name = n.name + " (Clone)" // TODO append count?
 	clone.loaderID = n.loaderID

+ 1 - 1
experimental/collision/collision.go

@@ -13,4 +13,4 @@ import "github.com/g3n/engine/geometry"
 func CheckConvex(g1, g2 *geometry.Geometry) bool {
 
 	return true
-}
+}

+ 9 - 9
experimental/collision/matrix_test.go

@@ -13,24 +13,24 @@ func Test(t *testing.T) {
 
 	// m.Get(1, 1) // panics with "runtime error: index out of range" as expected
 
-	m.Set(2,4, true)
-	m.Set(3,2, true)
-	if m.Get(1,1) != false {
+	m.Set(2, 4, true)
+	m.Set(3, 2, true)
+	if m.Get(1, 1) != false {
 		t.Error("Get failed")
 	}
-	if m.Get(2,4) != true {
+	if m.Get(2, 4) != true {
 		t.Error("Get failed")
 	}
-	if m.Get(3,2) != true {
+	if m.Get(3, 2) != true {
 		t.Error("Get failed")
 	}
 
-	m.Set(2,4, false)
-	m.Set(3,2, false)
-	if m.Get(2,4) != false {
+	m.Set(2, 4, false)
+	m.Set(3, 2, false)
+	if m.Get(2, 4) != false {
 		t.Error("Get failed")
 	}
-	if m.Get(3,2) != false {
+	if m.Get(3, 2) != false {
 		t.Error("Get failed")
 	}
 

+ 2 - 1
experimental/collision/raycaster.go

@@ -5,13 +5,14 @@
 package collision
 
 import (
+	"sort"
+
 	"github.com/g3n/engine/camera"
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/graphic"
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/math32"
-	"sort"
 )
 
 // Raycaster represents an empty object that can cast rays and check for ray intersections.

+ 0 - 1
experimental/collision/shape/capsule.go

@@ -3,4 +3,3 @@
 // license that can be found in the LICENSE file.
 
 package shape
-

+ 0 - 1
experimental/collision/shape/cone.go

@@ -3,4 +3,3 @@
 // license that can be found in the LICENSE file.
 
 package shape
-

+ 7 - 10
experimental/collision/shape/convexhull.go

@@ -5,9 +5,9 @@
 package shape
 
 import (
+	"github.com/g3n/engine/experimental/collision"
 	"github.com/g3n/engine/geometry"
 	"github.com/g3n/engine/math32"
-	"github.com/g3n/engine/experimental/collision"
 )
 
 // ConvexHull is a convex triangle-based geometry used for collision detection and contact resolution.
@@ -20,7 +20,6 @@ type ConvexHull struct {
 	worldFaceNormals []math32.Vector3
 	uniqueEdges      []math32.Vector3
 	worldUniqueEdges []math32.Vector3
-
 }
 
 func NewConvexHull(geom *geometry.Geometry) *ConvexHull {
@@ -357,7 +356,6 @@ func (ch *ConvexHull) clipFaceAgainstHull(posA, penAxis *math32.Vector3, quatA *
 		}
 	}
 
-
 	worldClosestFaceA := ch.WorldFace(closestFaceA, posA, quatA)
 	// DEBUGGING
 	//if n.debugging {
@@ -386,7 +384,7 @@ func (ch *ConvexHull) clipFaceAgainstHull(posA, penAxis *math32.Vector3, quatA *
 		connFaceNormal := worldFaceNormalsA[cfidx]
 		// Choose a vertex in the connected face and use it to find the plane constant
 		worldFirstVertex := connFace[0].Clone().ApplyQuaternion(quatA).Add(posA)
-		planeDelta := - worldFirstVertex.Dot(&connFaceNormal)
+		planeDelta := -worldFirstVertex.Dot(&connFaceNormal)
 		clippedFace = ch.clipFaceAgainstPlane(clippedFace, connFaceNormal.Clone(), planeDelta)
 	}
 
@@ -397,7 +395,7 @@ func (ch *ConvexHull) clipFaceAgainstHull(posA, penAxis *math32.Vector3, quatA *
 	//}
 
 	closestFaceAnormal := worldFaceNormalsA[closestFaceAidx]
-	worldFirstVertex := worldClosestFaceA[0].Clone()//.ApplyQuaternion(quatA).Add(&posA)
+	worldFirstVertex := worldClosestFaceA[0].Clone() //.ApplyQuaternion(quatA).Add(&posA)
 	planeDelta := -worldFirstVertex.Dot(&closestFaceAnormal)
 
 	// For each vertex in the clipped face resolve its depth (relative to the closestFaceA) and create a contact
@@ -410,9 +408,9 @@ func (ch *ConvexHull) clipFaceAgainstHull(posA, penAxis *math32.Vector3, quatA *
 		if depth <= maxDist {
 			if depth <= 0 {
 				contacts = append(contacts, collision.Contact{
-					Point: vertex,
+					Point:  vertex,
 					Normal: closestFaceAnormal,
-					Depth: depth,
+					Depth:  depth,
 				})
 			}
 		}
@@ -422,7 +420,6 @@ func (ch *ConvexHull) clipFaceAgainstHull(posA, penAxis *math32.Vector3, quatA *
 	return contacts
 }
 
-
 // clipFaceAgainstPlane clips the specified face against the back of the specified plane.
 // This is used multiple times when finding the portion of a face of one convex hull that is inside another convex hull.
 // planeNormal and planeConstant satisfy the plane equation n*x = p where n is the planeNormal and p is the planeConstant (and x is a point on the plane).
@@ -447,12 +444,12 @@ func (ch *ConvexHull) clipFaceAgainstPlane(face []math32.Vector3, planeNormal *m
 			if dotLast < 0 { // Start < 0, end < 0, so output lastVertex
 				clippedFace = append(clippedFace, lastVertex)
 			} else { // Start < 0, end >= 0, so output intersection
-				newv := firstVertex.Clone().Lerp(&lastVertex, dotFirst / (dotFirst - dotLast))
+				newv := firstVertex.Clone().Lerp(&lastVertex, dotFirst/(dotFirst-dotLast))
 				clippedFace = append(clippedFace, *newv)
 			}
 		} else { // Outside hull
 			if dotLast < 0 { // Start >= 0, end < 0 so output intersection and end
-				newv := firstVertex.Clone().Lerp(&lastVertex, dotFirst / (dotFirst - dotLast))
+				newv := firstVertex.Clone().Lerp(&lastVertex, dotFirst/(dotFirst-dotLast))
 				clippedFace = append(clippedFace, *newv)
 				clippedFace = append(clippedFace, lastVertex)
 			}

+ 0 - 1
experimental/collision/shape/cylinder.go

@@ -3,4 +3,3 @@
 // license that can be found in the LICENSE file.
 
 package shape
-

+ 0 - 1
experimental/collision/shape/heightfield.go

@@ -3,4 +3,3 @@
 // license that can be found in the LICENSE file.
 
 package shape
-

+ 1 - 1
experimental/collision/shape/plane.go

@@ -16,7 +16,7 @@ type Plane struct {
 func NewPlane() *Plane {
 
 	p := new(Plane)
-	p.normal = *math32.NewVector3(0,0,1) //*normal
+	p.normal = *math32.NewVector3(0, 0, 1) //*normal
 	return p
 }
 

+ 2 - 2
experimental/collision/shape/sphere.go

@@ -54,13 +54,13 @@ func (s *Sphere) Area() float32 {
 // Volume computes and returns the volume of the analytical collision sphere.
 func (s *Sphere) Volume() float32 {
 
-	return (4/3) * math32.Pi * s.radius * s.radius * s.radius
+	return (4 / 3) * math32.Pi * s.radius * s.radius * s.radius
 }
 
 // RotationalInertia computes and returns the rotational inertia of the analytical collision sphere.
 func (s *Sphere) RotationalInertia(mass float32) math32.Matrix3 {
 
-	v := (2/5) * mass * s.radius * s.radius
+	v := (2 / 5) * mass * s.radius * s.radius
 	return *math32.NewMatrix3().Set(
 		v, 0, 0,
 		0, v, 0,

+ 2 - 2
experimental/physics/broadphase.go

@@ -16,7 +16,7 @@ type CollisionPair struct {
 }
 
 // Broadphase is the base class for broadphase implementations.
-type Broadphase struct {}
+type Broadphase struct{}
 
 // NewBroadphase creates and returns a pointer to a new Broadphase.
 func NewBroadphase() *Broadphase {
@@ -28,7 +28,7 @@ func NewBroadphase() *Broadphase {
 // FindCollisionPairs (naive implementation)
 func (b *Broadphase) FindCollisionPairs(objects []*object.Body) []CollisionPair {
 
-	pairs := make([]CollisionPair,0)
+	pairs := make([]CollisionPair, 0)
 
 	for iA, bodyA := range objects {
 		for _, bodyB := range objects[iA+1:] {

+ 1 - 1
experimental/physics/constraint/conetwist.go

@@ -5,8 +5,8 @@
 package constraint
 
 import (
-	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/experimental/physics/equation"
+	"github.com/g3n/engine/math32"
 )
 
 // ConeTwist constraint.

+ 2 - 2
experimental/physics/constraint/distance.go

@@ -12,8 +12,8 @@ import (
 // Constrains two bodies to be at a constant distance from each others center of mass.
 type Distance struct {
 	Constraint
-	distance   float32 // Distance
-	equation   *equation.Contact
+	distance float32 // Distance
+	equation *equation.Contact
 }
 
 // NewDistance creates and returns a pointer to a new Distance constraint object.

+ 2 - 2
experimental/physics/constraint/hinge.go

@@ -14,8 +14,8 @@ import (
 // It tries to keep the door in the correct place and with the correct orientation.
 type Hinge struct {
 	PointToPoint
-	axisA   *math32.Vector3           // Rotation axis, defined locally in bodyA.
-	axisB   *math32.Vector3           // Rotation axis, defined locally in bodyB.
+	axisA   *math32.Vector3 // Rotation axis, defined locally in bodyA.
+	axisB   *math32.Vector3 // Rotation axis, defined locally in bodyB.
 	rotEq1  *equation.Rotational
 	rotEq2  *equation.Rotational
 	motorEq *equation.RotationalMotor

+ 1 - 1
experimental/physics/constraint/lock.go

@@ -43,7 +43,7 @@ func NewLock(bodyA, bodyB IBody, maxForce float32) *Lock {
 	lc.initialize(bodyA, bodyB, &pivotA, &pivotB, maxForce)
 
 	// Store initial rotation of the bodies as unit vectors in the local body spaces
-	UnitX := math32.NewVector3(1,0,0)
+	UnitX := math32.NewVector3(1, 0, 0)
 
 	localA := bodyA.VectorToLocal(UnitX)
 	localB := bodyB.VectorToLocal(UnitX)

+ 5 - 5
experimental/physics/constraint/pointtopoint.go

@@ -13,8 +13,8 @@ import (
 // Connects two bodies at the specified offset points.
 type PointToPoint struct {
 	Constraint
-	pivotA *math32.Vector3   // Pivot, defined locally in bodyA.
-	pivotB *math32.Vector3   // Pivot, defined locally in bodyB.
+	pivotA *math32.Vector3 // Pivot, defined locally in bodyA.
+	pivotB *math32.Vector3 // Pivot, defined locally in bodyB.
 	eqX    *equation.Contact
 	eqY    *equation.Contact
 	eqZ    *equation.Contact
@@ -40,9 +40,9 @@ func (ptpc *PointToPoint) initialize(bodyA, bodyB IBody, pivotA, pivotB *math32.
 	ptpc.eqY = equation.NewContact(bodyA, bodyB, -maxForce, maxForce)
 	ptpc.eqZ = equation.NewContact(bodyA, bodyB, -maxForce, maxForce)
 
-	ptpc.eqX.SetNormal(&math32.Vector3{1,0,0})
-	ptpc.eqY.SetNormal(&math32.Vector3{0,1,0})
-	ptpc.eqZ.SetNormal(&math32.Vector3{0,0,1})
+	ptpc.eqX.SetNormal(&math32.Vector3{1, 0, 0})
+	ptpc.eqY.SetNormal(&math32.Vector3{0, 1, 0})
+	ptpc.eqZ.SetNormal(&math32.Vector3{0, 0, 1})
 
 	ptpc.AddEquation(&ptpc.eqX.Equation)
 	ptpc.AddEquation(&ptpc.eqY.Equation)

+ 3 - 3
experimental/physics/equation/cone.go

@@ -12,9 +12,9 @@ import (
 // Works to keep the given body world vectors aligned, or tilted within a given angle from each other.
 type Cone struct {
 	Equation
-	axisA    *math32.Vector3 // Local axis in A
-	axisB    *math32.Vector3 // Local axis in B
-	angle    float32         // The "cone angle" to keep
+	axisA *math32.Vector3 // Local axis in A
+	axisB *math32.Vector3 // Local axis in B
+	angle float32         // The "cone angle" to keep
 }
 
 // NewCone creates and returns a pointer to a new Cone equation object.

+ 7 - 7
experimental/physics/equation/contact.go

@@ -25,9 +25,9 @@ func NewContact(bodyA, bodyB IBody, minForce, maxForce float32) *Contact {
 	// minForce default should be 0.
 
 	ce.restitution = 0.5
-	ce.rA = &math32.Vector3{0,0,0}
-	ce.rB = &math32.Vector3{0,0,0}
-	ce.nA = &math32.Vector3{0,0,0}
+	ce.rA = &math32.Vector3{0, 0, 0}
+	ce.rB = &math32.Vector3{0, 0, 0}
+	ce.nA = &math32.Vector3{0, 0, 0}
 
 	ce.Equation.initialize(bodyA, bodyB, minForce, maxForce)
 
@@ -44,7 +44,7 @@ func (ce *Contact) Restitution() float32 {
 	return ce.restitution
 }
 
-func (ce *Contact) SetNormal(newNormal *math32.Vector3)  {
+func (ce *Contact) SetNormal(newNormal *math32.Vector3) {
 
 	ce.nA = newNormal
 }
@@ -54,7 +54,7 @@ func (ce *Contact) Normal() math32.Vector3 {
 	return *ce.nA
 }
 
-func (ce *Contact) SetRA(newRa *math32.Vector3)  {
+func (ce *Contact) SetRA(newRa *math32.Vector3) {
 
 	ce.rA = newRa
 }
@@ -64,7 +64,7 @@ func (ce *Contact) RA() math32.Vector3 {
 	return *ce.rA
 }
 
-func (ce *Contact) SetRB(newRb *math32.Vector3)  {
+func (ce *Contact) SetRB(newRb *math32.Vector3) {
 
 	ce.rB = newRb
 }
@@ -103,7 +103,7 @@ func (ce *Contact) ComputeB(h float32) float32 {
 
 	// Compute iteration
 	ePlusOne := ce.restitution + 1
-	GW := ePlusOne * vB.Dot(ce.nA) - ePlusOne * vA.Dot(ce.nA) + wB.Dot(rnB) - wA.Dot(rnA)
+	GW := ePlusOne*vB.Dot(ce.nA) - ePlusOne*vA.Dot(ce.nA) + wB.Dot(rnB) - wA.Dot(rnA)
 	GiMf := ce.ComputeGiMf()
 
 	return -g*ce.a - GW*ce.b - h*GiMf

+ 7 - 7
experimental/physics/equation/equation.go

@@ -45,13 +45,13 @@ type IEquation interface {
 // Equation is a SPOOK constraint equation.
 type Equation struct {
 	id         int
-	minForce   float32       // Minimum (read: negative max) force to be applied by the constraint.
-	maxForce   float32       // Maximum (read: positive max) force to be applied by the constraint.
-	bA         IBody // Body "i"
-	bB         IBody // Body "j"
-	a          float32       // SPOOK parameter
-	b          float32       // SPOOK parameter
-	eps        float32       // SPOOK parameter
+	minForce   float32 // Minimum (read: negative max) force to be applied by the constraint.
+	maxForce   float32 // Maximum (read: positive max) force to be applied by the constraint.
+	bA         IBody   // Body "i"
+	bB         IBody   // Body "j"
+	a          float32 // SPOOK parameter
+	b          float32 // SPOOK parameter
+	eps        float32 // SPOOK parameter
 	jeA        JacobianElement
 	jeB        JacobianElement
 	enabled    bool

+ 3 - 3
experimental/physics/equation/friction.go

@@ -31,7 +31,7 @@ func NewFriction(bodyA, bodyB IBody, slipForce float32) *Friction {
 	return fe
 }
 
-func (fe *Friction) SetTangent(newTangent *math32.Vector3)  {
+func (fe *Friction) SetTangent(newTangent *math32.Vector3) {
 
 	fe.t = newTangent
 }
@@ -41,7 +41,7 @@ func (fe *Friction) Tangent() math32.Vector3 {
 	return *fe.t
 }
 
-func (fe *Friction) SetRA(newRa *math32.Vector3)  {
+func (fe *Friction) SetRA(newRa *math32.Vector3) {
 
 	fe.rA = newRa
 }
@@ -51,7 +51,7 @@ func (fe *Friction) RA() math32.Vector3 {
 	return *fe.rA
 }
 
-func (fe *Friction) SetRB(newRb *math32.Vector3)  {
+func (fe *Friction) SetRB(newRb *math32.Vector3) {
 
 	fe.rB = newRb
 }

+ 1 - 1
experimental/physics/equation/rotational.go

@@ -14,7 +14,7 @@ type Rotational struct {
 	Equation
 	axisA    *math32.Vector3 // Local axis in A
 	axisB    *math32.Vector3 // Local axis in B
-	maxAngle float32        // Max angle
+	maxAngle float32         // Max angle
 }
 
 // NewRotational creates and returns a pointer to a new Rotational equation object.

+ 1 - 1
experimental/physics/equation/rotationalmotor.go

@@ -11,7 +11,7 @@ import (
 // RotationalMotor is a rotational motor constraint equation.
 // Tries to keep the relative angular velocity of the bodies to a given value.
 type RotationalMotor struct {
-	Equation // TODO maybe this should embed Rotational instead ?
+	Equation                    // TODO maybe this should embed Rotational instead ?
 	axisA       *math32.Vector3 // World oriented rotational axis
 	axisB       *math32.Vector3 // World oriented rotational axis
 	targetSpeed float32         // Target speed

+ 2 - 2
experimental/physics/forcefield.go

@@ -99,7 +99,7 @@ func (pa *AttractorForceField) ForceAt(pos *math32.Vector3) math32.Vector3 {
 	var val float32
 	//log.Error("dist %v", dist)
 	if dist > 0 {
-		val = pa.mass/(dist*dist)
+		val = pa.mass / (dist * dist)
 	} else {
 		val = 0
 	}
@@ -159,6 +159,6 @@ func (pr *RepellerForceField) ForceAt(pos *math32.Vector3) math32.Vector3 {
 	dir.Add(pos)
 	dist := dir.Length()
 	dir.Normalize()
-	dir.MultiplyScalar(pr.mass/(dist*dist)) // TODO multiply by gravitational constant: 6.673×10−11 (N–m2)/kg2
+	dir.MultiplyScalar(pr.mass / (dist * dist)) // TODO multiply by gravitational constant: 6.673×10−11 (N–m2)/kg2
 	return dir
 }

+ 116 - 118
experimental/physics/narrowphase.go

@@ -5,10 +5,10 @@
 package physics
 
 import (
-	"github.com/g3n/engine/experimental/physics/object"
+	"github.com/g3n/engine/experimental/collision/shape"
 	"github.com/g3n/engine/experimental/physics/equation"
+	"github.com/g3n/engine/experimental/physics/object"
 	"github.com/g3n/engine/math32"
-	"github.com/g3n/engine/experimental/collision/shape"
 )
 
 // Narrowphase
@@ -159,9 +159,9 @@ func (n *Narrowphase) GenerateEquations(pairs []CollisionPair) ([]*equation.Cont
 			allContactEqs = append(allContactEqs, contactEqs...)
 			allFrictionEqs = append(allFrictionEqs, frictionEqs...)
 		}
-   }
+	}
 
-   return allContactEqs, allFrictionEqs
+	return allContactEqs, allFrictionEqs
 }
 
 // ResolveCollision figures out which implementation of collision detection and contact resolution to use depending on the shapes involved.
@@ -216,7 +216,7 @@ func (n *Narrowphase) SphereSphere(bodyA, bodyB *object.Body, sphereA, sphereB *
 	radiusA := sphereA.Radius()
 	radiusB := sphereB.Radius()
 
-	if posA.DistanceToSquared(posB) > math32.Pow(radiusA + radiusB, 2) {
+	if posA.DistanceToSquared(posB) > math32.Pow(radiusA+radiusB, 2) {
 		// No collision
 		return contactEqs, frictionEqs
 	}
@@ -263,8 +263,8 @@ func (n *Narrowphase) SpherePlane(bodyA, bodyB *object.Body, sphereA *shape.Sphe
 		// We will have one contact in this case
 		contactEq := equation.NewContact(bodyA, bodyB, 0, 1e6)
 		contactEq.SetSpookParams(1e6, 3, n.simulation.dt)
-		contactEq.SetNormal(normal) // Normalize() might not be needed
-		contactEq.SetRA(normal.Clone().MultiplyScalar(sphereRadius)) // Vector from sphere center to contact point
+		contactEq.SetNormal(normal)                                                                   // Normalize() might not be needed
+		contactEq.SetRA(normal.Clone().MultiplyScalar(sphereRadius))                                  // Vector from sphere center to contact point
 		contactEq.SetRB(math32.NewVec3().SubVectors(point_on_plane_to_sphere, plane_to_sphere_ortho)) // The sphere position projected to plane
 		contactEqs = append(contactEqs, contactEq)
 
@@ -285,13 +285,13 @@ func (n *Narrowphase) SphereConvex(bodyA, bodyB *object.Body, sphereA *shape.Sph
 	// TODO
 	//v3pool := this.v3pool
 	//convex_to_sphere := math32.NewVec3().SubVectors(posA, posB)
-    //normals := sj.faceNormals
-    //faces := sj.faces
-    //verts := sj.vertices
-    //R :=     si.radius
-    //penetrating_sides := []
+	//normals := sj.faceNormals
+	//faces := sj.faces
+	//verts := sj.vertices
+	//R :=     si.radius
+	//penetrating_sides := []
 
-    // COMMENTED OUT
+	// COMMENTED OUT
 	// if(convex_to_sphere.norm2() > si.boundingSphereRadius + sj.boundingSphereRadius){
 	//     return;
 	// }
@@ -302,7 +302,7 @@ func (n *Narrowphase) SphereConvex(bodyA, bodyB *object.Body, sphereA *shape.Sph
 	convexB.Geometry.ReadVertices(func(vertex math32.Vector3) bool {
 		worldVertex := vertex.ApplyQuaternion(quatA).Add(posB)
 		sphereToCorner := math32.NewVec3().SubVectors(worldVertex, posA)
-		if sphereToCorner.LengthSq() < sphereRadius * sphereRadius {
+		if sphereToCorner.LengthSq() < sphereRadius*sphereRadius {
 			// Colliding! worldVertex is inside sphere.
 
 			// Create contact equation
@@ -329,30 +329,30 @@ func (n *Narrowphase) SphereConvex(bodyA, bodyB *object.Body, sphereA *shape.Sph
 	}
 
 	//Check side (plane) intersections TODO NOTE THIS IS UNTESTED
-    convexFaces := convexB.Faces()
-    convexWorldFaceNormals := convexB.WorldFaceNormals()
-    for i := 0; i < len(convexFaces); i++ {
+	convexFaces := convexB.Faces()
+	convexWorldFaceNormals := convexB.WorldFaceNormals()
+	for i := 0; i < len(convexFaces); i++ {
 		worldNormal := convexWorldFaceNormals[i]
-     	face := convexFaces[i]
-     	// Get a world vertex from the face
-     	var worldPoint = face[0].Clone().ApplyQuaternion(quatB).Add(posB)
-     	// Get a point on the sphere, closest to the face normal
-     	var worldSpherePointClosestToPlane = worldNormal.Clone().MultiplyScalar(-sphereRadius).Add(posA)
-     	// Vector from a face point to the closest point on the sphere
-     	var penetrationVec = math32.NewVec3().SubVectors(worldSpherePointClosestToPlane, worldPoint)
-     	// The penetration. Negative value means overlap.
-     	var penetration = penetrationVec.Dot(&worldNormal)
-     	var worldPointToSphere = math32.NewVec3().SubVectors(posA, worldPoint)
-     	if penetration < 0 && worldPointToSphere.Dot(&worldNormal) > 0 {
-         	// Intersects plane. Now check if the sphere is inside the face polygon
-         	worldFace := convexB.WorldFace(face, posB, quatB)
-         	if n.pointBehindFace(worldFace, &worldNormal, posA) { // Is the sphere center behind the face (inside the convex polygon?
+		face := convexFaces[i]
+		// Get a world vertex from the face
+		var worldPoint = face[0].Clone().ApplyQuaternion(quatB).Add(posB)
+		// Get a point on the sphere, closest to the face normal
+		var worldSpherePointClosestToPlane = worldNormal.Clone().MultiplyScalar(-sphereRadius).Add(posA)
+		// Vector from a face point to the closest point on the sphere
+		var penetrationVec = math32.NewVec3().SubVectors(worldSpherePointClosestToPlane, worldPoint)
+		// The penetration. Negative value means overlap.
+		var penetration = penetrationVec.Dot(&worldNormal)
+		var worldPointToSphere = math32.NewVec3().SubVectors(posA, worldPoint)
+		if penetration < 0 && worldPointToSphere.Dot(&worldNormal) > 0 {
+			// Intersects plane. Now check if the sphere is inside the face polygon
+			worldFace := convexB.WorldFace(face, posB, quatB)
+			if n.pointBehindFace(worldFace, &worldNormal, posA) { // Is the sphere center behind the face (inside the convex polygon?
 				// TODO NEVER GETTING INSIDE THIS IF STATEMENT!
-				ShowWorldFace(n.simulation.Scene(), worldFace[:], &math32.Color{0,0,2})
+				ShowWorldFace(n.simulation.Scene(), worldFace[:], &math32.Color{0, 0, 2})
 
 				// if justTest {
-             	//    return true
-             	//}
+				//    return true
+				//}
 
 				// Create contact equation
 				contactEq := equation.NewContact(bodyA, bodyB, 0, 1e6)
@@ -368,31 +368,31 @@ func (n *Narrowphase) SphereConvex(bodyA, bodyB *object.Body, sphereA *shape.Sph
 				fEq1, fEq2 := n.createFrictionEquationsFromContact(contactEq)
 				frictionEqs = append(frictionEqs, fEq1, fEq2)
 				// Exit method (we only expect *one* face contact)
-             	return contactEqs, frictionEqs
-         	} else {
+				return contactEqs, frictionEqs
+			} else {
 				// Edge?
-             	for j := 0; j < len(worldFace); j++ {
-             		// Get two world transformed vertices
-                	v1 := worldFace[(j+1)%3].Clone()//.ApplyQuaternion(quatB).Add(posB)
-                	v2 := worldFace[(j+2)%3].Clone()//.ApplyQuaternion(quatB).Add(posB)
-                	// Construct edge vector
-                	edge := math32.NewVec3().SubVectors(v2, v1)
-                	// Construct the same vector, but normalized
-                	edgeUnit := edge.Clone().Normalize()
-                	// p is xi projected onto the edge
-                	v1ToPosA := math32.NewVec3().SubVectors(posA, v1)
-                	dot := v1ToPosA.Dot(edgeUnit)
+				for j := 0; j < len(worldFace); j++ {
+					// Get two world transformed vertices
+					v1 := worldFace[(j+1)%3].Clone() //.ApplyQuaternion(quatB).Add(posB)
+					v2 := worldFace[(j+2)%3].Clone() //.ApplyQuaternion(quatB).Add(posB)
+					// Construct edge vector
+					edge := math32.NewVec3().SubVectors(v2, v1)
+					// Construct the same vector, but normalized
+					edgeUnit := edge.Clone().Normalize()
+					// p is xi projected onto the edge
+					v1ToPosA := math32.NewVec3().SubVectors(posA, v1)
+					dot := v1ToPosA.Dot(edgeUnit)
 					p := edgeUnit.Clone().MultiplyScalar(dot).Add(v1)
-                	// Compute a vector from p to the center of the sphere
-                	var posAtoP = math32.NewVec3().SubVectors(p, posA)
-                	// Collision if the edge-sphere distance is less than the radius AND if p is in between v1 and v2
-                	edgeL2 := edge.LengthSq()
-                	patp2 := posAtoP.LengthSq()
-                	if (dot > 0) && (dot*dot < edgeL2) && (patp2 < sphereRadius*sphereRadius) { // Collision if the edge-sphere distance is less than the radius
-                	   // Edge contact!
-                	   //if justTest {
-                	   //    return true
-                	   //}
+					// Compute a vector from p to the center of the sphere
+					var posAtoP = math32.NewVec3().SubVectors(p, posA)
+					// Collision if the edge-sphere distance is less than the radius AND if p is in between v1 and v2
+					edgeL2 := edge.LengthSq()
+					patp2 := posAtoP.LengthSq()
+					if (dot > 0) && (dot*dot < edgeL2) && (patp2 < sphereRadius*sphereRadius) { // Collision if the edge-sphere distance is less than the radius
+						// Edge contact!
+						//if justTest {
+						//    return true
+						//}
 						// Create contact equation
 						contactEq := equation.NewContact(bodyA, bodyB, 0, 1e6)
 						contactEq.SetSpookParams(1e6, 3, n.simulation.dt)
@@ -407,11 +407,11 @@ func (n *Narrowphase) SphereConvex(bodyA, bodyB *object.Body, sphereA *shape.Sph
 						//frictionEqs = append(frictionEqs, fEq1, fEq2)
 						// Exit method (we only expect *one* edge contact)
 						return contactEqs, frictionEqs
-                	}
-             	}
+					}
+				}
 			}
-     	}
-    }
+		}
+	}
 
 	return contactEqs, frictionEqs
 }
@@ -517,7 +517,6 @@ func (n *Narrowphase) ConvexConvex(bodyA, bodyB *object.Body, convexA, convexB *
 	return contactEqs, frictionEqs
 }
 
-
 //// TODO ?
 //func (n *Narrowphase) GetAveragePointLocal(target) {
 //
@@ -531,7 +530,6 @@ func (n *Narrowphase) ConvexConvex(bodyA, bodyB *object.Body, convexA, convexB *
 //   return target
 //}
 
-
 // Checks whether p is inside the polyhedra. Must be in local coords.
 // The point lies outside of the convex hull of the other points if and only if
 // the direction of all the vectors from it to those other points are on less than one half of a sphere around it.
@@ -585,59 +583,59 @@ func (n *Narrowphase) PlaneConvex(bodyA, bodyB *object.Body, planeA *shape.Plane
 	//si,
 	//sj,
 	//justTest) {
-   //
+	//
 	//// Simply return the points behind the plane.
-   //worldVertex := planeConvex_v
-   //worldNormal := planeConvex_normal
-   //worldNormal.set(0,0,1)
-   //planeQuat.vmult(worldNormal,worldNormal) // Turn normal according to plane orientation
-   //
-   //var numContacts = 0
-   //var relpos = planeConvex_relpos
-   //for i := 0; i < len(convexShape.vertices); i++ {
-   //
-   //    // Get world convex vertex
-   //    worldVertex.copy(convexShape.vertices[i])
-   //    convexQuat.vmult(worldVertex, worldVertex)
-   //    convexPosition.vadd(worldVertex, worldVertex)
-   //    worldVertex.vsub(planePosition, relpos)
-   //
-   //    var dot = worldNormal.dot(relpos)
-   //    if dot <= 0.0 {
-   //        if justTest {
-   //            return true
-   //        }
-   //
-   //        var r = this.createContactEquation(planeBody, convexBody, planeShape, convexShape, si, sj)
-   //
-   //        // Get vertex position projected on plane
-   //        var projected = planeConvex_projected
-   //        worldNormal.mult(worldNormal.dot(relpos),projected)
-   //        worldVertex.vsub(projected, projected)
-   //        projected.vsub(planePosition, r.ri) // From plane to vertex projected on plane
-   //
-   //        r.ni.copy(worldNormal) // Contact normal is the plane normal out from plane
-   //
-   //        // rj is now just the vector from the convex center to the vertex
-   //        worldVertex.vsub(convexPosition, r.rj)
-   //
-   //        // Make it relative to the body
-   //        r.ri.vadd(planePosition, r.ri)
-   //        r.ri.vsub(planeBody.position, r.ri)
-   //        r.rj.vadd(convexPosition, r.rj)
-   //        r.rj.vsub(convexBody.position, r.rj)
-   //
-   //        this.result.push(r)
-   //        numContacts++
-   //        if !this.enableFrictionReduction {
-   //            this.createFrictionEquationsFromContact(r, this.frictionResult)
-   //        }
-   //    }
-   //}
-   //
-   //if this.enableFrictionReduction && numContacts {
-   //    this.createFrictionFromAverage(numContacts)
-   //}
-
-   return contactEqs, frictionEqs
-}
+	//worldVertex := planeConvex_v
+	//worldNormal := planeConvex_normal
+	//worldNormal.set(0,0,1)
+	//planeQuat.vmult(worldNormal,worldNormal) // Turn normal according to plane orientation
+	//
+	//var numContacts = 0
+	//var relpos = planeConvex_relpos
+	//for i := 0; i < len(convexShape.vertices); i++ {
+	//
+	//    // Get world convex vertex
+	//    worldVertex.copy(convexShape.vertices[i])
+	//    convexQuat.vmult(worldVertex, worldVertex)
+	//    convexPosition.vadd(worldVertex, worldVertex)
+	//    worldVertex.vsub(planePosition, relpos)
+	//
+	//    var dot = worldNormal.dot(relpos)
+	//    if dot <= 0.0 {
+	//        if justTest {
+	//            return true
+	//        }
+	//
+	//        var r = this.createContactEquation(planeBody, convexBody, planeShape, convexShape, si, sj)
+	//
+	//        // Get vertex position projected on plane
+	//        var projected = planeConvex_projected
+	//        worldNormal.mult(worldNormal.dot(relpos),projected)
+	//        worldVertex.vsub(projected, projected)
+	//        projected.vsub(planePosition, r.ri) // From plane to vertex projected on plane
+	//
+	//        r.ni.copy(worldNormal) // Contact normal is the plane normal out from plane
+	//
+	//        // rj is now just the vector from the convex center to the vertex
+	//        worldVertex.vsub(convexPosition, r.rj)
+	//
+	//        // Make it relative to the body
+	//        r.ri.vadd(planePosition, r.ri)
+	//        r.ri.vsub(planeBody.position, r.ri)
+	//        r.rj.vadd(convexPosition, r.rj)
+	//        r.rj.vsub(convexBody.position, r.rj)
+	//
+	//        this.result.push(r)
+	//        numContacts++
+	//        if !this.enableFrictionReduction {
+	//            this.createFrictionEquationsFromContact(r, this.frictionResult)
+	//        }
+	//    }
+	//}
+	//
+	//if this.enableFrictionReduction && numContacts {
+	//    this.createFrictionFromAverage(numContacts)
+	//}
+
+	return contactEqs, frictionEqs
+}

+ 18 - 18
experimental/physics/object/body.go

@@ -5,19 +5,19 @@
 package object
 
 import (
+	"github.com/g3n/engine/experimental/collision/shape"
 	"github.com/g3n/engine/graphic"
-	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/material"
-	"github.com/g3n/engine/experimental/collision/shape"
+	"github.com/g3n/engine/math32"
 )
 
 // Body represents a physics-driven body.
 type Body struct {
 	*graphic.Graphic // TODO future - embed core.Node instead and calculate properties recursively
 
-	material             *material.Material   // Physics material specifying friction and restitution
-	index int
-	name string
+	material *material.Material // Physics material specifying friction and restitution
+	index    int
+	name     string
 
 	// Mass properties
 	mass       float32 // Total mass
@@ -61,12 +61,12 @@ type Body struct {
 	angularFactor  *math32.Vector3 // Use this property to limit the rotational motion along any world axis. (1,1,1) will allow rotation along all axes while (0,0,0) allows none.
 
 	// Body type and sleep settings
-	bodyType        BodyType
-	sleepState      BodySleepState // Current sleep state.
-	allowSleep      bool           // If true, the body will automatically fall to sleep.
-	sleepSpeedLimit float32        // If the speed (the norm of the velocity) is smaller than this value, the body is considered sleepy.
-	sleepTimeLimit  float32        // If the body has been sleepy for this sleepTimeLimit seconds, it is considered sleeping.
-	timeLastSleepy  float32
+	bodyType               BodyType
+	sleepState             BodySleepState // Current sleep state.
+	allowSleep             bool           // If true, the body will automatically fall to sleep.
+	sleepSpeedLimit        float32        // If the speed (the norm of the velocity) is smaller than this value, the body is considered sleepy.
+	sleepTimeLimit         float32        // If the body has been sleepy for this sleepTimeLimit seconds, it is considered sleeping.
+	timeLastSleepy         float32
 	wakeUpAfterNarrowphase bool
 
 	// Collision settings
@@ -79,7 +79,7 @@ type Body struct {
 	boundingRadius  float32      // Total bounding radius of the body (TODO including its shapes, relative to body.position.)
 
 	// Cached geometry properties
-	faces            [][3]math32.Vector3
+	faces [][3]math32.Vector3
 
 	faceNormals      []math32.Vector3
 	worldFaceNormals []math32.Vector3
@@ -140,13 +140,13 @@ const (
 
 // TODO
 type HullType int
+
 const (
 	Sphere = HullType(iota)
 	Capsule
 	Mesh // use mesh itself
 )
 
-
 // NewBody creates and returns a pointer to a new RigidBody.
 // The igraphic's geometry *must* be convex.
 func NewBody(igraphic graphic.IGraphic) *Body {
@@ -231,7 +231,7 @@ func (b *Body) Shape() shape.IShape {
 func (b *Body) BoundingBox() math32.Box3 {
 
 	// TODO future allow multiple shapes
-	mat4 := math32.NewMatrix4().Compose(b.position, b.quaternion, math32.NewVector3(1,1,1))
+	mat4 := math32.NewMatrix4().Compose(b.position, b.quaternion, math32.NewVector3(1, 1, 1))
 	localBB := b.shape.BoundingBox()
 	worldBB := localBB.ApplyMatrix4(mat4)
 	return *worldBB
@@ -327,7 +327,7 @@ func (b *Body) SetBodyType(bodyType BodyType) {
 
 func (b *Body) BodyType() BodyType {
 
-	return b. bodyType
+	return b.bodyType
 }
 
 func (b *Body) SetWakeUpAfterNarrowphase(state bool) {
@@ -426,8 +426,8 @@ func (b *Body) AngularDamping() float32 {
 
 func (b *Body) ApplyDamping(dt float32) {
 
-	b.velocity.MultiplyScalar(math32.Pow(1.0 - b.linearDamping, dt))
-	b.angularVelocity.MultiplyScalar(math32.Pow(1.0 - b.angularDamping, dt))
+	b.velocity.MultiplyScalar(math32.Pow(1.0-b.linearDamping, dt))
+	b.angularVelocity.MultiplyScalar(math32.Pow(1.0-b.angularDamping, dt))
 }
 
 func (b *Body) SetLinearFactor(factor *math32.Vector3) {
@@ -581,7 +581,7 @@ func (b *Body) UpdateMassProperties() {
 		b.invRotInertia.Zero()
 	} else {
 		*b.rotInertia = b.GetGeometry().RotationalInertia(b.mass)
-		b.rotInertia.MultiplyScalar(10) // multiply by high density // TODO remove this ?
+		b.rotInertia.MultiplyScalar(10)          // multiply by high density // TODO remove this ?
 		b.invRotInertia.GetInverse(b.rotInertia) // Note: rotInertia is always positive definite and thus always invertible
 	}
 

+ 6 - 6
experimental/physics/solver/gs.go

@@ -65,13 +65,13 @@ func (gs *GaussSeidel) Solve(dt float32, nBodies int) *Solution {
 	// Things that do not change during iteration can be computed once
 	for i := 0; i < nEquations; i++ {
 		eq := gs.equations[i]
-		gs.solveInvCs = append(gs.solveInvCs, 1.0 / eq.ComputeC())
+		gs.solveInvCs = append(gs.solveInvCs, 1.0/eq.ComputeC())
 		gs.solveBs = append(gs.solveBs, eq.ComputeB(h))
 		gs.solveLambda = append(gs.solveLambda, 0.0)
 	}
 
 	if nEquations > 0 {
-		tolSquared := gs.tolerance*gs.tolerance
+		tolSquared := gs.tolerance * gs.tolerance
 
 		// Iterate over equations
 		for iter = 0; iter < gs.maxIter; iter++ {
@@ -96,12 +96,12 @@ func (gs *GaussSeidel) Solve(dt float32, nBodies int) *Solution {
 				jeB := eq.JeB()
 				GWlambda := jeA.MultiplyVectors(&vA, &wA) + jeB.MultiplyVectors(&vB, &wB)
 
-				deltaLambda := gs.solveInvCs[j] * ( gs.solveBs[j]  - GWlambda - eq.Eps() *lambdaJ)
+				deltaLambda := gs.solveInvCs[j] * (gs.solveBs[j] - GWlambda - eq.Eps()*lambdaJ)
 
 				// Clamp if we are outside the min/max interval
-				if lambdaJ + deltaLambda < eq.MinForce() {
+				if (lambdaJ + deltaLambda) < eq.MinForce() {
 					deltaLambda = eq.MinForce() - lambdaJ
-				} else if lambdaJ + deltaLambda > eq.MaxForce() {
+				} else if (lambdaJ + deltaLambda) > eq.MaxForce() {
 					deltaLambda = eq.MaxForce() - lambdaJ
 				}
 				gs.solveLambda[j] += deltaLambda
@@ -136,4 +136,4 @@ func (gs *GaussSeidel) Solve(dt float32, nBodies int) *Solution {
 	gs.Iterations = iter
 
 	return &gs.Solution
-}
+}

+ 2 - 1
geometry/cone-cylinder.go

@@ -5,9 +5,10 @@
 package geometry
 
 import (
+	"math"
+
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
-	"math"
 )
 
 // NewCone creates a cone geometry with the specified base radius, height,

+ 2 - 1
geometry/disk.go

@@ -5,9 +5,10 @@
 package geometry
 
 import (
+	"math"
+
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
-	"math"
 )
 
 // NewDisk creates a disk (filled circle) geometry with the specified

+ 3 - 2
geometry/morph.go

@@ -5,10 +5,11 @@
 package geometry
 
 import (
-	"github.com/g3n/engine/gls"
-	"github.com/g3n/engine/math32"
 	"sort"
 	"strconv"
+
+	"github.com/g3n/engine/gls"
+	"github.com/g3n/engine/math32"
 )
 
 // MorphGeometry represents a base geometry and its morph targets.

+ 2 - 1
geometry/sphere.go

@@ -5,9 +5,10 @@
 package geometry
 
 import (
+	"math"
+
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
-	"math"
 )
 
 // NewSphere creates a sphere geometry with the specified radius and number of radial segments in each dimension.

+ 2 - 1
geometry/torus.go

@@ -5,9 +5,10 @@
 package geometry
 
 import (
+	"math"
+
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
-	"math"
 )
 
 // NewTorus creates a torus geometry with the specified revolution radius, tube radius,

+ 39 - 38
geometry/tube.go

@@ -6,23 +6,24 @@
 package geometry
 
 import (
-	"github.com/g3n/engine/math32"
-	"github.com/g3n/engine/gls"
 	"math"
+
+	"github.com/g3n/engine/gls"
+	"github.com/g3n/engine/math32"
 )
 
 func CalculateNormals(indices math32.ArrayU32, positions, normals math32.ArrayF32) math32.ArrayF32 {
 	var x1x2, y1y2, z1z2, x3x2, y3y2, z3z2, x, y, z, l float32
 	var x1, y1, z1, x2, y2, z2, x3, y3, z3 int // position indexes
 
-	for i := 0; i < len(indices) / 3; i++ {
-		x1 = int(indices[i * 3] * uint32(3))
+	for i := 0; i < len(indices)/3; i++ {
+		x1 = int(indices[i*3] * uint32(3))
 		y1 = x1 + 1
 		z1 = x1 + 2
-		x2 = int(indices[i * 3 + 1] * uint32(3))
+		x2 = int(indices[i*3+1] * uint32(3))
 		y2 = x2 + 1
 		z2 = x2 + 2
-		x3 = int(indices[i * 3 + 2] * uint32(3))
+		x3 = int(indices[i*3+2] * uint32(3))
 		y3 = x3 + 1
 		z3 = x3 + 2
 
@@ -33,11 +34,11 @@ func CalculateNormals(indices math32.ArrayU32, positions, normals math32.ArrayF3
 		y3y2 = positions[y3] - positions[y2]
 		z3z2 = positions[z3] - positions[z2]
 
-		x = y1y2 * z3z2 - z1z2 * y3y2
-		y = z1z2 * x3x2 - x1x2 * z3z2
-		z = x1x2 * y3y2 - y1y2 * x3x2
+		x = y1y2*z3z2 - z1z2*y3y2
+		y = z1z2*x3x2 - x1x2*z3z2
+		z = x1x2*y3y2 - y1y2*x3x2
 
-		l = float32(math.Sqrt(float64(x) * float64(x) + float64(y) * float64(y) + float64(z) * float64(z)))
+		l = float32(math.Sqrt(float64(x)*float64(x) + float64(y)*float64(y) + float64(z)*float64(z)))
 		if l == 0 {
 			l = 1.0
 		}
@@ -52,26 +53,26 @@ func CalculateNormals(indices math32.ArrayU32, positions, normals math32.ArrayF3
 		normals[y3] += y / l
 		normals[z3] += z / l
 	}
-	for i := 0; i < len(normals) / 3; i++ {
-		x = normals[i * 3]
-		y = normals[i * 3 + 1]
-		z = normals[i * 3 + 2]
-		l = float32(math.Sqrt(float64(x) * float64(x) + float64(y) * float64(y) + float64(z) * float64(z)))
+	for i := 0; i < len(normals)/3; i++ {
+		x = normals[i*3]
+		y = normals[i*3+1]
+		z = normals[i*3+2]
+		l = float32(math.Sqrt(float64(x)*float64(x) + float64(y)*float64(y) + float64(z)*float64(z)))
 		if l == 0 {
 			l = 1.0
 		}
-		normals[i * 3] = x / l
-		normals[i * 3 + 1] = y / l
-		normals[i * 3 + 2] = z / l
+		normals[i*3] = x / l
+		normals[i*3+1] = y / l
+		normals[i*3+2] = z / l
 	}
 	return normals
 }
 
 func NewRibbon(paths [][]math32.Vector3, close bool) *Geometry {
 	/*
-	if len(paths) < 3 {
-		close = false
-	}
+		if len(paths) < 3 {
+			close = false
+		}
 	*/
 	c := NewGeometry()
 
@@ -99,8 +100,8 @@ func NewRibbon(paths [][]math32.Vector3, close bool) *Geometry {
 	}
 	p := 0
 	i = 0
-	for i <= min && p < len(ls) - 1 {
-		t := is[p+1] - is[p] 
+	for i <= min && p < len(ls)-1 {
+		t := is[p+1] - is[p]
 
 		indices.Append(uint32(i), uint32(i+t), uint32(i+1))
 		indices.Append(uint32(i+t+1), uint32(i+1), uint32(i+t))
@@ -111,7 +112,7 @@ func NewRibbon(paths [][]math32.Vector3, close bool) *Geometry {
 				indices.Append(uint32(is[p]+t), uint32(is[p]), uint32(i+t))
 			}
 			p++
-			if p == len(ls) - 1 {
+			if p == len(ls)-1 {
 				break
 			}
 			l1 = ls[p] - 1
@@ -149,11 +150,11 @@ func NewTube(path []math32.Vector3, radius float32, radialSegments int, close bo
 	tangents[l-1].Normalize()
 
 	var tmpVertex *math32.Vector3
-	if (tangents[0].X != 1) {
+	if tangents[0].X != 1 {
 		tmpVertex = math32.NewVector3(1, 0, 0)
-	} else if (tangents[0].Y != 1) {
+	} else if tangents[0].Y != 1 {
 		tmpVertex = math32.NewVector3(0, 1, 0)
-	} else if (tangents[0].Z != 1) {
+	} else if tangents[0].Z != 1 {
 		tmpVertex = math32.NewVector3(0, 0, 1)
 	}
 
@@ -161,14 +162,14 @@ func NewTube(path []math32.Vector3, radius float32, radialSegments int, close bo
 	normals[0].Normalize()
 	binormals[0] = *tangents[0].Clone().Cross(&normals[0])
 	binormals[0].Normalize()
-	
+
 	for i := 1; i < l; i++ {
 		prev := *path[i].Clone().Sub(&path[i-1])
-		if (i < l-1) {
-		  cur := *path[i+1].Clone().Sub(&path[i])
-		  tangents[i] = *prev.Clone().Add(&cur)
-		  tangents[i].Normalize()
-		  
+		if i < l-1 {
+			cur := *path[i+1].Clone().Sub(&path[i])
+			tangents[i] = *prev.Clone().Add(&cur)
+			tangents[i].Normalize()
+
 		}
 		normals[i] = *binormals[i-1].Clone().Cross(&tangents[i])
 		normals[i].Normalize()
@@ -190,11 +191,11 @@ func NewTube(path []math32.Vector3, radius float32, radialSegments int, close bo
 			x := normals[i].X
 			y := normals[i].Y
 			z := normals[i].Z
-			rw := 1 / (x * matrix[3] + y * matrix[7] + z * matrix[11] + matrix[15])
-			newX := (x * matrix[0] + y * matrix[4] + z * matrix[8] + matrix[12]) * rw
-			newY := (x * matrix[1] + y * matrix[5] + z * matrix[9] + matrix[13]) * rw
-			newZ := (x * matrix[2] + y * matrix[6] + z * matrix[10] + matrix[14]) * rw
-			
+			rw := 1 / (x*matrix[3] + y*matrix[7] + z*matrix[11] + matrix[15])
+			newX := (x*matrix[0] + y*matrix[4] + z*matrix[8] + matrix[12]) * rw
+			newY := (x*matrix[1] + y*matrix[5] + z*matrix[9] + matrix[13]) * rw
+			newZ := (x*matrix[2] + y*matrix[6] + z*matrix[10] + matrix[14]) * rw
+
 			rotated := math32.NewVector3(newX, newY, newZ).MultiplyScalar(radius).Add(&path[i])
 			radialPath = append(radialPath, *rotated)
 		}

+ 1 - 1
gls/build.go

@@ -9,7 +9,7 @@ package gls
 
 // // Platform build flags
 // #cgo freebsd CFLAGS:  -DGL_GLEXT_PROTOTYPES
-// #cgo freebsd LDFLAGS: 
+// #cgo freebsd LDFLAGS:
 //
 // #cgo linux   CFLAGS:  -DGL_GLEXT_PROTOTYPES
 // #cgo linux   LDFLAGS: -ldl

+ 24 - 17
gls/gls-browser.go

@@ -8,6 +8,7 @@ package gls
 
 import (
 	"fmt"
+	"github.com/g3n/engine/util/wasm"
 	"syscall/js"
 	"unsafe"
 )
@@ -279,10 +280,10 @@ func (gs *GLS) BlendFuncSeparate(srcRGB uint32, dstRGB uint32, srcAlpha uint32,
 // bound to target, deleting any pre-existing data store.
 func (gs *GLS) BufferData(target uint32, size int, data interface{}, usage uint32) {
 
-	dataTA := js.TypedArrayOf(data)
+	dataTA, free := wasm.SliceToTypedArray(data)
 	gs.gl.Call("bufferData", int(target), dataTA, int(usage))
 	gs.checkError("BufferData")
-	dataTA.Release()
+	free()
 }
 
 // ClearColor specifies the red, green, blue, and alpha values
@@ -589,7 +590,7 @@ func (gs *GLS) GetString(name uint32) string {
 func (gs *GLS) GetUniformLocation(program uint32, name string) int32 {
 
 	loc := gs.gl.Call("getUniformLocation", gs.programMap[program], name)
-	if loc == js.Null() {
+	if wasm.Equal(loc, js.Null()) {
 		return -1
 	}
 	gs.uniformMap[gs.uniformMapIndex] = loc
@@ -657,10 +658,16 @@ func (gs *GLS) ShaderSource(shader uint32, src string) {
 // TexImage2D specifies a two-dimensional texture image.
 func (gs *GLS) TexImage2D(target uint32, level int32, iformat int32, width int32, height int32, format uint32, itype uint32, data interface{}) {
 
-	dataTA := js.TypedArrayOf(data)
+	dataTA, free := wasm.SliceToTypedArray(data)
 	gs.gl.Call("texImage2D", int(target), level, iformat, width, height, 0, int(format), int(itype), dataTA)
 	gs.checkError("TexImage2D")
-	dataTA.Release()
+	free()
+}
+
+// CompressedTexImage2D specifies a two-dimensional compressed texture image.
+func (gs *GLS) CompressedTexImage2D(target uint32, level uint32, iformat uint32, width int32, height int32, size int32, data interface{}) {
+
+	// todo
 }
 
 // TexParameteri sets the specified texture parameter on the specified texture.
@@ -732,9 +739,9 @@ func (gs *GLS) Uniform4f(location int32, v0, v1, v2, v3 float32) {
 func (gs *GLS) UniformMatrix3fv(location int32, count int32, transpose bool, pm *float32) {
 
 	data := (*[1 << 30]float32)(unsafe.Pointer(pm))[:9*count]
-	dataTA := js.TypedArrayOf(data)
+	dataTA, free := wasm.SliceToTypedArray(data)
 	gs.gl.Call("uniformMatrix3fv", gs.uniformMap[uint32(location)], transpose, dataTA)
-	dataTA.Release()
+	free()
 	gs.checkError("UniformMatrix3fv")
 	gs.stats.Unisets++
 }
@@ -743,9 +750,9 @@ func (gs *GLS) UniformMatrix3fv(location int32, count int32, transpose bool, pm
 func (gs *GLS) UniformMatrix4fv(location int32, count int32, transpose bool, pm *float32) {
 
 	data := (*[1 << 30]float32)(unsafe.Pointer(pm))[:16*count]
-	dataTA := js.TypedArrayOf(data)
+	dataTA, free := wasm.SliceToTypedArray(data)
 	gs.gl.Call("uniformMatrix4fv", gs.uniformMap[uint32(location)], transpose, dataTA)
-	dataTA.Release()
+	free()
 	gs.checkError("UniformMatrix4fv")
 	gs.stats.Unisets++
 }
@@ -754,9 +761,9 @@ func (gs *GLS) UniformMatrix4fv(location int32, count int32, transpose bool, pm
 func (gs *GLS) Uniform1fv(location int32, count int32, v *float32) {
 
 	data := (*[1 << 30]float32)(unsafe.Pointer(v))[:count]
-	dataTA := js.TypedArrayOf(data)
+	dataTA, free := wasm.SliceToTypedArray(data)
 	gs.gl.Call("uniform1fv", gs.uniformMap[uint32(location)], dataTA)
-	dataTA.Release()
+	free()
 	gs.checkError("Uniform1fv")
 	gs.stats.Unisets++
 }
@@ -765,9 +772,9 @@ func (gs *GLS) Uniform1fv(location int32, count int32, v *float32) {
 func (gs *GLS) Uniform2fv(location int32, count int32, v *float32) {
 
 	data := (*[1 << 30]float32)(unsafe.Pointer(v))[:2*count]
-	dataTA := js.TypedArrayOf(data)
+	dataTA, free := wasm.SliceToTypedArray(data)
 	gs.gl.Call("uniform2fv", gs.uniformMap[uint32(location)], dataTA)
-	dataTA.Release()
+	free()
 	gs.checkError("Uniform2fv")
 	gs.stats.Unisets++
 }
@@ -776,9 +783,9 @@ func (gs *GLS) Uniform2fv(location int32, count int32, v *float32) {
 func (gs *GLS) Uniform3fv(location int32, count int32, v *float32) {
 
 	data := (*[1 << 30]float32)(unsafe.Pointer(v))[:3*count]
-	dataTA := js.TypedArrayOf(data)
+	dataTA, free := wasm.SliceToTypedArray(data)
 	gs.gl.Call("uniform3fv", gs.uniformMap[uint32(location)], dataTA)
-	dataTA.Release()
+	free()
 	gs.checkError("Uniform3fv")
 	gs.stats.Unisets++
 }
@@ -787,9 +794,9 @@ func (gs *GLS) Uniform3fv(location int32, count int32, v *float32) {
 func (gs *GLS) Uniform4fv(location int32, count int32, v *float32) {
 
 	data := (*[1 << 30]float32)(unsafe.Pointer(v))[:4*count]
-	dataTA := js.TypedArrayOf(data)
+	dataTA, free := wasm.SliceToTypedArray(data)
 	gs.gl.Call("uniform4fv", gs.uniformMap[uint32(location)], dataTA)
-	dataTA.Release()
+	free()
 	gs.checkError("Uniform4fv")
 	gs.stats.Unisets++
 }

+ 81 - 0
gls/gls-desktop.go

@@ -418,6 +418,13 @@ func (gs *GLS) DrawElements(mode uint32, count int32, itype uint32, start uint32
 	gs.stats.Drawcalls++
 }
 
+// DrawBuffer sets which color buffers are to be drawn into.
+// Mode is one of NONE, FRONT_LEFT, FRONT_RIGHT, BACK_LEFT, BACK_RIGHT, FRONT, BACK, LEFT, RIGHT, and FRONT_AND_BACK.
+func (gs *GLS) DrawBuffer(mode uint) {
+
+	C.glDrawBuffer(C.GLuint(mode))
+}
+
 // Enable enables the specified capability.
 func (gs *GLS) Enable(cap int) {
 
@@ -471,6 +478,80 @@ func (gs *GLS) GenBuffer() uint32 {
 	return buf
 }
 
+// GenFramebuffer creates a new framebuffer.
+// Framebuffers store (usually two) render buffers.
+func (gs *GLS) GenFramebuffer() uint32 {
+
+	var fb uint32
+	C.glGenFramebuffers(1, (*C.GLuint)(&fb))
+	return fb
+}
+
+// GenRenderbuffer creates a new render buffer.
+func (gs *GLS) GenRenderbuffer() uint32 {
+
+	var rb uint32
+	C.glGenRenderbuffers(1, (*C.GLuint)(&rb))
+	return rb
+}
+
+// BindFramebuffer sets the current framebuffer.
+func (gs *GLS) BindFramebuffer(fb uint32) {
+
+	C.glBindFramebuffer(FRAMEBUFFER, C.GLuint(fb))
+}
+
+// BindRenderbuffer sets the current render buffer.
+func (gs *GLS) BindRenderbuffer(rb uint32) {
+
+	C.glBindRenderbuffer(RENDERBUFFER, C.GLuint(rb))
+}
+
+// RenderbufferStorage allocates space for the bound render buffer.
+// Format is the internal storage format, e.g. RGBA32F
+func (gs *GLS) RenderbufferStorage(format uint, width int, height int) {
+
+	C.glRenderbufferStorage(RENDERBUFFER, C.GLuint(format), C.GLint(width), C.GLint(height))
+}
+
+// FramebufferRenderbuffer attaches a renderbuffer object to the bound framebuffer object.
+// Attachment is one of COLOR_ATTACHMENT0, DEPTH_ATTACHMENT, or STENCIL_ATTACHMENT.
+func (gs *GLS) FramebufferRenderbuffer(attachment uint, rb uint32) {
+
+	C.glFramebufferRenderbuffer(DRAW_FRAMEBUFFER, C.GLuint(attachment), RENDERBUFFER, C.GLuint(rb))
+}
+
+// FramebufferTexture attaches a level of a texture object as a logical buffer of a framebuffer object.
+func (gs *GLS) FramebufferTexture(attachment uint, tex uint32) {
+
+	C.glFramebufferTexture(FRAMEBUFFER, C.GLuint(attachment), C.GLuint(tex), 0)
+}
+
+// FramebufferTexture1D attaches a level of a texture object as a logical buffer to the currently bound framebuffer object
+func (gs *GLS) FramebufferTexture1D(attachment uint, textarget uint, tex uint32) {
+
+	C.glFramebufferTexture1D(FRAMEBUFFER, C.GLenum(attachment), C.GLenum(textarget), C.GLuint(tex), 0)
+}
+
+// FramebufferTexture2D attaches a level of a texture object as a logical buffer to the currently bound framebuffer object
+func (gs *GLS) FramebufferTexture2D(attachment uint, textarget uint, tex uint32) {
+
+	C.glFramebufferTexture2D(FRAMEBUFFER, C.GLenum(attachment), C.GLenum(textarget), C.GLuint(tex), 0)
+}
+
+// FramebufferTexture3D attaches a level of a texture object as a logical buffer to the currently bound framebuffer object
+func (gs *GLS) FramebufferTexture3D(attachment uint, textarget uint, tex uint32, layer int) {
+
+	C.glFramebufferTexture3D(FRAMEBUFFER, C.GLenum(attachment), C.GLenum(textarget), C.GLuint(tex), 0, C.GLint(layer))
+}
+
+// ReadBuffer sets the buffer for reading using ReadPixels.
+// Attachment is one of COLOR_ATTACHMENT0, DEPTH_ATTACHMENT, or STENCIL_ATTACHMENT.
+func (gs *GLS) ReadBuffer(attachment uint) {
+
+	C.glReadBuffer(C.GLuint(attachment))
+}
+
 // GenerateMipmap generates mipmaps for the specified texture target.
 func (gs *GLS) GenerateMipmap(target uint32) {
 

+ 2 - 1
gls/gls.go

@@ -13,9 +13,10 @@
 package gls
 
 import (
-	"github.com/g3n/engine/util/logger"
 	"math"
 	"unsafe"
+
+	"github.com/g3n/engine/util/logger"
 )
 
 // Package logger

+ 1 - 1
gls/shaderdefines.go

@@ -29,7 +29,7 @@ func (sd *ShaderDefines) Unset(name string) {
 // Add adds to this ShaderDefines all the key-value pairs in the specified ShaderDefines.
 func (sd *ShaderDefines) Add(other *ShaderDefines) {
 
-	for k, v := range map[string]string(*other){
+	for k, v := range map[string]string(*other) {
 		(*sd)[k] = v
 	}
 }

+ 4 - 4
go.mod

@@ -1,10 +1,10 @@
 module github.com/g3n/engine
 
-go 1.12
+go 1.13
 
 require (
-	github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1
+	github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb
 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
-	golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a
-	gopkg.in/yaml.v2 v2.2.2
+	golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9
+	gopkg.in/yaml.v2 v2.4.0
 )

+ 8 - 7
go.sum

@@ -1,11 +1,12 @@
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
-golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0=
-golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 h1:D0iM1dTCbD5Dg1CbuvLC/v/agLc79efSj/L35Q3Vqhs=
+golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

+ 1 - 1
graphic/graphic.go

@@ -207,7 +207,7 @@ func (gr *Graphic) GetMaterial(vpos int) material.IMaterial {
 		if gmat.count == 0 {
 			return gmat.imat
 		}
-		if gmat.start >= vpos && gmat.start+gmat.count <= vpos {
+		if gmat.start <= vpos && gmat.start+gmat.count >= vpos {
 			return gmat.imat
 		}
 	}

+ 2 - 1
graphic/rigged_mesh.go

@@ -5,9 +5,10 @@
 package graphic
 
 import (
-	"github.com/g3n/engine/gls"
 	"strconv"
+
 	"github.com/g3n/engine/core"
+	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
 )
 

+ 1 - 1
graphic/skeleton.go

@@ -7,7 +7,7 @@ package graphic
 import (
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/math32"
-	)
+)
 
 // Skeleton contains armature information.
 type Skeleton struct {

+ 11 - 2
graphic/sprite.go

@@ -71,8 +71,17 @@ func (s *Sprite) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 
 	// Removes any rotation in X and Y axes and compose new model view matrix
 	rotation := s.Rotation()
-	rotation.X = 0
-	rotation.Y = 0
+	actualScale := s.Scale()
+	if actualScale.X >= 0 {
+		rotation.Y = 0
+	} else {
+		rotation.Y = math32.Pi
+	}
+	if actualScale.Y >= 0 {
+		rotation.X = 0
+	} else {
+		rotation.X = math32.Pi
+	}
 	quaternion.SetFromEuler(&rotation)
 	var mvmNew math32.Matrix4
 	mvmNew.Compose(&position, &quaternion, &scale)

+ 9 - 9
gui/assets/data.go

@@ -243,12 +243,12 @@ func AssetNames() []string {
 
 // _bindata is a table, holding each asset generator, mapped to its name.
 var _bindata = map[string]func() (*asset, error){
-	"fonts/FreeMono.ttf": fontsFreemonoTtf,
-	"fonts/FreeSans.ttf": fontsFreesansTtf,
-	"fonts/FreeSansBold.ttf": fontsFreesansboldTtf,
+	"fonts/FreeMono.ttf":              fontsFreemonoTtf,
+	"fonts/FreeSans.ttf":              fontsFreesansTtf,
+	"fonts/FreeSansBold.ttf":          fontsFreesansboldTtf,
 	"fonts/MaterialIcons-Regular.ttf": fontsMaterialiconsRegularTtf,
-	"cursors/diag1.png": cursorsDiag1Png,
-	"cursors/diag2.png": cursorsDiag2Png,
+	"cursors/diag1.png":               cursorsDiag1Png,
+	"cursors/diag2.png":               cursorsDiag2Png,
 }
 
 // AssetDir returns the file names below a certain
@@ -290,15 +290,16 @@ type bintree struct {
 	Func     func() (*asset, error)
 	Children map[string]*bintree
 }
+
 var _bintree = &bintree{nil, map[string]*bintree{
 	"cursors": &bintree{nil, map[string]*bintree{
 		"diag1.png": &bintree{cursorsDiag1Png, map[string]*bintree{}},
 		"diag2.png": &bintree{cursorsDiag2Png, map[string]*bintree{}},
 	}},
 	"fonts": &bintree{nil, map[string]*bintree{
-		"FreeMono.ttf": &bintree{fontsFreemonoTtf, map[string]*bintree{}},
-		"FreeSans.ttf": &bintree{fontsFreesansTtf, map[string]*bintree{}},
-		"FreeSansBold.ttf": &bintree{fontsFreesansboldTtf, map[string]*bintree{}},
+		"FreeMono.ttf":              &bintree{fontsFreemonoTtf, map[string]*bintree{}},
+		"FreeSans.ttf":              &bintree{fontsFreesansTtf, map[string]*bintree{}},
+		"FreeSansBold.ttf":          &bintree{fontsFreesansboldTtf, map[string]*bintree{}},
 		"MaterialIcons-Regular.ttf": &bintree{fontsMaterialiconsRegularTtf, map[string]*bintree{}},
 	}},
 }}
@@ -349,4 +350,3 @@ func _filePath(dir, name string) string {
 	cannonicalName := strings.Replace(name, "\\", "/", -1)
 	return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
 }
-

+ 75 - 75
gui/assets/icon/icodes.go

@@ -1808,79 +1808,79 @@ var name2Codepoint = map[string]string{
 	"Traffic":                               string(0xe565),
 	"Train":                                 string(0xe570),
 	"Tram":                                  string(0xe571),
-	"TransferWithinAStation": string(0xe572),
-	"Transform":              string(0xe428),
-	"Translate":              string(0xe8e2),
-	"TrendingDown":           string(0xe8e3),
-	"TrendingFlat":           string(0xe8e4),
-	"TrendingUp":             string(0xe8e5),
-	"Tune":                   string(0xe429),
-	"TurnedIn":               string(0xe8e6),
-	"TurnedInNot":            string(0xe8e7),
-	"Tv":                     string(0xe333),
-	"Unarchive":              string(0xe169),
-	"Undo":                   string(0xe166),
-	"UnfoldLess":             string(0xe5d6),
-	"UnfoldMore":             string(0xe5d7),
-	"Update":                 string(0xe923),
-	"Usb":                    string(0xe1e0),
-	"VerifiedUser":           string(0xe8e8),
-	"VerticalAlignBottom":    string(0xe258),
-	"VerticalAlignCenter":    string(0xe259),
-	"VerticalAlignTop":       string(0xe25a),
-	"Vibration":              string(0xe62d),
-	"VideoCall":              string(0xe070),
-	"VideoLabel":             string(0xe071),
-	"VideoLibrary":           string(0xe04a),
-	"Videocam":               string(0xe04b),
-	"VideocamOff":            string(0xe04c),
-	"VideogameAsset":         string(0xe338),
-	"ViewAgenda":             string(0xe8e9),
-	"ViewArray":              string(0xe8ea),
-	"ViewCarousel":           string(0xe8eb),
-	"ViewColumn":             string(0xe8ec),
-	"ViewComfy":              string(0xe42a),
-	"ViewCompact":            string(0xe42b),
-	"ViewDay":                string(0xe8ed),
-	"ViewHeadline":           string(0xe8ee),
-	"ViewList":               string(0xe8ef),
-	"ViewModule":             string(0xe8f0),
-	"ViewQuilt":              string(0xe8f1),
-	"ViewStream":             string(0xe8f2),
-	"ViewWeek":               string(0xe8f3),
-	"Vignette":               string(0xe435),
-	"Visibility":             string(0xe8f4),
-	"VisibilityOff":          string(0xe8f5),
-	"VoiceChat":              string(0xe62e),
-	"Voicemail":              string(0xe0d9),
-	"VolumeDown":             string(0xe04d),
-	"VolumeMute":             string(0xe04e),
-	"VolumeOff":              string(0xe04f),
-	"VolumeUp":               string(0xe050),
-	"VpnKey":                 string(0xe0da),
-	"VpnLock":                string(0xe62f),
-	"Wallpaper":              string(0xe1bc),
-	"Warning":                string(0xe002),
-	"Watch":                  string(0xe334),
-	"WatchLater":             string(0xe924),
-	"WbAuto":                 string(0xe42c),
-	"WbCloudy":               string(0xe42d),
-	"WbIncandescent":         string(0xe42e),
-	"WbIridescent":           string(0xe436),
-	"WbSunny":                string(0xe430),
-	"Wc":                     string(0xe63d),
-	"Web":                    string(0xe051),
-	"WebAsset":               string(0xe069),
-	"Weekend":                string(0xe16b),
-	"Whatshot":               string(0xe80e),
-	"Widgets":                string(0xe1bd),
-	"Wifi":                   string(0xe63e),
-	"WifiLock":               string(0xe1e1),
-	"WifiTethering":          string(0xe1e2),
-	"Work":                   string(0xe8f9),
-	"WrapText":               string(0xe25b),
-	"YoutubeSearchedFor":     string(0xe8fa),
-	"ZoomIn":                 string(0xe8ff),
-	"ZoomOut":                string(0xe900),
-	"ZoomOutMap":             string(0xe56b),
+	"TransferWithinAStation":                string(0xe572),
+	"Transform":                             string(0xe428),
+	"Translate":                             string(0xe8e2),
+	"TrendingDown":                          string(0xe8e3),
+	"TrendingFlat":                          string(0xe8e4),
+	"TrendingUp":                            string(0xe8e5),
+	"Tune":                                  string(0xe429),
+	"TurnedIn":                              string(0xe8e6),
+	"TurnedInNot":                           string(0xe8e7),
+	"Tv":                                    string(0xe333),
+	"Unarchive":                             string(0xe169),
+	"Undo":                                  string(0xe166),
+	"UnfoldLess":                            string(0xe5d6),
+	"UnfoldMore":                            string(0xe5d7),
+	"Update":                                string(0xe923),
+	"Usb":                                   string(0xe1e0),
+	"VerifiedUser":                          string(0xe8e8),
+	"VerticalAlignBottom":                   string(0xe258),
+	"VerticalAlignCenter":                   string(0xe259),
+	"VerticalAlignTop":                      string(0xe25a),
+	"Vibration":                             string(0xe62d),
+	"VideoCall":                             string(0xe070),
+	"VideoLabel":                            string(0xe071),
+	"VideoLibrary":                          string(0xe04a),
+	"Videocam":                              string(0xe04b),
+	"VideocamOff":                           string(0xe04c),
+	"VideogameAsset":                        string(0xe338),
+	"ViewAgenda":                            string(0xe8e9),
+	"ViewArray":                             string(0xe8ea),
+	"ViewCarousel":                          string(0xe8eb),
+	"ViewColumn":                            string(0xe8ec),
+	"ViewComfy":                             string(0xe42a),
+	"ViewCompact":                           string(0xe42b),
+	"ViewDay":                               string(0xe8ed),
+	"ViewHeadline":                          string(0xe8ee),
+	"ViewList":                              string(0xe8ef),
+	"ViewModule":                            string(0xe8f0),
+	"ViewQuilt":                             string(0xe8f1),
+	"ViewStream":                            string(0xe8f2),
+	"ViewWeek":                              string(0xe8f3),
+	"Vignette":                              string(0xe435),
+	"Visibility":                            string(0xe8f4),
+	"VisibilityOff":                         string(0xe8f5),
+	"VoiceChat":                             string(0xe62e),
+	"Voicemail":                             string(0xe0d9),
+	"VolumeDown":                            string(0xe04d),
+	"VolumeMute":                            string(0xe04e),
+	"VolumeOff":                             string(0xe04f),
+	"VolumeUp":                              string(0xe050),
+	"VpnKey":                                string(0xe0da),
+	"VpnLock":                               string(0xe62f),
+	"Wallpaper":                             string(0xe1bc),
+	"Warning":                               string(0xe002),
+	"Watch":                                 string(0xe334),
+	"WatchLater":                            string(0xe924),
+	"WbAuto":                                string(0xe42c),
+	"WbCloudy":                              string(0xe42d),
+	"WbIncandescent":                        string(0xe42e),
+	"WbIridescent":                          string(0xe436),
+	"WbSunny":                               string(0xe430),
+	"Wc":                                    string(0xe63d),
+	"Web":                                   string(0xe051),
+	"WebAsset":                              string(0xe069),
+	"Weekend":                               string(0xe16b),
+	"Whatshot":                              string(0xe80e),
+	"Widgets":                               string(0xe1bd),
+	"Wifi":                                  string(0xe63e),
+	"WifiLock":                              string(0xe1e1),
+	"WifiTethering":                         string(0xe1e2),
+	"Work":                                  string(0xe8f9),
+	"WrapText":                              string(0xe25b),
+	"YoutubeSearchedFor":                    string(0xe8fa),
+	"ZoomIn":                                string(0xe8ff),
+	"ZoomOut":                               string(0xe900),
+	"ZoomOutMap":                            string(0xe56b),
 }

+ 2 - 1
gui/chart.go

@@ -6,6 +6,8 @@ package gui
 
 import (
 	"fmt"
+	"math"
+
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/geometry"
 	"github.com/g3n/engine/gls"
@@ -13,7 +15,6 @@ import (
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/renderer/shaders"
-	"math"
 )
 
 func init() {

+ 3 - 2
gui/edit.go

@@ -5,11 +5,12 @@
 package gui
 
 import (
+	"strings"
+	"time"
+
 	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/text"
 	"github.com/g3n/engine/window"
-	"strings"
-	"time"
 )
 
 // Edit represents a text edit box GUI element

+ 2 - 1
gui/image.go

@@ -5,8 +5,9 @@
 package gui
 
 import (
-	"github.com/g3n/engine/texture"
 	"image"
+
+	"github.com/g3n/engine/texture"
 )
 
 // Image is a Panel which contains a single Image

+ 2 - 1
gui/itemscroller.go

@@ -5,8 +5,9 @@
 package gui
 
 import (
-	"github.com/g3n/engine/window"
 	"math"
+
+	"github.com/g3n/engine/window"
 )
 
 // ItemScroller is the GUI element that allows scrolling of IPanels

+ 2 - 1
loader/collada/animation.go

@@ -6,9 +6,10 @@ package collada
 
 import (
 	"fmt"
+	"strings"
+
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/math32"
-	"strings"
 )
 
 // AnimationTarget contains all animation channels for an specific target node

+ 3 - 2
loader/collada/collada.go

@@ -8,11 +8,12 @@ package collada
 import (
 	"encoding/xml"
 	"fmt"
+	"io"
+	"os"
+
 	"github.com/g3n/engine/geometry"
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/texture"
-	"io"
-	"os"
 )
 
 // Decoder contains all decoded data from collada file

+ 179 - 3
loader/collada/geometry.go

@@ -6,11 +6,12 @@ package collada
 
 import (
 	"fmt"
+	"reflect"
+	"strings"
+
 	"github.com/g3n/engine/geometry"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
-	"reflect"
-	"strings"
 )
 
 // GetGeometry returns a pointer to an instance of the geometry
@@ -308,7 +309,182 @@ func newMeshPolylist(m *Mesh, pels []interface{}) (*geometry.Geometry, uint32, e
 
 func newMeshTriangles(m *Mesh, tr *Triangles) (*geometry.Geometry, uint32, error) {
 
-	return nil, 0, fmt.Errorf("not implemented yet")
+	// Get vertices positions
+	if len(m.Vertices.Input) != 1 {
+		return nil, 0, fmt.Errorf("Mesh.Vertices.Input length not supported")
+	}
+	vinp := m.Vertices.Input[0]
+	if vinp.Semantic != "POSITION" {
+		return nil, 0, fmt.Errorf("Mesh.Vertices.Input.Semantic:%s not supported", vinp.Semantic)
+	}
+
+	// Get vertices input source
+	inps := getMeshSource(m, vinp.Source)
+	if inps == nil {
+		return nil, 0, fmt.Errorf("Source:%s not found", vinp.Source)
+	}
+
+	// Get vertices input float array
+	// Ignore Accessor (??)
+	posArray, ok := inps.ArrayElement.(*FloatArray)
+	if !ok {
+		return nil, 0, fmt.Errorf("Mesh.Vertices.Input.Source not FloatArray")
+	}
+
+	// Creates buffers
+	positions := math32.NewArrayF32(0, 0)
+	normals := math32.NewArrayF32(0, 0)
+	uvs := math32.NewArrayF32(0, 0)
+	indices := math32.NewArrayU32(0, 0)
+
+	// Creates vertices attributes map for reusing indices
+	mVindex := make(map[[8]float32]uint32)
+	var index uint32
+	geomGroups := make([]geometry.Group, 0)
+	groupMatindex := 0
+
+	// Get VERTEX input
+	inpVertex := getInputSemantic(tr.Input, "VERTEX")
+	if inpVertex == nil {
+		return nil, 0, fmt.Errorf("VERTEX input not found")
+	}
+
+	// Get optional NORMAL input
+	inpNormal := getInputSemantic(tr.Input, "NORMAL")
+	var normArray *FloatArray
+	if inpNormal != nil {
+		// Get normals source
+		source := getMeshSource(m, inpNormal.Source)
+		if source == nil {
+			return nil, 0, fmt.Errorf("NORMAL source:%s not found", inpNormal.Source)
+		}
+		// Get normals source float array
+		normArray, ok = source.ArrayElement.(*FloatArray)
+		if !ok {
+			return nil, 0, fmt.Errorf("NORMAL source:%s not float array", inpNormal.Source)
+		}
+	}
+
+	// Get optional TEXCOORD input
+	inpTexcoord := getInputSemantic(tr.Input, "TEXCOORD")
+	var texArray *FloatArray
+	if inpTexcoord != nil {
+		// Get texture coordinates source
+		source := getMeshSource(m, inpTexcoord.Source)
+		if source == nil {
+			return nil, 0, fmt.Errorf("TEXCOORD source:%s not found", inpTexcoord.Source)
+		}
+		// Get texture coordinates source float array
+		texArray, ok = source.ArrayElement.(*FloatArray)
+		if !ok {
+			return nil, 0, fmt.Errorf("TEXCOORD source:%s not float array", inpTexcoord.Source)
+		}
+	}
+
+	// Initialize geometry group
+	groupStart := indices.Size()
+	// For each primitive index
+	inputCount := len(tr.Input)
+	for i := 0; i < len(tr.P); i += inputCount {
+		// Vertex attributes: position(3) + normal(3) + uv(2)
+		var vx [8]float32
+
+		// Vertex position
+		posIndex := tr.P[i+inpVertex.Offset] * 3
+		// Get position vector and appends to its buffer
+		vx[0] = posArray.Data[posIndex]
+		vx[1] = posArray.Data[posIndex+1]
+		vx[2] = posArray.Data[posIndex+2]
+
+		// Optional vertex normal
+		if inpNormal != nil {
+			// Get normal index from P
+			normIndex := tr.P[i+inpNormal.Offset] * 3
+			// Get normal vector and appends to its buffer
+			vx[3] = normArray.Data[normIndex]
+			vx[4] = normArray.Data[normIndex+1]
+			vx[5] = normArray.Data[normIndex+2]
+		}
+
+		// Optional vertex texture coordinate
+		if inpTexcoord != nil {
+			// Get normal index from P
+			texIndex := tr.P[i+inpTexcoord.Offset] * 2
+			// Get normal vector and appends to its buffer
+			vx[6] = texArray.Data[texIndex]
+			vx[7] = texArray.Data[texIndex+1]
+		}
+
+		// If this vertex and its attributes has already been appended,
+		// reuse it, adding its index to the index buffer
+		// to reuse its index
+		idx, ok := mVindex[vx]
+		if ok {
+			indices.Append(idx)
+			continue
+		}
+		// Appends new vertex position and attributes to its buffers
+		positions.Append(vx[0], vx[1], vx[2])
+		if inpNormal != nil {
+			normals.Append(vx[3], vx[4], vx[5])
+		}
+		if inpTexcoord != nil {
+			uvs.Append(vx[6], vx[7])
+		}
+		indices.Append(index)
+		// Save the index to this vertex position and attributes for
+		// future reuse
+		mVindex[vx] = index
+		index++
+	}
+	// Adds this geometry group to the list
+	geomGroups = append(geomGroups, geometry.Group{
+		Start:    groupStart,
+		Count:    indices.Size() - groupStart,
+		Matindex: groupMatindex,
+		Matid:    tr.Material,
+	})
+
+	// Debug dump
+	//for i := 0; i < positions.Size()/3; i++ {
+	//    vidx := i*3
+	//    msg := fmt.Sprintf("i:%2d position:%v %v %v",
+	//        i, positions.Get(vidx), positions.Get(vidx+1), positions.Get(vidx+2))
+	//    if normals.Size() > 0 {
+	//        msg += fmt.Sprintf("\tnormal:%v %v %v",
+	//            normals.Get(vidx), normals.Get(vidx+1), normals.Get(vidx+2))
+	//    }
+	//    if uvs.Size() > 0 {
+	//	    msg += fmt.Sprintf("\tuv:%v %v", uvs.Get(i*2), uvs.Get(i*2+1))
+	//    }
+	//    log.Debug("%s", msg)
+	//}
+	//log.Debug("indices(%d):%v", indices.Size(), indices)
+	//log.Debug("groups:%v", geomGroups)
+
+	// Creates geometry
+	geom := geometry.NewGeometry()
+
+	// Creates VBO with vertex positions
+	geom.AddVBO(gls.NewVBO(positions).AddAttrib(gls.VertexPosition))
+
+	// Creates VBO with vertex normals
+	if normals.Size() > 0 {
+		geom.AddVBO(gls.NewVBO(normals).AddAttrib(gls.VertexNormal))
+	}
+
+	// Creates VBO with uv coordinates
+	if uvs.Size() > 0 {
+		geom.AddVBO(gls.NewVBO(uvs).AddAttrib(gls.VertexTexcoord))
+	}
+
+	// Sets the geometry indices buffer
+	geom.SetIndices(indices)
+
+	// Add material groups to the geometry
+	geom.AddGroupList(geomGroups)
+
+	return geom, gls.TRIANGLES, nil
 }
 
 func newMeshLines(m *Mesh, ln *Lines) (*geometry.Geometry, uint32, error) {

+ 42 - 0
loader/collada/library_geometries.go

@@ -334,6 +334,14 @@ func (d *Decoder) decMesh(start xml.StartElement, geom *Geometry) error {
 			}
 			continue
 		}
+		// Decodes triangles
+		if child.Name.Local == "triangles" {
+			err = d.decTriangles(child, mesh)
+			if err != nil {
+				return err
+			}
+			continue
+		}
 	}
 }
 
@@ -444,6 +452,40 @@ func (d *Decoder) decPolylist(start xml.StartElement, mesh *Mesh) error {
 	}
 }
 
+func (d *Decoder) decTriangles(start xml.StartElement, mesh *Mesh) error {
+	tr := &Triangles{}
+
+	tr.Name = findAttrib(start, "name").Value
+	tr.Count, _ = strconv.Atoi(findAttrib(start, "count").Value)
+	tr.Material = findAttrib(start, "material").Value
+	mesh.PrimitiveElements = append(mesh.PrimitiveElements, tr)
+
+	for {
+		// Get next child
+		child, data, err := d.decNextChild(start)
+		if err != nil || child.Name.Local == "" {
+			return err
+		}
+		// Decode input shared
+		if child.Name.Local == "input" {
+			inp, err := d.decInputShared(child)
+			if err != nil {
+				return err
+			}
+			tr.Input = append(tr.Input, inp)
+			continue
+		}
+		// Decode p (primitive)
+		if child.Name.Local == "p" {
+			p, err := d.decPrimitive(child, data)
+			if err != nil {
+				return err
+			}
+			tr.P = p
+		}
+	}
+}
+
 func (d *Decoder) decInputShared(start xml.StartElement) (InputShared, error) {
 
 	var inp InputShared

+ 3 - 2
loader/collada/material.go

@@ -6,11 +6,12 @@ package collada
 
 import (
 	"fmt"
+	"path/filepath"
+	"strings"
+
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/texture"
-	"path/filepath"
-	"strings"
 )
 
 // GetMaterial returns a pointer to an instance of the material

+ 2 - 1
loader/collada/scene.go

@@ -6,12 +6,13 @@ package collada
 
 import (
 	"fmt"
+	"strings"
+
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/graphic"
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/math32"
-	"strings"
 )
 
 // NewScene returns a new collada empty scene

+ 6 - 5
loader/gltf/gltf.go

@@ -6,14 +6,15 @@
 package gltf
 
 import (
+	"image"
+
 	"github.com/g3n/engine/animation"
 	"github.com/g3n/engine/camera"
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/gls"
+	"github.com/g3n/engine/graphic"
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/math32"
-	"image"
-	"github.com/g3n/engine/graphic"
 )
 
 // glTF Extensions.
@@ -325,9 +326,9 @@ type Sparse struct {
 type Target struct {
 	Node int    // The index of the node to target. Not required.
 	Path string // The name of the node's TRS property to modify, or the "weights" of the Morph Targets it instantiates. Required.
-	            // For the "translation" property, the values that are provided by the sampler are the translation along the x, y, and z axes.
-	            // For the "rotation" property, the values are a quaternion in the order (x, y, z, w), where w is the scalar.
-	            // For the "scale" property, the values are the scaling factors along the x, y, and z axes.
+	// For the "translation" property, the values that are provided by the sampler are the translation along the x, y, and z axes.
+	// For the "rotation" property, the values are a quaternion in the order (x, y, z, w), where w is the scalar.
+	// For the "scale" property, the values are the scaling factors along the x, y, and z axes.
 	Extensions map[string]interface{} // Dictionary object with extension-specific objects. Not required.
 	Extras     interface{}            // Application-specific data. Not required.
 }

+ 3 - 3
loader/gltf/material_pbr.go

@@ -26,7 +26,7 @@ func (g *GLTF) loadMaterialPBR(m *Material) (material.IMaterial, error) {
 	}
 
 	var alphaMode string
-	if len(m.AlphaMode) > 0{
+	if len(m.AlphaMode) > 0 {
 		alphaMode = m.AlphaMode
 	} else {
 		alphaMode = "OPAQUE"
@@ -47,7 +47,7 @@ func (g *GLTF) loadMaterialPBR(m *Material) (material.IMaterial, error) {
 	if pbr.BaseColorFactor != nil {
 		baseColorFactor = math32.Color4{pbr.BaseColorFactor[0], pbr.BaseColorFactor[1], pbr.BaseColorFactor[2], pbr.BaseColorFactor[3]}
 	} else {
-		baseColorFactor = math32.Color4{1,1,1,1}
+		baseColorFactor = math32.Color4{1, 1, 1, 1}
 	}
 	pm.SetBaseColorFactor(&baseColorFactor)
 
@@ -81,7 +81,7 @@ func (g *GLTF) loadMaterialPBR(m *Material) (material.IMaterial, error) {
 		if m.EmissiveTexture != nil {
 			emissiveFactor = math32.Color{1, 1, 1}
 		} else {
-			emissiveFactor = math32.Color{0,0,0}
+			emissiveFactor = math32.Color{0, 0, 0}
 		}
 	}
 	pm.SetEmissiveFactor(&emissiveFactor)

+ 2 - 5
loader/obj/obj.go

@@ -704,11 +704,8 @@ func (dec *Decoder) parseSmooth(fields []string) error {
 		dec.smoothCurrent = false
 		return nil
 	}
-	if fields[0] == "1" || fields[0] == "on" {
-		dec.smoothCurrent = true
-		return nil
-	}
-	return dec.formatError("'s' with invalid value")
+	dec.smoothCurrent = true
+	return nil
 }
 
 /******************************************************************************

+ 6 - 0
material/material.go

@@ -375,3 +375,9 @@ func (mat *Material) TextureCount() int {
 
 	return len(mat.textures)
 }
+
+// Textures returns a slice with this material's textures
+func (mat *Material) Textures() []*texture.Texture2D {
+
+	return mat.textures
+}

+ 10 - 0
material/standard.go

@@ -38,6 +38,16 @@ func NewStandard(color *math32.Color) *Standard {
 	return ms
 }
 
+// NewBlinnPhong creates and returns a pointer to a new Standard material using Blinn-Phong model
+// It is very close to Standard (Phong) model so we need only pass a parameter
+func NewBlinnPhong(color *math32.Color) *Standard {
+
+	ms := new(Standard)
+	ms.Init("standard", color)
+	ms.ShaderDefines.Set("BLINN", "true")
+	return ms
+}
+
 // Init initializes the material setting the specified shader and color
 // It is used mainly when the material is embedded in another type
 func (ms *Standard) Init(shader string, color *math32.Color) {

+ 2 - 2
math32/array.go

@@ -209,7 +209,7 @@ func (a ArrayF32) SetColor4(pos int, v *Color4) {
 // ToFloat32 converts this array to an array of float32
 func (a ArrayF32) ToFloat32() []float32 {
 
-	return (*[1 << 27]float32)(unsafe.Pointer(&a[0]))[:len(a)]
+	return a
 }
 
 // ArrayU32 is a slice of uint32 with additional convenience methods
@@ -249,5 +249,5 @@ func (a *ArrayU32) Append(v ...uint32) {
 // ToUint32 converts this array to an array of uint32
 func (a ArrayU32) ToUint32() []uint32 {
 
-	return (*[1 << 27]uint32)(unsafe.Pointer(&a[0]))[:len(a)]
+	return a
 }

+ 22 - 25
math32/curves.go

@@ -23,7 +23,7 @@ func (c *Curve) SetLength() {
 	l := float32(0.0)
 	for i := 1; i < len(points); i++ {
 		p0 := points[i].Clone()
-		p1 := points[i - 1].Clone()
+		p1 := points[i-1].Clone()
 		l += (p0.Sub(p1)).Length()
 	}
 	c.length = l
@@ -33,7 +33,7 @@ func (c *Curve) SetLength() {
 // creates and returns a pointer to a new curve
 // combined curves are unaffected
 func (c *Curve) Continue(other *Curve) *Curve {
-	last := c.points[len(c.points) - 1].Clone()
+	last := c.points[len(c.points)-1].Clone()
 	first := other.points[0].Clone()
 
 	var continued, otherpoints []Vector3
@@ -61,10 +61,10 @@ func NewBezierQuadratic(origin, control, destination *Vector3, npoints int) *Cur
 	if npoints <= 2 {
 		npoints = 3
 	}
-	var equation func(float32, float32, float32, float32) float32
-	equation = func(t, v0, v1, v2 float32) float32 {
+
+	var equation = func(t, v0, v1, v2 float32) float32 {
 		a0 := 1.0 - t
-		result := a0 * a0 * v0 + 2.0 * t * a0 * v1 + t * t * v2
+		result := a0*a0*v0 + 2.0*t*a0*v1 + t*t*v2
 		return result
 	}
 	var bezier []Vector3
@@ -92,11 +92,10 @@ func NewBezierCubic(origin, control1, control2, destination *Vector3, npoints in
 	if npoints <= 3 {
 		npoints = 4
 	}
-	
-	var equation func(float32, float32, float32, float32, float32) float32
-	equation = func(t, v0, v1, v2, v3 float32) float32 {
+
+	var equation = func(t, v0, v1, v2, v3 float32) float32 {
 		a0 := 1.0 - t
-		result := a0 * a0 * a0 * v0 + 3.0 * t * a0 * a0 * v1 + 3.0 * t * t * a0 * v2 + t * t * t * v3
+		result := a0*a0*a0*v0 + 3.0*t*a0*a0*v1 + 3.0*t*t*a0*v2 + t*t*t*v3
 		return result
 	}
 	var bezier []Vector3
@@ -121,8 +120,7 @@ func NewBezierCubic(origin, control1, control2, destination *Vector3, npoints in
 func NewHermiteSpline(origin, tangent1, destination, tangent2 *Vector3, npoints int) *Curve {
 	c := new(Curve)
 
-	var equation func(float32, *Vector3, *Vector3, *Vector3, *Vector3) *Vector3
-	equation = func(t float32, v0, tan0, v1, tan1 *Vector3) *Vector3 {
+	var equation = func(t float32, v0, tan0, v1, tan1 *Vector3) *Vector3 {
 		t2 := t * t
 		t3 := t * t2
 		p0 := (2.0 * t3) - (3.0 * t2) + 1.0
@@ -138,7 +136,7 @@ func NewHermiteSpline(origin, tangent1, destination, tangent2 *Vector3, npoints
 	step := float32(1.0) / float32(npoints)
 	var hermite []Vector3
 	for i := 0; i <= npoints; i++ {
-		vect := equation(float32(i) * step, origin, tangent1, destination, tangent2)
+		vect := equation(float32(i)*step, origin, tangent1, destination, tangent2)
 		hermite = append(hermite, *vect)
 	}
 	c.points = hermite
@@ -152,20 +150,19 @@ func NewHermiteSpline(origin, tangent1, destination, tangent2 *Vector3, npoints
 func NewCatmullRomSpline(points []*Vector3, npoints int, closed bool) *Curve {
 	c := new(Curve)
 
-	var equation func(float32, *Vector3, *Vector3, *Vector3, *Vector3) *Vector3
-	equation = func(t float32, v0, v1, v2, v3 *Vector3) *Vector3 {
-		t2 := t * t;
-		t3 := t * t2;
+	var equation = func(t float32, v0, v1, v2, v3 *Vector3) *Vector3 {
+		t2 := t * t
+		t3 := t * t2
 		x := 0.5 * ((((2.0 * v1.X) + ((-v0.X + v2.X) * t)) +
 			(((((2.0 * v0.X) - (5.0 * v1.X)) + (4.0 * v2.X)) - v3.X) * t2)) +
-			((((-v0.X + (3.0 * v1.X)) - (3.0 * v2.X)) + v3.X) * t3));
+			((((-v0.X + (3.0 * v1.X)) - (3.0 * v2.X)) + v3.X) * t3))
 		y := 0.5 * ((((2.0 * v1.Y) + ((-v0.Y + v2.Y) * t)) +
 			(((((2.0 * v0.Y) - (5.0 * v1.Y)) + (4.0 * v2.Y)) - v3.Y) * t2)) +
-			((((-v0.Y + (3.0 * v1.Y)) - (3.0 * v2.Y)) + v3.Y) * t3));
+			((((-v0.Y + (3.0 * v1.Y)) - (3.0 * v2.Y)) + v3.Y) * t3))
 		z := 0.5 * ((((2.0 * v1.Z) + ((-v0.Z + v2.Z) * t)) +
 			(((((2.0 * v0.Z) - (5.0 * v1.Z)) + (4.0 * v2.Z)) - v3.Z) * t2)) +
-			((((-v0.Z + (3.0 * v1.Z)) - (3.0 * v2.Z)) + v3.Z) * t3));
-		return NewVector3(x, y, z);
+			((((-v0.Z + (3.0 * v1.Z)) - (3.0 * v2.Z)) + v3.Z) * t3))
+		return NewVector3(x, y, z)
 	}
 
 	step := float32(1.0) / float32(npoints)
@@ -176,7 +173,7 @@ func NewCatmullRomSpline(points []*Vector3, npoints int, closed bool) *Curve {
 		for i := 0; i < count; i++ {
 			t = 0.0
 			for n := 0; n < npoints; n++ {
-				vect := equation(t, points[i % count], points[(i + 1) % count], points[(i + 2) % count], points[(i + 3) % count])
+				vect := equation(t, points[i%count], points[(i+1)%count], points[(i+2)%count], points[(i+3)%count])
 				catmull = append(catmull, *vect)
 				t += step
 			}
@@ -185,18 +182,18 @@ func NewCatmullRomSpline(points []*Vector3, npoints int, closed bool) *Curve {
 	} else {
 		total := []*Vector3{points[0].Clone()}
 		total = append(total, points...)
-		total = append(total, points[len(points) - 1].Clone())
+		total = append(total, points[len(points)-1].Clone())
 		var i int
-		for i = 0; i < len(total) - 3; i++ {
+		for i = 0; i < len(total)-3; i++ {
 			t = 0
 			for n := 0; n < npoints; n++ {
-				vect := equation(t, total[i], total[i + 1], total[i + 2], total[i + 3])
+				vect := equation(t, total[i], total[i+1], total[i+2], total[i+3])
 				catmull = append(catmull, *vect)
 				t += step
 			}
 		}
 		i--
-		vect := equation(t, total[i], total[i + 1], total[i + 2], total[i + 3])
+		vect := equation(t, total[i], total[i+1], total[i+2], total[i+3])
 		catmull = append(catmull, *vect)
 	}
 	c.points = catmull

+ 20 - 0
math32/matrix4.go

@@ -753,3 +753,23 @@ func (m *Matrix4) Clone() *Matrix4 {
 	cloned = *m
 	return &cloned
 }
+
+// GetColumn returns the ith column.
+func (m *Matrix4) GetColumn(i int) *Vector4 {
+	return NewVector4(m[i*4], m[i*4+1], m[i*4+2], m[i*4+3])
+}
+
+// GetRow returns the ith row.
+func (m *Matrix4) GetRow(i int) *Vector4 {
+	return NewVector4(m[i], m[i*4], m[i+8], m[i+12])
+}
+
+// GetColumn returns the ith column.
+func (m *Matrix4) GetColumnVector3(i int) *Vector3 {
+	return NewVector3(m[i*4], m[i*4+1], m[i*4+2])
+}
+
+// GetRow returns the ith row.
+func (m *Matrix4) GetRowVector3(i int) *Vector3 {
+	return NewVector3(m[i], m[i*4], m[i+8])
+}

+ 5 - 5
math32/quaternion.go

@@ -265,7 +265,7 @@ func (q *Quaternion) Normalize() *Quaternion {
 // Returns pointer to this updated quaternion.
 func (q *Quaternion) NormalizeFast() *Quaternion {
 
-	f := (3.0-(q.X*q.X + q.Y*q.Y + q.Z*q.Z + q.W*q.W))/2.0
+	f := (3.0 - (q.X*q.X + q.Y*q.Y + q.Z*q.Z + q.W*q.W)) / 2.0
 	if f == 0 {
 		q.X = 0
 		q.Y = 0
@@ -346,9 +346,9 @@ func (q *Quaternion) Slerp(other *Quaternion, t float32) *Quaternion {
 		return q
 	}
 
-	sqrSinHalfTheta := 1.0 - cosHalfTheta * cosHalfTheta
+	sqrSinHalfTheta := 1.0 - cosHalfTheta*cosHalfTheta
 	if sqrSinHalfTheta < 0.001 {
-		s := 1-t
+		s := 1 - t
 		q.W = s*w + t*q.W
 		q.X = s*x + t*q.X
 		q.Y = s*y + t*q.Y
@@ -356,8 +356,8 @@ func (q *Quaternion) Slerp(other *Quaternion, t float32) *Quaternion {
 		return q.Normalize()
 	}
 
-	sinHalfTheta := Sqrt( sqrSinHalfTheta )
-	halfTheta := Atan2( sinHalfTheta, cosHalfTheta )
+	sinHalfTheta := Sqrt(sqrSinHalfTheta)
+	halfTheta := Atan2(sinHalfTheta, cosHalfTheta)
 	ratioA := Sin((1-t)*halfTheta) / sinHalfTheta
 	ratioB := Sin(t*halfTheta) / sinHalfTheta
 

+ 24 - 0
math32/vector2.go

@@ -23,6 +23,12 @@ func NewVec2() *Vector2 {
 	return &Vector2{X: 0, Y: 0}
 }
 
+// Clone returns a copy of this vector
+func (v *Vector2) Clone() *Vector2 {
+
+	return NewVector2(v.X, v.Y)
+}
+
 // Set sets this vector X and Y components.
 // Returns the pointer to this updated vector.
 func (v *Vector2) Set(x, y float32) *Vector2 {
@@ -404,3 +410,21 @@ func (v *Vector2) InTriangle(p0, p1, p2 *Vector2) bool {
 
 	return s >= 0 && t >= 0 && (s+t) < 2*A*sign
 }
+
+// AlmostEquals returns whether the vector is almost equal to another vector within the specified tolerance.
+func (v *Vector2) AlmostEquals(other *Vector2, tolerance float32) bool {
+
+	if (Abs(v.X-other.X) < tolerance) &&
+		(Abs(v.Y-other.Y) < tolerance) {
+		return true
+	}
+	return false
+}
+
+// AngleTo returns the angle between this vector and other
+func (v *Vector2) AngleTo(other *Vector2) float32 {
+
+	theta := v.Dot(other) / (v.Length() * other.Length())
+	// clamp, to handle numerical problems
+	return Acos(Clamp(theta, -1, 1))
+}

+ 13 - 8
math32/vector3.go

@@ -393,7 +393,7 @@ func (v *Vector3) SetLength(l float32) *Vector3 {
 }
 
 // Lerp sets each of this vector's components to the linear interpolated value of
-// alpha between ifself and the corresponding other component.
+// alpha between itself and the corresponding other component.
 // Returns the pointer to this updated vector.
 func (v *Vector3) Lerp(other *Vector3, alpha float32) *Vector3 {
 
@@ -642,12 +642,12 @@ func (v *Vector3) SetFromQuaternion(q *Quaternion) *Vector3 {
 // RandomTangents computes and returns two arbitrary tangents to the vector.
 func (v *Vector3) RandomTangents() (*Vector3, *Vector3) {
 
-	t1 := NewVector3(0,0,0)
-	t2 := NewVector3(0,0,0)
+	t1 := NewVector3(0, 0, 0)
+	t2 := NewVector3(0, 0, 0)
 	length := v.Length()
 	if length > 0 {
 		n := NewVector3(v.X/length, v.Y/length, v.Z/length)
-		randVec := NewVector3(0,0,0)
+		randVec := NewVector3(0, 0, 0)
 		if Abs(n.X) < 0.9 {
 			randVec.SetX(1)
 			t1.CrossVectors(n, randVec)
@@ -671,10 +671,15 @@ func (v *Vector3) RandomTangents() (*Vector3, *Vector3) {
 // AlmostEquals returns whether the vector is almost equal to another vector within the specified tolerance.
 func (v *Vector3) AlmostEquals(other *Vector3, tolerance float32) bool {
 
-	if (Abs(v.X - other.X) < tolerance) &&
-		(Abs(v.Y - other.Y) < tolerance) &&
-		(Abs(v.Z - other.Z) < tolerance) {
-			return true
+	if (Abs(v.X-other.X) < tolerance) &&
+		(Abs(v.Y-other.Y) < tolerance) &&
+		(Abs(v.Z-other.Z) < tolerance) {
+		return true
 	}
 	return false
 }
+
+// Vector4 returns a new Vector4 based on this vector and the provided w value.
+func (v *Vector3) Vector4(w float32) *Vector4 {
+	return &Vector4{X: v.X, Y: v.Y, Z: v.Z, W: w}
+}

+ 18 - 1
math32/vector4.go

@@ -506,7 +506,7 @@ func (v *Vector4) SetAxisAngleFromQuaternion(q *Quaternion) *Vector4 {
 	return v
 }
 
-// SetAxisFromRotationMatrix this vector to be the axis (x, y, z) and angle (w) of a rotation specified the matrix m.
+// SetAxisFromRotationMatrix sets this vector to be the axis (x, y, z) and angle (w) of a rotation specified the matrix m.
 // Assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled).
 func (v *Vector4) SetAxisFromRotationMatrix(m *Matrix4) *Vector4 {
 
@@ -630,3 +630,20 @@ func (v *Vector4) Clone() *Vector4 {
 
 	return NewVector4(v.X, v.Y, v.Z, v.W)
 }
+
+// AlmostEquals returns whether the vector is almost equal to another vector within the specified tolerance.
+func (v *Vector4) AlmostEquals(other *Vector4, tolerance float32) bool {
+
+	if (Abs(v.X-other.X) < tolerance) &&
+		(Abs(v.Y-other.Y) < tolerance) &&
+		(Abs(v.Z-other.Z) < tolerance) &&
+		(Abs(v.W-other.W) < tolerance) {
+		return true
+	}
+	return false
+}
+
+// Vector3 returns a new Vector3 based on this vector, without the w value.
+func (v *Vector4) Vector3() *Vector3 {
+	return &Vector3{X: v.X, Y: v.Y, Z: v.Z}
+}

+ 2 - 1
renderer/renderer.go

@@ -6,6 +6,8 @@
 package renderer
 
 import (
+	"sort"
+
 	"github.com/g3n/engine/camera"
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/gls"
@@ -15,7 +17,6 @@ import (
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/util/logger"
-	"sort"
 )
 
 // Package logger

+ 23 - 3
renderer/shaders/include/phong_model.glsl

@@ -28,6 +28,8 @@ void phongModel(vec4 position, vec3 normal, vec3 camDir, vec3 matAmbient, vec3 m
     bool noLights = true;
     const float EPS = 0.00001;
 
+    float specular;
+
 #if AMB_LIGHTS>0
     noLights = false;
     // Ambient lights
@@ -44,7 +46,13 @@ void phongModel(vec4 position, vec3 normal, vec3 camDir, vec3 matAmbient, vec3 m
         float dotNormal = dot(lightDirection, normal); // Dot product between light direction and fragment normal
         if (dotNormal > EPS) { // If the fragment is lit
             diffuseTotal += DirLightColor(i) * matDiffuse * dotNormal;
-            specularTotal += DirLightColor(i) * MatSpecularColor * pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+
+#ifdef BLINN
+            specular = pow(max(dot(normal, normalize(lightDirection + camDir)), 0.0), MatShininess);
+#else
+            specular = pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+#endif
+            specularTotal += DirLightColor(i) * MatSpecularColor * specular;
         }
     }
 #endif
@@ -61,7 +69,13 @@ void phongModel(vec4 position, vec3 normal, vec3 camDir, vec3 matAmbient, vec3 m
             float attenuation = 1.0 / (1.0 + lightDistance * (PointLightLinearDecay(i) + PointLightQuadraticDecay(i) * lightDistance));
             vec3 attenuatedColor = PointLightColor(i) * attenuation;
             diffuseTotal += attenuatedColor * matDiffuse * dotNormal;
-            specularTotal += attenuatedColor * MatSpecularColor * pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+
+#ifdef BLINN
+            specular = pow(max(dot(normal, normalize(lightDirection + camDir)), 0.0), MatShininess);
+#else
+            specular = pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+#endif
+            specularTotal += attenuatedColor * MatSpecularColor * specular;
         }
     }
 #endif
@@ -83,7 +97,13 @@ void phongModel(vec4 position, vec3 normal, vec3 camDir, vec3 matAmbient, vec3 m
                 float spotFactor = pow(angleDot, SpotLightAngularDecay(i));
                 vec3 attenuatedColor = SpotLightColor(i) * attenuation * spotFactor;
                 diffuseTotal += attenuatedColor * matDiffuse * dotNormal;
-                specularTotal += attenuatedColor * MatSpecularColor * pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+
+#ifdef BLINN
+                specular = pow(max(dot(normal, normalize(lightDirection + camDir)), 0.0), MatShininess);
+#else
+                specular = pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+#endif
+                specularTotal += attenuatedColor * MatSpecularColor * specular;
             }
         }
     }

+ 23 - 3
renderer/shaders/sources.go

@@ -192,6 +192,8 @@ void phongModel(vec4 position, vec3 normal, vec3 camDir, vec3 matAmbient, vec3 m
     bool noLights = true;
     const float EPS = 0.00001;
 
+    float specular;
+
 #if AMB_LIGHTS>0
     noLights = false;
     // Ambient lights
@@ -208,7 +210,13 @@ void phongModel(vec4 position, vec3 normal, vec3 camDir, vec3 matAmbient, vec3 m
         float dotNormal = dot(lightDirection, normal); // Dot product between light direction and fragment normal
         if (dotNormal > EPS) { // If the fragment is lit
             diffuseTotal += DirLightColor(i) * matDiffuse * dotNormal;
-            specularTotal += DirLightColor(i) * MatSpecularColor * pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+
+#ifdef BLINN
+            specular = pow(max(dot(normal, normalize(lightDirection + camDir)), 0.0), MatShininess);
+#else
+            specular = pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+#endif
+            specularTotal += DirLightColor(i) * MatSpecularColor * specular;
         }
     }
 #endif
@@ -225,7 +233,13 @@ void phongModel(vec4 position, vec3 normal, vec3 camDir, vec3 matAmbient, vec3 m
             float attenuation = 1.0 / (1.0 + lightDistance * (PointLightLinearDecay(i) + PointLightQuadraticDecay(i) * lightDistance));
             vec3 attenuatedColor = PointLightColor(i) * attenuation;
             diffuseTotal += attenuatedColor * matDiffuse * dotNormal;
-            specularTotal += attenuatedColor * MatSpecularColor * pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+
+#ifdef BLINN
+            specular = pow(max(dot(normal, normalize(lightDirection + camDir)), 0.0), MatShininess);
+#else
+            specular = pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+#endif
+            specularTotal += attenuatedColor * MatSpecularColor * specular;
         }
     }
 #endif
@@ -247,7 +261,13 @@ void phongModel(vec4 position, vec3 normal, vec3 camDir, vec3 matAmbient, vec3 m
                 float spotFactor = pow(angleDot, SpotLightAngularDecay(i));
                 vec3 attenuatedColor = SpotLightColor(i) * attenuation * spotFactor;
                 diffuseTotal += attenuatedColor * matDiffuse * dotNormal;
-                specularTotal += attenuatedColor * MatSpecularColor * pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+
+#ifdef BLINN
+                specular = pow(max(dot(normal, normalize(lightDirection + camDir)), 0.0), MatShininess);
+#else
+                specular = pow(max(dot(reflect(-lightDirection, normal), camDir), 0.0), MatShininess);
+#endif
+                specularTotal += attenuatedColor * MatSpecularColor * specular;
             }
         }
     }

+ 2 - 1
renderer/shaman.go

@@ -9,10 +9,11 @@ import (
 	"regexp"
 	"strings"
 
+	"strconv"
+
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/renderer/shaders"
-	"strconv"
 )
 
 // Regular expression to parse #include <name> [quantity] directive

+ 2 - 1
text/atlas.go

@@ -7,11 +7,12 @@ package text
 import (
 	"bufio"
 	"fmt"
-	"github.com/g3n/engine/math32"
 	"image"
 	"image/png"
 	"os"
 	"unicode/utf8"
+
+	"github.com/g3n/engine/math32"
 )
 
 // CharInfo contains the information to locate a character in an Atlas

+ 5 - 4
text/font.go

@@ -5,15 +5,16 @@
 package text
 
 import (
-	"github.com/g3n/engine/math32"
-	"github.com/golang/freetype/truetype"
-	"golang.org/x/image/font"
-	"golang.org/x/image/math/fixed"
 	"image"
 	"image/color"
 	"image/draw"
 	"io/ioutil"
 	"strings"
+
+	"github.com/g3n/engine/math32"
+	"github.com/golang/freetype/truetype"
+	"golang.org/x/image/font"
+	"golang.org/x/image/math/fixed"
 )
 
 // Font represents a TrueType font face.

+ 2 - 1
texture/animator.go

@@ -5,8 +5,9 @@
 package texture
 
 import (
-	"github.com/g3n/engine/gls"
 	"time"
+
+	"github.com/g3n/engine/gls"
 )
 
 // Animator can generate a texture animation based on a texture sheet

+ 8 - 1
texture/texture2D.go

@@ -7,7 +7,6 @@ package texture
 
 import (
 	"fmt"
-	"github.com/g3n/engine/util/logger"
 	"image"
 	"image/draw"
 	_ "image/gif"
@@ -15,6 +14,8 @@ import (
 	_ "image/png"
 	"os"
 
+	"github.com/g3n/engine/util/logger"
+
 	"github.com/g3n/engine/gls"
 )
 
@@ -142,6 +143,12 @@ func (t *Texture2D) Dispose() {
 	}
 }
 
+// TexName returns the texture handle for the texture
+func (t *Texture2D) TexName() uint32 {
+
+	return t.texname
+}
+
 // SetUniformNames sets the names of the uniforms in the shader for sampler and texture info.
 func (t *Texture2D) SetUniformNames(sampler, info string) {
 

+ 1 - 1
tools/g3nshaders/main.go

@@ -123,7 +123,7 @@ func main() {
 	flag.Parse()
 
 	// If requested, print version and exits
-	if *oVersion == true {
+	if *oVersion {
 		fmt.Fprintf(os.Stderr, "%s v%d.%d\n", PROGNAME, VMAJOR, VMINOR)
 		return
 	}

+ 2 - 1
util/stats/stats.go

@@ -1,9 +1,10 @@
 package stats
 
 import (
-	"github.com/g3n/engine/gls"
 	"runtime"
 	"time"
+
+	"github.com/g3n/engine/gls"
 )
 
 // Stats contains several statistics useful for performance evaluation

+ 122 - 0
util/wasm/wasm.go

@@ -0,0 +1,122 @@
+// +build wasm
+// +build go1.13
+
+package wasm
+
+import (
+	"reflect"
+	"runtime"
+	"syscall/js"
+	"unsafe"
+)
+
+func sliceToByteSlice(s interface{}) []byte {
+	switch s := s.(type) {
+	case []int8:
+		h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
+		return *(*[]byte)(unsafe.Pointer(h))
+	case []int16:
+		h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
+		h.Len *= 2
+		h.Cap *= 2
+		return *(*[]byte)(unsafe.Pointer(h))
+	case []int32:
+		h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
+		h.Len *= 4
+		h.Cap *= 4
+		return *(*[]byte)(unsafe.Pointer(h))
+	case []int64:
+		h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
+		h.Len *= 8
+		h.Cap *= 8
+		return *(*[]byte)(unsafe.Pointer(h))
+	case []uint8:
+		return s
+	case []uint16:
+		h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
+		h.Len *= 2
+		h.Cap *= 2
+		return *(*[]byte)(unsafe.Pointer(h))
+	case []uint32:
+		h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
+		h.Len *= 4
+		h.Cap *= 4
+		return *(*[]byte)(unsafe.Pointer(h))
+	case []uint64:
+		h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
+		h.Len *= 8
+		h.Cap *= 8
+		return *(*[]byte)(unsafe.Pointer(h))
+	case []float32:
+		h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
+		h.Len *= 4
+		h.Cap *= 4
+		return *(*[]byte)(unsafe.Pointer(h))
+	case []float64:
+		h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
+		h.Len *= 8
+		h.Cap *= 8
+		return *(*[]byte)(unsafe.Pointer(h))
+	default:
+		panic("jsutil: unexpected value at sliceToBytesSlice()")
+	}
+}
+
+func SliceToTypedArray(s interface{}) (val js.Value, free func()) {
+	free = func() {}
+	switch s := s.(type) {
+	case []int8:
+		a := js.Global().Get("Uint8Array").New(len(s))
+		js.CopyBytesToJS(a, sliceToByteSlice(s))
+		runtime.KeepAlive(s)
+		buf := a.Get("buffer")
+		return js.Global().Get("Int8Array").New(buf, a.Get("byteOffset"), a.Get("byteLength")), free
+	case []int16:
+		a := js.Global().Get("Uint8Array").New(len(s) * 2)
+		js.CopyBytesToJS(a, sliceToByteSlice(s))
+		runtime.KeepAlive(s)
+		buf := a.Get("buffer")
+		return js.Global().Get("Int16Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/2), free
+	case []int32:
+		a := js.Global().Get("Uint8Array").New(len(s) * 4)
+		js.CopyBytesToJS(a, sliceToByteSlice(s))
+		runtime.KeepAlive(s)
+		buf := a.Get("buffer")
+		return js.Global().Get("Int32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4), free
+	case []uint8:
+		a := js.Global().Get("Uint8Array").New(len(s))
+		js.CopyBytesToJS(a, s)
+		runtime.KeepAlive(s)
+		return a, free
+	case []uint16:
+		a := js.Global().Get("Uint8Array").New(len(s) * 2)
+		js.CopyBytesToJS(a, sliceToByteSlice(s))
+		runtime.KeepAlive(s)
+		buf := a.Get("buffer")
+		return js.Global().Get("Uint16Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/2), free
+	case []uint32:
+		a := js.Global().Get("Uint8Array").New(len(s) * 4)
+		js.CopyBytesToJS(a, sliceToByteSlice(s))
+		runtime.KeepAlive(s)
+		buf := a.Get("buffer")
+		return js.Global().Get("Uint32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4), free
+	case []float32:
+		a := js.Global().Get("Uint8Array").New(len(s) * 4)
+		js.CopyBytesToJS(a, sliceToByteSlice(s))
+		runtime.KeepAlive(s)
+		buf := a.Get("buffer")
+		return js.Global().Get("Float32Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/4), free
+	case []float64:
+		a := js.Global().Get("Uint8Array").New(len(s) * 8)
+		js.CopyBytesToJS(a, sliceToByteSlice(s))
+		runtime.KeepAlive(s)
+		buf := a.Get("buffer")
+		return js.Global().Get("Float64Array").New(buf, a.Get("byteOffset"), a.Get("byteLength").Int()/8), free
+	default:
+		panic("jsutil: unexpected value at SliceToTypedArray()")
+	}
+}
+
+func Equal(a, b js.Value) bool {
+	return a.Equal(b)
+}

+ 3 - 2
window/canvas.go

@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/gls"
+	"github.com/g3n/engine/util/wasm"
 	_ "image/png"
 	"syscall/js"
 )
@@ -357,14 +358,14 @@ func Init(canvasId string) error {
 		w.canvas = doc.Call("createElement", "WebGlCanvas")
 	} else {
 		w.canvas = doc.Call("getElementById", canvasId)
-		if w.canvas == js.Null() {
+		if wasm.Equal(w.canvas, js.Null()) {
 			panic(fmt.Sprintf("Cannot find canvas with provided id: %s", canvasId))
 		}
 	}
 
 	// Get reference to WebGL context
 	webglCtx := w.canvas.Call("getContext", "webgl2")
-	if webglCtx == js.Undefined() {
+	if wasm.Equal(webglCtx, js.Undefined()) {
 		return fmt.Errorf("Browser doesn't support WebGL2")
 	}
 

+ 3 - 3
window/glfw.go

@@ -17,7 +17,7 @@ import (
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/gui/assets"
-	"github.com/go-gl/glfw/v3.2/glfw"
+	"github.com/go-gl/glfw/v3.3/glfw"
 )
 
 // Keycodes
@@ -319,8 +319,8 @@ func Init(width, height int, title string) error {
 		xpos, ypos := x.GetCursorPos()
 		w.mouseEv.Button = MouseButton(button)
 		w.mouseEv.Mods = ModifierKey(mods)
-		w.mouseEv.Xpos = float32(xpos * w.scaleX)
-		w.mouseEv.Ypos = float32(ypos * w.scaleY)
+		w.mouseEv.Xpos = float32(xpos) //* float32(w.scaleX) TODO
+		w.mouseEv.Ypos = float32(ypos) //* float32(w.scaleY)
 		if action == glfw.Press {
 			w.Dispatch(OnMouseDown, &w.mouseEv)
 		} else if action == glfw.Release {