orbit_control.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package control
  5. import (
  6. "github.com/g3n/engine/camera"
  7. "github.com/g3n/engine/math32"
  8. "github.com/g3n/engine/util/logger"
  9. "github.com/g3n/engine/window"
  10. "math"
  11. )
  12. // OrbitControl is a camera controller that allows orbiting a center point while looking at it.
  13. type OrbitControl struct {
  14. Enabled bool // Control enabled state
  15. EnableRotate bool // Rotate enabled state
  16. EnableZoom bool // Zoom enabled state
  17. EnablePan bool // Pan enabled state
  18. EnableKeys bool // Enable keys state
  19. ZoomSpeed float32 // Zoom speed factor. Default is 1.0
  20. RotateSpeed float32 // Rotate speed factor. Default is 1.0
  21. MinDistance float32 // Minimum distance from target. Default is 0.01
  22. MaxDistance float32 // Maximum distance from target. Default is infinity
  23. MinPolarAngle float32 // Minimum polar angle for rotatiom
  24. MaxPolarAngle float32
  25. MinAzimuthAngle float32
  26. MaxAzimuthAngle float32
  27. KeyRotateSpeed float32
  28. KeyPanSpeed float32
  29. // Internal
  30. icam camera.ICamera
  31. cam *camera.Camera
  32. camPersp *camera.Perspective
  33. camOrtho *camera.Orthographic
  34. win window.IWindow
  35. position0 math32.Vector3 // Initial camera position
  36. target0 math32.Vector3 // Initial camera target position
  37. state int // current active state
  38. phiDelta float32 // rotation delta in the XZ plane
  39. thetaDelta float32 // rotation delta in the YX plane
  40. rotateStart math32.Vector2
  41. rotateEnd math32.Vector2
  42. rotateDelta math32.Vector2
  43. panStart math32.Vector2 // initial pan screen coordinates
  44. panEnd math32.Vector2 // final pan scren coordinates
  45. panDelta math32.Vector2
  46. panOffset math32.Vector2
  47. zoomStart float32
  48. zoomEnd float32
  49. zoomDelta float32
  50. subsEvents int // Address of this field is used as events subscription id
  51. subsPos int // Address of this field is used as cursor pos events subscription id
  52. }
  53. const (
  54. stateNone = iota
  55. stateRotate
  56. stateZoom
  57. statePan
  58. )
  59. // Package logger
  60. var log = logger.New("ORBIT", logger.Default)
  61. // NewOrbitControl creates and returns a pointer to a new orbito control for
  62. // the specified camera and window
  63. func NewOrbitControl(icam camera.ICamera, win window.IWindow) *OrbitControl {
  64. oc := new(OrbitControl)
  65. oc.icam = icam
  66. oc.win = win
  67. oc.cam = icam.GetCamera()
  68. if persp, ok := icam.(*camera.Perspective); ok {
  69. oc.camPersp = persp
  70. } else if ortho, ok := icam.(*camera.Orthographic); ok {
  71. oc.camOrtho = ortho
  72. } else {
  73. panic("Invalid camera type")
  74. }
  75. // Set defaults
  76. oc.Enabled = true
  77. oc.EnableRotate = true
  78. oc.EnableZoom = true
  79. oc.EnablePan = true
  80. oc.EnableKeys = true
  81. oc.ZoomSpeed = 1.0
  82. oc.RotateSpeed = 1.0
  83. oc.MinDistance = 0.01
  84. oc.MaxDistance = float32(math.Inf(1))
  85. oc.MinPolarAngle = 0
  86. oc.MaxPolarAngle = math32.Pi
  87. oc.MinAzimuthAngle = float32(math.Inf(-1))
  88. oc.MaxAzimuthAngle = float32(math.Inf(1))
  89. oc.KeyPanSpeed = 5.0
  90. oc.KeyRotateSpeed = 0.02
  91. // Saves initial camera parameters
  92. oc.position0 = oc.cam.Position()
  93. oc.target0 = oc.cam.Target()
  94. // Subscribe to events
  95. oc.win.SubscribeID(window.OnMouseUp, &oc.subsEvents, oc.onMouse)
  96. oc.win.SubscribeID(window.OnMouseDown, &oc.subsEvents, oc.onMouse)
  97. oc.win.SubscribeID(window.OnScroll, &oc.subsEvents, oc.onScroll)
  98. oc.win.SubscribeID(window.OnKeyDown, &oc.subsEvents, oc.onKey)
  99. return oc
  100. }
  101. // Dispose unsubscribes from all events
  102. func (oc *OrbitControl) Dispose() {
  103. // Unsubscribe to event handlers
  104. oc.win.UnsubscribeID(window.OnMouseUp, &oc.subsEvents)
  105. oc.win.UnsubscribeID(window.OnMouseDown, &oc.subsEvents)
  106. oc.win.UnsubscribeID(window.OnScroll, &oc.subsEvents)
  107. oc.win.UnsubscribeID(window.OnKeyDown, &oc.subsEvents)
  108. oc.win.UnsubscribeID(window.OnCursor, &oc.subsPos)
  109. }
  110. // Reset to initial camera position
  111. func (oc *OrbitControl) Reset() {
  112. oc.state = stateNone
  113. oc.cam.SetPositionVec(&oc.position0)
  114. oc.cam.LookAt(&oc.target0)
  115. }
  116. // Pan the camera and target by the specified deltas
  117. func (oc *OrbitControl) Pan(deltaX, deltaY float32) {
  118. width, height := oc.win.Size()
  119. oc.pan(deltaX, deltaY, width, height)
  120. oc.updatePan()
  121. }
  122. // Zoom in or out
  123. func (oc *OrbitControl) Zoom(delta float32) {
  124. oc.zoomDelta = delta
  125. oc.updateZoom()
  126. }
  127. // RotateLeft rotates the camera left by specified angle
  128. func (oc *OrbitControl) RotateLeft(angle float32) {
  129. oc.thetaDelta -= angle
  130. oc.updateRotate()
  131. }
  132. // RotateUp rotates the camera up by specified angle
  133. func (oc *OrbitControl) RotateUp(angle float32) {
  134. oc.phiDelta -= angle
  135. oc.updateRotate()
  136. }
  137. // Updates the camera rotation from thetaDelta and phiDelta
  138. func (oc *OrbitControl) updateRotate() {
  139. const EPS = 0.01
  140. // Get camera parameters
  141. position := oc.cam.Position()
  142. target := oc.cam.Target()
  143. up := oc.cam.Up()
  144. // Camera UP is the orbit axis
  145. var quat math32.Quaternion
  146. quat.SetFromUnitVectors(&up, &math32.Vector3{0, 1, 0})
  147. quatInverse := quat
  148. quatInverse.Inverse()
  149. // Calculates direction vector from camera position to target
  150. vdir := position
  151. vdir.Sub(&target)
  152. vdir.ApplyQuaternion(&quat)
  153. // Calculate angles from current camera position
  154. radius := vdir.Length()
  155. theta := math32.Atan2(vdir.X, vdir.Z)
  156. phi := math32.Acos(vdir.Y / radius)
  157. // Add deltas to the angles
  158. theta += oc.thetaDelta
  159. phi += oc.phiDelta
  160. // Restrict phi (elevation) to be between desired limits
  161. phi = math32.Max(oc.MinPolarAngle, math32.Min(oc.MaxPolarAngle, phi))
  162. phi = math32.Max(EPS, math32.Min(math32.Pi-EPS, phi))
  163. // Restrict theta to be between desired limits
  164. theta = math32.Max(oc.MinAzimuthAngle, math32.Min(oc.MaxAzimuthAngle, theta))
  165. // Calculate new cartesian coordinates
  166. vdir.X = radius * math32.Sin(phi) * math32.Sin(theta)
  167. vdir.Y = radius * math32.Cos(phi)
  168. vdir.Z = radius * math32.Sin(phi) * math32.Cos(theta)
  169. // Rotate offset back to "camera-up-vector-is-up" space
  170. vdir.ApplyQuaternion(&quatInverse)
  171. position = target
  172. position.Add(&vdir)
  173. oc.cam.SetPositionVec(&position)
  174. oc.cam.LookAt(&target)
  175. // Reset deltas
  176. oc.thetaDelta = 0
  177. oc.phiDelta = 0
  178. }
  179. // Updates camera rotation from tethaDelta and phiDelta
  180. // ALTERNATIVE rotation algorithm
  181. func (oc *OrbitControl) updateRotate2() {
  182. const EPS = 0.01
  183. // Get camera parameters
  184. position := oc.cam.Position()
  185. target := oc.cam.Target()
  186. up := oc.cam.Up()
  187. // Calculates direction vector from target to camera
  188. vdir := position
  189. vdir.Sub(&target)
  190. // Calculates right and up vectors
  191. var vright math32.Vector3
  192. vright.CrossVectors(&up, &vdir)
  193. vright.Normalize()
  194. var vup math32.Vector3
  195. vup.CrossVectors(&vdir, &vright)
  196. vup.Normalize()
  197. phi := vdir.AngleTo(&math32.Vector3{0, 1, 0})
  198. newphi := phi + oc.phiDelta
  199. if newphi < EPS || newphi > math32.Pi-EPS {
  200. oc.phiDelta = 0
  201. } else if newphi < oc.MinPolarAngle || newphi > oc.MaxPolarAngle {
  202. oc.phiDelta = 0
  203. }
  204. // Rotates position around the two vectors
  205. vdir.ApplyAxisAngle(&vup, oc.thetaDelta)
  206. vdir.ApplyAxisAngle(&vright, oc.phiDelta)
  207. // Adds target back get final position
  208. position = target
  209. position.Add(&vdir)
  210. log.Debug("orbit set position")
  211. oc.cam.SetPositionVec(&position)
  212. oc.cam.LookAt(&target)
  213. // Reset deltas
  214. oc.thetaDelta = 0
  215. oc.phiDelta = 0
  216. }
  217. // Updates camera pan from panOffset
  218. func (oc *OrbitControl) updatePan() {
  219. // Get camera parameters
  220. position := oc.cam.Position()
  221. target := oc.cam.Target()
  222. up := oc.cam.Up()
  223. // Calculates direction vector from camera position to target
  224. vdir := target
  225. vdir.Sub(&position)
  226. vdir.Normalize()
  227. // Calculates vector perpendicular to direction and up (side vector)
  228. var vpanx math32.Vector3
  229. vpanx.CrossVectors(&up, &vdir)
  230. vpanx.Normalize()
  231. // Calculates vector perpendicular to direction and vpanx
  232. var vpany math32.Vector3
  233. vpany.CrossVectors(&vdir, &vpanx)
  234. vpany.Normalize()
  235. // Adds pan offsets
  236. vpanx.MultiplyScalar(oc.panOffset.X)
  237. vpany.MultiplyScalar(oc.panOffset.Y)
  238. var vpan math32.Vector3
  239. vpan.AddVectors(&vpanx, &vpany)
  240. // Adds offsets to camera position and target
  241. position.Add(&vpan)
  242. target.Add(&vpan)
  243. // Sets new camera parameters
  244. oc.cam.SetPositionVec(&position)
  245. oc.cam.LookAt(&target)
  246. // Reset deltas
  247. oc.panOffset.Set(0, 0)
  248. }
  249. // Updates camera zoom from zoomDelta
  250. func (oc *OrbitControl) updateZoom() {
  251. if oc.camOrtho != nil {
  252. zoom := oc.camOrtho.Zoom() - 0.01*oc.zoomDelta
  253. oc.camOrtho.SetZoom(zoom)
  254. // Reset delta
  255. oc.zoomDelta = 0
  256. return
  257. }
  258. // Get camera and target positions
  259. position := oc.cam.Position()
  260. target := oc.cam.Target()
  261. // Calculates direction vector from target to camera position
  262. vdir := position
  263. vdir.Sub(&target)
  264. // Calculates new distance from target and applies limits
  265. dist := vdir.Length() * (1.0 + oc.zoomDelta*oc.ZoomSpeed/10.0)
  266. dist = math32.Max(oc.MinDistance, math32.Min(oc.MaxDistance, dist))
  267. vdir.SetLength(dist)
  268. // Adds new distance to target to get new camera position
  269. target.Add(&vdir)
  270. oc.cam.SetPositionVec(&target)
  271. // Reset delta
  272. oc.zoomDelta = 0
  273. }
  274. // Called when mouse button event is received
  275. func (oc *OrbitControl) onMouse(evname string, ev interface{}) {
  276. // If control not enabled ignore event
  277. if !oc.Enabled {
  278. return
  279. }
  280. mev := ev.(*window.MouseEvent)
  281. // Mouse button pressed
  282. if mev.Action == window.Press {
  283. // Left button pressed sets Rotate state
  284. if mev.Button == window.MouseButtonLeft {
  285. if !oc.EnableRotate {
  286. return
  287. }
  288. oc.state = stateRotate
  289. oc.rotateStart.Set(float32(mev.Xpos), float32(mev.Ypos))
  290. } else
  291. // Middle button pressed sets Zoom state
  292. if mev.Button == window.MouseButtonMiddle {
  293. if !oc.EnableZoom {
  294. return
  295. }
  296. oc.state = stateZoom
  297. oc.zoomStart = float32(mev.Ypos)
  298. } else
  299. // Right button pressed sets Pan state
  300. if mev.Button == window.MouseButtonRight {
  301. if !oc.EnablePan {
  302. return
  303. }
  304. oc.state = statePan
  305. oc.panStart.Set(float32(mev.Xpos), float32(mev.Ypos))
  306. }
  307. // If a valid state is set requests mouse position events
  308. if oc.state != stateNone {
  309. oc.win.SubscribeID(window.OnCursor, &oc.subsPos, oc.onCursorPos)
  310. }
  311. return
  312. }
  313. // Mouse button released
  314. if mev.Action == window.Release {
  315. oc.win.UnsubscribeID(window.OnCursor, &oc.subsPos)
  316. oc.state = stateNone
  317. }
  318. }
  319. // Called when cursor position event is received
  320. func (oc *OrbitControl) onCursorPos(evname string, ev interface{}) {
  321. // If control not enabled ignore event
  322. if !oc.Enabled {
  323. return
  324. }
  325. mev := ev.(*window.CursorEvent)
  326. // Rotation
  327. if oc.state == stateRotate {
  328. oc.rotateEnd.Set(float32(mev.Xpos), float32(mev.Ypos))
  329. oc.rotateDelta.SubVectors(&oc.rotateEnd, &oc.rotateStart)
  330. oc.rotateStart = oc.rotateEnd
  331. // rotating across whole screen goes 360 degrees around
  332. width, height := oc.win.Size()
  333. oc.RotateLeft(2 * math32.Pi * oc.rotateDelta.X / float32(width) * oc.RotateSpeed)
  334. // rotating up and down along whole screen attempts to go 360, but limited to 180
  335. oc.RotateUp(2 * math32.Pi * oc.rotateDelta.Y / float32(height) * oc.RotateSpeed)
  336. return
  337. }
  338. // Panning
  339. if oc.state == statePan {
  340. oc.panEnd.Set(float32(mev.Xpos), float32(mev.Ypos))
  341. oc.panDelta.SubVectors(&oc.panEnd, &oc.panStart)
  342. oc.panStart = oc.panEnd
  343. oc.Pan(oc.panDelta.X, oc.panDelta.Y)
  344. return
  345. }
  346. // Zooming
  347. if oc.state == stateZoom {
  348. oc.zoomEnd = float32(mev.Ypos)
  349. oc.zoomDelta = oc.zoomEnd - oc.zoomStart
  350. oc.zoomStart = oc.zoomEnd
  351. oc.Zoom(oc.zoomDelta)
  352. }
  353. }
  354. // Called when mouse button scroll event is received
  355. func (oc *OrbitControl) onScroll(evname string, ev interface{}) {
  356. if !oc.Enabled || !oc.EnableZoom || oc.state != stateNone {
  357. return
  358. }
  359. sev := ev.(*window.ScrollEvent)
  360. oc.Zoom(float32(-sev.Yoffset))
  361. }
  362. // Called when key is pressed, released or repeats.
  363. func (oc *OrbitControl) onKey(evname string, ev interface{}) {
  364. if !oc.Enabled || !oc.EnableKeys {
  365. return
  366. }
  367. kev := ev.(*window.KeyEvent)
  368. if kev.Action == window.Release {
  369. return
  370. }
  371. if oc.EnablePan && kev.Mods == 0 {
  372. switch kev.Keycode {
  373. case window.KeyUp:
  374. oc.Pan(0, oc.KeyPanSpeed)
  375. case window.KeyDown:
  376. oc.Pan(0, -oc.KeyPanSpeed)
  377. case window.KeyLeft:
  378. oc.Pan(oc.KeyPanSpeed, 0)
  379. case window.KeyRight:
  380. oc.Pan(-oc.KeyPanSpeed, 0)
  381. }
  382. }
  383. if oc.EnableRotate && kev.Mods == window.ModShift {
  384. switch kev.Keycode {
  385. case window.KeyUp:
  386. oc.RotateUp(oc.KeyRotateSpeed)
  387. case window.KeyDown:
  388. oc.RotateUp(-oc.KeyRotateSpeed)
  389. case window.KeyLeft:
  390. oc.RotateLeft(-oc.KeyRotateSpeed)
  391. case window.KeyRight:
  392. oc.RotateLeft(oc.KeyRotateSpeed)
  393. }
  394. }
  395. if oc.EnableZoom && kev.Mods == window.ModControl {
  396. switch kev.Keycode {
  397. case window.KeyUp:
  398. oc.Zoom(-1.0)
  399. case window.KeyDown:
  400. oc.Zoom(1.0)
  401. }
  402. }
  403. }
  404. func (oc *OrbitControl) pan(deltaX, deltaY float32, swidth, sheight int) {
  405. // Perspective camera
  406. if oc.camPersp != nil {
  407. position := oc.cam.Position()
  408. target := oc.cam.Target()
  409. offset := position.Clone().Sub(&target)
  410. targetDistance := offset.Length()
  411. // Half the FOV is center to top of screen
  412. targetDistance += math32.Tan((oc.camPersp.Fov() / 2.0) * math32.Pi / 180.0)
  413. // we actually don't use screenWidth, since perspective camera is fixed to screen height
  414. oc.panLeft(2 * deltaX * targetDistance / float32(sheight))
  415. oc.panUp(2 * deltaY * targetDistance / float32(sheight))
  416. return
  417. }
  418. // Orthographic camera
  419. left, right, top, bottom, _, _ := oc.camOrtho.Planes()
  420. oc.panLeft(deltaX * (right - left) / float32(swidth))
  421. oc.panUp(deltaY * (top - bottom) / float32(sheight))
  422. }
  423. func (oc *OrbitControl) panLeft(distance float32) {
  424. oc.panOffset.X += distance
  425. }
  426. func (oc *OrbitControl) panUp(distance float32) {
  427. oc.panOffset.Y += distance
  428. }