110 Commitit 13e8582dad ... a2e58abab5

Tekijä SHA1 Viesti Päivämäärä
  João Freitas a2e58abab5 Removed deprecated go get -u outside module (#286) 2 vuotta sitten
  Kevin Z 721b7465e9 fix #301 (#302) 2 vuotta sitten
  Kevin Z 4e30d5c3f7 Fix #280, blur font on darwin (#298) 2 vuotta sitten
  Kevin Z 26e011b12f Fix #299 (#300) 2 vuotta sitten
  Daniel Salvadori fc34de52eb Merge pull request #293 from testwill/slice 2 vuotta sitten
  guoguangwu 29cf04713d chore: slice loop replace 2 vuotta sitten
  Daniel Salvadori b5c63e94be Fix Graphic bounding box 3 vuotta sitten
  Daniel Salvadori e1613f0475 Merge pull request #265 from countcb/editbox-textselection 3 vuotta sitten
  Daniel Salvadori dae3124083 Fix app-browser arguments 3 vuotta sitten
  Daniel Salvadori 193a5ba924 Merge pull request #271 from leylandski/master 3 vuotta sitten
  leylandski e6a3bb79a6 Fix alpha assignment in Color4.Set 3 vuotta sitten
  Daniel Salvadori 253be6caa1 Merge branch 'master' of https://github.com/g3n/engine 3 vuotta sitten
  Daniel Salvadori 7b89285c3b Various fixes and improvements 3 vuotta sitten
  Daniel Salvadori a0970e4f9b Update Discord badge in README.md 3 vuotta sitten
  count 237c500988 gui.Edit: Text can be selected with mouse 3 vuotta sitten
  count 748000dd0e gui.Edit: Added possibility to select text 3 vuotta sitten
  Daniel Salvadori 00f54a0d42 Merge pull request #256 from fakke/node_findpath 4 vuotta sitten
  Daniel Salvadori e5e6830dae Merge pull request #258 from fakke/node_loaderid 4 vuotta sitten
  fakke 8eab2950d1 add loader id to parsed attributes of a panel 4 vuotta sitten
  fakke 7a967f2a30 remove root node's own name from search path 4 vuotta sitten
  Daniel Salvadori f484cd7dab Add libXxf86vm-devel to Fedora instructions 4 vuotta sitten
  Daniel Salvadori 6a9ddcacb6 Merge pull request #252 from jordan4ibanez/patch-1 4 vuotta sitten
  jordan4ibanez 973c966dad Add dependency install instructions for Void Linux 4 vuotta sitten
  Daniel Salvadori 78f0dd1862 Merge pull request #250 from jordan4ibanez/patch-1 4 vuotta sitten
  jordan4ibanez aa6baddf0d Add dependency install instructions for Arch Linux 4 vuotta sitten
  Daniel Salvadori 84753eeaf1 Fix OBJ parsing when there's no material file 4 vuotta sitten
  Daniel Salvadori 549ee35827 Fix OBJ parsing when there's no material file 4 vuotta sitten
  Daniel Salvadori 216f7825db Merge pull request #212 from uzudil/fbo3 4 vuotta sitten
  uzudil 0099a2d0e4 postprocessor in new file 4 vuotta sitten
  uzudil 2be1b6fc03 renderer bugfix 4 vuotta sitten
  uzudil ed2a4128f9 postprocessing idea 5 vuotta sitten
  Daniel Salvadori 9fc9a1ec88 Merge pull request #144 from felzix/master 4 vuotta sitten
  Daniel Salvadori beaa4f65ca Merge pull request #106 from lian/master 4 vuotta sitten
  Daniel Salvadori 0d2fbcb743 Merge pull request #162 from burner-account/gui-window-fix 4 vuotta sitten
  Daniel Salvadori 5bc272204e Merge pull request #178 from metalim/i177-fullscreen 4 vuotta sitten
  Daniel Salvadori 30af58ec69 Merge branch 'master' into i177-fullscreen 4 vuotta sitten
  Daniel Salvadori 9c2abe60be Merge pull request #208 from oflebbe/master 4 vuotta sitten
  Daniel Salvadori 0653c2ddda Merge pull request #213 from wolfgarnet/feature/angle-to-vector2 4 vuotta sitten
  Daniel Salvadori c9fb8e529a Merge pull request #214 from wolfgarnet/feature/dispose-children 4 vuotta sitten
  Daniel Salvadori 451fb52cf1 Merge pull request #217 from Innoviox/master 4 vuotta sitten
  Daniel Salvadori f09c3af521 Merge pull request #225 from maugre/texture-funcs 4 vuotta sitten
  Daniel Salvadori e8d86a253b Merge pull request #226 from maugre/feature/blinn-phong 4 vuotta sitten
  Daniel Salvadori e7cd344d00 Merge pull request #248 from billdc/master 4 vuotta sitten
  Daniel Salvadori 677610d53e Merge pull request #247 from maugre/framebuffer-methods 4 vuotta sitten
  DC 69a36e7380 Fix core/node Clone 4 vuotta sitten
  Maugre 19830e16f0 Add various glFramebuffer methods 4 vuotta sitten
  Daniel Salvadori a70b9ffade Fix signature of al.GetListenerfv 4 vuotta sitten
  Daniel Salvadori 4b371a87a0 Allow arbitrary smoothing groups in OBJ files 4 vuotta sitten
  Daniel Salvadori 7d02fe7c6e Merge branch 'master' of https://github.com/g3n/engine 4 vuotta sitten
  Daniel Salvadori acb0efaaa9 Fix linguist configuration 4 vuotta sitten
  Daniel Salvadori 206e01450e Update README.md 4 vuotta sitten
  Daniel Salvadori c745e4aed7 Update README.md 4 vuotta sitten
  Daniel Salvadori 907b22454f Update README.md 4 vuotta sitten
  Daniel Salvadori abef212b4d Fix sprite negative scale 4 vuotta sitten
  Daniel Salvadori 18b19ddef9 Merge pull request #237 from mat007/fix-initial-mouse-xy 4 vuotta sitten
  Daniel Salvadori ced72d529c Merge pull request #236 from mat007/apple-silicon-includes-and-libs 4 vuotta sitten
  Mathieu Champlon a2497cdcc4 Add Apple silicon cgo flags 4 vuotta sitten
  Mathieu Champlon 3d80de26fd Fix mouse coordinates in mouse events 4 vuotta sitten
  Daniel Salvadori de78e3c420 Update dependencies and clean up 4 vuotta sitten
  Maugre e56e219515 Implement Blinn-Phong model and material 4 vuotta sitten
  Maugre 542147ba97 Enable access to material's textures as discussed in PR #190 4 vuotta sitten
  Daniel Salvadori 6d0136f344 Update README.md 4 vuotta sitten
  Daniel Salvadori 70f187ac96 Merge pull request #219 from golemwire/patch-1 5 vuotta sitten
  Ethan Black 29e8cf1d96 Fix spelling error in README.md 5 vuotta sitten
  Simon Chervenak d2c2888f82 fix issue #216 5 vuotta sitten
  Christian Wolfgang e55b0cc7a8 Node dispose disposes all children as well 5 vuotta sitten
  Olaf Flebbe b61c7016ee simple cast enough to convert 5 vuotta sitten
  Christian Wolfgang 2e6ad0a78f Adding angle to for vector2 5 vuotta sitten
  danaugrs 39ccc1c74e Fix horizontal FOV 5 vuotta sitten
  danaugrs 0da024ed5f Fix horizontal FOV 5 vuotta sitten
  danaugrs 69383454cb Update to GLFW 3.3 5 vuotta sitten
  danaugrs df2b6d4aea Merge branch 'master' of https://github.com/g3n/engine 5 vuotta sitten
  danaugrs 4fd61a31aa Fix doc and improve formatting 5 vuotta sitten
  Daniel Salvadori f571b86be2 Merge pull request #175 from wolfgarnet/row-and-column-of-matrix4 5 vuotta sitten
  Daniel Salvadori 6206b7326a Merge pull request #194 from wolfgarnet/feature/collada-triangles 5 vuotta sitten
  danaugrs 0e39060fc5 Change support to Go 13+ 5 vuotta sitten
  Daniel Salvadori 8b2d7dab53 Merge pull request #198 from DamianImrich/go114 5 vuotta sitten
  Daniel Salvadori f135552851 Merge pull request #197 from DamianImrich/pr2 5 vuotta sitten
  Damián Imrich d7b83506ae wasm go1.13+ support 5 vuotta sitten
  Damián Imrich dd097447ec Fixed WASM build error: texture2D.go:356:6: gs.CompressedTexImage2D undefined 5 vuotta sitten
  Christian Wolfgang 002283cdfc Adding support for collada triangles 5 vuotta sitten
  Daniel Salvadori 391d397cdd Merge pull request #172 from wolfgarnet/vector-shorten-and-reduce 5 vuotta sitten
  Christian Wolfgang 8888dd9055 Added functionality to shorten and extend vector4 and vector3 5 vuotta sitten
  Daniel Salvadori 54a1202382 Merge pull request #170 from wolfgarnet/clone-vecter2 5 vuotta sitten
  Daniel Salvadori 714f8d82e6 Merge pull request #185 from wolfgarnet/almost-equal 5 vuotta sitten
  Daniel Salvadori ec5f075d66 Merge pull request #189 from etherealmachine/patch-1 5 vuotta sitten
  James Pettit 5977648f6e Fix typo in doc. 5 vuotta sitten
  Christian Wolfgang 3af8bab010 Added almost equal for vector2 and vector4 5 vuotta sitten
  Maksim Litvinov 63827bca58 Fix SetFullscreen(false) 5 vuotta sitten
  Christian Wolfgang 555c99bb81 Added get row and get column for matrix4 5 vuotta sitten
  Christian Wolfgang 3d47c1521a Added clone for Vector2 5 vuotta sitten
  Daniel Salvadori 78541849ab Merge pull request #169 from breiting/i168-bbox 5 vuotta sitten
  Bernhard Reitinger 61b0322c16 Initialize bounding box for node and geometry with min/max bounds 5 vuotta sitten
  Daniel Salvadori db7282a2ba Merge pull request #156 from danjpar/master 5 vuotta sitten
  Daniel Salvadori 3d51e63d2a Merge pull request #166 from MrWaggel/master 5 vuotta sitten
  MrWaggel 7e7566a672 Added Remove method for the Window type 5 vuotta sitten
  = b0c4c613b7 fixed the spaces 6 vuotta sitten
  burner-account 186a4c7343 fix window dimension calculation by safe-guarding against window with nil title field 6 vuotta sitten
  = db33da33e0 Add SetTarget 6 vuotta sitten
  danjpar 60a9d3032e Added tube.go 6 vuotta sitten
  Daniel Salvadori d46afd9929 Merge pull request #155 from danjpar/master 6 vuotta sitten
  Daniel Salvadori ba7aa38786 Delete go.yml 6 vuotta sitten
  Daniel Salvadori ad5f4df10c Merge pull request #148 from leylandski/master 6 vuotta sitten
  Daniel Salvadori 1847f17a35 Create go.yml 6 vuotta sitten
  = 0cbca66910 Added curves.go 6 vuotta sitten
  Adam Leyland e20af296a1 Rename SetDataCompressed 6 vuotta sitten
  Adam Leyland 0cddff5a3b Comment compressed texture methods 6 vuotta sitten
  Adam Leyland b6dc5ed49c Add compressed texture support 6 vuotta sitten
  robert e432cbe08a added focus event 6 vuotta sitten
  Julian Langschaedel ca61923779 Fix blender v2.79 collada exports loading 7 vuotta sitten
100 muutettua tiedostoa jossa 2398 lisäystä ja 1059 poistoa
  1. 1 1
      .gitattributes
  2. 20 6
      README.md
  3. 2 1
      app/app-browser.go
  4. 6 11
      app/app-desktop.go
  5. 17 33
      audio/al/al.go
  6. 4 2
      audio/audio_file.go
  7. 1 0
      audio/listener-browser.go
  8. 1 0
      audio/listener-desktop.go
  9. 11 12
      audio/ov/vorbisfile.go
  10. 32 32
      audio/player.go
  11. 10 8
      audio/vorbis/vorbis.go
  12. 15 4
      camera/camera.go
  13. 7 1
      camera/orbit_control.go
  14. 1 2
      core/dispatcher.go
  15. 29 33
      core/node.go
  16. 1 1
      experimental/collision/collision.go
  17. 9 9
      experimental/collision/matrix_test.go
  18. 2 1
      experimental/collision/raycaster.go
  19. 0 1
      experimental/collision/shape/capsule.go
  20. 0 1
      experimental/collision/shape/cone.go
  21. 7 10
      experimental/collision/shape/convexhull.go
  22. 0 1
      experimental/collision/shape/cylinder.go
  23. 0 1
      experimental/collision/shape/heightfield.go
  24. 1 1
      experimental/collision/shape/plane.go
  25. 2 2
      experimental/collision/shape/sphere.go
  26. 2 2
      experimental/physics/broadphase.go
  27. 1 1
      experimental/physics/constraint/conetwist.go
  28. 2 2
      experimental/physics/constraint/distance.go
  29. 2 2
      experimental/physics/constraint/hinge.go
  30. 1 1
      experimental/physics/constraint/lock.go
  31. 5 5
      experimental/physics/constraint/pointtopoint.go
  32. 3 3
      experimental/physics/equation/cone.go
  33. 7 7
      experimental/physics/equation/contact.go
  34. 7 7
      experimental/physics/equation/equation.go
  35. 3 3
      experimental/physics/equation/friction.go
  36. 1 1
      experimental/physics/equation/rotational.go
  37. 1 1
      experimental/physics/equation/rotationalmotor.go
  38. 2 2
      experimental/physics/forcefield.go
  39. 116 118
      experimental/physics/narrowphase.go
  40. 18 18
      experimental/physics/object/body.go
  41. 6 6
      experimental/physics/solver/gs.go
  42. 2 1
      geometry/cone-cylinder.go
  43. 2 1
      geometry/disk.go
  44. 6 7
      geometry/geometry.go
  45. 3 2
      geometry/morph.go
  46. 2 1
      geometry/sphere.go
  47. 2 1
      geometry/torus.go
  48. 206 0
      geometry/tube.go
  49. 1 1
      gls/build.go
  50. 2 2
      gls/glapi.c
  51. 1 1
      gls/glcorearb.h
  52. 25 17
      gls/gls-browser.go
  53. 104 1
      gls/gls-desktop.go
  54. 4 1
      gls/gls.go
  55. 1 1
      gls/shaderdefines.go
  56. 4 4
      go.mod
  57. 8 7
      go.sum
  58. 3 1
      graphic/graphic.go
  59. 2 1
      graphic/rigged_mesh.go
  60. 1 1
      graphic/skeleton.go
  61. 11 2
      graphic/sprite.go
  62. 9 9
      gui/assets/data.go
  63. 1 1
      gui/assets/gen.go
  64. 75 75
      gui/assets/icon/icodes.go
  65. 5 1
      gui/builder.go
  66. 2 1
      gui/chart.go
  67. 310 45
      gui/edit.go
  68. 2 1
      gui/image.go
  69. 2 1
      gui/itemscroller.go
  70. 14 4
      gui/label.go
  71. 9 0
      gui/manager.go
  72. 1 1
      gui/panel.go
  73. 16 0
      gui/slider.go
  74. 23 7
      gui/window.go
  75. 2 1
      loader/collada/animation.go
  76. 3 2
      loader/collada/collada.go
  77. 179 3
      loader/collada/geometry.go
  78. 52 1
      loader/collada/library_geometries.go
  79. 3 2
      loader/collada/material.go
  80. 2 1
      loader/collada/scene.go
  81. 6 5
      loader/gltf/gltf.go
  82. 3 3
      loader/gltf/material_pbr.go
  83. 61 63
      loader/obj/obj.go
  84. 6 0
      material/material.go
  85. 10 0
      material/standard.go
  86. 2 2
      math32/array.go
  87. 1 1
      math32/color4.go
  88. 202 0
      math32/curves.go
  89. 20 0
      math32/matrix4.go
  90. 5 5
      math32/quaternion.go
  91. 24 0
      math32/vector2.go
  92. 13 8
      math32/vector3.go
  93. 18 1
      math32/vector4.go
  94. 119 0
      renderer/postprocessor.go
  95. 2 1
      renderer/renderer.go
  96. 23 3
      renderer/shaders/include/phong_model.glsl
  97. 424 404
      renderer/shaders/sources.go
  98. 2 1
      renderer/shaman.go
  99. 1 0
      renderer/version-browser.go
  100. 0 0
      renderer/version-desktop.go

+ 1 - 1
.gitattributes

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

+ 20 - 6
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/NfaeVr8zDg"><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>
 
@@ -33,16 +35,24 @@ On Unix-based systems the engine depends on some C libraries that can be install
 
 ### Fedora
 
-    $ sudo dnf -y install xorg-x11-proto-devel mesa-libGL mesa-libGL-devel openal-soft openal-soft-devel libvorbis libvorbis-devel glfw-devel libXi-devel
+    $ sudo dnf -y install xorg-x11-proto-devel mesa-libGL mesa-libGL-devel openal-soft openal-soft-devel libvorbis libvorbis-devel glfw-devel libXi-devel libXxf86vm-devel
 
 ### CentOS 7
 
 Enable the EPEL repository:
 
     $ sudo yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
-
+    
 Then install the same packages as for Fedora - remember to use `yum` instead of `dnf` for the package installation command.
+    
+### Arch
 
+    $ sudo pacman -S base-devel xorg-server mesa openal libvorbis
+    
+### Void
+
+    $ sudo xbps-install git xorg-server-devel base-devel libvorbis-devel libvorbis libXxf86vm-devel libXcursor-devel libXrandr-devel libXinerama-devel libopenal libopenal-devel libglvnd-devel
+    
 ### Windows
 
 We tested the Windows build using the [mingw-w64](https://mingw-w64.org) toolchain (you can download [this file](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/8.1.0/threads-posix/seh/x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z) in particular).
@@ -78,7 +88,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
@@ -183,7 +193,7 @@ func main() {
   
 You can download and install `hellog3n` via:
     
-    go get -u github.com/g3n/demos/hellog3n
+    go install github.com/g3n/demos/hellog3n@latest
 
 For more complex demos please see the [G3N demo program](https://github.com/g3n/g3nd).
 
@@ -204,4 +214,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)

+ 2 - 1
app/app-browser.go

@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build wasm
 // +build wasm
 
 package app
@@ -30,7 +31,7 @@ type Application struct {
 }
 
 // App returns the Application singleton, creating it the first time.
-func App() *Application {
+func App(width, height int, title string) *Application {
 
 	// Return singleton if already created
 	if a != nil {

+ 6 - 11
app/app-desktop.go

@@ -2,24 +2,19 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !wasm
 // +build !wasm
 
 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
-const (
-	title  = "G3N Application"
-	width  = 800
-	height = 600
 )
 
 // Application
@@ -34,7 +29,7 @@ type Application struct {
 }
 
 // App returns the Application singleton, creating it the first time.
-func App() *Application {
+func App(width, height int, title string) *Application {
 
 	// Return singleton if already created
 	if a != nil {
@@ -67,7 +62,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 +116,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) {

+ 4 - 2
audio/audio_file.go

@@ -2,17 +2,19 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !wasm
 // +build !wasm
 
 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 (

+ 1 - 0
audio/listener-browser.go

@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build wasm
 // +build wasm
 
 package audio

+ 1 - 0
audio/listener-desktop.go

@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !wasm
 // +build !wasm
 
 package audio

+ 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.

+ 32 - 32
audio/player.go

@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !wasm
 // +build !wasm
 
 package audio
@@ -10,13 +11,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 (
@@ -95,10 +97,6 @@ func (p *Player) State() int {
 func (p *Player) Play() error {
 
 	state := p.State()
-	// Already playing, nothing to do
-	if state == al.Playing {
-		return nil
-	}
 
 	// If paused, goroutine should be running, just starts playing
 	if state == al.Paused {
@@ -106,37 +104,39 @@ func (p *Player) Play() error {
 		return nil
 	}
 
-	// Inactive or Stopped state
-	if state == al.Initial || state == al.Stopped {
+	// Already playing - stop in order to start from beginning
+	if state == al.Playing {
+		p.Stop()
+	}
 
-		// Sets file pointer to the beginning
-		err := p.af.Seek(0)
-		if err != nil {
-			return err
-		}
+	// Sets file pointer to the beginning
+	err := p.af.Seek(0)
+	if err != nil {
+		return err
+	}
 
-		// Fill buffers with decoded data
-		for i := 0; i < playerBufferCount; i++ {
-			err = p.fillBuffer(p.buffers[i])
-			if err != nil {
-				if err != io.EOF {
-					return err
-				}
-				break
+	// Fill buffers with decoded data
+	for i := 0; i < playerBufferCount; i++ {
+		err = p.fillBuffer(p.buffers[i])
+		if err != nil {
+			if err != io.EOF {
+				return err
 			}
+			break
 		}
-		p.nextBuf = 0
+	}
+	p.nextBuf = 0
 
-		// Clear previous goroutine response channel
-		select {
-		case _ = <-p.gchan:
-		default:
-		}
-		// Starts playing and starts goroutine to fill buffers
-		al.SourcePlay(p.source)
-		go p.run()
-		return nil
+	// Clear previous goroutine response channel
+	select {
+	case _ = <-p.gchan:
+	default:
 	}
+
+	// Starts playing and starts goroutine to fill buffers
+	al.SourcePlay(p.source)
+	go p.run()
+	
 	return nil
 }
 

+ 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"
 

+ 15 - 4
camera/camera.go

@@ -62,6 +62,7 @@ func NewPerspective(aspect, near, far, fov float32, axis Axis) *Camera {
 
 	c := new(Camera)
 	c.Node.Init(c)
+	c.SetDirection(0, 0, -1)
 	c.aspect = aspect
 	c.near = near
 	c.far = far
@@ -78,6 +79,7 @@ func NewOrthographic(aspect, near, far, size float32, axis Axis) *Camera {
 
 	c := new(Camera)
 	c.Node.Init(c)
+	c.SetDirection(0, 0, -1)
 	c.aspect = aspect
 	c.near = near
 	c.far = far
@@ -242,11 +244,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

+ 7 - 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.
@@ -120,6 +121,11 @@ func (oc *OrbitControl) Target() math32.Vector3 {
 	return oc.target
 }
 
+//Set camera orbit target Vector3
+func (oc *OrbitControl) SetTarget(v math32.Vector3) {
+	oc.target = v
+}
+
 // Enabled returns the current OrbitEnabled bitmask.
 func (oc *OrbitControl) Enabled() OrbitEnabled {
 

+ 1 - 2
core/dispatcher.go

@@ -49,10 +49,9 @@ func (d *Dispatcher) Subscribe(evname string, cb Callback) {
 	d.evmap[evname] = append(d.evmap[evname], subscription{nil, cb})
 }
 
-// SubscribeID subscribes a callback to events events with the given name.
+// SubscribeID subscribes a callback to events with the given name.
 // The user-provided unique id can be used to unsubscribe via UnsubscribeID.
 func (d *Dispatcher) SubscribeID(evname string, id interface{}, cb Callback) {
-
 	d.evmap[evname] = append(d.evmap[evname], subscription{id, cb})
 }
 

+ 29 - 33
core/node.go

@@ -5,9 +5,11 @@
 package core
 
 import (
+	"math"
+	"strings"
+
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
-	"strings"
 )
 
 // INode is the interface for all node types.
@@ -115,7 +117,10 @@ func (n *Node) GetNode() *Node {
 // Computes union of own bounding box with those of all descendents.
 func (n *Node) BoundingBox() math32.Box3 {
 
-	bbox := math32.Box3{n.position, n.position}
+	bbox := math32.Box3{
+		Min: math32.Vector3{X: math.MaxFloat32, Y: math.MaxFloat32, Z: math.MaxFloat32},
+		Max: math32.Vector3{X: -math.MaxFloat32, Y: -math.MaxFloat32, Z: -math.MaxFloat32},
+	}
 	for _, inode := range n.Children() {
 		childBbox := inode.BoundingBox()
 		bbox.Union(&childBbox)
@@ -127,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 {
@@ -137,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
@@ -236,41 +246,27 @@ func (n *Node) UserData() interface{} {
 	return n.userData
 }
 
-// FindPath finds a node with the specified path starting with this node and
-// searching in all its children recursively.
-// A path is the sequence of the names from the first node to the desired node
-// separated by the forward slash.
+// FindPath finds a node with the specified path by recursively searching the children.
+// A path is a sequence of names of nested child nodes, separated by a forward slash.
 func (n *Node) FindPath(path string) INode {
 
-	// Internal recursive function to find node
-	var finder func(inode INode, path string) INode
-	finder = func(inode INode, path string) INode {
-		// Get first component of the path
-		parts := strings.Split(path, "/")
-		if len(parts) == 0 {
-			return nil
-		}
-		first := parts[0]
-		// Checks current node
-		node := inode.GetNode()
-		if node.name != first {
-			return nil
-		}
-		// If the path has finished this is the desired node
-		rest := strings.Join(parts[1:], "/")
-		if rest == "" {
-			return inode
-		}
-		// Otherwise search in this node children
-		for _, ichild := range node.children {
-			found := finder(ichild, rest)
-			if found != nil {
-				return found
+	// Split the path into head + tail
+	parts := strings.SplitN(path, "/", 2)
+	if len(parts) != 1 && len(parts) != 2 {
+		panic("expected 1 or 2 parts from SplitN")
+	}
+
+	// Search the children
+	for _, ichild := range n.children {
+		if ichild.Name() == parts[0] {
+			if len(parts) == 1 {
+				return ichild
 			}
+			return ichild.GetNode().FindPath(parts[1])
 		}
-		return nil
 	}
-	return finder(n, path)
+
+	return nil
 }
 
 // FindLoaderID looks in the specified node and in all its children

+ 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

+ 6 - 7
geometry/geometry.go

@@ -6,10 +6,12 @@
 package geometry
 
 import (
+	"math"
+	"strconv"
+
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/util/logger"
-	"strconv"
 )
 
 // Package logger
@@ -93,10 +95,7 @@ func (g *Geometry) AddGroup(start, count, matIndex int) *Group {
 
 // AddGroupList adds the specified list of groups to this geometry.
 func (g *Geometry) AddGroupList(groups []Group) {
-
-	for _, group := range groups {
-		g.groups = append(g.groups, group)
-	}
+	g.groups = append(g.groups, groups...)
 }
 
 // GroupCount returns the number of geometry groups (for multimaterial).
@@ -317,8 +316,8 @@ func (g *Geometry) BoundingBox() math32.Box3 {
 	}
 
 	// Reset bounding box
-	g.boundingBox.Min.Set(0, 0, 0)
-	g.boundingBox.Max.Set(0, 0, 0)
+	g.boundingBox.Min.Set(math.MaxFloat32, math.MaxFloat32, math.MaxFloat32)
+	g.boundingBox.Max.Set(-math.MaxFloat32, -math.MaxFloat32, -math.MaxFloat32)
 
 	// Expand bounding box by each vertex
 	g.ReadVertices(func(vertex math32.Vector3) bool {

+ 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,

+ 206 - 0
geometry/tube.go

@@ -0,0 +1,206 @@
+// Copyright 2016 The G3N Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// TODO: UVs, Caps
+
+package geometry
+
+import (
+	"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))
+		y1 = x1 + 1
+		z1 = x1 + 2
+		x2 = int(indices[i*3+1] * uint32(3))
+		y2 = x2 + 1
+		z2 = x2 + 2
+		x3 = int(indices[i*3+2] * uint32(3))
+		y3 = x3 + 1
+		z3 = x3 + 2
+
+		x1x2 = positions[x1] - positions[x2]
+		y1y2 = positions[y1] - positions[y2]
+		z1z2 = positions[z1] - positions[z2]
+		x3x2 = positions[x3] - positions[x2]
+		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
+
+		l = float32(math.Sqrt(float64(x)*float64(x) + float64(y)*float64(y) + float64(z)*float64(z)))
+		if l == 0 {
+			l = 1.0
+		}
+
+		normals[x1] += x / l
+		normals[y1] += y / l
+		normals[z1] += z / l
+		normals[x2] += x / l
+		normals[y2] += y / l
+		normals[z2] += z / l
+		normals[x3] += x / l
+		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)))
+		if l == 0 {
+			l = 1.0
+		}
+		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
+		}
+	*/
+	c := NewGeometry()
+
+	var ls, is []int // path lengths, path indexes
+	positions := math32.NewArrayF32(0, 0)
+	indices := math32.NewArrayU32(0, 0)
+
+	i := 0
+	for p := 0; p < len(paths); p++ {
+		path := paths[p]
+		l := len(path)
+		ls = append(ls, l)
+		is = append(is, i)
+		for j := 0; j < l; j++ {
+			positions.AppendVector3(&path[j])
+		}
+		i += l
+	}
+
+	l1 := ls[0] - 1 // path1 length
+	l2 := ls[1] - 1 // path2 length
+	min := l2
+	if l1 < l2 {
+		min = l1
+	}
+	p := 0
+	i = 0
+	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))
+		i++
+		if i == min {
+			if close {
+				indices.Append(uint32(i), uint32(i+t), uint32(is[p]))
+				indices.Append(uint32(is[p]+t), uint32(is[p]), uint32(i+t))
+			}
+			p++
+			if p == len(ls)-1 {
+				break
+			}
+			l1 = ls[p] - 1
+			l2 = ls[p+1] - 1
+			i = is[p]
+			if l1 < l2 {
+				min = l1 + i
+			} else {
+				min = l2 + i
+			}
+		}
+	}
+
+	normals := math32.NewArrayF32(positions.Size(), positions.Size())
+	normals = CalculateNormals(indices, positions, normals)
+
+	c.SetIndices(indices)
+	c.AddVBO(gls.NewVBO(positions).AddAttrib(gls.VertexPosition))
+	c.AddVBO(gls.NewVBO(normals).AddAttrib(gls.VertexNormal))
+
+	return c
+}
+
+func NewTube(path []math32.Vector3, radius float32, radialSegments int, close bool) *Geometry {
+	l := len(path)
+
+	var tangents, normals, binormals []math32.Vector3
+	tangents = make([]math32.Vector3, l)
+	normals = make([]math32.Vector3, l)
+	binormals = make([]math32.Vector3, l)
+
+	tangents[0] = *path[1].Clone().Sub(&path[0])
+	tangents[0].Normalize()
+	tangents[l-1] = *path[l-1].Clone().Sub(&path[l-2])
+	tangents[l-1].Normalize()
+
+	var tmpVertex *math32.Vector3
+	if tangents[0].X != 1 {
+		tmpVertex = math32.NewVector3(1, 0, 0)
+	} else if tangents[0].Y != 1 {
+		tmpVertex = math32.NewVector3(0, 1, 0)
+	} else if tangents[0].Z != 1 {
+		tmpVertex = math32.NewVector3(0, 0, 1)
+	}
+
+	normals[0] = *tangents[0].Clone().Cross(tmpVertex)
+	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()
+
+		}
+		normals[i] = *binormals[i-1].Clone().Cross(&tangents[i])
+		normals[i].Normalize()
+		binormals[i] = *tangents[i].Clone().Cross(&normals[i])
+		binormals[i].Normalize()
+	}
+
+	pi2 := math.Pi * 2
+	step := pi2 / float64(radialSegments)
+
+	var radialPaths [][]math32.Vector3
+	for i := 0; i < l; i++ {
+		var radialPath []math32.Vector3
+		var ang float32
+		for ang = 0.0; ang < float32(pi2); ang += float32(step) {
+			matrix := math32.NewMatrix4()
+			matrix.MakeRotationAxis(&tangents[i], ang)
+
+			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
+
+			rotated := math32.NewVector3(newX, newY, newZ).MultiplyScalar(radius).Add(&path[i])
+			radialPath = append(radialPath, *rotated)
+		}
+		radialPaths = append(radialPaths, radialPath)
+	}
+
+	return NewRibbon(radialPaths, close)
+}

+ 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

+ 2 - 2
gls/glapi.c

@@ -3055,9 +3055,9 @@ void glVertexAttrib4usv(GLuint index, const GLushort *v) {
 	}
 }
 
-void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer) {
+void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLsizeiptr pointer) {
 
-	pglVertexAttribPointer(index, size, type, normalized, stride, pointer);
+	pglVertexAttribPointer(index, size, type, normalized, stride, (void*)(pointer));
 	if (checkError) {
 		GLenum err = pglGetError();
 		if (err != GL_NO_ERROR) {

+ 1 - 1
gls/glcorearb.h

@@ -956,7 +956,7 @@ GLAPI void APIENTRY glVertexAttrib4sv (GLuint index, const GLshort *v);
 GLAPI void APIENTRY glVertexAttrib4ubv (GLuint index, const GLubyte *v);
 GLAPI void APIENTRY glVertexAttrib4uiv (GLuint index, const GLuint *v);
 GLAPI void APIENTRY glVertexAttrib4usv (GLuint index, const GLushort *v);
-GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);
+GLAPI void APIENTRY glVertexAttribPointer (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLsizeiptr pointer);
 #endif
 #endif /* GL_VERSION_2_0 */
 

+ 25 - 17
gls/gls-browser.go

@@ -2,12 +2,14 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build wasm
 // +build wasm
 
 package gls
 
 import (
 	"fmt"
+	"github.com/g3n/engine/util/wasm"
 	"syscall/js"
 	"unsafe"
 )
@@ -279,10 +281,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 +591,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 +659,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 +740,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 +751,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 +762,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 +773,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 +784,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 +795,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++
 }

+ 104 - 1
gls/gls-desktop.go

@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build !wasm
 // +build !wasm
 
 package gls
@@ -418,6 +419,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 +479,88 @@ 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))
+	gs.stats.Fbos++
+	return fb
+}
+
+// GenRenderbuffer creates a new render buffer.
+func (gs *GLS) GenRenderbuffer() uint32 {
+
+	var rb uint32
+	C.glGenRenderbuffers(1, (*C.GLuint)(&rb))
+	gs.stats.Rbos++
+	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))
+}
+
+// CheckFramebufferStatus get the framebuffer status
+func (gs *GLS) CheckFramebufferStatus() uint32 {
+	res := C.glCheckFramebufferStatus(C.GLenum(FRAMEBUFFER))
+	return uint32(res)
+}
+
+// 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) {
 
@@ -601,6 +691,19 @@ func (gs *GLS) TexImage2D(target uint32, level int32, iformat int32, width int32
 		ptr(data))
 }
 
+// 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{}) {
+
+	C.glCompressedTexImage2D(C.GLenum(target),
+		C.GLint(level),
+		C.GLenum(iformat),
+		C.GLsizei(width),
+		C.GLsizei(height),
+		C.GLint(0),
+		C.GLsizei(size),
+		ptr(data))
+}
+
 // TexParameteri sets the specified texture parameter on the specified texture.
 func (gs *GLS) TexParameteri(target uint32, pname uint32, param int32) {
 
@@ -709,7 +812,7 @@ func (gs *GLS) Uniform4fv(location int32, count int32, v *float32) {
 // VertexAttribPointer defines an array of generic vertex attribute data.
 func (gs *GLS) VertexAttribPointer(index uint32, size int32, xtype uint32, normalized bool, stride int32, offset uint32) {
 
-	C.glVertexAttribPointer(C.GLuint(index), C.GLint(size), C.GLenum(xtype), bool2c(normalized), C.GLsizei(stride), unsafe.Pointer(uintptr(offset)))
+	C.glVertexAttribPointer(C.GLuint(index), C.GLint(size), C.GLenum(xtype), bool2c(normalized), C.GLsizei(stride), C.GLsizeiptr(offset))
 }
 
 // Viewport sets the viewport.

+ 4 - 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
@@ -33,6 +34,8 @@ type Stats struct {
 	UnilocMiss uint64 // Cumulative number of uniform location cache misses
 	Unisets    uint64 // Cumulative number of uniform sets
 	Drawcalls  uint64 // Cumulative number of draw calls
+	Fbos       uint64 // Number of frame buffer objects
+	Rbos       uint64 // Number of render buffer objects
 }
 
 const (

+ 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=

+ 3 - 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
 		}
 	}
@@ -234,6 +234,8 @@ func (gr *Graphic) BoundingBox() math32.Box3 {
 
 	geom := gr.igeom.GetGeometry()
 	bbox := geom.BoundingBox()
+	m := gr.MatrixWorld()
+	bbox.ApplyMatrix4(&m)
 	for _, inode := range gr.Children() {
 		childGraphic, ok := inode.(*Graphic)
 		if ok {

+ 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, "/")...)...)
 }
-

+ 1 - 1
gui/assets/gen.go

@@ -2,7 +2,7 @@ package assets
 
 // To generate file with fonts binary data install "go-bindata" from:
 // https://github.com/go-bindata/go-bindata
-// > go get -u github.com/go-bindata/go-bindata/...
+// > go install github.com/go-bindata/go-bindata@latest
 
 //go:generate go-bindata -o data.go -pkg assets fonts cursors
 //go:generate g3nicodes -pkg icon icon/codepoints icon/icodes.go

+ 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),
 }

+ 5 - 1
gui/builder.go

@@ -563,6 +563,10 @@ func (b *Builder) SetAttribs(am map[string]interface{}, ipan IPanel) error {
 		panel.SetName(am[AttribName].(string))
 	}
 
+	if am[AttribId] != nil {
+		panel.SetLoaderID(am[AttribId].(string))
+	}
+
 	if am[AttribVisible] != nil {
 		panel.SetVisible(am[AttribVisible].(bool))
 	}
@@ -875,7 +879,7 @@ func AttribCheckIcons(b *Builder, am map[string]interface{}, fname string) error
 		if err != nil {
 			return b.err(am, fname, fmt.Sprintf("Invalid icon codepoint value/name:%v", parts[i]))
 		}
-		text += string(val)
+		text += string(rune(val))
 	}
 	am[fname] = text
 	return nil

+ 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() {

+ 310 - 45
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
@@ -20,8 +21,11 @@ type Edit struct {
 	placeHolder string // place holder string
 	text        string // current edit text
 	col         int    // current column
+	selStart    int    // start column of selection. always < selEnd. if selStart == selEnd then nothing is selected.
+	selEnd      int    // end column of selection. always > selStart. if selStart == selEnd then nothing is selected.
 	focus       bool   // key focus flag
 	cursorOver  bool
+	mouseDrag   bool // true when the mouse is moved while left mouse button is down. Used for selecting text via mouse
 	blinkID     int
 	caretOn     bool
 	styles      *EditStyles
@@ -62,15 +66,19 @@ func NewEdit(width int, placeHolder string) *Edit {
 	ed.text = ""
 	ed.MaxLength = 80
 	ed.col = 0
+	ed.selStart = 0
+	ed.selEnd = 0
 	ed.focus = false
 
 	ed.Label.initialize("", StyleDefault().Font)
 	ed.Label.Subscribe(OnKeyDown, ed.onKey)
 	ed.Label.Subscribe(OnKeyRepeat, ed.onKey)
 	ed.Label.Subscribe(OnChar, ed.onChar)
-	ed.Label.Subscribe(OnMouseDown, ed.onMouse)
+	ed.Label.Subscribe(OnMouseDown, ed.onMouseDown)
+	ed.Label.Subscribe(OnMouseUp, ed.onMouseUp)
 	ed.Label.Subscribe(OnCursorEnter, ed.onCursor)
 	ed.Label.Subscribe(OnCursorLeave, ed.onCursor)
+	ed.Label.Subscribe(OnCursor, ed.onCursor)
 	ed.Label.Subscribe(OnEnable, func(evname string, ev interface{}) { ed.update() })
 	ed.Subscribe(OnFocusLost, ed.OnFocusLost)
 
@@ -79,10 +87,13 @@ func NewEdit(width int, placeHolder string) *Edit {
 }
 
 // SetText sets this edit text
-func (ed *Edit) SetText(text string) *Edit {
+func (ed *Edit) SetText(newText string) *Edit {
 
 	// Remove new lines from text
-	ed.text = strings.Replace(text, "\n", "", -1)
+	ed.text = strings.Replace(newText, "\n", "", -1)
+	ed.col = text.StrCount(ed.text)
+	ed.selStart = ed.col
+	ed.selEnd = ed.col
 	ed.update()
 	return ed
 }
@@ -93,6 +104,28 @@ func (ed *Edit) Text() string {
 	return ed.text
 }
 
+// SelectedText returns the currently selected text
+// or empty string when nothing is selected
+func (ed *Edit) SelectedText() string {
+
+	if ed.selStart == ed.selEnd {
+		return ""
+	}
+
+	s := ""
+	charNum := 0
+	for _, currentRune := range ed.text {
+		if charNum >= ed.selEnd {
+			break
+		}
+		if charNum >= ed.selStart {
+			s += string(currentRune)
+		}
+		charNum++
+	}
+	return s
+}
+
 // SetFontSize sets label font size (overrides Label.SetFontSize)
 func (ed *Edit) SetFontSize(size float64) *Edit {
 
@@ -123,36 +156,165 @@ func (ed *Edit) CursorPos(col int) {
 
 	if col <= text.StrCount(ed.text) {
 		ed.col = col
+		ed.selStart = col
+		ed.selEnd = col
 		ed.redraw(ed.focus)
 	}
 }
 
+// SetSelection selects the text between start and end
+func (ed *Edit) SetSelection(start, end int) {
+
+	// make sure end is bigger than start
+	if start > end {
+		start, end = end, start
+	}
+
+	if start < 0 {
+		start = 0
+	}
+	if end > text.StrCount(ed.text) {
+		end = text.StrCount(ed.text)
+	}
+
+	ed.selStart = start
+	ed.selEnd = end
+	ed.col = end
+	ed.redraw(ed.focus)
+}
+
 // CursorLeft moves the edit cursor one character left if possible
+// If text is selected the cursor is moved to the beginning of the selection instead
+// and the selection is removed
 func (ed *Edit) CursorLeft() {
 
-	if ed.col > 0 {
-		ed.col--
+	if ed.selStart == ed.selEnd {
+		// no selection
+		// move cursor to the left if possible
+		if ed.col > 0 {
+			ed.col--
+			ed.selStart = ed.col
+			ed.selEnd = ed.col
+			ed.redraw(ed.focus)
+		}
+	} else {
+		// reset selection and move cursor to start of selection
+		ed.col = ed.selStart
+		ed.selStart = ed.col
+		ed.selEnd = ed.col
 		ed.redraw(ed.focus)
 	}
 }
 
 // CursorRight moves the edit cursor one character right if possible
+// If text is selected the cursor is moved to the end of the selection instead
+// and the selection is removed
 func (ed *Edit) CursorRight() {
 
-	if ed.col < text.StrCount(ed.text) {
-		ed.col++
+	if ed.selStart == ed.selEnd {
+		// no selection
+		// move cursor to the right if possible
+		if ed.col < text.StrCount(ed.text) {
+			ed.col++
+			ed.selStart = ed.col
+			ed.selEnd = ed.col
+			ed.redraw(ed.focus)
+		}
+	} else {
+		// reset selection and move cursor to end of selection
+		ed.col = ed.selEnd
+		ed.selStart = ed.col
+		ed.selEnd = ed.col
 		ed.redraw(ed.focus)
 	}
 }
 
-// CursorBack deletes the character at left of the cursor if possible
-func (ed *Edit) CursorBack() {
+// SelectLeft expands/shrinks the selection to the left if possible
+func (ed *Edit) SelectLeft() {
 
 	if ed.col > 0 {
-		ed.col--
-		ed.text = text.StrRemove(ed.text, ed.col)
-		ed.redraw(ed.focus)
-		ed.Dispatch(OnChange, nil)
+		if ed.col == ed.selStart {
+			// cursor is at the start of selection
+			// expand selection to the left
+			ed.col--
+			ed.selStart = ed.col
+			ed.redraw(ed.focus)
+		} else {
+			// cursor is at the end of selection:
+			// remove selection from the end
+			ed.col--
+			ed.selEnd = ed.col
+			ed.redraw(ed.focus)
+		}
+	}
+}
+
+// SelectRight expands/shrinks the selection to the right if possible
+func (ed *Edit) SelectRight() {
+
+	if ed.col < text.StrCount(ed.text) {
+		if ed.col == ed.selEnd {
+			// cursor is at the end of selection:
+			// expand selection to the right if possible
+			ed.col++
+			ed.selEnd = ed.col
+			ed.redraw(ed.focus)
+		} else {
+			// cursor is at the start of selection:
+			// remove selection from the start
+			ed.col++
+			ed.selStart = ed.col
+			ed.redraw(ed.focus)
+		}
+	}
+}
+
+// SelectHome expands the selection to the left to the beginning of the text
+func (ed *Edit) SelectHome() {
+
+	if ed.selStart < ed.col {
+		ed.selEnd = ed.selStart
+	}
+	ed.col = 0
+	ed.selStart = 0
+	ed.redraw(ed.focus)
+}
+
+// SelectEnd expands the selection to the right to the end of the text
+func (ed *Edit) SelectEnd() {
+
+	if ed.selEnd > ed.col {
+		ed.selStart = ed.selEnd
+	}
+	ed.col = text.StrCount(ed.text)
+	ed.selEnd = ed.col
+	ed.redraw(ed.focus)
+}
+
+// SelectAll selects all text
+func (ed *Edit) SelectAll() {
+
+	ed.selStart = 0
+	ed.selEnd = text.StrCount(ed.text)
+	ed.col = ed.selEnd
+	ed.redraw(ed.focus)
+}
+
+// CursorBack either deletes the character at left of the cursor if possible
+// Or if text is selected the selected text is removed all at once
+func (ed *Edit) CursorBack() {
+
+	if ed.selStart == ed.selEnd {
+		if ed.col > 0 {
+			ed.col--
+			ed.selStart = ed.col
+			ed.selEnd = ed.col
+			ed.text = text.StrRemove(ed.text, ed.col)
+			ed.redraw(ed.focus)
+			ed.Dispatch(OnChange, nil)
+		}
+	} else {
+		ed.DeleteSelection()
 	}
 }
 
@@ -160,6 +322,8 @@ func (ed *Edit) CursorBack() {
 func (ed *Edit) CursorHome() {
 
 	ed.col = 0
+	ed.selStart = ed.col
+	ed.selEnd = ed.col
 	ed.redraw(ed.focus)
 }
 
@@ -167,22 +331,55 @@ func (ed *Edit) CursorHome() {
 func (ed *Edit) CursorEnd() {
 
 	ed.col = text.StrCount(ed.text)
+	ed.selStart = ed.col
+	ed.selEnd = ed.col
 	ed.redraw(ed.focus)
 }
 
-// CursorDelete deletes the character at the right of the cursor if possible
+// CursorDelete either deletes the character at the right of the cursor if possible
+// Or if text is selected the selected text is removed all at once
 func (ed *Edit) CursorDelete() {
 
-	if ed.col < text.StrCount(ed.text) {
-		ed.text = text.StrRemove(ed.text, ed.col)
-		ed.redraw(ed.focus)
+	if ed.selStart == ed.selEnd {
+		if ed.col < text.StrCount(ed.text) {
+			ed.text = text.StrRemove(ed.text, ed.col)
+			ed.redraw(ed.focus)
+			ed.Dispatch(OnChange, nil)
+		}
+	} else {
+		ed.DeleteSelection()
+	}
+}
+
+// DeleteSelection deletes the selected characters. Does nothing if nothing is selected.
+func (ed *Edit) DeleteSelection() {
+
+	if ed.selStart == ed.selEnd {
+		return
+	}
+
+	changed := false
+	ed.col = ed.selStart
+	for ed.selEnd > ed.selStart {
+		if ed.col < text.StrCount(ed.text) {
+			changed = true
+			ed.text = text.StrRemove(ed.text, ed.col)
+			ed.selEnd--
+		}
+	}
+	if changed {
 		ed.Dispatch(OnChange, nil)
+		ed.redraw(ed.focus)
 	}
 }
 
 // CursorInput inserts the specified string at the current cursor position
+// If text is selected the selected text gets overwritten
 func (ed *Edit) CursorInput(s string) {
 
+	if ed.selStart != ed.selEnd {
+		ed.DeleteSelection()
+	}
 	if text.StrCount(ed.text) >= ed.MaxLength {
 		return
 	}
@@ -197,46 +394,72 @@ func (ed *Edit) CursorInput(s string) {
 
 	// Checks if new text exceeds edit width
 	width, _ := ed.Label.font.MeasureText(newText)
-	if float32(width)+editMarginX+float32(1) >= ed.Label.ContentWidth() {
+	if float32(width) / float32(ed.Label.font.ScaleX()) + editMarginX + float32(1) >= ed.Label.ContentWidth() {
 		return
 	}
 
 	ed.text = newText
 	ed.col++
+	ed.selStart = ed.col
+	ed.selEnd = ed.col
 
 	ed.Dispatch(OnChange, nil)
 	ed.redraw(ed.focus)
 }
 
 // redraw redraws the text showing the caret if specified
+// the selection caret is always shown (when text is selected)
 func (ed *Edit) redraw(caret bool) {
 
 	line := 0
-	if !caret {
-		line = -1
-	}
-	ed.Label.setTextCaret(ed.text, editMarginX, ed.width, line, ed.col)
+	scaleX, _ := window.Get().GetScale()
+	ed.Label.setTextCaret(ed.text, editMarginX, int(float64(ed.width) * scaleX), caret, line, ed.col, ed.selStart, ed.selEnd)
 }
 
 // onKey receives subscribed key events
 func (ed *Edit) onKey(evname string, ev interface{}) {
 
 	kev := ev.(*window.KeyEvent)
-	switch kev.Key {
-	case window.KeyLeft:
-		ed.CursorLeft()
-	case window.KeyRight:
-		ed.CursorRight()
-	case window.KeyHome:
-		ed.CursorHome()
-	case window.KeyEnd:
-		ed.CursorEnd()
-	case window.KeyBackspace:
-		ed.CursorBack()
-	case window.KeyDelete:
-		ed.CursorDelete()
-	default:
-		return
+	if kev.Mods != window.ModShift && kev.Mods != window.ModControl {
+		switch kev.Key {
+		case window.KeyLeft:
+			ed.CursorLeft()
+		case window.KeyRight:
+			ed.CursorRight()
+		case window.KeyHome:
+			ed.CursorHome()
+		case window.KeyEnd:
+			ed.CursorEnd()
+		case window.KeyBackspace:
+			ed.CursorBack()
+		case window.KeyDelete:
+			ed.CursorDelete()
+		default:
+			return
+		}
+	} else if kev.Mods == window.ModShift {
+		switch kev.Key {
+		case window.KeyLeft:
+			ed.SelectLeft()
+		case window.KeyRight:
+			ed.SelectRight()
+		case window.KeyHome:
+			ed.SelectHome()
+		case window.KeyEnd:
+			ed.SelectEnd()
+		case window.KeyBackspace:
+			ed.CursorBack()
+		case window.KeyDelete:
+			ed.SelectAll()
+			ed.DeleteSelection()
+		default:
+			return
+		}
+	} else if kev.Mods == window.ModControl {
+		switch kev.Key {
+		case window.KeyA:
+			ed.SelectAll()
+		}
 	}
 }
 
@@ -247,23 +470,37 @@ func (ed *Edit) onChar(evname string, ev interface{}) {
 	ed.CursorInput(string(cev.Char))
 }
 
-// onMouseEvent receives subscribed mouse down events
-func (ed *Edit) onMouse(evname string, ev interface{}) {
+// onMouseDown receives subscribed mouse down events
+func (ed *Edit) onMouseDown(evname string, ev interface{}) {
 
 	e := ev.(*window.MouseEvent)
 	if e.Button != window.MouseButtonLeft {
 		return
 	}
 
+	// set caret to clicked position
+	ed.handleMouse(e.Xpos, false)
+
+	ed.mouseDrag = true
+
 	// Set key focus to this panel
+	// Set the focus AFTER the mouse selection is handled
+	// Otherwise the OnFocus event would fire before the cursor is set.
+	// That way the OnFocus handler could NOT influence the selection
+	// Because it would be overridden/cleared directly afterwards.
 	Manager().SetKeyFocus(ed)
+}
+
+// handleMouse is setting the caret when the mouse is clicked
+// or setting the text selection when the mouse is dragged
+func (ed *Edit) handleMouse(mouseX float32, dragged bool) {
 
 	// Find clicked column
 	var nchars int
 	for nchars = 1; nchars <= text.StrCount(ed.text); nchars++ {
 		width, _ := ed.Label.font.MeasureText(text.StrPrefix(ed.text, nchars))
-		posx := e.Xpos - ed.pospix.X
-		if posx < editMarginX+float32(width) {
+		posx := mouseX - ed.pospix.X
+		if posx < editMarginX + float32(float64(width) / ed.Label.font.ScaleX()) {
 			break
 		}
 	}
@@ -271,7 +508,28 @@ func (ed *Edit) onMouse(evname string, ev interface{}) {
 		ed.focus = true
 		ed.blinkID = Manager().SetInterval(750*time.Millisecond, nil, ed.blink)
 	}
-	ed.CursorPos(nchars - 1)
+	if !dragged {
+		ed.CursorPos(nchars - 1)
+	} else {
+		newPos := nchars - 1
+		if newPos > ed.col {
+			distance := newPos - ed.col
+			for i := 0; i < distance; i++ {
+				ed.SelectRight()
+			}
+		} else if newPos < ed.col {
+			distance := ed.col - newPos
+			for i := 0; i < distance; i++ {
+				ed.SelectLeft()
+			}
+		}
+	}
+}
+
+// onMouseEvent receives subscribed mouse up events
+func (ed *Edit) onMouseUp(evname string, ev interface{}) {
+
+	ed.mouseDrag = false
 }
 
 // onCursor receives subscribed cursor events
@@ -286,9 +544,15 @@ func (ed *Edit) onCursor(evname string, ev interface{}) {
 	if evname == OnCursorLeave {
 		window.Get().SetCursor(window.ArrowCursor)
 		ed.cursorOver = false
+		ed.mouseDrag = false
 		ed.update()
 		return
 	}
+	if ed.mouseDrag {
+		e := ev.(*window.CursorEvent)
+		// select text based on mouse position
+		ed.handleMouse(e.Xpos, true)
+	}
 }
 
 // blink blinks the caret
@@ -334,8 +598,9 @@ func (ed *Edit) applyStyle(s *EditStyle) {
 	//ed.Label.SetBgAlpha(s.BgAlpha)
 
 	if !ed.focus && len(ed.text) == 0 && len(ed.placeHolder) > 0 {
+		scaleX, _ := window.Get().GetScale()
 		ed.Label.SetColor4(&s.HolderColor)
-		ed.Label.setTextCaret(ed.placeHolder, editMarginX, ed.width, -1, ed.col)
+		ed.Label.setTextCaret(ed.placeHolder, editMarginX, int(float64(ed.width) * scaleX), false, -1, ed.col, ed.selStart, ed.selEnd)
 	} else {
 		ed.Label.SetColor4(&s.FgColor)
 		ed.redraw(ed.focus)

+ 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

+ 14 - 4
gui/label.go

@@ -9,6 +9,7 @@ import (
 	"github.com/g3n/engine/math32"
 	"github.com/g3n/engine/text"
 	"github.com/g3n/engine/texture"
+	"github.com/g3n/engine/window"
 )
 
 // Label is a panel which contains a texture with text.
@@ -83,6 +84,9 @@ func (l *Label) SetText(text string) {
 	l.font.SetAttributes(&l.style.FontAttributes)
 	l.font.SetColor(&l.style.FgColor)
 
+	scaleX, scaleY := window.Get().GetScale()
+	l.font.SetScaleXY(scaleX, scaleY)
+
 	// Create an image with the text
 	textImage := l.font.DrawText(text)
 
@@ -98,7 +102,10 @@ func (l *Label) SetText(text string) {
 	}
 
 	// Update label panel dimensions
-	l.Panel.SetContentSize(float32(textImage.Rect.Dx()), float32(textImage.Rect.Dy()))
+	width, height := float32(textImage.Rect.Dx()), float32(textImage.Rect.Dy())
+	// since we enlarged the font texture for higher quality, we have to scale it back to it's original point size
+	width, height = width / float32(scaleX), height / float32(scaleY)
+	l.Panel.SetContentSize(width, height)
 }
 
 // Text returns the label text.
@@ -213,16 +220,19 @@ func (l *Label) LineSpacing() float64 {
 // setTextCaret sets the label text and draws a caret at the
 // specified line and column.
 // It is normally used by the Edit widget.
-func (l *Label) setTextCaret(msg string, mx, width, line, col int) {
+func (l *Label) setTextCaret(msg string, mx, width int, drawCaret bool, line, col, selStart, selEnd int) {
 
 	// Set font properties
 	l.font.SetAttributes(&l.style.FontAttributes)
 	l.font.SetColor(&l.style.FgColor)
 
+	scaleX, scaleY := window.Get().GetScale()
+	l.font.SetScaleXY(scaleX, scaleY)
+
 	// Create canvas and draw text
 	_, height := l.font.MeasureText(msg)
 	canvas := text.NewCanvas(width, height, &l.style.BgColor)
-	canvas.DrawTextCaret(mx, 0, msg, l.font, line, col)
+	canvas.DrawTextCaret(mx, 0, msg, l.font, drawCaret, line, col, selStart, selEnd)
 
 	// Creates texture if if doesnt exist.
 	if l.tex == nil {
@@ -237,6 +247,6 @@ func (l *Label) setTextCaret(msg string, mx, width, line, col int) {
 	l.tex.SetMinFilter(gls.NEAREST)
 
 	// Updates label panel dimensions
-	l.Panel.SetContentSize(float32(width), float32(height))
+	l.Panel.SetContentSize(float32(width) / float32(scaleX), float32(height) / float32(scaleY))
 	l.text = msg
 }

+ 9 - 0
gui/manager.go

@@ -113,6 +113,15 @@ func (gm *manager) onKeyboard(evname string, ev interface{}) {
 // OnMouseDown/OnMouseUp are dispatched to gm.target or to non-GUI, while
 // OnMouseDownOut/OnMouseUpOut are dispatched to all non-target panels.
 func (gm *manager) onMouse(evname string, ev interface{}) {
+	// To fix #299
+	if gm.cev == nil {
+		mev := ev.(*window.MouseEvent)
+		gm.cev = &window.CursorEvent{
+			Xpos: mev.Xpos,
+			Ypos: mev.Ypos,
+			Mods: mev.Mods,
+		}
+	}
 
 	// Check if gm.scene is nil and if so then there are no IPanels to send events to
 	if gm.scene == nil {

+ 1 - 1
gui/panel.go

@@ -592,7 +592,7 @@ func (p *Panel) Intersects(other *Panel) bool {
 }
 
 // SetEnabled sets the panel enabled state
-// A disabled panel do not process key or mouse events.
+// A disabled panel does not process events.
 func (p *Panel) SetEnabled(state bool) {
 
 	p.enabled = state

+ 16 - 0
gui/slider.go

@@ -163,6 +163,10 @@ func (s *Slider) setPos(pos float32) {
 // onMouse process subscribed mouse events over the outer panel
 func (s *Slider) onMouse(evname string, ev interface{}) {
 
+	if !s.Enabled() {
+		return
+	}
+
 	mev := ev.(*window.MouseEvent)
 	if mev.Button != window.MouseButtonLeft {
 		return
@@ -188,6 +192,10 @@ func (s *Slider) onMouse(evname string, ev interface{}) {
 // onCursor process subscribed cursor events
 func (s *Slider) onCursor(evname string, ev interface{}) {
 
+	if !s.Enabled() {
+		return
+	}
+
 	if evname == OnCursorEnter {
 		s.cursorOver = true
 		if s.horiz {
@@ -224,6 +232,10 @@ func (s *Slider) onCursor(evname string, ev interface{}) {
 // onScroll process subscribed scroll events
 func (s *Slider) onScroll(evname string, ev interface{}) {
 
+	if !s.Enabled() {
+		return
+	}
+
 	sev := ev.(*window.ScrollEvent)
 	v := s.pos
 	v += sev.Yoffset * 0.01
@@ -233,6 +245,10 @@ func (s *Slider) onScroll(evname string, ev interface{}) {
 // onKey process subscribed key events
 func (s *Slider) onKey(evname string, ev interface{}) {
 
+	if !s.Enabled() {
+		return
+	}
+
 	kev := ev.(*window.KeyEvent)
 	delta := float32(0.01)
 	// Horizontal slider

+ 23 - 7
gui/window.go

@@ -16,7 +16,7 @@ import (
  +-------------------------------------+---+
  |              Title panel            | X |
  +-------------------------------------+---+
- |  Content panel                          |
+ |  Client (content) panel                 |
  |  +-----------------------------------+  |
  |  |                                   |  |
  |  |                                   |  |
@@ -35,7 +35,7 @@ type Window struct {
 	Panel       // Embedded Panel
 	styles      *WindowStyles
 	title       *WindowTitle // internal optional title panel
-	client      Panel        // internal client panel
+	client      Panel        // internal client (content) panel
 	resizable   bool         // Specifies whether the window is resizable
 	drag        bool         // Whether the mouse buttons is pressed (i.e. when dragging)
 	dragPadding float32      // Extra width used to resize (in addition to border sizes)
@@ -123,6 +123,11 @@ func (w *Window) Add(ichild IPanel) *Window {
 	return w
 }
 
+// Removes a child from the client (content) panel
+func (w *Window) Remove(ichild IPanel) bool {
+	return w.client.Remove(ichild)
+}
+
 // SetLayout sets the layout of the client panel.
 func (w *Window) SetLayout(layout ILayout) {
 
@@ -162,32 +167,43 @@ func (w *Window) onCursor(evname string, ev interface{}) {
 		// If already dragging - update window size and position depending
 		// on the cursor position and the borders being dragged
 		if w.drag {
+			titleHeight := float32(0)
+			titleLabelWidth := float32(0)
+			titleCloseBtnWidth := float32(0)
+			if w.title != nil {
+				titleHeight = w.title.height
+				titleLabelWidth = w.title.label.Width()
+				if w.title.closeButton != nil {
+					titleCloseBtnWidth = w.title.closeButton.Width()
+				}
+			}
+
 			if w.overTop {
 				delta := cev.Ypos - w.pospix.Y
 				newHeight := w.Height() - delta
-				minHeight := w.title.height
+				minHeight := titleHeight
 				if newHeight >= minHeight {
 					w.SetPositionY(w.Position().Y + delta)
 					w.SetHeight(math32.Max(newHeight, minHeight))
 				} else {
 					w.SetPositionY(w.Position().Y + w.Height() - minHeight)
-					w.SetHeight(w.title.height)
+					w.SetHeight(titleHeight)
 				}
 			}
 			if w.overRight {
 				delta := cev.Xpos - (w.pospix.X + w.width)
 				newWidth := w.Width() + delta
-				w.SetWidth(math32.Max(newWidth, w.title.label.Width()+w.title.closeButton.Width()))
+				w.SetWidth(math32.Max(newWidth, titleLabelWidth+titleCloseBtnWidth))
 			}
 			if w.overBottom {
 				delta := cev.Ypos - (w.pospix.Y + w.height)
 				newHeight := w.Height() + delta
-				w.SetHeight(math32.Max(newHeight, w.title.height))
+				w.SetHeight(math32.Max(newHeight, titleHeight))
 			}
 			if w.overLeft {
 				delta := cev.Xpos - w.pospix.X
 				newWidth := w.Width() - delta
-				minWidth := w.title.label.Width() + w.title.closeButton.Width()
+				minWidth := titleLabelWidth + titleCloseBtnWidth
 				if newWidth >= minWidth {
 					w.SetPositionX(w.Position().X + delta)
 					w.SetWidth(math32.Max(newWidth, minWidth))

+ 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) {

+ 52 - 1
loader/collada/library_geometries.go

@@ -327,13 +327,21 @@ func (d *Decoder) decMesh(start xml.StartElement, geom *Geometry) error {
 			continue
 		}
 		// Decodes polylist
-		if child.Name.Local == "polylist" {
+		if child.Name.Local == "polylist" || child.Name.Local == "triangles" {
 			err = d.decPolylist(child, mesh)
 			if err != nil {
 				return err
 			}
 			continue
 		}
+		// Decodes triangles
+		if child.Name.Local == "triangles" {
+			err = d.decTriangles(child, mesh)
+			if err != nil {
+				return err
+			}
+			continue
+		}
 	}
 }
 
@@ -409,6 +417,15 @@ func (d *Decoder) decPolylist(start xml.StartElement, mesh *Mesh) error {
 	pl.Material = findAttrib(start, "material").Value
 	mesh.PrimitiveElements = append(mesh.PrimitiveElements, pl)
 
+	// blender exporter now (since v2.79) exports meshes as <Triangles> when all contained polygons are tris
+	// https://developer.blender.org/rBc9b95c28f64e9d7421b00cbf8ed4ecddd6471ae5
+	if start.Name.Local == "triangles" {
+		pl.Vcount = make([]int, pl.Count)
+		for i := range pl.Vcount {
+			pl.Vcount[i] = 3
+		}
+	}
+
 	for {
 		// Get next child
 		child, data, err := d.decNextChild(start)
@@ -444,6 +461,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)

+ 61 - 63
loader/obj/obj.go

@@ -145,72 +145,74 @@ func DecodeReader(objreader, mtlreader io.Reader) (*Decoder, error) {
 		return nil, err
 	}
 
-	// Parses mtl lines
-	// 1) try passed in mtlreader,
-	// 2) try file in mtllib line
-	// 3) try <obj_filename>.mtl
-	// 4) use default material as last resort
-	dec.matCurrent = nil
-	dec.line = 1
-	// first try: use the material file passed in as an io.Reader
-	err = dec.parse(mtlreader, dec.parseMtlLine)
-	if err != nil {
+	if (mtlreader != nil) {
+		// Parses mtl lines
+		// 1) try passed in mtlreader,
+		// 2) try file in mtllib line
+		// 3) try <obj_filename>.mtl
+		// 4) use default material as last resort
+		dec.matCurrent = nil
+		dec.line = 1
+		// first try: use the material file passed in as an io.Reader
+		err = dec.parse(mtlreader, dec.parseMtlLine)
+		if err != nil {
 
-		// 2) if mtlreader produces an error (eg. it's nil), try the file listed
-		// in the OBJ's matlib line, if it exists.
-		if dec.Matlib != "" {
-			// ... first need to get the path of the OBJ, since mtllib is relative
-			var mtllibPath string
-			if objf, ok := objreader.(*os.File); ok {
-				// NOTE (quillaja): this is a hack because we need the directory of
-				// the OBJ, but can't get it any other way (dec.mtlDir isn't set
-				// until AFTER this function is finished).
-				objdir := filepath.Dir(objf.Name())
-				mtllibPath = filepath.Join(objdir, dec.Matlib)
-				dec.mtlDir = objdir // NOTE (quillaja): should this be set?
-			}
-			mtlf, errMTL := os.Open(mtllibPath)
-			defer mtlf.Close()
-			if errMTL == nil {
-				err = dec.parse(mtlf, dec.parseMtlLine) // will set err to nil if successful
+			// 2) if mtlreader produces an error (eg. it's nil), try the file listed
+			// in the OBJ's matlib line, if it exists.
+			if dec.Matlib != "" {
+				// ... first need to get the path of the OBJ, since mtllib is relative
+				var mtllibPath string
+				if objf, ok := objreader.(*os.File); ok {
+					// NOTE (quillaja): this is a hack because we need the directory of
+					// the OBJ, but can't get it any other way (dec.mtlDir isn't set
+					// until AFTER this function is finished).
+					objdir := filepath.Dir(objf.Name())
+					mtllibPath = filepath.Join(objdir, dec.Matlib)
+					dec.mtlDir = objdir // NOTE (quillaja): should this be set?
+				}
+				mtlf, errMTL := os.Open(mtllibPath)
+				defer mtlf.Close()
+				if errMTL == nil {
+					err = dec.parse(mtlf, dec.parseMtlLine) // will set err to nil if successful
+				}
 			}
-		}
 
-		// 3) if the mtllib line fails try <obj_filename>.mtl in the same directory.
-		// process is basically identical to the above code block.
-		if err != nil {
-			var mtlpath string
-			if objf, ok := objreader.(*os.File); ok {
-				objdir := strings.TrimSuffix(objf.Name(), ".obj")
-				mtlpath = objdir + ".mtl"
-				dec.mtlDir = objdir // NOTE (quillaja): should this be set?
-			}
-			mtlf, errMTL := os.Open(mtlpath)
-			defer mtlf.Close()
-			if errMTL == nil {
-				err = dec.parse(mtlf, dec.parseMtlLine) // will set err to nil if successful
-				if err == nil {
-					// log a warning
-					msg := fmt.Sprintf("using material file %s", mtlpath)
-					dec.appendWarn(mtlType, msg)
+			// 3) if the mtllib line fails try <obj_filename>.mtl in the same directory.
+			// process is basically identical to the above code block.
+			if err != nil {
+				var mtlpath string
+				if objf, ok := objreader.(*os.File); ok {
+					objdir := strings.TrimSuffix(objf.Name(), ".obj")
+					mtlpath = objdir + ".mtl"
+					dec.mtlDir = objdir // NOTE (quillaja): should this be set?
+				}
+				mtlf, errMTL := os.Open(mtlpath)
+				defer mtlf.Close()
+				if errMTL == nil {
+					err = dec.parse(mtlf, dec.parseMtlLine) // will set err to nil if successful
+					if err == nil {
+						// log a warning
+						msg := fmt.Sprintf("using material file %s", mtlpath)
+						dec.appendWarn(mtlType, msg)
+					}
 				}
 			}
-		}
 
-		// 4) handle error(s) instead of simply passing it up the call stack.
-		// range over the materials named in the OBJ file and substitute a default
-		// But log that an error occured.
-		if err != nil {
-			for key := range dec.Materials {
-				dec.Materials[key] = defaultMat
+			// 4) handle error(s) instead of simply passing it up the call stack.
+			// range over the materials named in the OBJ file and substitute a default
+			// But log that an error occured.
+			if err != nil {
+				fmt.Println("Using default material")
+				for key := range dec.Materials {
+					dec.Materials[key] = defaultMat
+				}
+				// NOTE (quillaja): could be an error of some custom type. But people
+				// tend to ignore errors and pass them up the call stack instead
+				// of handling them... so all this work would probably be wasted.
+				dec.appendWarn(mtlType, "unable to parse a material file for obj. using default material instead.")
 			}
-			// NOTE (quillaja): could be an error of some custom type. But people
-			// tend to ignore errors and pass them up the call stack instead
-			// of handling them... so all this work would probably be wasted.
-			dec.appendWarn(mtlType, "unable to parse a material file for obj. using default material instead.")
 		}
 	}
-
 	return dec, nil
 }
 
@@ -399,7 +401,6 @@ func (dec *Decoder) loadTex(mat *material.Material, desc *Material) error {
 // parse reads the lines from the specified reader and dispatch them
 // to the specified line parser.
 func (dec *Decoder) parse(reader io.Reader, parseLine func(string) error) error {
-
 	bufin := bufio.NewReader(reader)
 	dec.line = 1
 	for {
@@ -704,11 +705,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
 }

+ 1 - 1
math32/color4.go

@@ -52,7 +52,7 @@ func (c *Color4) Set(r, g, b, a float32) *Color4 {
 	c.R = r
 	c.G = g
 	c.B = b
-	c.A = b
+	c.A = a
 	return c
 }
 

+ 202 - 0
math32/curves.go

@@ -0,0 +1,202 @@
+// Copyright 2016 The G3N Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package math32
+
+// Curve constructs an array of Vector3
+type Curve struct {
+	points []Vector3
+	length float32
+}
+
+func (c *Curve) GetPoints() []Vector3 {
+	return c.points
+}
+
+func (c *Curve) GetLength() float32 {
+	return c.length
+}
+
+func (c *Curve) SetLength() {
+	points := c.points
+	l := float32(0.0)
+	for i := 1; i < len(points); i++ {
+		p0 := points[i].Clone()
+		p1 := points[i-1].Clone()
+		l += (p0.Sub(p1)).Length()
+	}
+	c.length = l
+}
+
+// Continue combines two curves
+// 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()
+	first := other.points[0].Clone()
+
+	var continued, otherpoints []Vector3
+	for i := 0; i < len(c.points); i++ {
+		continued = append(continued, *c.points[i].Clone())
+	}
+	for i := 1; i < len(other.points); i++ {
+		otherpoints = append(otherpoints, *other.points[i].Clone())
+	}
+	for i := 0; i < len(otherpoints); i++ {
+		continued = append(continued, *otherpoints[i].Sub(first).Add(last))
+	}
+	newC := new(Curve)
+	newC.points = continued
+	newC.SetLength()
+	return newC
+}
+
+// NewBezierQuadratic creates and returns a pointer to a new curve
+// Uses Vector3 pointers origin, control, and destination to calculate with
+// int npoints as the desired number of points along the curve
+func NewBezierQuadratic(origin, control, destination *Vector3, npoints int) *Curve {
+	c := new(Curve)
+
+	if npoints <= 2 {
+		npoints = 3
+	}
+
+	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
+		return result
+	}
+	var bezier []Vector3
+
+	for i := 0; i <= npoints; i++ {
+		t := float32(i) / float32(npoints)
+		x := equation(t, origin.X, control.X, destination.X)
+		y := equation(t, origin.Y, control.Y, destination.Y)
+		z := equation(t, origin.Z, control.Z, destination.Z)
+		vect := NewVector3(x, y, z)
+		bezier = append(bezier, *vect)
+	}
+
+	c.points = bezier
+	c.SetLength()
+	return c
+}
+
+// NewBezierCubic creates and returns a pointer to a new curve
+// Uses Vector3 pointers origin, control1, control2, and destination to calculate with
+// int npoints as the desired number of points along the curve
+func NewBezierCubic(origin, control1, control2, destination *Vector3, npoints int) *Curve {
+	c := new(Curve)
+
+	if npoints <= 3 {
+		npoints = 4
+	}
+
+	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
+		return result
+	}
+	var bezier []Vector3
+
+	for i := 0; i <= npoints; i++ {
+		t := float32(i) / float32(npoints)
+		x := equation(t, origin.X, control1.X, control2.X, destination.X)
+		y := equation(t, origin.Y, control1.Y, control2.Y, destination.Y)
+		z := equation(t, origin.Z, control1.Z, control2.Z, destination.Z)
+		vect := NewVector3(x, y, z)
+		bezier = append(bezier, *vect)
+	}
+
+	c.points = bezier
+	c.SetLength()
+	return c
+}
+
+// NewHermiteSpline creates and returns a pointer to a new curve
+// Uses Vector3 pointers origin, tangent1, destination, and tangent2 to calculate with
+// int npoints as the desired number of points along the curve
+func NewHermiteSpline(origin, tangent1, destination, tangent2 *Vector3, npoints int) *Curve {
+	c := new(Curve)
+
+	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
+		p1 := (-2.0 * t3) + (3.0 * t2)
+		p2 := t3 - (2.0 * t2) + t
+		p3 := t3 - t2
+		x := (v0.X * p0) + (v1.X * p1) + (tan0.X * p2) + (tan1.X * p3)
+		y := (v0.Y * p0) + (v1.Y * p1) + (tan0.Y * p2) + (tan1.Y * p3)
+		z := (v0.Z * p0) + (v1.Z * p1) + (tan0.Z * p2) + (tan1.Z * p3)
+		return NewVector3(x, y, z)
+	}
+
+	step := float32(1.0) / float32(npoints)
+	var hermite []Vector3
+	for i := 0; i <= npoints; i++ {
+		vect := equation(float32(i)*step, origin, tangent1, destination, tangent2)
+		hermite = append(hermite, *vect)
+	}
+	c.points = hermite
+	c.SetLength()
+	return c
+}
+
+// NewCatmullRomSpline creates and returns a pointer to a new curve
+// Uses array of Vector3 pointers with int npoints as the desired number of points between supplied points
+// Use Boolean closed with true to close the start and end points
+func NewCatmullRomSpline(points []*Vector3, npoints int, closed bool) *Curve {
+	c := new(Curve)
+
+	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))
+		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))
+		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)
+	}
+
+	step := float32(1.0) / float32(npoints)
+	var catmull []Vector3
+	var t float32
+	if closed {
+		count := len(points)
+		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])
+				catmull = append(catmull, *vect)
+				t += step
+			}
+		}
+		catmull = append(catmull, catmull[0])
+	} else {
+		total := []*Vector3{points[0].Clone()}
+		total = append(total, points...)
+		total = append(total, points[len(points)-1].Clone())
+		var i int
+		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])
+				catmull = append(catmull, *vect)
+				t += step
+			}
+		}
+		i--
+		vect := equation(t, total[i], total[i+1], total[i+2], total[i+3])
+		catmull = append(catmull, *vect)
+	}
+	c.points = catmull
+	c.SetLength()
+	return c
+}

+ 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}
+}

+ 119 - 0
renderer/postprocessor.go

@@ -0,0 +1,119 @@
+// Copyright 2016 The G3N Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package renderer implements the scene renderer.
+package renderer
+
+import "github.com/g3n/engine/gls"
+
+type Postprocessor struct {
+	Width    int32
+	Height   int32
+	Fbo      uint32
+	Tex      uint32
+	Vao      uint32
+	Prg      *gls.Program
+	screen   []float32
+	Renderer *Renderer
+}
+
+func (r *Renderer) CreatePostprocessor(width, height int32, vertexShaderSource, fragmentShaderSource string) *Postprocessor {
+	pp := &Postprocessor{
+		Width:    width,
+		Height:   height,
+		Renderer: r,
+		screen: []float32{
+			// xyz		color		texture coords
+			-1, 1, 0, 1, 1, 1, 0, 1,
+			-1, -1, 0, 1, 1, 1, 0, 0,
+			1, -1, 0, 1, 1, 1, 1, 0,
+			1, 1, 0, 1, 1, 1, 1, 1,
+			-1, 1, 0, 1, 1, 1, 0, 1,
+			1, -1, 0, 1, 1, 1, 1, 0,
+		},
+	}
+
+	pp.Fbo = r.gs.GenFramebuffer()
+	r.gs.BindFramebuffer(pp.Fbo)
+
+	// set up a texture to render into
+	pp.Tex = r.gs.GenTexture()
+	r.gs.BindTexture(gls.TEXTURE_2D, pp.Tex)
+	r.gs.TexImage2D(gls.TEXTURE_2D, 0, gls.RGB, width, height, gls.RGB, gls.UNSIGNED_BYTE, nil)
+	r.gs.TexParameteri(gls.TEXTURE_2D, gls.TEXTURE_WRAP_S, gls.CLAMP_TO_EDGE)
+	r.gs.TexParameteri(gls.TEXTURE_2D, gls.TEXTURE_WRAP_T, gls.CLAMP_TO_EDGE)
+	r.gs.TexParameteri(gls.TEXTURE_2D, gls.TEXTURE_MIN_FILTER, gls.NEAREST)
+	r.gs.TexParameteri(gls.TEXTURE_2D, gls.TEXTURE_MAG_FILTER, gls.NEAREST)
+	r.gs.BindTexture(gls.TEXTURE_2D, 0)
+	r.gs.FramebufferTexture2D(gls.COLOR_ATTACHMENT0, gls.TEXTURE_2D, pp.Tex)
+
+	// attach depth and stencil buffers
+	rbo := r.gs.GenRenderbuffer()
+	r.gs.BindRenderbuffer(rbo)
+	r.gs.RenderbufferStorage(gls.DEPTH24_STENCIL8, int(width), int(height))
+	r.gs.BindRenderbuffer(0)
+	r.gs.FramebufferRenderbuffer(gls.DEPTH_STENCIL_ATTACHMENT, rbo)
+
+	// check the framebuffer status
+	if r.gs.CheckFramebufferStatus() != gls.FRAMEBUFFER_COMPLETE {
+		log.Fatal("Can't create frame buffer")
+	}
+	r.gs.BindFramebuffer(0)
+
+	// create the "screen" quad
+	vbo := r.gs.GenBuffer()
+	r.gs.BindBuffer(gls.ARRAY_BUFFER, vbo)
+	r.gs.BufferData(gls.ARRAY_BUFFER, 4*len(pp.screen), pp.screen, gls.STATIC_DRAW)
+
+	pp.Vao = r.gs.GenVertexArray()
+	r.gs.BindVertexArray(pp.Vao)
+	r.gs.BindBuffer(gls.ARRAY_BUFFER, vbo)
+	var offset uint32
+
+	// position attribute
+	r.gs.VertexAttribPointer(0, 3, gls.FLOAT, false, 8*4, offset)
+	r.gs.EnableVertexAttribArray(0)
+	offset += 3 * 4
+
+	// color attribute
+	r.gs.VertexAttribPointer(1, 3, gls.FLOAT, false, 8*4, offset)
+	r.gs.EnableVertexAttribArray(1)
+	offset += 3 * 4
+
+	// texture coord attribute
+	r.gs.VertexAttribPointer(2, 2, gls.FLOAT, false, 8*4, offset)
+	r.gs.EnableVertexAttribArray(2)
+	offset += 2 * 4
+
+	// the screen shaders
+	pp.Prg = r.gs.NewProgram()
+	pp.Prg.AddShader(gls.VERTEX_SHADER, vertexShaderSource)
+	pp.Prg.AddShader(gls.FRAGMENT_SHADER, fragmentShaderSource)
+	err := pp.Prg.Build()
+	if err != nil {
+		log.Fatal("can't create shader: %e", err)
+	}
+
+	return pp
+}
+
+func (pp *Postprocessor) Render(fbwidth, fbheight int, render func()) {
+	// render into the low-res texture
+	gs := pp.Renderer.gs
+	gs.Viewport(0, 0, pp.Width, pp.Height)
+	gs.BindFramebuffer(pp.Fbo)
+	gs.Enable(gls.DEPTH_TEST)
+	render()
+
+	// show texture on screen
+	gs.Viewport(0, 0, int32(fbwidth), int32(fbheight))
+	gs.BindFramebuffer(0)
+	gs.ClearColor(1, 1, 1, 1)
+	gs.Clear(gls.COLOR_BUFFER_BIT)
+	gs.UseProgram(pp.Prg)
+	gs.Disable(gls.DEPTH_TEST)
+	gs.BindTexture(gls.TEXTURE_2D, pp.Tex)
+	gs.BindVertexArray(pp.Vao)
+	gs.DrawArrays(gls.TRIANGLES, 0, int32(len(pp.screen)/8))
+}

+ 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;
             }
         }
     }

+ 424 - 404
renderer/shaders/sources.go

@@ -4,13 +4,28 @@
 
 package shaders
 
-const include_attributes_source = `//
-// Vertex attributes
-//
-layout(location = 0) in  vec3  VertexPosition;
-layout(location = 1) in  vec3  VertexNormal;
-layout(location = 2) in  vec3  VertexColor;
-layout(location = 3) in  vec2  VertexTexcoord;
+const include_morphtarget_vertex2_source = `	vPosition += MorphPosition{i} * morphTargetInfluences[{i}];
+  #ifdef MORPHTARGETS_NORMAL
+	vNormal += MorphNormal{i} * morphTargetInfluences[{i}];
+  #endif`
+
+const include_morphtarget_vertex_declaration2_source = `	in vec3 MorphPosition{i};
+  #ifdef MORPHTARGETS_NORMAL
+	in vec3 MorphNormal{i};
+  #endif
+`
+
+const include_morphtarget_vertex_source = `#ifdef MORPHTARGETS
+
+    #include <morphtarget_vertex2> [MORPHTARGETS]
+
+#endif
+`
+
+const include_morphtarget_vertex_declaration_source = `#ifdef MORPHTARGETS
+	uniform float morphTargetInfluences[MORPHTARGETS];
+	#include <morphtarget_vertex_declaration2> [MORPHTARGETS]
+#endif
 `
 
 const include_bones_vertex_source = `#ifdef BONE_INFLUENCERS
@@ -45,123 +60,6 @@ const include_bones_vertex_source = `#ifdef BONE_INFLUENCERS
 #endif
 `
 
-const include_bones_vertex_declaration_source = `#ifdef BONE_INFLUENCERS
-    #if BONE_INFLUENCERS > 0
-	uniform mat4 mBones[TOTAL_BONES];
-    in vec4 matricesIndices;
-    in vec4 matricesWeights;
-//    #if BONE_INFLUENCERS > 4
-//        in vec4 matricesIndicesExtra;
-//        in vec4 matricesWeightsExtra;
-//    #endif
-    #endif
-#endif
-`
-
-const include_lights_source = `//
-// Lights uniforms
-//
-
-#if AMB_LIGHTS>0
-    // Ambient lights color uniform
-    uniform vec3 AmbientLightColor[AMB_LIGHTS];
-#endif
-
-#if DIR_LIGHTS>0
-    // Directional lights uniform array. Each directional light uses 2 elements
-    uniform vec3 DirLight[2*DIR_LIGHTS];
-    // Macros to access elements inside the DirectionalLight uniform array
-    #define DirLightColor(a)		DirLight[2*a]
-    #define DirLightPosition(a)		DirLight[2*a+1]
-#endif
-
-#if POINT_LIGHTS>0
-    // Point lights uniform array. Each point light uses 3 elements
-    uniform vec3 PointLight[3*POINT_LIGHTS];
-    // Macros to access elements inside the PointLight uniform array
-    #define PointLightColor(a)			PointLight[3*a]
-    #define PointLightPosition(a)		PointLight[3*a+1]
-    #define PointLightLinearDecay(a)	PointLight[3*a+2].x
-    #define PointLightQuadraticDecay(a)	PointLight[3*a+2].y
-#endif
-
-#if SPOT_LIGHTS>0
-    // Spot lights uniforms. Each spot light uses 5 elements
-    uniform vec3  SpotLight[5*SPOT_LIGHTS];
-    // Macros to access elements inside the PointLight uniform array
-    #define SpotLightColor(a)			SpotLight[5*a]
-    #define SpotLightPosition(a)		SpotLight[5*a+1]
-    #define SpotLightDirection(a)		SpotLight[5*a+2]
-    #define SpotLightAngularDecay(a)	SpotLight[5*a+3].x
-    #define SpotLightCutoffAngle(a)		SpotLight[5*a+3].y
-    #define SpotLightLinearDecay(a)		SpotLight[5*a+3].z
-    #define SpotLightQuadraticDecay(a)	SpotLight[5*a+4].x
-#endif
-`
-
-const include_material_source = `//
-// Material properties uniform
-//
-
-// Material parameters uniform array
-uniform vec3 Material[6];
-// Macros to access elements inside the Material array
-#define MatAmbientColor		Material[0]
-#define MatDiffuseColor     Material[1]
-#define MatSpecularColor    Material[2]
-#define MatEmissiveColor    Material[3]
-#define MatShininess        Material[4].x
-#define MatOpacity          Material[4].y
-#define MatPointSize        Material[4].z
-#define MatPointRotationZ   Material[5].x
-
-#if MAT_TEXTURES > 0
-    // Texture unit sampler array
-    uniform sampler2D MatTexture[MAT_TEXTURES];
-    // Texture parameters (3*vec2 per texture)
-    uniform vec2 MatTexinfo[3*MAT_TEXTURES];
-    // Macros to access elements inside the MatTexinfo array
-    #define MatTexOffset(a)		MatTexinfo[(3*a)]
-    #define MatTexRepeat(a)		MatTexinfo[(3*a)+1]
-    #define MatTexFlipY(a)		bool(MatTexinfo[(3*a)+2].x)
-    #define MatTexVisible(a)	bool(MatTexinfo[(3*a)+2].y)
-    // Alpha compositing (see here: https://ciechanow.ski/alpha-compositing/)
-    vec4 Blend(vec4 texMixed, vec4 texColor) {
-        texMixed.rgb *= texMixed.a;
-        texColor.rgb *= texColor.a;
-        texMixed = texColor + texMixed * (1 - texColor.a);
-        if (texMixed.a > 0.0) {
-            texMixed.rgb /= texMixed.a;
-        }
-        return texMixed;
-    }
-#endif
-`
-
-const include_morphtarget_vertex_source = `#ifdef MORPHTARGETS
-
-    #include <morphtarget_vertex2> [MORPHTARGETS]
-
-#endif
-`
-
-const include_morphtarget_vertex2_source = `	vPosition += MorphPosition{i} * morphTargetInfluences[{i}];
-  #ifdef MORPHTARGETS_NORMAL
-	vNormal += MorphNormal{i} * morphTargetInfluences[{i}];
-  #endif`
-
-const include_morphtarget_vertex_declaration_source = `#ifdef MORPHTARGETS
-	uniform float morphTargetInfluences[MORPHTARGETS];
-	#include <morphtarget_vertex_declaration2> [MORPHTARGETS]
-#endif
-`
-
-const include_morphtarget_vertex_declaration2_source = `	in vec3 MorphPosition{i};
-  #ifdef MORPHTARGETS_NORMAL
-	in vec3 MorphNormal{i};
-  #endif
-`
-
 const include_phong_model_source = `/***
  phong lighting model
  Parameters:
@@ -192,6 +90,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 +108,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 +131,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 +159,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;
             }
         }
     }
@@ -261,176 +179,206 @@ void phongModel(vec4 position, vec3 normal, vec3 camDir, vec3 matAmbient, vec3 m
 }
 `
 
-const basic_fragment_source = `precision highp float;
+const include_attributes_source = `//
+// Vertex attributes
+//
+layout(location = 0) in  vec3  VertexPosition;
+layout(location = 1) in  vec3  VertexNormal;
+layout(location = 2) in  vec3  VertexColor;
+layout(location = 3) in  vec2  VertexTexcoord;
+`
 
-in vec3 Color;
-out vec4 FragColor;
+const include_material_source = `//
+// Material properties uniform
+//
 
-void main() {
+// Material parameters uniform array
+uniform vec3 Material[6];
+// Macros to access elements inside the Material array
+#define MatAmbientColor		Material[0]
+#define MatDiffuseColor     Material[1]
+#define MatSpecularColor    Material[2]
+#define MatEmissiveColor    Material[3]
+#define MatShininess        Material[4].x
+#define MatOpacity          Material[4].y
+#define MatPointSize        Material[4].z
+#define MatPointRotationZ   Material[5].x
 
-    FragColor = vec4(Color, 1.0);
-}
+#if MAT_TEXTURES > 0
+    // Texture unit sampler array
+    uniform sampler2D MatTexture[MAT_TEXTURES];
+    // Texture parameters (3*vec2 per texture)
+    uniform vec2 MatTexinfo[3*MAT_TEXTURES];
+    // Macros to access elements inside the MatTexinfo array
+    #define MatTexOffset(a)		MatTexinfo[(3*a)]
+    #define MatTexRepeat(a)		MatTexinfo[(3*a)+1]
+    #define MatTexFlipY(a)		bool(MatTexinfo[(3*a)+2].x)
+    #define MatTexVisible(a)	bool(MatTexinfo[(3*a)+2].y)
+    // Alpha compositing (see here: https://ciechanow.ski/alpha-compositing/)
+    vec4 Blend(vec4 texMixed, vec4 texColor) {
+        texMixed.rgb *= texMixed.a;
+        texColor.rgb *= texColor.a;
+        texMixed = texColor + texMixed * (1 - texColor.a);
+        if (texMixed.a > 0.0) {
+            texMixed.rgb /= texMixed.a;
+        }
+        return texMixed;
+    }
+#endif
 `
 
-const basic_vertex_source = `#include <attributes>
+const include_lights_source = `//
+// Lights uniforms
+//
 
-// Model uniforms
-uniform mat4 MVP;
+#if AMB_LIGHTS>0
+    // Ambient lights color uniform
+    uniform vec3 AmbientLightColor[AMB_LIGHTS];
+#endif
 
-// Final output color for fragment shader
-out vec3 Color;
+#if DIR_LIGHTS>0
+    // Directional lights uniform array. Each directional light uses 2 elements
+    uniform vec3 DirLight[2*DIR_LIGHTS];
+    // Macros to access elements inside the DirectionalLight uniform array
+    #define DirLightColor(a)		DirLight[2*a]
+    #define DirLightPosition(a)		DirLight[2*a+1]
+#endif
 
-void main() {
+#if POINT_LIGHTS>0
+    // Point lights uniform array. Each point light uses 3 elements
+    uniform vec3 PointLight[3*POINT_LIGHTS];
+    // Macros to access elements inside the PointLight uniform array
+    #define PointLightColor(a)			PointLight[3*a]
+    #define PointLightPosition(a)		PointLight[3*a+1]
+    #define PointLightLinearDecay(a)	PointLight[3*a+2].x
+    #define PointLightQuadraticDecay(a)	PointLight[3*a+2].y
+#endif
 
-    Color = VertexColor;
-    gl_Position = MVP * vec4(VertexPosition, 1.0);
-}
+#if SPOT_LIGHTS>0
+    // Spot lights uniforms. Each spot light uses 5 elements
+    uniform vec3  SpotLight[5*SPOT_LIGHTS];
+    // Macros to access elements inside the PointLight uniform array
+    #define SpotLightColor(a)			SpotLight[5*a]
+    #define SpotLightPosition(a)		SpotLight[5*a+1]
+    #define SpotLightDirection(a)		SpotLight[5*a+2]
+    #define SpotLightAngularDecay(a)	SpotLight[5*a+3].x
+    #define SpotLightCutoffAngle(a)		SpotLight[5*a+3].y
+    #define SpotLightLinearDecay(a)		SpotLight[5*a+3].z
+    #define SpotLightQuadraticDecay(a)	SpotLight[5*a+4].x
+#endif
 `
 
-const panel_fragment_source = `precision highp float;
-
-// Texture uniforms
-uniform sampler2D	MatTexture;
-uniform vec2		MatTexinfo[3];
-
-// Macros to access elements inside the MatTexinfo array
-#define MatTexOffset		MatTexinfo[0]
-#define MatTexRepeat		MatTexinfo[1]
-#define MatTexFlipY	    	bool(MatTexinfo[2].x) // not used
-#define MatTexVisible	    bool(MatTexinfo[2].y) // not used
+const include_bones_vertex_declaration_source = `#ifdef BONE_INFLUENCERS
+    #if BONE_INFLUENCERS > 0
+	uniform mat4 mBones[TOTAL_BONES];
+    in vec4 matricesIndices;
+    in vec4 matricesWeights;
+//    #if BONE_INFLUENCERS > 4
+//        in vec4 matricesIndicesExtra;
+//        in vec4 matricesWeightsExtra;
+//    #endif
+    #endif
+#endif
+`
 
-// Inputs from vertex shader
-in vec2 FragTexcoord;
+const point_fragment_source = `precision highp float;
 
-// Input uniform
-uniform vec4 Panel[8];
-#define Bounds			Panel[0]		  // panel bounds in texture coordinates
-#define Border			Panel[1]		  // panel border in texture coordinates
-#define Padding			Panel[2]		  // panel padding in texture coordinates
-#define Content			Panel[3]		  // panel content area in texture coordinates
-#define BorderColor		Panel[4]		  // panel border color
-#define PaddingColor	Panel[5]		  // panel padding color
-#define ContentColor	Panel[6]		  // panel content color
-#define TextureValid	bool(Panel[7].x)  // texture valid flag
+#include <material>
+
+// Inputs from vertex shader
+in vec3 Color;
+flat in mat2 Rotation;
 
 // Output
 out vec4 FragColor;
 
-
-/***
-* Checks if current fragment texture coordinate is inside the
-* supplied rectangle in texture coordinates:
-* rect[0] - position x [0,1]
-* rect[1] - position y [0,1]
-* rect[2] - width [0,1]
-* rect[3] - height [0,1]
-*/
-bool checkRect(vec4 rect) {
-
-    if (FragTexcoord.x < rect[0]) {
-        return false;
-    }
-    if (FragTexcoord.x > rect[0] + rect[2]) {
-        return false;
-    }
-    if (FragTexcoord.y < rect[1]) {
-        return false;
-    }
-    if (FragTexcoord.y > rect[1] + rect[3]) {
-        return false;
-    }
-    return true;
-}
-
-
 void main() {
 
-    // Discard fragment outside of received bounds
-    // Bounds[0] - xmin
-    // Bounds[1] - ymin
-    // Bounds[2] - xmax
-    // Bounds[3] - ymax
-    if (FragTexcoord.x <= Bounds[0] || FragTexcoord.x >= Bounds[2]) {
-        discard;
-    }
-    if (FragTexcoord.y <= Bounds[1] || FragTexcoord.y >= Bounds[3]) {
-        discard;
-    }
-
-    // Check if fragment is inside content area
-    if (checkRect(Content)) {
-
-        // If no texture, the color will be the material color.
-        vec4 color = ContentColor;
-
-		if (TextureValid) {
-            // Adjust texture coordinates to fit texture inside the content area
-            vec2 offset = vec2(-Content[0], -Content[1]);
-            vec2 factor = vec2(1.0/Content[2], 1.0/Content[3]);
-            vec2 texcoord = (FragTexcoord + offset) * factor;
-            vec4 texColor = texture(MatTexture, texcoord * MatTexRepeat + MatTexOffset);
-
-            // Mix content color with texture color.
-            // Note that doing a simple linear interpolation (e.g. using mix()) is not correct!
-            // The right formula can be found here: https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
-            // For a more in-depth discussion: http://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha.html#toc4
-            // Another great discussion here: https://ciechanow.ski/alpha-compositing/
-
-            // Alpha premultiply the content color
-            vec4 contentPre = ContentColor;
-            contentPre.rgb *= contentPre.a;
-
-            // Alpha premultiply the content color
-            vec4 texPre = texColor;
-            texPre.rgb *= texPre.a;
+    // Compute final texture color
+    vec4 texMixed = vec4(1);
+    #if MAT_TEXTURES > 0
+        vec2 pointCoord = Rotation * gl_PointCoord - vec2(0.5) + vec2(0.5);
+        bool firstTex = true;
+        if (MatTexVisible(0)) {
+            vec4 texColor = texture(MatTexture[0], pointCoord * MatTexRepeat(0) + MatTexOffset(0));
+            if (firstTex) {
+                texMixed = texColor;
+                firstTex = false;
+            } else {
+                texMixed = Blend(texMixed, texColor);
+            }
+        }
+        #if MAT_TEXTURES > 1
+            if (MatTexVisible(1)) {
+                vec4 texColor = texture(MatTexture[1], pointCoord * MatTexRepeat(1) + MatTexOffset(1));
+                if (firstTex) {
+                    texMixed = texColor;
+                    firstTex = false;
+                } else {
+                    texMixed = Blend(texMixed, texColor);
+                }
+            }
+            #if MAT_TEXTURES > 2
+                if (MatTexVisible(2)) {
+                    vec4 texColor = texture(MatTexture[2], pointCoord * MatTexRepeat(2) + MatTexOffset(2));
+                    if (firstTex) {
+                        texMixed = texColor;
+                        firstTex = false;
+                    } else {
+                        texMixed = Blend(texMixed, texColor);
+                    }
+                }
+            #endif
+        #endif
+    #endif
 
-            // Combine colors to obtain the alpha premultiplied final color
-            color = texPre + contentPre * (1.0 - texPre.a);
+    // Generates final color
+    FragColor = min(vec4(Color, MatOpacity) * texMixed, vec4(1));
+}
+`
 
-            // Un-alpha-premultiply
-            color.rgb /= color.a;
-		}
+const physical_vertex_source = `//
+// Physically Based Shading of a microfacet surface material - Vertex Shader
+// Modified from reference implementation at https://github.com/KhronosGroup/glTF-WebGL-PBR
+//
+#include <attributes>
 
-        FragColor = color;
-        return;
-    }
+// Model uniforms
+uniform mat4 ModelViewMatrix;
+uniform mat3 NormalMatrix;
+uniform mat4 MVP;
 
-    // Checks if fragment is inside paddings area
-    if (checkRect(Padding)) {
-        FragColor = PaddingColor;
-        return;
-    }
+#include <morphtarget_vertex_declaration>
+#include <bones_vertex_declaration>
 
-    // Checks if fragment is inside borders area
-    if (checkRect(Border)) {
-        FragColor = BorderColor;
-        return;
-    }
+// Output variables for Fragment shader
+out vec3 Position;
+out vec3 Normal;
+out vec3 CamDir;
+out vec2 FragTexcoord;
 
-    // Fragment is in margins area (always transparent)
-    FragColor = vec4(1,1,1,0);
-}
-`
+void main() {
 
-const panel_vertex_source = `#include <attributes>
+    // Transform this vertex position to camera coordinates.
+    Position = vec3(ModelViewMatrix * vec4(VertexPosition, 1.0));
 
-// Model uniforms
-uniform mat4 ModelMatrix;
+    // Transform this vertex normal to camera coordinates.
+    Normal = normalize(NormalMatrix * VertexNormal);
 
-// Outputs for fragment shader
-out vec2 FragTexcoord;
+    // Calculate the direction vector from the vertex to the camera
+    // The camera is at 0,0,0
+    CamDir = normalize(-Position.xyz);
 
+    // Output texture coordinates to fragment shader
+    FragTexcoord = VertexTexcoord;
 
-void main() {
+    vec3 vPosition = VertexPosition;
+    mat4 finalWorld = mat4(1.0);
+    #include <morphtarget_vertex>
+    #include <bones_vertex>
 
-    // Always flip texture coordinates
-    vec2 texcoord = VertexTexcoord;
-    texcoord.y = 1.0 - texcoord.y;
-    FragTexcoord = texcoord;
+    gl_Position = MVP * finalWorld * vec4(vPosition, 1.0);
 
-    // Set position
-    vec4 pos = vec4(VertexPosition.xyz, 1);
-    gl_Position = ModelMatrix * pos;
 }
 `
 
@@ -849,139 +797,95 @@ void main() {
 }
 `
 
-const physical_vertex_source = `//
-// Physically Based Shading of a microfacet surface material - Vertex Shader
-// Modified from reference implementation at https://github.com/KhronosGroup/glTF-WebGL-PBR
-//
-#include <attributes>
+const point_vertex_source = `#include <attributes>
+
+// Model uniforms
+uniform mat4 MVP;
+uniform mat4 MV;
+
+// Material uniforms
+#include <material>
+
+// Outputs for fragment shader
+out vec3 Color;
+flat out mat2 Rotation;
+
+void main() {
+
+    // Rotation matrix for fragment shader
+    float rotSin = sin(MatPointRotationZ);
+    float rotCos = cos(MatPointRotationZ);
+    Rotation = mat2(rotCos, rotSin, - rotSin, rotCos);
+
+    // Sets the vertex position
+    vec4 pos = MVP * vec4(VertexPosition, 1.0);
+    gl_Position = pos;
+
+    // Sets the size of the rasterized point decreasing with distance
+    vec4 posMV = MV * vec4(VertexPosition, 1.0);
+    gl_PointSize = MatPointSize / -posMV.z;
+
+    // Outputs color
+    Color = MatEmissiveColor;
+}
+
+`
+
+const standard_vertex_source = `#include <attributes>
 
 // Model uniforms
 uniform mat4 ModelViewMatrix;
 uniform mat3 NormalMatrix;
 uniform mat4 MVP;
 
+#include <material>
 #include <morphtarget_vertex_declaration>
 #include <bones_vertex_declaration>
 
 // Output variables for Fragment shader
-out vec3 Position;
+out vec4 Position;
 out vec3 Normal;
-out vec3 CamDir;
 out vec2 FragTexcoord;
 
 void main() {
 
-    // Transform this vertex position to camera coordinates.
-    Position = vec3(ModelViewMatrix * vec4(VertexPosition, 1.0));
+    // Transform vertex position to camera coordinates
+    Position = ModelViewMatrix * vec4(VertexPosition, 1.0);
 
-    // Transform this vertex normal to camera coordinates.
+    // Transform vertex normal to camera coordinates
     Normal = normalize(NormalMatrix * VertexNormal);
 
-    // Calculate the direction vector from the vertex to the camera
-    // The camera is at 0,0,0
-    CamDir = normalize(-Position.xyz);
-
-    // Output texture coordinates to fragment shader
-    FragTexcoord = VertexTexcoord;
-
+    vec2 texcoord = VertexTexcoord;
+#if MAT_TEXTURES > 0
+    // Flip texture coordinate Y if requested.
+    if (MatTexFlipY(0)) {
+        texcoord.y = 1.0 - texcoord.y;
+    }
+#endif
+    FragTexcoord = texcoord;
     vec3 vPosition = VertexPosition;
     mat4 finalWorld = mat4(1.0);
     #include <morphtarget_vertex>
     #include <bones_vertex>
 
+    // Output projected and transformed vertex position
     gl_Position = MVP * finalWorld * vec4(vPosition, 1.0);
-
 }
 `
 
-const point_fragment_source = `precision highp float;
-
-#include <material>
-
-// Inputs from vertex shader
-in vec3 Color;
-flat in mat2 Rotation;
-
-// Output
-out vec4 FragColor;
-
-void main() {
-
-    // Compute final texture color
-    vec4 texMixed = vec4(1);
-    #if MAT_TEXTURES > 0
-        vec2 pointCoord = Rotation * gl_PointCoord - vec2(0.5) + vec2(0.5);
-        bool firstTex = true;
-        if (MatTexVisible(0)) {
-            vec4 texColor = texture(MatTexture[0], pointCoord * MatTexRepeat(0) + MatTexOffset(0));
-            if (firstTex) {
-                texMixed = texColor;
-                firstTex = false;
-            } else {
-                texMixed = Blend(texMixed, texColor);
-            }
-        }
-        #if MAT_TEXTURES > 1
-            if (MatTexVisible(1)) {
-                vec4 texColor = texture(MatTexture[1], pointCoord * MatTexRepeat(1) + MatTexOffset(1));
-                if (firstTex) {
-                    texMixed = texColor;
-                    firstTex = false;
-                } else {
-                    texMixed = Blend(texMixed, texColor);
-                }
-            }
-            #if MAT_TEXTURES > 2
-                if (MatTexVisible(2)) {
-                    vec4 texColor = texture(MatTexture[2], pointCoord * MatTexRepeat(2) + MatTexOffset(2));
-                    if (firstTex) {
-                        texMixed = texColor;
-                        firstTex = false;
-                    } else {
-                        texMixed = Blend(texMixed, texColor);
-                    }
-                }
-            #endif
-        #endif
-    #endif
-
-    // Generates final color
-    FragColor = min(vec4(Color, MatOpacity) * texMixed, vec4(1));
-}
-`
-
-const point_vertex_source = `#include <attributes>
+const basic_vertex_source = `#include <attributes>
 
 // Model uniforms
 uniform mat4 MVP;
-uniform mat4 MV;
 
-// Material uniforms
-#include <material>
-
-// Outputs for fragment shader
+// Final output color for fragment shader
 out vec3 Color;
-flat out mat2 Rotation;
 
 void main() {
 
-    // Rotation matrix for fragment shader
-    float rotSin = sin(MatPointRotationZ);
-    float rotCos = cos(MatPointRotationZ);
-    Rotation = mat2(rotCos, rotSin, - rotSin, rotCos);
-
-    // Sets the vertex position
-    vec4 pos = MVP * vec4(VertexPosition, 1.0);
-    gl_Position = pos;
-
-    // Sets the size of the rasterized point decreasing with distance
-    vec4 posMV = MV * vec4(VertexPosition, 1.0);
-    gl_PointSize = MatPointSize / -posMV.z;
-
-    // Outputs color
-    Color = MatEmissiveColor;
+    Color = VertexColor;
+    gl_Position = MVP * vec4(VertexPosition, 1.0);
 }
-
 `
 
 const standard_fragment_source = `precision highp float;
@@ -1064,76 +968,192 @@ void main() {
 }
 `
 
-const standard_vertex_source = `#include <attributes>
+const panel_vertex_source = `#include <attributes>
 
 // Model uniforms
-uniform mat4 ModelViewMatrix;
-uniform mat3 NormalMatrix;
-uniform mat4 MVP;
-
-#include <material>
-#include <morphtarget_vertex_declaration>
-#include <bones_vertex_declaration>
+uniform mat4 ModelMatrix;
 
-// Output variables for Fragment shader
-out vec4 Position;
-out vec3 Normal;
+// Outputs for fragment shader
 out vec2 FragTexcoord;
 
+
 void main() {
 
-    // Transform vertex position to camera coordinates
-    Position = ModelViewMatrix * vec4(VertexPosition, 1.0);
+    // Always flip texture coordinates
+    vec2 texcoord = VertexTexcoord;
+    texcoord.y = 1.0 - texcoord.y;
+    FragTexcoord = texcoord;
 
-    // Transform vertex normal to camera coordinates
-    Normal = normalize(NormalMatrix * VertexNormal);
+    // Set position
+    vec4 pos = vec4(VertexPosition.xyz, 1);
+    gl_Position = ModelMatrix * pos;
+}
+`
 
-    vec2 texcoord = VertexTexcoord;
-#if MAT_TEXTURES > 0
-    // Flip texture coordinate Y if requested.
-    if (MatTexFlipY(0)) {
-        texcoord.y = 1.0 - texcoord.y;
+const basic_fragment_source = `precision highp float;
+
+in vec3 Color;
+out vec4 FragColor;
+
+void main() {
+
+    FragColor = vec4(Color, 1.0);
+}
+`
+
+const panel_fragment_source = `precision highp float;
+
+// Texture uniforms
+uniform sampler2D	MatTexture;
+uniform vec2		MatTexinfo[3];
+
+// Macros to access elements inside the MatTexinfo array
+#define MatTexOffset		MatTexinfo[0]
+#define MatTexRepeat		MatTexinfo[1]
+#define MatTexFlipY	    	bool(MatTexinfo[2].x) // not used
+#define MatTexVisible	    bool(MatTexinfo[2].y) // not used
+
+// Inputs from vertex shader
+in vec2 FragTexcoord;
+
+// Input uniform
+uniform vec4 Panel[8];
+#define Bounds			Panel[0]		  // panel bounds in texture coordinates
+#define Border			Panel[1]		  // panel border in texture coordinates
+#define Padding			Panel[2]		  // panel padding in texture coordinates
+#define Content			Panel[3]		  // panel content area in texture coordinates
+#define BorderColor		Panel[4]		  // panel border color
+#define PaddingColor	Panel[5]		  // panel padding color
+#define ContentColor	Panel[6]		  // panel content color
+#define TextureValid	bool(Panel[7].x)  // texture valid flag
+
+// Output
+out vec4 FragColor;
+
+
+/***
+* Checks if current fragment texture coordinate is inside the
+* supplied rectangle in texture coordinates:
+* rect[0] - position x [0,1]
+* rect[1] - position y [0,1]
+* rect[2] - width [0,1]
+* rect[3] - height [0,1]
+*/
+bool checkRect(vec4 rect) {
+
+    if (FragTexcoord.x < rect[0]) {
+        return false;
     }
-#endif
-    FragTexcoord = texcoord;
-    vec3 vPosition = VertexPosition;
-    mat4 finalWorld = mat4(1.0);
-    #include <morphtarget_vertex>
-    #include <bones_vertex>
+    if (FragTexcoord.x > rect[0] + rect[2]) {
+        return false;
+    }
+    if (FragTexcoord.y < rect[1]) {
+        return false;
+    }
+    if (FragTexcoord.y > rect[1] + rect[3]) {
+        return false;
+    }
+    return true;
+}
 
-    // Output projected and transformed vertex position
-    gl_Position = MVP * finalWorld * vec4(vPosition, 1.0);
+
+void main() {
+
+    // Discard fragment outside of received bounds
+    // Bounds[0] - xmin
+    // Bounds[1] - ymin
+    // Bounds[2] - xmax
+    // Bounds[3] - ymax
+    if (FragTexcoord.x <= Bounds[0] || FragTexcoord.x >= Bounds[2]) {
+        discard;
+    }
+    if (FragTexcoord.y <= Bounds[1] || FragTexcoord.y >= Bounds[3]) {
+        discard;
+    }
+
+    // Check if fragment is inside content area
+    if (checkRect(Content)) {
+
+        // If no texture, the color will be the material color.
+        vec4 color = ContentColor;
+
+		if (TextureValid) {
+            // Adjust texture coordinates to fit texture inside the content area
+            vec2 offset = vec2(-Content[0], -Content[1]);
+            vec2 factor = vec2(1.0/Content[2], 1.0/Content[3]);
+            vec2 texcoord = (FragTexcoord + offset) * factor;
+            vec4 texColor = texture(MatTexture, texcoord * MatTexRepeat + MatTexOffset);
+
+            // Mix content color with texture color.
+            // Note that doing a simple linear interpolation (e.g. using mix()) is not correct!
+            // The right formula can be found here: https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
+            // For a more in-depth discussion: http://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha.html#toc4
+            // Another great discussion here: https://ciechanow.ski/alpha-compositing/
+
+            // Alpha premultiply the content color
+            vec4 contentPre = ContentColor;
+            contentPre.rgb *= contentPre.a;
+
+            // Alpha premultiply the content color
+            vec4 texPre = texColor;
+            texPre.rgb *= texPre.a;
+
+            // Combine colors to obtain the alpha premultiplied final color
+            color = texPre + contentPre * (1.0 - texPre.a);
+
+            // Un-alpha-premultiply
+            color.rgb /= color.a;
+		}
+
+        FragColor = color;
+        return;
+    }
+
+    // Checks if fragment is inside paddings area
+    if (checkRect(Padding)) {
+        FragColor = PaddingColor;
+        return;
+    }
+
+    // Checks if fragment is inside borders area
+    if (checkRect(Border)) {
+        FragColor = BorderColor;
+        return;
+    }
+
+    // Fragment is in margins area (always transparent)
+    FragColor = vec4(1,1,1,0);
 }
 `
 
 // Maps include name with its source code
 var includeMap = map[string]string{
 
-	"attributes":                      include_attributes_source,
-	"bones_vertex":                    include_bones_vertex_source,
-	"bones_vertex_declaration":        include_bones_vertex_declaration_source,
-	"lights":                          include_lights_source,
-	"material":                        include_material_source,
-	"morphtarget_vertex":              include_morphtarget_vertex_source,
 	"morphtarget_vertex2":             include_morphtarget_vertex2_source,
-	"morphtarget_vertex_declaration":  include_morphtarget_vertex_declaration_source,
 	"morphtarget_vertex_declaration2": include_morphtarget_vertex_declaration2_source,
+	"morphtarget_vertex":              include_morphtarget_vertex_source,
+	"morphtarget_vertex_declaration":  include_morphtarget_vertex_declaration_source,
+	"bones_vertex":                    include_bones_vertex_source,
 	"phong_model":                     include_phong_model_source,
+	"attributes":                      include_attributes_source,
+	"material":                        include_material_source,
+	"lights":                          include_lights_source,
+	"bones_vertex_declaration":        include_bones_vertex_declaration_source,
 }
 
 // Maps shader name with its source code
 var shaderMap = map[string]string{
 
-	"basic_fragment":    basic_fragment_source,
-	"basic_vertex":      basic_vertex_source,
-	"panel_fragment":    panel_fragment_source,
-	"panel_vertex":      panel_vertex_source,
-	"physical_fragment": physical_fragment_source,
-	"physical_vertex":   physical_vertex_source,
 	"point_fragment":    point_fragment_source,
+	"physical_vertex":   physical_vertex_source,
+	"physical_fragment": physical_fragment_source,
 	"point_vertex":      point_vertex_source,
-	"standard_fragment": standard_fragment_source,
 	"standard_vertex":   standard_vertex_source,
+	"basic_vertex":      basic_vertex_source,
+	"standard_fragment": standard_fragment_source,
+	"panel_vertex":      panel_vertex_source,
+	"basic_fragment":    basic_fragment_source,
+	"panel_fragment":    panel_fragment_source,
 }
 
 // Maps program name with Proginfo struct with shaders names

+ 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

+ 1 - 0
renderer/version-browser.go

@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build wasm
 // +build wasm
 
 package renderer

+ 0 - 0
renderer/version-desktop.go


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä