| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- // 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 (
- "math"
- "github.com/g3n/engine/core"
- "github.com/g3n/engine/gui"
- "github.com/g3n/engine/math32"
- "github.com/g3n/engine/window"
- )
- // 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()
- }
- // Target returns the current orbit target.
- 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 {
- 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)
- }
|