trackball.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. /*
  7. * This code has been modified from https://github.com/mrdoob/three.js/,
  8. * copyright (c) 2010-2018 three.js authors. MIT License
  9. */
  10. import { Quat, Vec2, Vec3, EPSILON } from 'mol-math/linear-algebra';
  11. import { cameraLookAt, Viewport } from '../camera/util';
  12. import InputObserver, { DragInput, WheelInput, ButtonsFlag, PinchInput } from 'mol-util/input/input-observer';
  13. import { Object3D } from 'mol-gl/object3d';
  14. export const DefaultTrackballControlsProps = {
  15. noScroll: true,
  16. target: [0, 0, 0] as Vec3,
  17. rotateSpeed: 3.0,
  18. zoomSpeed: 4.0,
  19. panSpeed: 0.8,
  20. staticMoving: true,
  21. dynamicDampingFactor: 0.2,
  22. minDistance: 0,
  23. maxDistance: Infinity
  24. }
  25. export type TrackballControlsProps = Partial<typeof DefaultTrackballControlsProps>
  26. interface TrackballControls {
  27. viewport: Viewport
  28. target: Vec3
  29. dynamicDampingFactor: number
  30. rotateSpeed: number
  31. zoomSpeed: number
  32. panSpeed: number
  33. update: () => void
  34. reset: () => void
  35. dispose: () => void
  36. }
  37. namespace TrackballControls {
  38. export function create (input: InputObserver, object: Object3D, props: TrackballControlsProps = {}): TrackballControls {
  39. const p = { ...DefaultTrackballControlsProps, ...props }
  40. const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 }
  41. const target: Vec3 = Vec3.clone(p.target)
  42. let { rotateSpeed, zoomSpeed, panSpeed } = p
  43. let { staticMoving, dynamicDampingFactor } = p
  44. let { minDistance, maxDistance } = p
  45. let disposed = false
  46. const dragSub = input.drag.subscribe(onDrag)
  47. const wheelSub = input.wheel.subscribe(onWheel)
  48. const pinchSub = input.pinch.subscribe(onPinch)
  49. // For internal use
  50. const lastPosition = Vec3.zero()
  51. const _eye = Vec3.zero()
  52. const _movePrev = Vec2.zero()
  53. const _moveCurr = Vec2.zero()
  54. const _lastAxis = Vec3.zero()
  55. let _lastAngle = 0
  56. const _zoomStart = Vec2.zero()
  57. const _zoomEnd = Vec2.zero()
  58. let _touchZoomDistanceStart = 0
  59. let _touchZoomDistanceEnd = 0
  60. const _panStart = Vec2.zero()
  61. const _panEnd = Vec2.zero()
  62. // Initial values for reseting
  63. const target0 = Vec3.clone(target)
  64. const position0 = Vec3.clone(object.position)
  65. const up0 = Vec3.clone(object.up)
  66. const mouseOnScreenVec2 = Vec2.zero()
  67. function getMouseOnScreen(pageX: number, pageY: number) {
  68. Vec2.set(
  69. mouseOnScreenVec2,
  70. (pageX - viewport.x) / viewport.width,
  71. (pageY - viewport.y) / viewport.height
  72. );
  73. return mouseOnScreenVec2;
  74. }
  75. const mouseOnCircleVec2 = Vec2.zero()
  76. function getMouseOnCircle(pageX: number, pageY: number) {
  77. Vec2.set(
  78. mouseOnCircleVec2,
  79. ((pageX - viewport.width * 0.5 - viewport.x) / (viewport.width * 0.5)),
  80. ((viewport.height + 2 * (viewport.y - pageY)) / viewport.width) // screen.width intentional
  81. );
  82. return mouseOnCircleVec2;
  83. }
  84. const rotAxis = Vec3.zero()
  85. const rotQuat = Quat.zero()
  86. const rotEyeDir = Vec3.zero()
  87. const rotObjUpDir = Vec3.zero()
  88. const rotObjSideDir = Vec3.zero()
  89. const rotMoveDir = Vec3.zero()
  90. function rotateCamera() {
  91. Vec3.set(rotMoveDir, _moveCurr[0] - _movePrev[0], _moveCurr[1] - _movePrev[1], 0);
  92. let angle = Vec3.magnitude(rotMoveDir);
  93. if (angle) {
  94. Vec3.copy(_eye, object.position)
  95. Vec3.sub(_eye, _eye, target)
  96. Vec3.normalize(rotEyeDir, Vec3.copy(rotEyeDir, _eye))
  97. Vec3.normalize(rotObjUpDir, Vec3.copy(rotObjUpDir, object.up))
  98. Vec3.normalize(rotObjSideDir, Vec3.cross(rotObjSideDir, rotObjUpDir, rotEyeDir))
  99. Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, _moveCurr[1] - _movePrev[1])
  100. Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, _moveCurr[0] - _movePrev[0])
  101. Vec3.add(rotMoveDir, Vec3.copy(rotMoveDir, rotObjUpDir), rotObjSideDir)
  102. Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye))
  103. angle *= rotateSpeed;
  104. Quat.setAxisAngle(rotQuat, rotAxis, angle )
  105. Vec3.transformQuat(_eye, _eye, rotQuat)
  106. Vec3.transformQuat(object.up, object.up, rotQuat)
  107. Vec3.copy(_lastAxis, rotAxis)
  108. _lastAngle = angle;
  109. } else if (!staticMoving && _lastAngle) {
  110. _lastAngle *= Math.sqrt(1.0 - dynamicDampingFactor);
  111. Vec3.sub(_eye, Vec3.copy(_eye, object.position), target)
  112. Quat.setAxisAngle(rotQuat, _lastAxis, _lastAngle)
  113. Vec3.transformQuat(_eye, _eye, rotQuat)
  114. Vec3.transformQuat(object.up, object.up, rotQuat)
  115. }
  116. Vec2.copy(_movePrev, _moveCurr)
  117. }
  118. function zoomCamera () {
  119. const factor = 1.0 + (_zoomEnd[1] - _zoomStart[1]) * zoomSpeed
  120. if (factor !== 1.0 && factor > 0.0) {
  121. Vec3.scale(_eye, _eye, factor)
  122. }
  123. if (staticMoving) {
  124. Vec2.copy(_zoomStart, _zoomEnd)
  125. } else {
  126. _zoomStart[1] += (_zoomEnd[1] - _zoomStart[1]) * dynamicDampingFactor
  127. }
  128. }
  129. const panMouseChange = Vec2.zero()
  130. const panObjUp = Vec3.zero()
  131. const panOffset = Vec3.zero()
  132. function panCamera() {
  133. Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart)
  134. if (Vec2.squaredMagnitude(panMouseChange)) {
  135. Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * panSpeed)
  136. Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), object.up)
  137. Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0])
  138. Vec3.setMagnitude(panObjUp, object.up, panMouseChange[1])
  139. Vec3.add(panOffset, panOffset, panObjUp)
  140. Vec3.add(object.position, object.position, panOffset)
  141. Vec3.add(target, target, panOffset)
  142. if (staticMoving) {
  143. Vec2.copy(_panStart, _panEnd)
  144. } else {
  145. Vec2.sub(panMouseChange, _panEnd, _panStart)
  146. Vec2.scale(panMouseChange, panMouseChange, dynamicDampingFactor)
  147. Vec2.add(_panStart, _panStart, panMouseChange)
  148. }
  149. }
  150. }
  151. /** Ensure the distance between object and target is within the min/max distance */
  152. function checkDistances() {
  153. if (Vec3.squaredMagnitude(_eye) > maxDistance * maxDistance) {
  154. Vec3.setMagnitude(_eye, _eye, maxDistance)
  155. Vec3.add(object.position, target, _eye)
  156. Vec2.copy(_zoomStart, _zoomEnd)
  157. }
  158. if (Vec3.squaredMagnitude(_eye) < minDistance * minDistance) {
  159. Vec3.setMagnitude(_eye, _eye, minDistance)
  160. Vec3.add(object.position, target, _eye)
  161. Vec2.copy(_zoomStart, _zoomEnd)
  162. }
  163. }
  164. /** Update the object's position, direction and up vectors */
  165. function update() {
  166. Vec3.sub(_eye, object.position, target)
  167. rotateCamera()
  168. zoomCamera()
  169. panCamera()
  170. Vec3.add(object.position, target, _eye)
  171. checkDistances()
  172. cameraLookAt(object.position, object.up, object.direction, target)
  173. if (Vec3.squaredDistance(lastPosition, object.position) > EPSILON.Value) {
  174. Vec3.copy(lastPosition, object.position)
  175. }
  176. }
  177. /** Reset object's vectors and the target vector to their initial values */
  178. function reset() {
  179. Vec3.copy(target, target0)
  180. Vec3.copy(object.position, position0)
  181. Vec3.copy(object.up, up0)
  182. Vec3.sub(_eye, object.position, target)
  183. cameraLookAt(object.position, object.up, object.direction, target)
  184. Vec3.copy(lastPosition, object.position)
  185. }
  186. // listeners
  187. function onDrag({ pageX, pageY, buttons, modifiers, isStart }: DragInput) {
  188. if (isStart) {
  189. if (buttons === ButtonsFlag.Primary) {
  190. Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
  191. Vec2.copy(_movePrev, _moveCurr)
  192. } else if (buttons === ButtonsFlag.Auxilary) {
  193. Vec2.copy(_zoomStart, getMouseOnScreen(pageX, pageY))
  194. Vec2.copy(_zoomEnd, _zoomStart)
  195. } else if (buttons === ButtonsFlag.Secondary) {
  196. Vec2.copy(_panStart, getMouseOnScreen(pageX, pageY))
  197. Vec2.copy(_panEnd, _panStart)
  198. }
  199. }
  200. if (buttons === ButtonsFlag.Primary) {
  201. Vec2.copy(_movePrev, _moveCurr)
  202. Vec2.copy(_moveCurr, getMouseOnCircle(pageX, pageY))
  203. } else if (buttons === ButtonsFlag.Auxilary) {
  204. Vec2.copy(_zoomEnd, getMouseOnScreen(pageX, pageY))
  205. } else if (buttons === ButtonsFlag.Secondary) {
  206. Vec2.copy(_panEnd, getMouseOnScreen(pageX, pageY))
  207. }
  208. }
  209. function onWheel({ dy }: WheelInput) {
  210. _zoomStart[1] -= dy
  211. }
  212. function onPinch({ distance, isStart }: PinchInput) {
  213. if (isStart) {
  214. _touchZoomDistanceStart = distance
  215. }
  216. _touchZoomDistanceEnd = distance
  217. const factor = (_touchZoomDistanceStart / _touchZoomDistanceEnd) * zoomSpeed
  218. _touchZoomDistanceStart = _touchZoomDistanceEnd;
  219. Vec3.scale(_eye, _eye, factor)
  220. }
  221. function dispose() {
  222. if (disposed) return
  223. disposed = true
  224. dragSub.unsubscribe()
  225. wheelSub.unsubscribe()
  226. pinchSub.unsubscribe()
  227. }
  228. // force an update at start
  229. update();
  230. return {
  231. viewport,
  232. target,
  233. get dynamicDampingFactor() { return dynamicDampingFactor },
  234. set dynamicDampingFactor(value: number ) { dynamicDampingFactor = value },
  235. get rotateSpeed() { return rotateSpeed },
  236. set rotateSpeed(value: number ) { rotateSpeed = value },
  237. get zoomSpeed() { return zoomSpeed },
  238. set zoomSpeed(value: number ) { zoomSpeed = value },
  239. get panSpeed() { return panSpeed },
  240. set panSpeed(value: number ) { panSpeed = value },
  241. update,
  242. reset,
  243. dispose
  244. }
  245. }
  246. }
  247. export default TrackballControls