ソースを参照

Overhaul camera package and refactor raycasting logic into collision package.

Daniel Salvadori 6 年 前
コミット
116597ce47

+ 6 - 1
animation/animation.go

@@ -5,6 +5,11 @@
 // Package animation
 package animation
 
+import "github.com/g3n/engine/util/logger"
+
+// Package logger
+var log = logger.New("ANIMATION", logger.Default)
+
 // Animation is a keyframe animation, containing channels.
 // Each channel animates a specific property of an object.
 // Animations can span multiple objects and properties.
@@ -105,7 +110,7 @@ func (anim *Animation) Update(delta float32) {
 	}
 
 	// Check if input is less than minimum
-	anim.time = anim.time + delta * anim.speed
+	anim.time = anim.time + delta*anim.speed
 	if anim.time < anim.minTime {
 		return
 	}

+ 0 - 12
animation/logger.go

@@ -1,12 +0,0 @@
-// 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 animation
-
-import (
-	"github.com/g3n/engine/util/logger"
-)
-
-// Package logger
-var log = logger.New("ANIMATION", logger.Default)

+ 5 - 0
app/app.go

@@ -5,6 +5,11 @@
 // Package app implements a cross-platform G3N app.
 package app
 
+import "github.com/g3n/engine/util/logger"
+
+// Package logger
+var log = logger.New("APP", logger.Default)
+
 // Application singleton
 var a *Application
 

+ 0 - 12
app/logger.go

@@ -1,12 +0,0 @@
-// 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 app
-
-import (
-	"github.com/g3n/engine/util/logger"
-)
-
-// Package logger
-var log = logger.New("APP", logger.Default)

+ 246 - 58
camera/camera.go

@@ -2,109 +2,297 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package camera contains common camera types used for rendering 3D scenes.
+// Package camera contains virtual cameras and associated controls.
 package camera
 
 import (
 	"github.com/g3n/engine/core"
 	"github.com/g3n/engine/math32"
+	"github.com/g3n/engine/util/logger"
 )
 
-// ICamera is interface for all camera types.
+// Package logger
+var log = logger.New("CAMERA", logger.Default)
+
+// Axis represents a camera axis.
+type Axis int
+
+// The two possible camera axes.
+const (
+	Vertical = Axis(iota)
+	Horizontal
+)
+
+// Projection represents a camera projection.
+type Projection int
+
+// The possible camera projections.
+const (
+	Perspective = Projection(iota)
+	Orthographic
+)
+
+// ICamera is the interface for all cameras.
 type ICamera interface {
-	GetCamera() *Camera
-	SetAspect(float32)
-	ViewMatrix(*math32.Matrix4)
-	ProjMatrix(*math32.Matrix4)
-	Project(*math32.Vector3) (*math32.Vector3, error)
-	Unproject(*math32.Vector3) (*math32.Vector3, error)
-	SetRaycaster(rc *core.Raycaster, x, y float32) error
+	ViewMatrix(m *math32.Matrix4)
+	ProjMatrix(m *math32.Matrix4)
 }
 
-// Camera is the base camera which is normally embedded in other camera types.
+// Camera represents a virtual camera, which specifies how to project a 3D scene onto an image.
 type Camera struct {
-	core.Node                 // Embedded Node
-	target     math32.Vector3 // Camera target in world coordinates
-	up         math32.Vector3 // Camera Up vector
-	viewMatrix math32.Matrix4 // Last calculated view matrix
+	core.Node                  // Embedded Node
+	aspect      float32        // Aspect ratio (width/height)
+	near        float32        // Near plane depth
+	far         float32        // Far plane depth
+	axis        Axis           // The reference axis
+	proj        Projection     // Projection method
+	fov         float32        // Perspective field-of-view along reference axis
+	size        float32        // Orthographic size along reference axis
+	projChanged bool           // Flag indicating that the projection matrix needs to be recalculated
+	projMatrix  math32.Matrix4 // Last calculated projection matrix
+}
+
+// New creates and returns a new perspective camera with the specified aspect ratio and default parameters.
+func New(aspect float32) *Camera {
+	return NewPerspective(aspect, 0.3, 1000, 60, Vertical)
+}
+
+// NewPersp creates and returns a new perspective camera with the specified parameters.
+func NewPerspective(aspect, near, far, fov float32, axis Axis) *Camera {
+
+	c := new(Camera)
+	c.Node.Init(c)
+	c.aspect = aspect
+	c.near = near
+	c.far = far
+	c.axis = axis
+	c.proj = Perspective
+	c.fov = fov
+	c.size = 8
+	c.projChanged = true
+	return c
+}
+
+// NewOrtho creates and returns a new orthographic camera with the specified parameters.
+func NewOrthographic(aspect, near, far, size float32, axis Axis) *Camera {
+
+	c := new(Camera)
+	c.Node.Init(c)
+	c.aspect = aspect
+	c.near = near
+	c.far = far
+	c.axis = axis
+	c.proj = Orthographic
+	c.fov = 60
+	c.size = size
+	c.projChanged = true
+	return c
+}
+
+// Aspect returns the camera aspect ratio.
+func (c *Camera) Aspect() float32 {
+
+	return c.aspect
+}
+
+// SetAspect sets the camera aspect ratio.
+func (c *Camera) SetAspect(aspect float32) {
+
+	if aspect == c.aspect {
+		return
+	}
+	c.aspect = aspect
+	c.projChanged = true
+}
+
+// Near returns the camera near plane Z coordinate.
+func (c *Camera) Near() float32 {
+
+	return c.near
+}
+
+// SetNear sets the camera near plane Z coordinate.
+func (c *Camera) SetNear(near float32) {
+
+	if near == c.near {
+		return
+	}
+	c.near = near
+	c.projChanged = true
+}
+
+// Far returns the camera far plane Z coordinate.
+func (c *Camera) Far() float32 {
+
+	return c.far
+}
+
+// SetFar sets the camera far plane Z coordinate.
+func (c *Camera) SetFar(far float32) {
+
+	if far == c.far {
+		return
+	}
+	c.far = far
+	c.projChanged = true
+}
+
+// Axis returns the reference axis associated with the camera size/fov.
+func (c *Camera) Axis() Axis {
+
+	return c.axis
+}
+
+// SetAxis sets the reference axis associated with the camera size/fov.
+func (c *Camera) SetAxis(axis Axis) {
+
+	if axis == c.axis {
+		return
+	}
+	c.axis = axis
+	c.projChanged = true
+}
+
+// Projection returns the projection method used by the camera.
+func (c *Camera) Projection() Projection {
+
+	return c.proj
 }
 
-// Initialize initializes the base camera.
-// Normally used by other camera types which embed this base camera.
-func (cam *Camera) Initialize() {
+// SetProjection sets the projection method used by the camera.
+func (c *Camera) SetProjection(proj Projection) {
 
-	cam.Node.Init(cam)
-	cam.target.Set(0, 0, 0)
-	cam.up.Set(0, 1, 0)
-	cam.SetDirection(0, 0, -1)
+	if proj == c.proj {
+		return
+	}
+	c.proj = proj
+	c.projChanged = true
 }
 
-// LookAt rotates the camera to look at the specified target position.
-// This method does not support objects with rotated and/or translated parent(s).
-// TODO: maybe move method to Node, or create similar in Node.
-func (cam *Camera) LookAt(target *math32.Vector3) {
+// Fov returns the perspective field-of-view in degrees along the reference axis.
+func (c *Camera) Fov() float32 {
 
-	cam.target = *target
+	return c.fov
+}
 
-	var rotMat math32.Matrix4
-	pos := cam.Position()
-	rotMat.LookAt(&pos, &cam.target, &cam.up)
+// SetFov sets the perspective field-of-view in degrees along the reference axis.
+func (c *Camera) SetFov(fov float32) {
 
-	var q math32.Quaternion
-	q.SetFromRotationMatrix(&rotMat)
-	cam.SetQuaternionQuat(&q)
+	if fov == c.fov {
+		return
+	}
+	c.fov = fov
+	if c.proj == Perspective {
+		c.projChanged = true
+	}
 }
 
-// GetCamera satisfies the ICamera interface.
-func (cam *Camera) GetCamera() *Camera {
+// UpdateFov updates the field-of-view such that the frustum matches
+// the orthographic size at the depth specified by targetDist.
+func (c *Camera) UpdateFov(targetDist float32) {
 
-	return cam
+	c.fov = 2 * math32.Atan(c.size/(2*targetDist)) * 180 / math32.Pi
+	if c.proj == Perspective {
+		c.projChanged = true
+	}
 }
 
-// Target get the current target position.
-func (cam *Camera) Target() math32.Vector3 {
+// Size returns the orthographic view size along the camera's reference axis.
+func (c *Camera) Size() float32 {
 
-	return cam.target
+	return c.size
 }
 
-// Up get the current camera up vector.
-func (cam *Camera) Up() math32.Vector3 {
+// SetSize sets the orthographic view size along the camera's reference axis.
+func (c *Camera) SetSize(size float32) {
 
-	return cam.up
+	if size == c.size {
+		return
+	}
+	c.size = size
+	if c.proj == Orthographic {
+		c.projChanged = true
+	}
 }
 
-// SetUp sets the camera up vector.
-func (cam *Camera) SetUp(up *math32.Vector3) {
+// UpdateSize updates the orthographic size to match the current
+// field-of-view frustum at the depth specified by targetDist.
+func (c *Camera) UpdateSize(targetDist float32) {
 
-	cam.up = *up
-	cam.LookAt(&cam.target) // TODO Maybe remove and let user call LookAt explicitly
+	c.size = 2 * targetDist * math32.Tan(math32.Pi/180*c.fov/2)
+	if c.proj == Orthographic {
+		c.projChanged = true
+	}
 }
 
-// ViewMatrix returns the current view matrix of this camera.
-func (cam *Camera) ViewMatrix(m *math32.Matrix4) {
+// ViewMatrix returns the view matrix of the camera.
+func (c *Camera) ViewMatrix(m *math32.Matrix4) {
 
-	cam.UpdateMatrixWorld()
-	matrixWorld := cam.MatrixWorld()
+	c.UpdateMatrixWorld()
+	matrixWorld := c.MatrixWorld()
 	err := m.GetInverse(&matrixWorld)
 	if err != nil {
 		panic("Camera.ViewMatrix: Couldn't invert matrix")
 	}
 }
 
-// Project satisfies the ICamera interface and can be implemented for specific camera types.
-func (cam *Camera) Project(v *math32.Vector3) (*math32.Vector3, error) {
+// ProjMatrix returns the projection matrix of the camera.
+func (c *Camera) ProjMatrix(m *math32.Matrix4) {
 
-	panic("Not implemented")
+	if c.projChanged {
+		switch c.proj {
+		case Perspective:
+			fov := c.fov
+			if c.axis == Horizontal {
+				fov *= c.aspect
+			}
+			c.projMatrix.MakePerspective(c.fov, c.aspect, c.near, c.far)
+		case Orthographic:
+			s := c.size / 2
+			var h, w float32
+			switch c.axis {
+			case Vertical:
+				h = s
+				w = s * c.aspect
+			case Horizontal:
+				h = s / c.aspect
+				w = s
+			}
+			c.projMatrix.MakeOrthographic(-w, w, h, -h, c.near, c.far)
+		}
+		c.projChanged = false
+	}
+	*m = c.projMatrix
 }
 
-// Unproject satisfies the ICamera interface and can implemented for specific camera types.
-func (cam *Camera) Unproject(v *math32.Vector3) (*math32.Vector3, error) {
+// Project transforms the specified position from world coordinates to this camera projected coordinates.
+func (c *Camera) Project(v *math32.Vector3) *math32.Vector3 {
 
-	panic("Not implemented")
+	// Get camera view matrix
+	var viewMat, projMat math32.Matrix4
+	c.ViewMatrix(&viewMat)
+	c.ProjMatrix(&projMat)
+
+	// Apply projMat * viewMat to the provided vector
+	v.ApplyProjection(projMat.Multiply(&viewMat))
+	return v
 }
 
-// SetRaycaster satisfies the ICamera interface and can implemented for specific camera types.
-func (cam *Camera) SetRaycaster(rc *core.Raycaster, x, y float32) error {
+// Unproject transforms the specified position from camera projected coordinates to world coordinates.
+func (c *Camera) Unproject(v *math32.Vector3) *math32.Vector3 {
+
+	// Get inverted camera view matrix
+	invViewMat := c.MatrixWorld()
+
+	// Get inverted camera projection matrix
+	var invProjMat math32.Matrix4
+	c.ProjMatrix(&invProjMat)
+	err := invProjMat.GetInverse(&invProjMat)
+	if err != nil {
+		panic("Camera.Unproject: Couldn't invert matrix")
+	}
 
-	panic("Not implemented")
+	// Apply invViewMat * invProjMat to the provided vector
+	v.ApplyProjection(invViewMat.Multiply(&invProjMat))
+	return v
 }

+ 0 - 6
camera/control/doc.go

@@ -1,6 +0,0 @@
-// 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 control implements controllers for cameras
-package control

+ 0 - 506
camera/control/orbit_control.go

@@ -1,506 +0,0 @@
-// 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 control
-
-import (
-	"github.com/g3n/engine/camera"
-	"github.com/g3n/engine/core"
-	"github.com/g3n/engine/gui"
-	"github.com/g3n/engine/math32"
-	"github.com/g3n/engine/util/logger"
-	"github.com/g3n/engine/window"
-	"math"
-)
-
-// OrbitControl is a camera controller that allows orbiting a center point while looking at it.
-type OrbitControl struct {
-	core.Dispatcher         // Embedded event dispatcher
-	Enabled         bool    // Control enabled state
-	EnableRotate    bool    // Rotate enabled state
-	EnableZoom      bool    // Zoom enabled state
-	EnablePan       bool    // Pan enabled state
-	EnableKeys      bool    // Enable keys state
-	ZoomSpeed       float32 // Zoom speed factor. Default is 1.0
-	RotateSpeed     float32 // Rotate speed factor. Default is 1.0
-	MinDistance     float32 // Minimum distance from target. Default is 0.01
-	MaxDistance     float32 // Maximum distance from target. Default is infinity
-	MinPolarAngle   float32 // Minimum polar angle for rotation
-	MaxPolarAngle   float32
-	MinAzimuthAngle float32
-	MaxAzimuthAngle float32
-	KeyRotateSpeed  float32
-	KeyPanSpeed     float32
-	// Internal
-	icam        camera.ICamera
-	cam         *camera.Camera
-	camPersp    *camera.Perspective
-	camOrtho    *camera.Orthographic
-	win         window.IWindow
-	position0   math32.Vector3 // Initial camera position
-	target0     math32.Vector3 // Initial camera target position
-	state       int            // current active state
-	phiDelta    float32        // rotation delta in the XZ plane
-	thetaDelta  float32        // rotation delta in the YX plane
-	rotateStart math32.Vector2
-	rotateEnd   math32.Vector2
-	rotateDelta math32.Vector2
-	panStart    math32.Vector2 // initial pan screen coordinates
-	panEnd      math32.Vector2 // final pan screen coordinates
-	panDelta    math32.Vector2
-	panOffset   math32.Vector2
-	zoomStart   float32
-	zoomEnd     float32
-	zoomDelta   float32
-	subsEvents  int // Address of this field is used as events subscription id
-	subsPos     int // Address of this field is used as cursor pos events subscription id
-}
-
-const (
-	stateNone = iota
-	stateRotate
-	stateZoom
-	statePan
-)
-
-// Package logger
-var log = logger.New("ORBIT", logger.Default)
-
-// NewOrbitControl creates and returns a pointer to a new orbit control for the specified camera.
-func NewOrbitControl(icam camera.ICamera) *OrbitControl {
-
-	oc := new(OrbitControl)
-	oc.Dispatcher.Initialize()
-	oc.icam = icam
-
-	oc.cam = icam.GetCamera()
-	if persp, ok := icam.(*camera.Perspective); ok {
-		oc.camPersp = persp
-	} else if ortho, ok := icam.(*camera.Orthographic); ok {
-		oc.camOrtho = ortho
-	} else {
-		panic("Invalid camera type")
-	}
-
-	// Set defaults
-	oc.Enabled = true
-	oc.EnableRotate = true
-	oc.EnableZoom = true
-	oc.EnablePan = true
-	oc.EnableKeys = true
-	oc.ZoomSpeed = 1.0
-	oc.RotateSpeed = 1.0
-	oc.MinDistance = 0.01
-	oc.MaxDistance = float32(math.Inf(1))
-	oc.MinPolarAngle = 0
-	oc.MaxPolarAngle = math32.Pi
-	oc.MinAzimuthAngle = float32(math.Inf(-1))
-	oc.MaxAzimuthAngle = float32(math.Inf(1))
-	oc.KeyPanSpeed = 5.0
-	oc.KeyRotateSpeed = 0.02
-
-	// Saves initial camera parameters
-	oc.position0 = oc.cam.Position()
-	oc.target0 = oc.cam.Target()
-
-	// Subscribe to events
-	gui.Manager().SubscribeID(window.OnMouseUp, &oc.subsEvents, oc.onMouse)
-	gui.Manager().SubscribeID(window.OnMouseDown, &oc.subsEvents, oc.onMouse)
-	gui.Manager().SubscribeID(window.OnScroll, &oc.subsEvents, oc.onScroll)
-	gui.Manager().SubscribeID(window.OnKeyDown, &oc.subsEvents, oc.onKey)
-	oc.SubscribeID(window.OnCursor, &oc.subsPos, oc.onCursorPos)
-	return oc
-}
-
-// Dispose unsubscribes from all events
-func (oc *OrbitControl) Dispose() {
-
-	// Unsubscribe to event handlers
-	gui.Manager().UnsubscribeID(window.OnMouseUp, &oc.subsEvents)
-	gui.Manager().UnsubscribeID(window.OnMouseDown, &oc.subsEvents)
-	gui.Manager().UnsubscribeID(window.OnScroll, &oc.subsEvents)
-	gui.Manager().UnsubscribeID(window.OnKeyDown, &oc.subsEvents)
-	oc.UnsubscribeID(window.OnCursor, &oc.subsPos)
-}
-
-// Reset to initial camera position
-func (oc *OrbitControl) Reset() {
-
-	oc.state = stateNone
-	oc.cam.SetPositionVec(&oc.position0)
-	oc.cam.LookAt(&oc.target0)
-}
-
-// Pan the camera and target by the specified deltas
-func (oc *OrbitControl) Pan(deltaX, deltaY float32) {
-
-	width, height := window.Get().GetSize()
-	oc.pan(deltaX, deltaY, width, height)
-	oc.updatePan()
-}
-
-// Zoom in or out
-func (oc *OrbitControl) Zoom(delta float32) {
-
-	oc.zoomDelta = delta
-	oc.updateZoom()
-}
-
-// RotateLeft rotates the camera left by specified angle
-func (oc *OrbitControl) RotateLeft(angle float32) {
-
-	oc.thetaDelta -= angle
-	oc.updateRotate()
-}
-
-// RotateUp rotates the camera up by specified angle
-func (oc *OrbitControl) RotateUp(angle float32) {
-
-	oc.phiDelta -= angle
-	oc.updateRotate()
-}
-
-// Updates the camera rotation from thetaDelta and phiDelta
-func (oc *OrbitControl) updateRotate() {
-
-	const EPS = 0.01
-
-	// Get camera parameters
-	position := oc.cam.Position()
-	target := oc.cam.Target()
-	up := oc.cam.Up()
-
-	// Camera UP is the orbit axis
-	var quat math32.Quaternion
-	quat.SetFromUnitVectors(&up, &math32.Vector3{0, 1, 0})
-	quatInverse := quat
-	quatInverse.Inverse()
-
-	// Calculates direction vector from camera position to target
-	vdir := position
-	vdir.Sub(&target)
-	vdir.ApplyQuaternion(&quat)
-
-	// Calculate angles from current camera position
-	radius := vdir.Length()
-	theta := math32.Atan2(vdir.X, vdir.Z)
-	phi := math32.Acos(vdir.Y / radius)
-
-	// Add deltas to the angles
-	theta += oc.thetaDelta
-	phi += oc.phiDelta
-
-	// Restrict phi (elevation) to be between desired limits
-	phi = math32.Max(oc.MinPolarAngle, math32.Min(oc.MaxPolarAngle, phi))
-	phi = math32.Max(EPS, math32.Min(math32.Pi-EPS, phi))
-	// Restrict theta to be between desired limits
-	theta = math32.Max(oc.MinAzimuthAngle, math32.Min(oc.MaxAzimuthAngle, theta))
-
-	// Calculate new cartesian coordinates
-	vdir.X = radius * math32.Sin(phi) * math32.Sin(theta)
-	vdir.Y = radius * math32.Cos(phi)
-	vdir.Z = radius * math32.Sin(phi) * math32.Cos(theta)
-
-	// Rotate offset back to "camera-up-vector-is-up" space
-	vdir.ApplyQuaternion(&quatInverse)
-
-	position = target
-	position.Add(&vdir)
-	oc.cam.SetPositionVec(&position)
-	oc.cam.LookAt(&target)
-
-	// Reset deltas
-	oc.thetaDelta = 0
-	oc.phiDelta = 0
-}
-
-// Updates camera rotation from thetaDelta and phiDelta
-// ALTERNATIVE rotation algorithm
-func (oc *OrbitControl) updateRotate2() {
-
-	const EPS = 0.01
-
-	// Get camera parameters
-	position := oc.cam.Position()
-	target := oc.cam.Target()
-	up := oc.cam.Up()
-
-	// Calculates direction vector from target to camera
-	vdir := position
-	vdir.Sub(&target)
-
-	// Calculates right and up vectors
-	var vright math32.Vector3
-	vright.CrossVectors(&up, &vdir)
-	vright.Normalize()
-	var vup math32.Vector3
-	vup.CrossVectors(&vdir, &vright)
-	vup.Normalize()
-
-	phi := vdir.AngleTo(&math32.Vector3{0, 1, 0})
-	newphi := phi + oc.phiDelta
-	if newphi < EPS || newphi > math32.Pi-EPS {
-		oc.phiDelta = 0
-	} else if newphi < oc.MinPolarAngle || newphi > oc.MaxPolarAngle {
-		oc.phiDelta = 0
-	}
-
-	// Rotates position around the two vectors
-	vdir.ApplyAxisAngle(&vup, oc.thetaDelta)
-	vdir.ApplyAxisAngle(&vright, oc.phiDelta)
-
-	// Adds target back get final position
-	position = target
-	position.Add(&vdir)
-	log.Debug("orbit set position")
-	oc.cam.SetPositionVec(&position)
-	oc.cam.LookAt(&target)
-
-	// Reset deltas
-	oc.thetaDelta = 0
-	oc.phiDelta = 0
-}
-
-// Updates camera pan from panOffset
-func (oc *OrbitControl) updatePan() {
-
-	// Get camera parameters
-	position := oc.cam.Position()
-	target := oc.cam.Target()
-	up := oc.cam.Up()
-
-	// Calculates direction vector from camera position to target
-	vdir := target
-	vdir.Sub(&position)
-	vdir.Normalize()
-
-	// Calculates vector perpendicular to direction and up (side vector)
-	var vpanx math32.Vector3
-	vpanx.CrossVectors(&up, &vdir)
-	vpanx.Normalize()
-
-	// Calculates vector perpendicular to direction and vpanx
-	var vpany math32.Vector3
-	vpany.CrossVectors(&vdir, &vpanx)
-	vpany.Normalize()
-
-	// Adds pan offsets
-	vpanx.MultiplyScalar(oc.panOffset.X)
-	vpany.MultiplyScalar(oc.panOffset.Y)
-	var vpan math32.Vector3
-	vpan.AddVectors(&vpanx, &vpany)
-
-	// Adds offsets to camera position and target
-	position.Add(&vpan)
-	target.Add(&vpan)
-
-	// Sets new camera parameters
-	oc.cam.SetPositionVec(&position)
-	oc.cam.LookAt(&target)
-
-	// Reset deltas
-	oc.panOffset.Set(0, 0)
-}
-
-// Updates camera zoom from zoomDelta
-func (oc *OrbitControl) updateZoom() {
-
-	if oc.camOrtho != nil {
-		zoom := oc.camOrtho.Zoom() - 0.01*oc.zoomDelta
-		oc.camOrtho.SetZoom(zoom)
-		// Reset delta
-		oc.zoomDelta = 0
-		return
-	}
-
-	// Get camera and target positions
-	position := oc.cam.Position()
-	target := oc.cam.Target()
-
-	// Calculates direction vector from target to camera position
-	vdir := position
-	vdir.Sub(&target)
-
-	// Calculates new distance from target and applies limits
-	dist := vdir.Length() * (1.0 + oc.zoomDelta*oc.ZoomSpeed/10.0)
-	dist = math32.Max(oc.MinDistance, math32.Min(oc.MaxDistance, dist))
-	vdir.SetLength(dist)
-
-	// Adds new distance to target to get new camera position
-	target.Add(&vdir)
-	oc.cam.SetPositionVec(&target)
-
-	// Reset delta
-	oc.zoomDelta = 0
-}
-
-// Called when mouse button event is received
-func (oc *OrbitControl) onMouse(evname string, ev interface{}) {
-
-	// If control not enabled ignore event
-	if !oc.Enabled {
-		return
-	}
-
-	mev := ev.(*window.MouseEvent)
-	// Mouse button pressed
-	switch evname {
-	case window.OnMouseDown:
-		gui.Manager().SetCursorFocus(oc)
-		// Left button pressed sets Rotate state
-		if mev.Button == window.MouseButtonLeft {
-			if !oc.EnableRotate {
-				return
-			}
-			oc.state = stateRotate
-			oc.rotateStart.Set(float32(mev.Xpos), float32(mev.Ypos))
-		} else
-		// Middle button pressed sets Zoom state
-		if mev.Button == window.MouseButtonMiddle {
-			if !oc.EnableZoom {
-				return
-			}
-			oc.state = stateZoom
-			oc.zoomStart = float32(mev.Ypos)
-		} else
-		// Right button pressed sets Pan state
-		if mev.Button == window.MouseButtonRight {
-			if !oc.EnablePan {
-				return
-			}
-			oc.state = statePan
-			oc.panStart.Set(float32(mev.Xpos), float32(mev.Ypos))
-		}
-		return
-	case window.OnMouseUp:
-		gui.Manager().SetCursorFocus(nil)
-		oc.state = stateNone
-	}
-}
-
-// Called when cursor position event is received
-func (oc *OrbitControl) onCursorPos(evname string, ev interface{}) {
-
-	// If control not enabled ignore event
-	if !oc.Enabled {
-		return
-	}
-
-	mev := ev.(*window.CursorEvent)
-	// Rotation
-	if oc.state == stateRotate {
-		oc.rotateEnd.Set(float32(mev.Xpos), float32(mev.Ypos))
-		oc.rotateDelta.SubVectors(&oc.rotateEnd, &oc.rotateStart)
-		oc.rotateStart = oc.rotateEnd
-		// rotating across whole screen goes 360 degrees around
-		width, height := window.Get().GetSize()
-		oc.RotateLeft(2 * math32.Pi * oc.rotateDelta.X / float32(width) * oc.RotateSpeed)
-		// rotating up and down along whole screen attempts to go 360, but limited to 180
-		oc.RotateUp(2 * math32.Pi * oc.rotateDelta.Y / float32(height) * oc.RotateSpeed)
-		return
-	}
-
-	// Panning
-	if oc.state == statePan {
-		oc.panEnd.Set(float32(mev.Xpos), float32(mev.Ypos))
-		oc.panDelta.SubVectors(&oc.panEnd, &oc.panStart)
-		oc.panStart = oc.panEnd
-		oc.Pan(oc.panDelta.X, oc.panDelta.Y)
-		return
-	}
-
-	// Zooming
-	if oc.state == stateZoom {
-		oc.zoomEnd = float32(mev.Ypos)
-		oc.zoomDelta = oc.zoomEnd - oc.zoomStart
-		oc.zoomStart = oc.zoomEnd
-		oc.Zoom(oc.zoomDelta)
-	}
-}
-
-// Called when mouse button scroll event is received
-func (oc *OrbitControl) onScroll(evname string, ev interface{}) {
-
-	if !oc.Enabled || !oc.EnableZoom || oc.state != stateNone {
-		return
-	}
-	sev := ev.(*window.ScrollEvent)
-	oc.Zoom(float32(-sev.Yoffset))
-}
-
-// Called when key is pressed, released or repeats.
-func (oc *OrbitControl) onKey(evname string, ev interface{}) {
-
-	if !oc.Enabled || !oc.EnableKeys {
-		return
-	}
-
-	kev := ev.(*window.KeyEvent)
-
-	if oc.EnablePan && kev.Mods == 0 {
-		switch kev.Key {
-		case window.KeyUp:
-			oc.Pan(0, oc.KeyPanSpeed)
-		case window.KeyDown:
-			oc.Pan(0, -oc.KeyPanSpeed)
-		case window.KeyLeft:
-			oc.Pan(oc.KeyPanSpeed, 0)
-		case window.KeyRight:
-			oc.Pan(-oc.KeyPanSpeed, 0)
-		}
-	}
-
-	if oc.EnableRotate && kev.Mods == window.ModShift {
-		switch kev.Key {
-		case window.KeyUp:
-			oc.RotateUp(oc.KeyRotateSpeed)
-		case window.KeyDown:
-			oc.RotateUp(-oc.KeyRotateSpeed)
-		case window.KeyLeft:
-			oc.RotateLeft(-oc.KeyRotateSpeed)
-		case window.KeyRight:
-			oc.RotateLeft(oc.KeyRotateSpeed)
-		}
-	}
-
-	if oc.EnableZoom && kev.Mods == window.ModControl {
-		switch kev.Key {
-		case window.KeyUp:
-			oc.Zoom(-1.0)
-		case window.KeyDown:
-			oc.Zoom(1.0)
-		}
-	}
-}
-
-func (oc *OrbitControl) pan(deltaX, deltaY float32, swidth, sheight int) {
-
-	// Perspective camera
-	if oc.camPersp != nil {
-		position := oc.cam.Position()
-		target := oc.cam.Target()
-		offset := position.Clone().Sub(&target)
-		targetDistance := offset.Length()
-		// Half the FOV is center to top of screen
-		targetDistance += math32.Tan((oc.camPersp.Fov() / 2.0) * math32.Pi / 180.0)
-		// we actually don't use screenWidth, since perspective camera is fixed to screen height
-		oc.panLeft(2 * deltaX * targetDistance / float32(sheight))
-		oc.panUp(2 * deltaY * targetDistance / float32(sheight))
-		return
-	}
-	// Orthographic camera
-	left, right, top, bottom, _, _ := oc.camOrtho.Planes()
-	oc.panLeft(deltaX * (right - left) / float32(swidth))
-	oc.panUp(deltaY * (top - bottom) / float32(sheight))
-}
-
-func (oc *OrbitControl) panLeft(distance float32) {
-
-	oc.panOffset.X += distance
-}
-
-func (oc *OrbitControl) panUp(distance float32) {
-
-	oc.panOffset.Y += distance
-}

+ 0 - 12
camera/logger.go

@@ -1,12 +0,0 @@
-// 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 camera
-
-import (
-	"github.com/g3n/engine/util/logger"
-)
-
-// Package logger
-var log = logger.New("CAMERA", logger.Default)

+ 319 - 0
camera/orbit_control.go

@@ -0,0 +1,319 @@
+// 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 camera
+
+import (
+	"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.
+type OrbitEnabled int
+
+// The possible control types.
+const (
+	OrbitNone OrbitEnabled = 0x00
+	OrbitRot  OrbitEnabled = 0x01
+	OrbitZoom OrbitEnabled = 0x02
+	OrbitPan  OrbitEnabled = 0x04
+	OrbitKeys OrbitEnabled = 0x08
+	OrbitAll  OrbitEnabled = 0xFF
+)
+
+// orbitState bitmask
+type orbitState int
+
+const (
+	stateNone = orbitState(iota)
+	stateRotate
+	stateZoom
+	statePan
+)
+
+// OrbitControl is a camera controller that allows orbiting a target point while looking at it.
+// It allows the user to rotate, zoom, and pan a 3D scene using the mouse or keyboard.
+type OrbitControl struct {
+	core.Dispatcher                // Embedded event dispatcher
+	cam             *Camera        // Controlled camera
+	target          math32.Vector3 // Camera target, around which the camera orbits
+	up              math32.Vector3 // The orbit axis (Y+)
+	enabled         OrbitEnabled   // Which controls are enabled
+	state           orbitState     // Current control state
+
+	// Public properties
+	MinDistance     float32 // Minimum distance from target (default is 1)
+	MaxDistance     float32 // Maximum distance from target (default is infinity)
+	MinPolarAngle   float32 // Minimum polar angle in radians (default is 0)
+	MaxPolarAngle   float32 // Maximum polar angle in radians (default is Pi)
+	MinAzimuthAngle float32 // Minimum azimuthal angle in radians (default is negative infinity)
+	MaxAzimuthAngle float32 // Maximum azimuthal angle in radians (default is infinity)
+	RotSpeed        float32 // Rotation speed factor (default is 1)
+	ZoomSpeed       float32 // Zoom speed factor (default is 0.1)
+	KeyRotSpeed     float32 // Rotation delta in radians used on each rotation key event (default is the equivalent of 15 degrees)
+	KeyZoomSpeed    float32 // Zoom delta used on each zoom key event (default is 2)
+	KeyPanSpeed     float32 // Pan delta used on each pan key event (default is 35)
+
+	// Internal
+	rotStart  math32.Vector2
+	panStart  math32.Vector2
+	zoomStart float32
+}
+
+// NewOrbitControl creates and returns a pointer to a new orbit control for the specified camera.
+func NewOrbitControl(cam *Camera) *OrbitControl {
+
+	oc := new(OrbitControl)
+	oc.Dispatcher.Initialize()
+	oc.cam = cam
+	oc.target = *math32.NewVec3()
+	oc.up = *math32.NewVector3(0, 1, 0)
+	oc.enabled = OrbitAll
+
+	oc.MinDistance = 1.0
+	oc.MaxDistance = float32(math.Inf(1))
+	oc.MinPolarAngle = 0
+	oc.MaxPolarAngle = math32.Pi // 180 degrees as radians
+	oc.MinAzimuthAngle = float32(math.Inf(-1))
+	oc.MaxAzimuthAngle = float32(math.Inf(1))
+	oc.RotSpeed = 1.0
+	oc.ZoomSpeed = 0.1
+	oc.KeyRotSpeed = 15 * math32.Pi / 180 // 15 degrees as radians
+	oc.KeyZoomSpeed = 2.0
+	oc.KeyPanSpeed = 35.0
+
+	// Subscribe to events
+	gui.Manager().SubscribeID(window.OnMouseUp, &oc, oc.onMouse)
+	gui.Manager().SubscribeID(window.OnMouseDown, &oc, oc.onMouse)
+	gui.Manager().SubscribeID(window.OnScroll, &oc, oc.onScroll)
+	gui.Manager().SubscribeID(window.OnKeyDown, &oc, oc.onKey)
+	gui.Manager().SubscribeID(window.OnKeyRepeat, &oc, oc.onKey)
+	oc.SubscribeID(window.OnCursor, &oc, oc.onCursor)
+
+	return oc
+}
+
+// Dispose unsubscribes from all events.
+func (oc *OrbitControl) Dispose() {
+
+	gui.Manager().UnsubscribeID(window.OnMouseUp, &oc)
+	gui.Manager().UnsubscribeID(window.OnMouseDown, &oc)
+	gui.Manager().UnsubscribeID(window.OnScroll, &oc)
+	gui.Manager().UnsubscribeID(window.OnKeyDown, &oc)
+	gui.Manager().UnsubscribeID(window.OnKeyRepeat, &oc)
+	oc.UnsubscribeID(window.OnCursor, &oc)
+}
+
+// Reset resets the orbit control.
+func (oc *OrbitControl) Reset() {
+
+	oc.target = *math32.NewVec3()
+}
+
+// Enabled returns the current OrbitEnabled bitmask.
+func (oc *OrbitControl) Enabled() OrbitEnabled {
+
+	return oc.enabled
+}
+
+// SetEnabled sets the current OrbitEnabled bitmask.
+func (oc *OrbitControl) SetEnabled(bitmask OrbitEnabled) {
+
+	oc.enabled = bitmask
+}
+
+// Rotate rotates the camera around the target by the specified angles.
+func (oc *OrbitControl) Rotate(thetaDelta, phiDelta float32) {
+
+	const EPS = 0.0001
+
+	// Compute direction vector from target to camera
+	tcam := oc.cam.Position()
+	tcam.Sub(&oc.target)
+
+	// Calculate angles based on current camera position plus deltas
+	radius := tcam.Length()
+	theta := math32.Atan2(tcam.X, tcam.Z) + thetaDelta
+	phi := math32.Acos(tcam.Y/radius) + phiDelta
+
+	// Restrict phi and theta to be between desired limits
+	phi = math32.Clamp(phi, oc.MinPolarAngle, oc.MaxPolarAngle)
+	phi = math32.Clamp(phi, EPS, math32.Pi-EPS)
+	theta = math32.Clamp(theta, oc.MinAzimuthAngle, oc.MaxAzimuthAngle)
+
+	// Calculate new cartesian coordinates
+	tcam.X = radius * math32.Sin(phi) * math32.Sin(theta)
+	tcam.Y = radius * math32.Cos(phi)
+	tcam.Z = radius * math32.Sin(phi) * math32.Cos(theta)
+
+	// Update camera position and orientation
+	oc.cam.SetPositionVec(oc.target.Clone().Add(&tcam))
+	oc.cam.LookAt(&oc.target, &oc.up)
+}
+
+// Zoom moves the camera closer or farther from the target the specified amount
+// and also updates the camera's orthographic size to match.
+func (oc *OrbitControl) Zoom(delta float32) {
+
+	// Compute direction vector from target to camera
+	tcam := oc.cam.Position()
+	tcam.Sub(&oc.target)
+
+	// Calculate new distance from target and apply limits
+	dist := tcam.Length() * (1 + delta/10)
+	dist = math32.Max(oc.MinDistance, math32.Min(oc.MaxDistance, dist))
+	tcam.SetLength(dist)
+
+	// Update orthographic size and camera position with new distance
+	oc.cam.UpdateSize(tcam.Length())
+	oc.cam.SetPositionVec(oc.target.Clone().Add(&tcam))
+}
+
+// Pan pans the camera and target the specified amount on the plane perpendicular to the viewing direction.
+func (oc *OrbitControl) Pan(deltaX, deltaY float32) {
+
+	// Compute direction vector from camera to target
+	position := oc.cam.Position()
+	vdir := oc.target.Clone().Sub(&position)
+
+	// Conversion constant between an on-screen cursor delta and its projection on the target plane
+	c := 2 * vdir.Length() * math32.Tan((oc.cam.Fov()/2.0)*math32.Pi/180.0) / oc.winSize()
+
+	// Calculate pan components, scale by the converted offsets and combine them
+	var pan, panX, panY math32.Vector3
+	panX.CrossVectors(&oc.up, vdir).Normalize()
+	panY.CrossVectors(vdir, &panX).Normalize()
+	panY.MultiplyScalar(c * deltaY)
+	panX.MultiplyScalar(c * deltaX)
+	pan.AddVectors(&panX, &panY)
+
+	// Add pan offset to camera and target
+	oc.cam.SetPositionVec(position.Add(&pan))
+	oc.target.Add(&pan)
+}
+
+// onMouse is called when an OnMouseDown/OnMouseUp event is received.
+func (oc *OrbitControl) onMouse(evname string, ev interface{}) {
+
+	// If nothing enabled ignore event
+	if oc.enabled == OrbitNone {
+		return
+	}
+
+	switch evname {
+	case window.OnMouseDown:
+		gui.Manager().SetCursorFocus(oc)
+		mev := ev.(*window.MouseEvent)
+		switch mev.Button {
+		case window.MouseButtonLeft: // Rotate
+			if oc.enabled&OrbitRot != 0 {
+				oc.state = stateRotate
+				oc.rotStart.Set(mev.Xpos, mev.Ypos)
+			}
+		case window.MouseButtonMiddle: // Zoom
+			if oc.enabled&OrbitZoom != 0 {
+				oc.state = stateZoom
+				oc.zoomStart = mev.Ypos
+			}
+		case window.MouseButtonRight: // Pan
+			if oc.enabled&OrbitPan != 0 {
+				oc.state = statePan
+				oc.panStart.Set(mev.Xpos, mev.Ypos)
+			}
+		}
+	case window.OnMouseUp:
+		gui.Manager().SetCursorFocus(nil)
+		oc.state = stateNone
+	}
+}
+
+// onCursor is called when an OnCursor event is received.
+func (oc *OrbitControl) onCursor(evname string, ev interface{}) {
+
+	// If nothing enabled ignore event
+	if oc.enabled == OrbitNone || oc.state == stateNone {
+		return
+	}
+
+	mev := ev.(*window.CursorEvent)
+	switch oc.state {
+	case stateRotate:
+		c := -2 * math32.Pi * oc.RotSpeed / oc.winSize()
+		oc.Rotate(c*(mev.Xpos-oc.rotStart.X),
+			c*(mev.Ypos-oc.rotStart.Y))
+		oc.rotStart.Set(mev.Xpos, mev.Ypos)
+	case stateZoom:
+		oc.Zoom(oc.ZoomSpeed * (mev.Ypos - oc.zoomStart))
+		oc.zoomStart = mev.Ypos
+	case statePan:
+		oc.Pan(mev.Xpos-oc.panStart.X,
+			mev.Ypos-oc.panStart.Y)
+		oc.panStart.Set(mev.Xpos, mev.Ypos)
+	}
+}
+
+// onScroll is called when an OnScroll event is received.
+func (oc *OrbitControl) onScroll(evname string, ev interface{}) {
+
+	if oc.enabled&OrbitZoom != 0 {
+		sev := ev.(*window.ScrollEvent)
+		oc.Zoom(-sev.Yoffset)
+	}
+}
+
+// onKey is called when an OnKeyDown/OnKeyRepeat event is received.
+func (oc *OrbitControl) onKey(evname string, ev interface{}) {
+
+	// If keyboard control is disabled ignore event
+	if oc.enabled&OrbitKeys == 0 {
+		return
+	}
+
+	kev := ev.(*window.KeyEvent)
+	if kev.Mods == 0 && oc.enabled&OrbitRot != 0 {
+		switch kev.Key {
+		case window.KeyUp:
+			oc.Rotate(0, -oc.KeyRotSpeed)
+		case window.KeyDown:
+			oc.Rotate(0, oc.KeyRotSpeed)
+		case window.KeyLeft:
+			oc.Rotate(-oc.KeyRotSpeed, 0)
+		case window.KeyRight:
+			oc.Rotate(oc.KeyRotSpeed, 0)
+		}
+	}
+	if kev.Mods == window.ModControl && oc.enabled&OrbitZoom != 0 {
+		switch kev.Key {
+		case window.KeyUp:
+			oc.Zoom(-oc.KeyZoomSpeed)
+		case window.KeyDown:
+			oc.Zoom(oc.KeyZoomSpeed)
+		}
+	}
+	if kev.Mods == window.ModShift && oc.enabled&OrbitPan != 0 {
+		switch kev.Key {
+		case window.KeyUp:
+			oc.Pan(0, oc.KeyPanSpeed)
+		case window.KeyDown:
+			oc.Pan(0, -oc.KeyPanSpeed)
+		case window.KeyLeft:
+			oc.Pan(oc.KeyPanSpeed, 0)
+		case window.KeyRight:
+			oc.Pan(-oc.KeyPanSpeed, 0)
+		}
+	}
+}
+
+// winSize returns the window height or width based on the camera reference axis.
+func (oc *OrbitControl) winSize() float32 {
+
+	width, size := window.Get().GetSize()
+	if oc.cam.Axis() == Horizontal {
+		size = width
+	}
+	return float32(size)
+}

+ 0 - 79
camera/orthographic.go

@@ -1,79 +0,0 @@
-// 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 camera
-
-import (
-	"github.com/g3n/engine/math32"
-)
-
-// Orthographic is an orthographic camera.
-type Orthographic struct {
-	Camera              // Embedded camera
-	left        float32 // left plane x coordinate
-	right       float32 // right plane x coordinate
-	top         float32 // top plane y coordinate
-	bottom      float32 // bottom plane y coordinate
-	near        float32 // near plane z coordinate
-	far         float32 // far plane z coordinate
-	zoom        float32
-	projChanged bool           // camera projection parameters changed (needs to recalculate projection matrix)
-	projMatrix  math32.Matrix4 // last calculated projection matrix
-}
-
-// NewOrthographic creates and returns a pointer to a new orthographic camera with the specified parameters.
-func NewOrthographic(left, right, top, bottom, near, far float32) *Orthographic {
-
-	cam := new(Orthographic)
-	cam.Camera.Initialize()
-	cam.left = left
-	cam.right = right
-	cam.top = top
-	cam.bottom = bottom
-	cam.near = near
-	cam.far = far
-	cam.zoom = 1.0
-	cam.projChanged = true
-	return cam
-}
-
-// SetAspect sets the camera aspect ratio (width/height).
-func (cam *Orthographic) SetAspect(aspect float32) {
-
-	height := cam.top - cam.bottom
-	halfwidth := height * aspect * 0.5
-	center := (cam.left + cam.right) * 0.5
-	cam.left = center - halfwidth
-	cam.right = center + halfwidth
-	cam.projChanged = true
-}
-
-// SetZoom sets the zoom factor of the camera.
-func (cam *Orthographic) SetZoom(zoom float32) {
-
-	cam.zoom = math32.Abs(zoom)
-	cam.projChanged = true
-}
-
-// Zoom returns the zoom factor of the camera.
-func (cam *Orthographic) Zoom() float32 {
-
-	return cam.zoom
-}
-
-// Planes returns the coordinates of the camera planes.
-func (cam *Orthographic) Planes() (left, right, top, bottom, near, far float32) {
-
-	return cam.left, cam.right, cam.top, cam.bottom, cam.near, cam.far
-}
-
-// ProjMatrix satisfies the ICamera interface.
-func (cam *Orthographic) ProjMatrix(m *math32.Matrix4) {
-
-	if cam.projChanged {
-		cam.projMatrix.MakeOrthographic(cam.left/cam.zoom, cam.right/cam.zoom, cam.top/cam.zoom, cam.bottom/cam.zoom, cam.near, cam.far)
-		cam.projChanged = false
-	}
-	*m = cam.projMatrix
-}

+ 0 - 147
camera/perspective.go

@@ -1,147 +0,0 @@
-// 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 camera
-
-import (
-	"github.com/g3n/engine/core"
-	"github.com/g3n/engine/math32"
-)
-
-// Perspective is a perspective camera.
-type Perspective struct {
-	Camera                     // Embedded camera
-	fov         float32        // field of view in degrees
-	aspect      float32        // aspect ratio (width/height)
-	near        float32        // near plane z coordinate
-	far         float32        // far plane z coordinate
-	projChanged bool           // camera projection parameters changed (needs to recalculate projection matrix)
-	projMatrix  math32.Matrix4 // last calculated projection matrix
-}
-
-// NewPerspective creates and returns a pointer to a new perspective camera with the
-// specified parameters.
-func NewPerspective(fov, aspect, near, far float32) *Perspective {
-
-	cam := new(Perspective)
-	cam.Camera.Initialize()
-	cam.fov = fov
-	cam.aspect = aspect
-	cam.near = near
-	cam.far = far
-	cam.projChanged = true
-	return cam
-}
-
-// SetFov sets the camera field of view in degrees.
-func (cam *Perspective) SetFov(fov float32) {
-
-	cam.fov = fov
-	cam.projChanged = true
-}
-
-// SetAspect sets the camera aspect ratio (width/height).
-func (cam *Perspective) SetAspect(aspect float32) {
-
-	cam.aspect = aspect
-	cam.projChanged = true
-}
-
-// Fov returns the current camera FOV (field of view) in degrees.
-func (cam *Perspective) Fov() float32 {
-
-	return cam.fov
-}
-
-// Aspect returns the current camera aspect ratio.
-func (cam Perspective) Aspect() float32 {
-
-	return cam.aspect
-}
-
-// Near returns the current camera near plane Z coordinate.
-func (cam *Perspective) Near() float32 {
-
-	return cam.near
-}
-
-// Far returns the current camera far plane Z coordinate.
-func (cam *Perspective) Far() float32 {
-
-	return cam.far
-}
-
-// ProjMatrix satisfies the ICamera interface.
-func (cam *Perspective) ProjMatrix(m *math32.Matrix4) {
-
-	cam.updateProjMatrix()
-	*m = cam.projMatrix
-}
-
-// Project transforms the specified position from world coordinates to this camera projected coordinates.
-func (cam *Perspective) Project(v *math32.Vector3) (*math32.Vector3, error) {
-
-	// Get camera view matrix
-	var matrix math32.Matrix4
-	cam.ViewMatrix(&matrix)
-
-	// Update camera projection matrix
-	cam.updateProjMatrix()
-
-	// Multiply viewMatrix by projMatrix and apply the resulting projection matrix to the provided vector
-	matrix.MultiplyMatrices(&cam.projMatrix, &matrix)
-	v.ApplyProjection(&matrix)
-	return v, nil
-}
-
-// Unproject transforms the specified position from camera projected coordinates to world coordinates.
-func (cam *Perspective) Unproject(v *math32.Vector3) (*math32.Vector3, error) {
-
-	// Get inverted camera view matrix
-	invertedViewMatrix := cam.MatrixWorld()
-
-	// Get inverted camera projection matrix
-	cam.updateProjMatrix()
-	var invertedProjMatrix math32.Matrix4
-	err := invertedProjMatrix.GetInverse(&cam.projMatrix)
-	if err != nil {
-		return nil, err
-	}
-
-	// Multiply invertedViewMatrix by invertedProjMatrix
-	// to get transformation from camera projected coordinates to world coordinates
-	// and project vector using this transformation
-	var matrix math32.Matrix4
-	matrix.MultiplyMatrices(&invertedViewMatrix, &invertedProjMatrix)
-	v.ApplyProjection(&matrix)
-	return v, nil
-}
-
-// SetRaycaster sets the specified raycaster with this camera position in world coordinates
-// pointing to the direction defined by the specified coordinates unprojected using this camera.
-func (cam *Perspective) SetRaycaster(rc *core.Raycaster, sx, sy float32) error {
-
-	var origin, direction math32.Vector3
-	matrixWorld := cam.MatrixWorld()
-	origin.SetFromMatrixPosition(&matrixWorld)
-	direction.Set(sx, sy, 0.5)
-	unproj, err := cam.Unproject(&direction)
-	if err != nil {
-		return err
-	}
-	unproj.Sub(&origin).Normalize()
-	rc.Set(&origin, &direction)
-	// Updates the view matrix of the raycaster
-	cam.ViewMatrix(&rc.ViewMatrix)
-	return nil
-}
-
-// updateProjMatrix updates the projection matrix if necessary.
-func (cam *Perspective) updateProjMatrix() {
-
-	if cam.projChanged {
-		cam.projMatrix.MakePerspective(cam.fov, cam.aspect, cam.near, cam.far)
-		cam.projChanged = false
-	}
-}

+ 11 - 4
core/node.go

@@ -24,7 +24,6 @@ type INode interface {
 	IsAncestorOf(INode) bool
 	LowestCommonAncestor(INode) INode
 	UpdateMatrixWorld()
-	Raycast(*Raycaster, *[]Intersect)
 	BoundingBox() math32.Box3
 	Render(gs *gls.GLS)
 	Clone() INode
@@ -112,9 +111,6 @@ func (n *Node) GetNode() *Node {
 	return n
 }
 
-// Raycast satisfies the INode interface.
-func (n *Node) Raycast(rc *Raycaster, intersects *[]Intersect) {}
-
 // BoundingBox satisfies the INode interface.
 // Computes union of own bounding box with those of all descendents.
 func (n *Node) BoundingBox() math32.Box3 {
@@ -674,6 +670,17 @@ func (n *Node) Quaternion() math32.Quaternion {
 	return n.quaternion
 }
 
+// LookAt rotates the node to look at the specified target position, using the specified up vector.
+func (n *Node) LookAt(target, up *math32.Vector3) {
+
+	var worldPos math32.Vector3
+	n.WorldPosition(&worldPos)
+	var rotMat math32.Matrix4
+	rotMat.LookAt(&worldPos, target, up)
+	n.quaternion.SetFromRotationMatrix(&rotMat)
+	n.rotNeedsUpdate = true
+}
+
 // SetScale sets the scale.
 func (n *Node) SetScale(x, y, z float32) {
 

+ 0 - 111
core/raycaster.go

@@ -1,111 +0,0 @@
-// 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 core
-
-import (
-	"github.com/g3n/engine/math32"
-	"sort"
-)
-
-// Raycaster represents an empty object that can cast rays and check for ray intersections.
-type Raycaster struct {
-	// The distance from the ray origin to the intersected points
-	// must be greater than the value of this field to be considered.
-	// The defaul value is 0.0
-	Near float32
-	// The distance from the ray origin to the intersected points
-	// must be less than the value of this field to be considered.
-	// The defaul value is +Infinity.
-	Far float32
-	// Minimum distance in world coordinates between the ray and
-	// a line segment when checking intersects with lines.
-	// The default value is 0.1
-	LinePrecision float32
-	// Minimum distance in world coordinates between the ray and
-	// a point when checking intersects with points.
-	// The default value is 0.1
-	PointPrecision float32
-	// This field must be set with the camera view matrix used
-	// when checking for sprite intersections.
-	// It is set automatically when using camera.SetRaycaster
-	ViewMatrix math32.Matrix4
-	// Embedded ray
-	math32.Ray
-}
-
-// Intersect describes the intersection between a ray and an object
-type Intersect struct {
-	// Distance between the origin of the ray and the intersect
-	Distance float32
-	// Point of intersection in world coordinates
-	Point math32.Vector3
-	// Intersected node
-	Object INode
-	// If the geometry has indices, this field is the
-	// index in the Indices buffer of the vertex intersected
-	// or the first vertex of the intersected face.
-	// If the geometry doesn't have indices, this field is the
-	// index in the positions buffer of the vertex intersected
-	// or the first vertex of the insersected face.
-	Index uint32
-}
-
-// NewRaycaster creates and returns a pointer to a new raycaster object
-// with the specified origin and direction.
-func NewRaycaster(origin, direction *math32.Vector3) *Raycaster {
-
-	rc := new(Raycaster)
-	rc.Ray.Set(origin, direction)
-	rc.Near = 0
-	rc.Far = math32.Inf(1)
-	rc.LinePrecision = 0.1
-	rc.PointPrecision = 0.1
-	return rc
-}
-
-// IntersectObject checks intersections between this raycaster and
-// and the specified node. If recursive is true, it also checks
-// the intersection with the node's children.
-// Intersections are returned sorted by distance, closest first.
-func (rc *Raycaster) IntersectObject(inode INode, recursive bool) []Intersect {
-
-	intersects := []Intersect{}
-	rc.intersectObject(inode, &intersects, recursive)
-	sort.Slice(intersects, func(i, j int) bool {
-		return intersects[i].Distance < intersects[j].Distance
-	})
-	return intersects
-}
-
-// IntersectObjects checks intersections between this raycaster and
-// the specified array of scene nodes. If recursive is true, it also checks
-// the intersection with each nodes' children.
-// Intersections are returned sorted by distance, closest first.
-func (rc *Raycaster) IntersectObjects(inodes []INode, recursive bool) []Intersect {
-
-	intersects := []Intersect{}
-	for _, inode := range inodes {
-		rc.intersectObject(inode, &intersects, recursive)
-	}
-	sort.Slice(intersects, func(i, j int) bool {
-		return intersects[i].Distance < intersects[j].Distance
-	})
-	return intersects
-}
-
-func (rc *Raycaster) intersectObject(inode INode, intersects *[]Intersect, recursive bool) {
-
-	node := inode.GetNode()
-	if !node.Visible() {
-		return
-	}
-	inode.Raycast(rc, intersects)
-	if recursive {
-		for _, child := range node.Children() {
-			rc.intersectObject(child, intersects, true)
-		}
-	}
-	return
-}

+ 2 - 0
experimental/collision/collision.go

@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// Package collision implements collision related algorithms and data structures.
+// WARNING: Parts of this package are experimental and incomplete!
 package collision
 
 import "github.com/g3n/engine/geometry"

+ 0 - 7
experimental/collision/doc.go

@@ -1,7 +0,0 @@
-// 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 collision implements collision related algorithms and data structures.
-// WARNING: This package is experimental and incomplete!
-package collision

+ 465 - 0
experimental/collision/raycaster.go

@@ -0,0 +1,465 @@
+// 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 collision
+
+import (
+	"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.
+type Raycaster struct {
+	// The distance from the ray origin to the intersected points
+	// must be greater than the value of this field to be considered.
+	// The defaul value is 0.0
+	Near float32
+	// The distance from the ray origin to the intersected points
+	// must be less than the value of this field to be considered.
+	// The defaul value is +Infinity.
+	Far float32
+	// Minimum distance in world coordinates between the ray and
+	// a line segment when checking intersects with lines.
+	// The default value is 0.1
+	LinePrecision float32
+	// Minimum distance in world coordinates between the ray and
+	// a point when checking intersects with points.
+	// The default value is 0.1
+	PointPrecision float32
+	// This field must be set with the camera view matrix used
+	// when checking for sprite intersections.
+	// It is set automatically when using camera.SetRaycaster
+	ViewMatrix math32.Matrix4
+	// Embedded ray
+	math32.Ray
+}
+
+// Intersect describes the intersection between a ray and an object
+type Intersect struct {
+	// Distance between the origin of the ray and the intersect
+	Distance float32
+	// Point of intersection in world coordinates
+	Point math32.Vector3
+	// Intersected node
+	Object core.INode
+	// If the geometry has indices, this field is the
+	// index in the Indices buffer of the vertex intersected
+	// or the first vertex of the intersected face.
+	// If the geometry doesn't have indices, this field is the
+	// index in the positions buffer of the vertex intersected
+	// or the first vertex of the insersected face.
+	Index uint32
+}
+
+// NewRaycaster creates and returns a pointer to a new raycaster object
+// with the specified origin and direction.
+func NewRaycaster(origin, direction *math32.Vector3) *Raycaster {
+
+	rc := new(Raycaster)
+	rc.Ray.Set(origin, direction)
+	rc.Near = 0
+	rc.Far = math32.Inf(1)
+	rc.LinePrecision = 0.1
+	rc.PointPrecision = 0.1
+	return rc
+}
+
+// IntersectObject checks intersections between this raycaster and
+// and the specified node. If recursive is true, it also checks
+// the intersection with the node's children.
+// Intersections are returned sorted by distance, closest first.
+func (rc *Raycaster) IntersectObject(inode core.INode, recursive bool) []Intersect {
+
+	intersects := []Intersect{}
+	rc.intersectObject(inode, &intersects, recursive)
+	sort.Slice(intersects, func(i, j int) bool {
+		return intersects[i].Distance < intersects[j].Distance
+	})
+	return intersects
+}
+
+// IntersectObjects checks intersections between this raycaster and
+// the specified array of scene nodes. If recursive is true, it also checks
+// the intersection with each nodes' children.
+// Intersections are returned sorted by distance, closest first.
+func (rc *Raycaster) IntersectObjects(inodes []core.INode, recursive bool) []Intersect {
+
+	intersects := []Intersect{}
+	for _, inode := range inodes {
+		rc.intersectObject(inode, &intersects, recursive)
+	}
+	sort.Slice(intersects, func(i, j int) bool {
+		return intersects[i].Distance < intersects[j].Distance
+	})
+	return intersects
+}
+
+func (rc *Raycaster) intersectObject(inode core.INode, intersects *[]Intersect, recursive bool) {
+
+	node := inode.GetNode()
+	if !node.Visible() {
+		return
+	}
+
+	switch in := inode.(type) {
+	case *graphic.Sprite:
+		rc.RaycastSprite(in, intersects)
+	case *graphic.Points:
+		rc.RaycastPoints(in, intersects)
+	case *graphic.Mesh:
+		rc.RaycastMesh(in, intersects)
+	case *graphic.Lines:
+		rc.RaycastLines(in, intersects)
+	case *graphic.LineStrip:
+		rc.RaycastLineStrip(in, intersects)
+	}
+
+	if recursive {
+		for _, child := range node.Children() {
+			rc.intersectObject(child, intersects, true)
+		}
+	}
+	return
+}
+
+// SetRaycaster sets the specified raycaster with this camera position in world coordinates
+// pointing to the direction defined by the specified coordinates unprojected using this camera.
+func (rc *Raycaster) SetFromCamera(cam *camera.Camera, sx, sy float32) error { // TODO maybe use ICamera
+
+	var origin, direction math32.Vector3
+	matrixWorld := cam.MatrixWorld()
+	origin.SetFromMatrixPosition(&matrixWorld)
+	direction.Set(sx, sy, 0.5)
+
+	unproj := cam.Unproject(&direction) // unproj = direction after this point TODO improve clarity
+	unproj.Sub(&origin).Normalize()
+	rc.Set(&origin, &direction)
+	cam.ViewMatrix(&rc.ViewMatrix) // Update the view matrix of the raycaster
+	return nil
+}
+
+// RaycastSprite checks intersections between the raycaster and the specified sprite
+// and if any found appends it to the specified intersects array.
+func (rc *Raycaster) RaycastSprite(s *graphic.Sprite, intersects *[]Intersect) {
+
+	// Copy and convert ray to camera coordinates
+	var ray math32.Ray
+	ray.Copy(&rc.Ray).ApplyMatrix4(&rc.ViewMatrix)
+
+	// Calculates ViewMatrix * MatrixWorld
+	var mv math32.Matrix4
+	matrixWorld := s.MatrixWorld()
+	mv.MultiplyMatrices(&rc.ViewMatrix, &matrixWorld)
+
+	// Decompose transformation matrix in its components
+	var position math32.Vector3
+	var quaternion math32.Quaternion
+	var scale math32.Vector3
+	mv.Decompose(&position, &quaternion, &scale)
+
+	// Remove any rotation in X and Y axis and
+	// compose new transformation matrix
+	rotation := s.Rotation()
+	rotation.X = 0
+	rotation.Y = 0
+	quaternion.SetFromEuler(&rotation)
+	mv.Compose(&position, &quaternion, &scale)
+
+	// Get buffer with vertices and uvs
+	geom := s.GetGeometry()
+	vboPos := geom.VBO(gls.VertexPosition)
+	if vboPos == nil {
+		panic("sprite.Raycast(): VertexPosition VBO not found")
+	}
+	// Get vertex positions, transform to camera coordinates and
+	// checks intersection with ray
+	buffer := vboPos.Buffer()
+	indices := geom.Indices()
+	var v1 math32.Vector3
+	var v2 math32.Vector3
+	var v3 math32.Vector3
+	var point math32.Vector3
+	intersect := false
+	for i := 0; i < indices.Size(); i += 3 {
+		pos := indices[i]
+		buffer.GetVector3(int(pos*5), &v1)
+		v1.ApplyMatrix4(&mv)
+		pos = indices[i+1]
+		buffer.GetVector3(int(pos*5), &v2)
+		v2.ApplyMatrix4(&mv)
+		pos = indices[i+2]
+		buffer.GetVector3(int(pos*5), &v3)
+		v3.ApplyMatrix4(&mv)
+		if ray.IntersectTriangle(&v1, &v2, &v3, false, &point) {
+			intersect = true
+			break
+		}
+	}
+	if !intersect {
+		return
+	}
+	// Get distance from intersection point
+	origin := ray.Origin()
+	distance := origin.DistanceTo(&point)
+
+	// Checks if distance is between the bounds of the raycaster
+	if distance < rc.Near || distance > rc.Far {
+		return
+	}
+
+	// Appends intersection to received parameter.
+	*intersects = append(*intersects, Intersect{
+		Distance: distance,
+		Point:    point,
+		Object:   s,
+	})
+}
+
+// RaycastPoints
+func (rc *Raycaster) RaycastPoints(p *graphic.Points, intersects *[]Intersect) {
+
+	// Checks intersection with the bounding sphere transformed to world coordinates
+	geom := p.GetGeometry()
+	sphere := geom.BoundingSphere()
+	matrixWorld := p.MatrixWorld()
+	sphere.ApplyMatrix4(&matrixWorld)
+	if !rc.IsIntersectionSphere(&sphere) {
+		return
+	}
+
+	// Copy ray and transforms to model coordinates
+	var inverseMatrix math32.Matrix4
+	var ray math32.Ray
+	inverseMatrix.GetInverse(&matrixWorld)
+	ray.Copy(&rc.Ray).ApplyMatrix4(&inverseMatrix)
+
+	// Checks intersection with all points
+	scale := p.Scale()
+	localThreshold := rc.PointPrecision / ((scale.X + scale.Y + scale.Z) / 3)
+	localThresholdSq := localThreshold * localThreshold
+
+	// internal function to check intersection with a point
+	testPoint := func(point *math32.Vector3, index int) {
+
+		// Get distance from ray to point and if greater than threshold,
+		// nothing to do.
+		rayPointDistanceSq := ray.DistanceSqToPoint(point)
+		if rayPointDistanceSq >= localThresholdSq {
+			return
+		}
+		var intersectPoint math32.Vector3
+		ray.ClosestPointToPoint(point, &intersectPoint)
+		intersectPoint.ApplyMatrix4(&matrixWorld)
+		origin := rc.Ray.Origin()
+		distance := origin.DistanceTo(&intersectPoint)
+		if distance < rc.Near || distance > rc.Far {
+			return
+		}
+		// Appends intersection of raycaster with this point
+		*intersects = append(*intersects, Intersect{
+			Distance: distance,
+			Point:    intersectPoint,
+			Index:    uint32(index),
+			Object:   p,
+		})
+	}
+
+	i := 0
+	geom.ReadVertices(func(vertex math32.Vector3) bool {
+		testPoint(&vertex, i)
+		i++
+		return false
+	})
+}
+
+// RaycastMesh
+func (rc *Raycaster) RaycastMesh(m *graphic.Mesh, intersects *[]Intersect) {
+
+	// Transform this mesh geometry bounding sphere from model
+	// to world coordinates and checks intersection with raycaster
+	geom := m.GetGeometry()
+	sphere := geom.BoundingSphere()
+	matrixWorld := m.MatrixWorld()
+	sphere.ApplyMatrix4(&matrixWorld)
+	if !rc.IsIntersectionSphere(&sphere) {
+		return
+	}
+
+	// Copy ray and transform to model coordinates
+	// This ray will will also be used to check intersects with
+	// the geometry, as is much less expensive to transform the
+	// ray to model coordinates than the geometry to world coordinates.
+	var inverseMatrix math32.Matrix4
+	inverseMatrix.GetInverse(&matrixWorld)
+	var ray math32.Ray
+	ray.Copy(&rc.Ray).ApplyMatrix4(&inverseMatrix)
+	bbox := geom.BoundingBox()
+	if !ray.IsIntersectionBox(&bbox) {
+		return
+	}
+
+	// Local function to check the intersection of the ray from the raycaster with
+	// the specified face defined by three poins.
+	checkIntersection := func(mat *material.Material, pA, pB, pC, point *math32.Vector3) *Intersect {
+
+		var intersect bool
+		switch mat.Side() {
+		case material.SideBack:
+			intersect = ray.IntersectTriangle(pC, pB, pA, true, point)
+		case material.SideFront:
+			intersect = ray.IntersectTriangle(pA, pB, pC, true, point)
+		case material.SideDouble:
+			intersect = ray.IntersectTriangle(pA, pB, pC, false, point)
+		}
+		if !intersect {
+			return nil
+		}
+
+		// Transform intersection point from model to world coordinates
+		var intersectionPointWorld = *point
+		intersectionPointWorld.ApplyMatrix4(&matrixWorld)
+
+		// Calculates the distance from the ray origin to intersection point
+		origin := rc.Ray.Origin()
+		distance := origin.DistanceTo(&intersectionPointWorld)
+
+		// Checks if distance is between the bounds of the raycaster
+		if distance < rc.Near || distance > rc.Far {
+			return nil
+		}
+
+		return &Intersect{
+			Distance: distance,
+			Point:    intersectionPointWorld,
+			Object:   m,
+		}
+	}
+
+	i := 0
+	geom.ReadFaces(func(vA, vB, vC math32.Vector3) bool {
+		// Checks intersection of the ray with this face
+		mat := m.GetMaterial(i).GetMaterial()
+		var point math32.Vector3
+		intersect := checkIntersection(mat, &vA, &vB, &vC, &point)
+		if intersect != nil {
+			intersect.Index = uint32(i)
+			*intersects = append(*intersects, *intersect)
+		}
+		i += 3
+		return false
+	})
+}
+
+// RaycastLines
+func (rc *Raycaster) RaycastLines(l *graphic.Lines, intersects *[]Intersect) {
+
+	lineRaycast(l, rc, intersects, 2)
+}
+
+// RaycastLineStrip
+func (rc *Raycaster) RaycastLineStrip(l *graphic.LineStrip, intersects *[]Intersect) {
+
+	lineRaycast(l, rc, intersects, 1)
+}
+
+// Internal function used by raycasting for Lines and LineStrip.
+func lineRaycast(igr graphic.IGraphic, rc *Raycaster, intersects *[]Intersect, step int) {
+
+	// Get the bounding sphere
+	gr := igr.GetGraphic()
+	geom := igr.GetGeometry()
+	sphere := geom.BoundingSphere()
+
+	// Transform bounding sphere from model to world coordinates and
+	// checks intersection with raycaster
+	matrixWorld := gr.MatrixWorld()
+	sphere.ApplyMatrix4(&matrixWorld)
+	if !rc.IsIntersectionSphere(&sphere) {
+		return
+	}
+
+	// Copy ray and transform to model coordinates
+	// This ray will will also be used to check intersects with
+	// the geometry, as is much less expensive to transform the
+	// ray to model coordinates than the geometry to world coordinates.
+	var inverseMatrix math32.Matrix4
+	var ray math32.Ray
+	inverseMatrix.GetInverse(&matrixWorld)
+	ray.Copy(&rc.Ray).ApplyMatrix4(&inverseMatrix)
+
+	var vstart math32.Vector3
+	var vend math32.Vector3
+	var interSegment math32.Vector3
+	var interRay math32.Vector3
+
+	// Get geometry positions and indices buffers
+	vboPos := geom.VBO(gls.VertexPosition)
+	if vboPos == nil {
+		return
+	}
+	positions := vboPos.Buffer()
+	indices := geom.Indices()
+	precisionSq := rc.LinePrecision * rc.LinePrecision
+
+	// Checks intersection with individual lines for indexed geometry
+	if indices.Size() > 0 {
+		for i := 0; i < indices.Size()-1; i += step {
+			// Calculates distance from ray to this line segment
+			a := indices[i]
+			b := indices[i+1]
+			positions.GetVector3(int(3*a), &vstart)
+			positions.GetVector3(int(3*b), &vend)
+			distSq := ray.DistanceSqToSegment(&vstart, &vend, &interRay, &interSegment)
+			if distSq > precisionSq {
+				continue
+			}
+			// Move back to world coordinates for distance calculation
+			interRay.ApplyMatrix4(&matrixWorld)
+			origin := rc.Ray.Origin()
+			distance := origin.DistanceTo(&interRay)
+			if distance < rc.Near || distance > rc.Far {
+				continue
+			}
+
+			interSegment.ApplyMatrix4(&matrixWorld)
+			*intersects = append(*intersects, Intersect{
+				Distance: distance,
+				Point:    interSegment,
+				Index:    uint32(i),
+				Object:   igr,
+			})
+		}
+		// Checks intersection with individual lines for NON indexed geometry
+	} else {
+		for i := 0; i < positions.Size()/3-1; i += step {
+			positions.GetVector3(int(3*i), &vstart)
+			positions.GetVector3(int(3*i+3), &vend)
+			distSq := ray.DistanceSqToSegment(&vstart, &vend, &interRay, &interSegment)
+			if distSq > precisionSq {
+				continue
+			}
+
+			// Move back to world coordinates for distance calculation
+			interRay.ApplyMatrix4(&matrixWorld)
+			origin := rc.Ray.Origin()
+			distance := origin.DistanceTo(&interRay)
+			if distance < rc.Near || distance > rc.Far {
+				continue
+			}
+
+			interSegment.ApplyMatrix4(&matrixWorld)
+			*intersects = append(*intersects, Intersect{
+				Distance: distance,
+				Point:    interSegment,
+				Index:    uint32(i),
+				Object:   igr,
+			})
+		}
+	}
+}

+ 6 - 8
experimental/physics/debug.go

@@ -2,22 +2,20 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package physics implements a basic physics engine.
 package physics
 
 import (
 	"github.com/g3n/engine/core"
-	"github.com/g3n/engine/math32"
+	"github.com/g3n/engine/experimental/collision"
 	"github.com/g3n/engine/geometry"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/graphic"
 	"github.com/g3n/engine/material"
-	"github.com/g3n/engine/experimental/collision"
+	"github.com/g3n/engine/math32"
 )
 
 // This file contains helpful infrastructure for debugging physics
 type DebugHelper struct {
-
 }
 
 func ShowWorldFace(scene *core.Node, face []math32.Vector3, color *math32.Color) {
@@ -40,7 +38,7 @@ func ShowWorldFace(scene *core.Node, face []math32.Vector3, color *math32.Color)
 	scene.Add(faceGraphic)
 }
 
-func ShowPenAxis(scene *core.Node, axis *math32.Vector3) {//}, min, max float32) {
+func ShowPenAxis(scene *core.Node, axis *math32.Vector3) { //}, min, max float32) {
 
 	vertices := math32.NewArrayF32(0, 16)
 
@@ -65,7 +63,7 @@ func ShowPenAxis(scene *core.Node, axis *math32.Vector3) {//}, min, max float32)
 	geom := geometry.NewGeometry()
 	geom.AddVBO(gls.NewVBO(vertices).AddAttrib(gls.VertexPosition))
 
-	mat := material.NewStandard(&math32.Color{1,1,1})
+	mat := material.NewStandard(&math32.Color{1, 1, 1})
 	faceGraphic := graphic.NewLines(geom, mat)
 	scene.Add(faceGraphic)
 }
@@ -88,7 +86,7 @@ func ShowContact(scene *core.Node, contact *collision.Contact) {
 	geom := geometry.NewGeometry()
 	geom.AddVBO(gls.NewVBO(vertices).AddAttrib(gls.VertexPosition))
 
-	mat := material.NewStandard(&math32.Color{0,0,1})
+	mat := material.NewStandard(&math32.Color{0, 0, 1})
 	faceGraphic := graphic.NewLines(geom, mat)
 	scene.Add(faceGraphic)
-}
+}

+ 1 - 3
experimental/physics/material.go

@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package physics implements a basic physics engine.
 package physics
 
 type Material struct {
@@ -34,10 +33,9 @@ func NewContactMaterial() *ContactMaterial {
 	return cm
 }
 
-
 //type intPair struct {
 //	i int
 //	j int
 //}
 
-//type ContactMaterialTable map[intPair]*ContactMaterial
+//type ContactMaterialTable map[intPair]*ContactMaterial

+ 0 - 6
geometry/doc.go

@@ -1,6 +0,0 @@
-// 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 geometry implements several primitive geometry generators.
-package geometry

+ 5 - 0
geometry/geometry.go

@@ -2,14 +2,19 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// Package geometry implements several primitive geometry generators.
 package geometry
 
 import (
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/math32"
+	"github.com/g3n/engine/util/logger"
 	"strconv"
 )
 
+// Package logger
+var log = logger.New("GEOMETRY", logger.Default)
+
 // IGeometry is the interface for all geometries.
 type IGeometry interface {
 	GetGeometry() *Geometry

+ 0 - 12
geometry/logger.go

@@ -1,12 +0,0 @@
-// 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 geometry
-
-import (
-	"github.com/g3n/engine/util/logger"
-)
-
-// Package logger
-var log = logger.New("GEOMETRY", logger.Default)

+ 0 - 13
gls/doc.go

@@ -1,13 +0,0 @@
-// 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 gls implements a loader of OpenGL functions for the platform
-// and a Go binding for selected OpenGL functions. The binding maintains
-// some cached state to minimize the number of C function calls.
-// The OpenGL function loader is generated by the "glapi2go" tool by
-// parsing the OpenGL "glcorearb.h" header file
-//
-// This package also contains abstractions for some OpenGL object such as Program,
-// Uniform, VBO and others.
-package gls

+ 16 - 0
gls/gls.go

@@ -1,10 +1,26 @@
+// 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 gls implements a loader of OpenGL functions for the platform
+// and a Go binding for selected OpenGL functions. The binding maintains
+// some cached state to minimize the number of C function calls.
+// The OpenGL function loader is generated by the "glapi2go" tool by
+// parsing the OpenGL "glcorearb.h" header file
+//
+// This package also contains abstractions for some OpenGL object such as Program,
+// Uniform, VBO and others.
 package gls
 
 import (
+	"github.com/g3n/engine/util/logger"
 	"math"
 	"unsafe"
 )
 
+// Package logger
+var log = logger.New("GLS", logger.Default)
+
 // Stats contains counters of WebGL resources being used as well
 // the cumulative numbers of some WebGL calls for performance evaluation.
 type Stats struct {

+ 0 - 12
gls/logger.go

@@ -1,12 +0,0 @@
-// 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 gls
-
-import (
-	"github.com/g3n/engine/util/logger"
-)
-
-// Package logger
-var log = logger.New("GLS", logger.Default)

+ 0 - 6
graphic/doc.go

@@ -1,6 +0,0 @@
-// 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 graphic implements scene objects which have a graphic representation.
-package graphic

+ 28 - 23
graphic/graphic.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.
 
+// Package graphic implements scene objects which have a graphic representation.
 package graphic
 
 import (
@@ -10,8 +11,25 @@ import (
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/material"
 	"github.com/g3n/engine/math32"
+	"github.com/g3n/engine/util/logger"
 )
 
+// Package logger
+var log = logger.New("GRAPHIC", logger.Default)
+
+// IGraphic is the interface for all Graphic objects.
+type IGraphic interface {
+	core.INode
+	GetGraphic() *Graphic
+	GetGeometry() *geometry.Geometry
+	IGeometry() geometry.IGeometry
+	SetRenderable(bool)
+	Renderable() bool
+	SetCullable(bool)
+	Cullable() bool
+	RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo)
+}
+
 // Graphic is a Node which has a visible representation in the scene.
 // It has an associated geometry and one or more materials.
 // It is the base type used by other graphics such as lines, line_strip,
@@ -32,29 +50,6 @@ type Graphic struct {
 	mvpm math32.Matrix4 // Cached ModelViewProjection matrix
 }
 
-// GraphicMaterial specifies the material to be used for
-// a subset of vertices from the Graphic geometry
-// A Graphic object has at least one GraphicMaterial.
-type GraphicMaterial struct {
-	imat     material.IMaterial // Associated material
-	start    int                // Index of first element in the geometry
-	count    int                // Number of elements
-	igraphic IGraphic           // Graphic which contains this GraphicMaterial
-}
-
-// IGraphic is the interface for all Graphic objects.
-type IGraphic interface {
-	core.INode
-	GetGraphic() *Graphic
-	GetGeometry() *geometry.Geometry
-	IGeometry() geometry.IGeometry
-	SetRenderable(bool)
-	Renderable() bool
-	SetCullable(bool)
-	Cullable() bool
-	RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo)
-}
-
 // NewGraphic creates and returns a pointer to a new graphic object with
 // the specified geometry and OpenGL primitive.
 // The created graphic object, though, has not materials.
@@ -275,6 +270,16 @@ func (gr *Graphic) ModelViewProjectionMatrix() *math32.Matrix4 {
 	return &gr.mvpm
 }
 
+// GraphicMaterial specifies the material to be used for
+// a subset of vertices from the Graphic geometry
+// A Graphic object has at least one GraphicMaterial.
+type GraphicMaterial struct {
+	imat     material.IMaterial // Associated material
+	start    int                // Index of first element in the geometry
+	count    int                // Number of elements
+	igraphic IGraphic           // Graphic which contains this GraphicMaterial
+}
+
 // IMaterial returns the material associated with the GraphicMaterial.
 func (grmat *GraphicMaterial) IMaterial() material.IMaterial {
 

+ 0 - 7
graphic/line_strip.go

@@ -36,10 +36,3 @@ func (l *LineStrip) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 	location := l.uniMVPm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvpm[0])
 }
-
-// Raycast satisfies the INode interface and checks the intersections
-// of this geometry with the specified raycaster.
-func (l *LineStrip) Raycast(rc *core.Raycaster, intersects *[]core.Intersect) {
-
-	lineRaycast(l, rc, intersects, 1)
-}

+ 0 - 104
graphic/lines.go

@@ -9,7 +9,6 @@ import (
 	"github.com/g3n/engine/geometry"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/material"
-	"github.com/g3n/engine/math32"
 )
 
 // Lines is a Graphic which is rendered as a collection of independent lines.
@@ -42,106 +41,3 @@ func (l *Lines) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 	location := l.uniMVPm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvpm[0])
 }
-
-// Raycast satisfies the INode interface and checks the intersections
-// of this geometry with the specified raycaster.
-func (l *Lines) Raycast(rc *core.Raycaster, intersects *[]core.Intersect) {
-
-	lineRaycast(l, rc, intersects, 2)
-}
-
-// Internal function used by raycasting for Lines and LineStrip.
-func lineRaycast(igr IGraphic, rc *core.Raycaster, intersects *[]core.Intersect, step int) {
-
-	// Get the bounding sphere
-	gr := igr.GetGraphic()
-	geom := igr.GetGeometry()
-	sphere := geom.BoundingSphere()
-
-	// Transform bounding sphere from model to world coordinates and
-	// checks intersection with raycaster
-	matrixWorld := gr.MatrixWorld()
-	sphere.ApplyMatrix4(&matrixWorld)
-	if !rc.IsIntersectionSphere(&sphere) {
-		return
-	}
-
-	// Copy ray and transform to model coordinates
-	// This ray will will also be used to check intersects with
-	// the geometry, as is much less expensive to transform the
-	// ray to model coordinates than the geometry to world coordinates.
-	var inverseMatrix math32.Matrix4
-	var ray math32.Ray
-	inverseMatrix.GetInverse(&matrixWorld)
-	ray.Copy(&rc.Ray).ApplyMatrix4(&inverseMatrix)
-
-	var vstart math32.Vector3
-	var vend math32.Vector3
-	var interSegment math32.Vector3
-	var interRay math32.Vector3
-
-	// Get geometry positions and indices buffers
-	vboPos := geom.VBO(gls.VertexPosition)
-	if vboPos == nil {
-		return
-	}
-	positions := vboPos.Buffer()
-	indices := geom.Indices()
-	precisionSq := rc.LinePrecision * rc.LinePrecision
-
-	// Checks intersection with individual lines for indexed geometry
-	if indices.Size() > 0 {
-		for i := 0; i < indices.Size()-1; i += step {
-			// Calculates distance from ray to this line segment
-			a := indices[i]
-			b := indices[i+1]
-			positions.GetVector3(int(3*a), &vstart)
-			positions.GetVector3(int(3*b), &vend)
-			distSq := ray.DistanceSqToSegment(&vstart, &vend, &interRay, &interSegment)
-			if distSq > precisionSq {
-				continue
-			}
-			// Move back to world coordinates for distance calculation
-			interRay.ApplyMatrix4(&matrixWorld)
-			origin := rc.Ray.Origin()
-			distance := origin.DistanceTo(&interRay)
-			if distance < rc.Near || distance > rc.Far {
-				continue
-			}
-
-			interSegment.ApplyMatrix4(&matrixWorld)
-			*intersects = append(*intersects, core.Intersect{
-				Distance: distance,
-				Point:    interSegment,
-				Index:    uint32(i),
-				Object:   igr,
-			})
-		}
-		// Checks intersection with individual lines for NON indexed geometry
-	} else {
-		for i := 0; i < positions.Size()/3-1; i += step {
-			positions.GetVector3(int(3*i), &vstart)
-			positions.GetVector3(int(3*i+3), &vend)
-			distSq := ray.DistanceSqToSegment(&vstart, &vend, &interRay, &interSegment)
-			if distSq > precisionSq {
-				continue
-			}
-
-			// Move back to world coordinates for distance calculation
-			interRay.ApplyMatrix4(&matrixWorld)
-			origin := rc.Ray.Origin()
-			distance := origin.DistanceTo(&interRay)
-			if distance < rc.Near || distance > rc.Far {
-				continue
-			}
-
-			interSegment.ApplyMatrix4(&matrixWorld)
-			*intersects = append(*intersects, core.Intersect{
-				Distance: distance,
-				Point:    interSegment,
-				Index:    uint32(i),
-				Object:   igr,
-			})
-		}
-	}
-}

+ 0 - 12
graphic/logger.go

@@ -1,12 +0,0 @@
-// 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 graphic
-
-import (
-	"github.com/g3n/engine/util/logger"
-)
-
-// Package logger
-var log = logger.New("GRAPHIC", logger.Default)

+ 0 - 79
graphic/mesh.go

@@ -109,82 +109,3 @@ func (m *Mesh) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 	location = m.uniNm.Location(gs)
 	gs.UniformMatrix3fv(location, 1, false, &nm[0])
 }
-
-// Raycast checks intersections between this geometry and the specified raycaster
-// and if any found appends it to the specified intersects array.
-func (m *Mesh) Raycast(rc *core.Raycaster, intersects *[]core.Intersect) {
-
-	// Transform this mesh geometry bounding sphere from model
-	// to world coordinates and checks intersection with raycaster
-	geom := m.GetGeometry()
-	sphere := geom.BoundingSphere()
-	matrixWorld := m.MatrixWorld()
-	sphere.ApplyMatrix4(&matrixWorld)
-	if !rc.IsIntersectionSphere(&sphere) {
-		return
-	}
-
-	// Copy ray and transform to model coordinates
-	// This ray will will also be used to check intersects with
-	// the geometry, as is much less expensive to transform the
-	// ray to model coordinates than the geometry to world coordinates.
-	var inverseMatrix math32.Matrix4
-	inverseMatrix.GetInverse(&matrixWorld)
-	var ray math32.Ray
-	ray.Copy(&rc.Ray).ApplyMatrix4(&inverseMatrix)
-	bbox := geom.BoundingBox()
-	if !ray.IsIntersectionBox(&bbox) {
-		return
-	}
-
-	// Local function to check the intersection of the ray from the raycaster with
-	// the specified face defined by three poins.
-	checkIntersection := func(mat *material.Material, pA, pB, pC, point *math32.Vector3) *core.Intersect {
-
-		var intersect bool
-		switch mat.Side() {
-		case material.SideBack:
-			intersect = ray.IntersectTriangle(pC, pB, pA, true, point)
-		case material.SideFront:
-			intersect = ray.IntersectTriangle(pA, pB, pC, true, point)
-		case material.SideDouble:
-			intersect = ray.IntersectTriangle(pA, pB, pC, false, point)
-		}
-		if !intersect {
-			return nil
-		}
-
-		// Transform intersection point from model to world coordinates
-		var intersectionPointWorld = *point
-		intersectionPointWorld.ApplyMatrix4(&matrixWorld)
-
-		// Calculates the distance from the ray origin to intersection point
-		origin := rc.Ray.Origin()
-		distance := origin.DistanceTo(&intersectionPointWorld)
-
-		// Checks if distance is between the bounds of the raycaster
-		if distance < rc.Near || distance > rc.Far {
-			return nil
-		}
-
-		return &core.Intersect{
-			Distance: distance,
-			Point:    intersectionPointWorld,
-			Object:   m,
-		}
-	}
-
-	i := 0
-	geom.ReadFaces(func(vA, vB, vC math32.Vector3) bool {
-		// Checks intersection of the ray with this face
-		mat := m.GetMaterial(i).GetMaterial()
-		var point math32.Vector3
-		intersect := checkIntersection(mat, &vA, &vB, &vC, &point)
-		if intersect != nil {
-			intersect.Index = uint32(i)
-			*intersects = append(*intersects, *intersect)
-		}
-		i += 3
-		return false
-	})
-}

+ 0 - 59
graphic/points.go

@@ -9,7 +9,6 @@ import (
 	"github.com/g3n/engine/geometry"
 	"github.com/g3n/engine/gls"
 	"github.com/g3n/engine/material"
-	"github.com/g3n/engine/math32"
 )
 
 // Points represents a geometry containing only points
@@ -39,61 +38,3 @@ func (p *Points) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 	location := p.uniMVPm.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvpm[0])
 }
-
-// Raycast satisfies the INode interface and checks the intersections
-// of this geometry with the specified raycaster.
-func (p *Points) Raycast(rc *core.Raycaster, intersects *[]core.Intersect) {
-
-	// Checks intersection with the bounding sphere transformed to world coordinates
-	geom := p.GetGeometry()
-	sphere := geom.BoundingSphere()
-	matrixWorld := p.MatrixWorld()
-	sphere.ApplyMatrix4(&matrixWorld)
-	if !rc.IsIntersectionSphere(&sphere) {
-		return
-	}
-
-	// Copy ray and transforms to model coordinates
-	var inverseMatrix math32.Matrix4
-	var ray math32.Ray
-	inverseMatrix.GetInverse(&matrixWorld)
-	ray.Copy(&rc.Ray).ApplyMatrix4(&inverseMatrix)
-
-	// Checks intersection with all points
-	scale := p.Scale()
-	localThreshold := rc.PointPrecision / ((scale.X + scale.Y + scale.Z) / 3)
-	localThresholdSq := localThreshold * localThreshold
-
-	// internal function to check intersection with a point
-	testPoint := func(point *math32.Vector3, index int) {
-
-		// Get distance from ray to point and if greater than threshold,
-		// nothing to do.
-		rayPointDistanceSq := ray.DistanceSqToPoint(point)
-		if rayPointDistanceSq >= localThresholdSq {
-			return
-		}
-		var intersectPoint math32.Vector3
-		ray.ClosestPointToPoint(point, &intersectPoint)
-		intersectPoint.ApplyMatrix4(&matrixWorld)
-		origin := rc.Ray.Origin()
-		distance := origin.DistanceTo(&intersectPoint)
-		if distance < rc.Near || distance > rc.Far {
-			return
-		}
-		// Appends intersection of raycaster with this point
-		*intersects = append(*intersects, core.Intersect{
-			Distance: distance,
-			Point:    intersectPoint,
-			Index:    uint32(index),
-			Object:   p,
-		})
-	}
-
-	i := 0
-	geom.ReadVertices(func(vertex math32.Vector3) bool {
-		testPoint(&vertex, i)
-		i++
-		return false
-	})
-}

+ 0 - 77
graphic/sprite.go

@@ -83,80 +83,3 @@ func (s *Sprite) RenderSetup(gs *gls.GLS, rinfo *core.RenderInfo) {
 	location := s.uniMVPM.Location(gs)
 	gs.UniformMatrix4fv(location, 1, false, &mvpm[0])
 }
-
-// Raycast checks intersections between this geometry and the specified raycaster
-// and if any found appends it to the specified intersects array.
-func (s *Sprite) Raycast(rc *core.Raycaster, intersects *[]core.Intersect) {
-
-	// Copy and convert ray to camera coordinates
-	var ray math32.Ray
-	ray.Copy(&rc.Ray).ApplyMatrix4(&rc.ViewMatrix)
-
-	// Calculates ViewMatrix * MatrixWorld
-	var mv math32.Matrix4
-	matrixWorld := s.MatrixWorld()
-	mv.MultiplyMatrices(&rc.ViewMatrix, &matrixWorld)
-
-	// Decompose transformation matrix in its components
-	var position math32.Vector3
-	var quaternion math32.Quaternion
-	var scale math32.Vector3
-	mv.Decompose(&position, &quaternion, &scale)
-
-	// Remove any rotation in X and Y axis and
-	// compose new transformation matrix
-	rotation := s.Rotation()
-	rotation.X = 0
-	rotation.Y = 0
-	quaternion.SetFromEuler(&rotation)
-	mv.Compose(&position, &quaternion, &scale)
-
-	// Get buffer with vertices and uvs
-	geom := s.GetGeometry()
-	vboPos := geom.VBO(gls.VertexPosition)
-	if vboPos == nil {
-		panic("sprite.Raycast(): VertexPosition VBO not found")
-	}
-	// Get vertex positions, transform to camera coordinates and
-	// checks intersection with ray
-	buffer := vboPos.Buffer()
-	indices := geom.Indices()
-	var v1 math32.Vector3
-	var v2 math32.Vector3
-	var v3 math32.Vector3
-	var point math32.Vector3
-	intersect := false
-	for i := 0; i < indices.Size(); i += 3 {
-		pos := indices[i]
-		buffer.GetVector3(int(pos*5), &v1)
-		v1.ApplyMatrix4(&mv)
-		pos = indices[i+1]
-		buffer.GetVector3(int(pos*5), &v2)
-		v2.ApplyMatrix4(&mv)
-		pos = indices[i+2]
-		buffer.GetVector3(int(pos*5), &v3)
-		v3.ApplyMatrix4(&mv)
-		if ray.IntersectTriangle(&v1, &v2, &v3, false, &point) {
-			intersect = true
-			break
-		}
-	}
-	if !intersect {
-		return
-	}
-	// Get distance from intersection point
-	origin := ray.Origin()
-	distance := origin.DistanceTo(&point)
-
-	// Checks if distance is between the bounds of the raycaster
-	if distance < rc.Near || distance > rc.Far {
-		return
-	}
-
-	// Appends intersection to received parameter.
-	*intersects = append(*intersects, core.Intersect{
-		Distance: distance,
-		Point:    point,
-		Object:   s,
-	})
-}

+ 1 - 1
gui/doc.go

@@ -2,5 +2,5 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package gui implements the GUI infraestructure and several widgets.
+// Package gui implements the GUI infrastructure and several widgets.
 package gui

+ 5 - 4
loader/gltf/loader.go

@@ -401,24 +401,25 @@ func (g *GLTF) LoadCamera(camIdx int) (core.INode, error) {
 	log.Debug("Loading Camera %d", camIdx)
 	camData := g.Cameras[camIdx]
 
+	aspect := float32(2) // TODO how to get the current aspect ratio of the viewport from here ?
+
 	if camData.Type == "perspective" {
 		desc := camData.Perspective
 		fov := 360 * (desc.Yfov) / 2 * math32.Pi
-		aspect := float32(2) // TODO how to get the current aspect ratio of the viewport from here ?
 		if desc.AspectRatio != nil {
 			aspect = *desc.AspectRatio
 		}
-		far := float32(2E6)
+		far := float32(2e6)
 		if desc.Zfar != nil {
 			far = *desc.Zfar
 		}
-		cam := camera.NewPerspective(fov, aspect, desc.Znear, far)
+		cam := camera.NewPerspective(aspect, desc.Znear, far, fov, camera.Vertical)
 		return cam, nil
 	}
 
 	if camData.Type == "orthographic" {
 		desc := camData.Orthographic
-		cam := camera.NewOrthographic(desc.Xmag/-2, desc.Xmag/2, desc.Ymag/2, desc.Ymag/-2, desc.Znear, desc.Zfar)
+		cam := camera.NewOrthographic(aspect, desc.Znear, desc.Zfar, desc.Ymag, camera.Vertical)
 		return cam, nil
 
 	}

+ 1 - 1
math32/matrix4.go

@@ -686,7 +686,7 @@ func (m *Matrix4) MakeFrustum(left, right, bottom, top, near, far float32) *Matr
 }
 
 // MakePerspective sets this matrix to a perspective projection matrix
-// with the specified field of view in degrees,
+// with the specified vertical field of view in degrees,
 // aspect ratio (width/height) and near and far planes.
 // Returns pointer to this updated matrix.
 func (m *Matrix4) MakePerspective(fov, aspect, near, far float32) *Matrix4 {