author Eberhard Graether / http://egraether.com/
 *  author Mark Lundin 	/ http://mark-lundin.com
 *  author Simone Manini / http://daron1337.github.io
 *  author Luca Antiga 	/ http://lantiga.github.io
  import { EventDispatcher , Quaternion , Vector2 , Vector3 } from "three" ; class TrackballControls extends EventDispatcher { constructor ( object , domElement ) { super ( ) ; const _this = this ; const STATE = { NONE : - 1 , ROTATE : 0 , ZOOM : 1 , PAN : 2 , TOUCH_ROTATE : 3 , TOUCH_ZOOM_PAN : 4 , } ; this . object = object ; this . domElement = domElement !== undefined ? domElement : document ; this . enabled = true ; this . screen = { left : 0 , top : 0 , width : 0 , height : 0 } ; this . length = 10 ; this . scale = 0.5 ; this . scale0 = 0.5 ; this . aspect = 1.0 ; this . rotateSpeed = 1.0 ; this . zoomSpeed = 1.2 ; this . panSpeed = 0.3 ; this . noRotate = false ; this . noZoom = false ; this . noPan = false ; this . staticMoving = false ; this . dynamicDampingFactor = 0.2 ; this . minDistance = 0 ; this . maxDistance = Infinity ; this . autoRotate = false ; this . autoRotateSpeed = 2.0 ; this . keys = [ 65 , 83 , 68 ] ; this . target = new Vector3 ( ) ; const EPS = 0.000001 ; const lastPosition = new Vector3 ( ) ; let _state = STATE . NONE , _prevState = STATE . NONE , _touchZoomDistanceStart = 0 , _touchZoomDistanceEnd = 0 , _lastAngle = 0 ; const _eye = new Vector3 ( ) , _movePrev = new Vector2 ( ) , _moveCurr = new Vector2 ( ) , _lastAxis = new Vector3 ( ) , _zoomStart = new Vector2 ( ) , _zoomEnd = new Vector2 ( ) , _panStart = new Vector2 ( ) , _panEnd = new Vector2 ( ) ; this . target0 = this . target . clone ( ) ; this . position0 = this . object . position . clone ( ) ; this . up0 = this . object . up . clone ( ) ; const changeEvent = { type : "change" } ; const startEvent = { type : "start" } ; const endEvent = { type : "end" } ; this . handleResize = function ( ) { if ( this . domElement === document ) { this . screen . left = 0 ; this . screen . top = 0 ; this . screen . width = window . innerWidth ; this . screen . height = window . innerHeight ; } else { const box = this . domElement . getBoundingClientRect ( ) ; const d = this . domElement . ownerDocument . documentElement ; this . screen . left = box . left + window . pageXOffset - d . clientLeft ; this . screen . top = box . top + window . pageYOffset - d . clientTop ; this . screen . width = box . width ; this . screen . height = box . height ; } } ; const getMouseOnScreen = ( function ( ) { const vector = new Vector2 ( ) ; return function getMouseOnScreen ( pageX , pageY ) { vector . set ( ( pageX - _this . screen . left ) / _this . screen . width , ( pageY - _this . screen . top ) / _this . screen . height ) ; return vector ; } ; } ) ( ) ; const getMouseOnCircle = ( function ( ) { const vector = new Vector2 ( ) ; return function getMouseOnCircle ( pageX , pageY ) { vector . set ( ( pageX - _this . screen . width * 0.5 - _this . screen . left ) / ( _this . screen . width * 0.5 ) , ( _this . screen . height + 2 * ( _this . screen . top - pageY ) ) / _this . screen . width ) ; return vector ; } ; } ) ( ) ; function getAutoRotationAngle ( delta ) { return ( ( 2 * Math . PI ) / 60 / 60 ) * _this . autoRotateSpeed * ( delta * 60.0 ) ; } this . rotateCamera = ( function ( ) { const axis = new Vector3 ( ) , quaternion = new Quaternion ( ) , eyeDirection = new Vector3 ( ) , objectUpDirection = new Vector3 ( ) , objectSidewaysDirection = new Vector3 ( ) , moveDirection = new Vector3 ( ) ; let angle , dx , dy ; return function rotateCamera ( delta ) { dx = _moveCurr . x - _movePrev . x ; dy = _moveCurr . y - _movePrev . y ; moveDirection . set ( dx , dy , 0 ) ; angle = moveDirection . length ( ) ; angle *= _this . rotateSpeed ; if ( _this . autoRotate && _state === STATE . NONE ) { angle = getAutoRotationAngle ( delta ) ; dx = angle ; dy = 0 ; moveDirection . set ( angle , 0 , 0 ) ; } if ( angle ) { _eye . copy ( _this . object . position ) . sub ( _this . target ) ; eyeDirection . copy ( _eye ) . normalize ( ) ; objectUpDirection . copy ( _this . object . up ) . normalize ( ) ; objectSidewaysDirection . crossVectors ( objectUpDirection , eyeDirection ) . normalize ( ) ; objectUpDirection . setLength ( dy ) ; objectSidewaysDirection . setLength ( dx ) ; moveDirection . copy ( objectUpDirection . add ( objectSidewaysDirection ) ) ; axis . crossVectors ( moveDirection , _eye ) . normalize ( ) ; quaternion . setFromAxisAngle ( axis , angle ) ; _eye . applyQuaternion ( quaternion ) ; _this . object . up . applyQuaternion ( quaternion ) ; _lastAxis . copy ( axis ) ; _lastAngle = angle ; } else if ( ! _this . staticMoving && _lastAngle ) { _lastAngle *= Math . sqrt ( 1.0 - _this . dynamicDampingFactor ) ; _eye . copy ( _this . object . position ) . sub ( _this . target ) ; quaternion . setFromAxisAngle ( _lastAxis , _lastAngle ) ; _eye . applyQuaternion ( quaternion ) ; _this . object . up . applyQuaternion ( quaternion ) ; } _movePrev . copy ( _moveCurr ) ; } ; } ) ( ) ; this . zoomCamera = function ( ) { let factor ; if ( _state === STATE . TOUCH_ZOOM_PAN ) { factor = _touchZoomDistanceStart / _touchZoomDistanceEnd ; _touchZoomDistanceStart = _touchZoomDistanceEnd ; _eye . multiplyScalar ( factor ) ; } else { factor = 1.0 + ( _zoomEnd . y - _zoomStart . y ) * _this . zoomSpeed ; if ( factor !== 1.0 && factor > 0.0 ) { if ( _this . object . type === "OrthographicCamera" ) { _this . scale = _this . scale * factor ; } else { _eye . multiplyScalar ( factor ) ; } } if ( _this . staticMoving ) { _zoomStart . copy ( _zoomEnd ) ; } else { _zoomStart . y += ( _zoomEnd . y - _zoomStart . y ) * this . dynamicDampingFactor ; } } } ; this . panCamera = ( function ( ) { const mouseChange = new Vector2 ( ) , objectUp = new Vector3 ( ) , pan = new Vector3 ( ) ; return function panCamera ( ) { mouseChange . copy ( _panEnd ) . sub ( _panStart ) ; if ( mouseChange . lengthSq ( ) ) { mouseChange . multiplyScalar ( _eye . length ( ) * _this . panSpeed ) ; pan . copy ( _eye ) . cross ( _this . object . up ) . setLength ( mouseChange . x ) ; pan . add ( objectUp . copy ( _this . object . up ) . setLength ( mouseChange . y ) ) ; _this . object . position . add ( pan ) ; _this . target . add ( pan ) ; if ( _this . staticMoving ) { _panStart . copy ( _panEnd ) ; } else { _panStart . add ( mouseChange . subVectors ( _panEnd , _panStart ) . multiplyScalar ( _this . dynamicDampingFactor ) ) ; } } } ; } ) ( ) ; this . checkDistances = function ( ) { if ( ! _this . noZoom || ! _this . noPan ) { if ( _eye . lengthSq ( ) > _this . maxDistance * _this . maxDistance ) { _this . object . position . addVectors ( _this . target , _eye . setLength ( _this . maxDistance ) ) ; _zoomStart . copy ( _zoomEnd ) ; } if ( _eye . lengthSq ( ) < _this . minDistance * _this . minDistance ) { _this . object . position . addVectors ( _this . target , _eye . setLength ( _this . minDistance ) ) ; _zoomStart . copy ( _zoomEnd ) ; } } } ; this . update = function ( delta ) { if ( _this . enabled === false ) return ; delta = delta || 0.0 ; _eye . subVectors ( _this . object . position , _this . target ) ; if ( ! _this . noRotate ) { _this . rotateCamera ( delta ) ; } if ( ! _this . noZoom ) { _this . zoomCamera ( ) ; } if ( ! _this . noPan ) { _this . panCamera ( ) ; } if ( _this . object . type === "OrthographicCamera" ) { _this . object . top = _this . scale ; _this . object . bottom = - _this . scale ; _this . object . right = _this . scale * _this . aspect ; _this . object . left = - _this . scale * _this . aspect ; _this . object . updateProjectionMatrix ( ) ; } else { _this . object . position . addVectors ( _this . target , _eye ) ; } _this . checkDistances ( ) ; _this . object . lookAt ( _this . target ) ; if ( lastPosition . distanceToSquared ( _this . object . position ) > EPS ) { _this . dispatchEvent ( changeEvent ) ; lastPosition . copy ( _this . object . position ) ; } } ; this . reset = function ( ) { if ( _this . enabled === false ) return ; _state = STATE . NONE ; _prevState = STATE . NONE ; _this . target . copy ( _this . target0 ) ; if ( _this . object . type === "OrthographicCamera" ) { _this . scale = _this . scale0 ; _this . object . top = _this . scale ; _this . object . bottom = - _this . scale ; _this . object . right = _this . scale * _this . aspect ; _this . object . left = - _this . scale * _this . aspect ; } else { _this . object . position . copy ( _this . position0 ) ; _this . object . up . copy ( _this . up0 ) ; } _eye . subVectors ( _this . object . position , _this . target ) ; _this . object . lookAt ( _this . target ) ; _this . dispatchEvent ( changeEvent ) ; lastPosition . copy ( _this . object . position ) ; } ; function keydown ( event ) { if ( _this . enabled === false ) return ; window . removeEventListener ( "keydown" , keydown ) ; _prevState = _state ; if ( _state !== STATE . NONE ) { return ; } else if ( event . keyCode === _this . keys [ STATE . ROTATE ] && ! _this . noRotate ) { _state = STATE . ROTATE ; } else if ( event . keyCode === _this . keys [ STATE . ZOOM ] && ! _this . noZoom ) { _state = STATE . ZOOM ; } else if ( event . keyCode === _this . keys [ STATE . PAN ] && ! _this . noPan ) { _state = STATE . PAN ; } } function keyup ( _event ) { if ( _this . enabled === false ) return ; _state = _prevState ; window . addEventListener ( "keydown" , keydown , false ) ; } function mousedown ( event ) { if ( _this . enabled === false ) return ; event . preventDefault ( ) ; event . stopPropagation ( ) ; if ( _state === STATE . NONE ) { _state = event . button ; } if ( _state === STATE . ROTATE && ! _this . noRotate ) { _moveCurr . copy ( getMouseOnCircle ( event . pageX , event . pageY ) ) ; _movePrev . copy ( _moveCurr ) ; } else if ( _state === STATE . ZOOM && ! _this . noZoom ) { _zoomStart . copy ( getMouseOnScreen ( event . pageX , event . pageY ) ) ; _zoomEnd . copy ( _zoomStart ) ; } else if ( _state === STATE . PAN && ! _this . noPan ) { _panStart . copy ( getMouseOnScreen ( event . pageX , event . pageY ) ) ; _panEnd . copy ( _panStart ) ; } document . addEventListener ( "mousemove" , mousemove , false ) ; document . addEventListener ( "mouseup" , mouseup , false ) ; _this . dispatchEvent ( startEvent ) ; } function mousemove ( event ) { if ( _this . enabled === false ) return ; event . preventDefault ( ) ; event . stopPropagation ( ) ; if ( _state === STATE . ROTATE && ! _this . noRotate ) { _moveCurr . copy ( getMouseOnCircle ( event . pageX , event . pageY ) ) ; } else if ( _state === STATE . ZOOM && ! _this . noZoom ) { _zoomEnd . copy ( getMouseOnScreen ( event . pageX , event . pageY ) ) ; } else if ( _state === STATE . PAN && ! _this . noPan ) { _panEnd . copy ( getMouseOnScreen ( event . pageX , event . pageY ) ) ; } } function mouseup ( event ) { if ( _this . enabled === false ) return ; event . preventDefault ( ) ; event . stopPropagation ( ) ; _state = STATE . NONE ; document . removeEventListener ( "mousemove" , mousemove ) ; document . removeEventListener ( "mouseup" , mouseup ) ; _this . dispatchEvent ( endEvent ) ; } function mousewheel ( event ) { if ( _this . enabled === false ) return ; if ( _this . noZoom === true ) return ; event . preventDefault ( ) ; event . stopPropagation ( ) ; switch ( event . deltaMode ) { case 2 : _zoomStart . y -= event . deltaY * 0.025 ; break ; case 1 : _zoomStart . y -= event . deltaY * 0.01 ; break ; default : _zoomStart . y -= event . deltaY * 0.00025 ; break ; } _this . dispatchEvent ( startEvent ) ; _this . dispatchEvent ( endEvent ) ; } function touchstart ( event ) { if ( _this . enabled === false ) return ; event . preventDefault ( ) ; let x , y , dx , dy ; switch ( event . touches . length ) { case 1 : _state = STATE . TOUCH_ROTATE ; _moveCurr . copy ( getMouseOnCircle ( event . touches [ 0 ] . pageX , event . touches [ 0 ] . pageY ) ) ; _movePrev . copy ( _moveCurr ) ; break ; default : _state = STATE . TOUCH_ZOOM_PAN ; dx = event . touches [ 0 ] . pageX - event . touches [ 1 ] . pageX ; dy = event . touches [ 0 ] . pageY - event . touches [ 1 ] . pageY ; _touchZoomDistanceEnd = _touchZoomDistanceStart = Math . sqrt ( dx * dx + dy * dy ) ; x = ( event . touches [ 0 ] . pageX + event . touches [ 1 ] . pageX ) / 2 ; y = ( event . touches [ 0 ] . pageY + event . touches [ 1 ] . pageY ) / 2 ; _panStart . copy ( getMouseOnScreen ( x , y ) ) ; _panEnd . copy ( _panStart ) ; break ; } _this . dispatchEvent ( startEvent ) ; } function touchmove ( event ) { if ( _this . enabled === false ) return ; event . preventDefault ( ) ; event . stopPropagation ( ) ; let x , y , dx , dy ; switch ( event . touches . length ) { case 1 : _movePrev . copy ( _moveCurr ) ; _moveCurr . copy ( getMouseOnCircle ( event . touches [ 0 ] . pageX , event . touches [ 0 ] . pageY ) ) ; break ; default : dx = event . touches [ 0 ] . pageX - event . touches [ 1 ] . pageX ; dy = event . touches [ 0 ] . pageY - event . touches [ 1 ] . pageY ; _touchZoomDistanceEnd = Math . sqrt ( dx * dx + dy * dy ) ; x = ( event . touches [ 0 ] . pageX + event . touches [ 1 ] . pageX ) / 2 ; y = ( event . touches [ 0 ] . pageY + event . touches [ 1 ] . pageY ) / 2 ; _panEnd . copy ( getMouseOnScreen ( x , y ) ) ; break ; } } function touchend ( event ) { if ( _this . enabled === false ) return ; switch ( event . touches . length ) { case 0 : _state = STATE . NONE ; break ; case 1 : _state = STATE . TOUCH_ROTATE ; _moveCurr . copy ( getMouseOnCircle ( event . touches [ 0 ] . pageX , event . touches [ 0 ] . pageY ) ) ; _movePrev . copy ( _moveCurr ) ; break ; } _this . dispatchEvent ( endEvent ) ; } function contextmenu ( event ) { if ( _this . enabled === false ) return ; event . preventDefault ( ) ; } this . dispose = function ( ) { this . domElement . removeEventListener ( "contextmenu" , contextmenu , false ) ; this . domElement . removeEventListener ( "mousedown" , mousedown , false ) ; this . domElement . removeEventListener ( "wheel" , mousewheel , false ) ; this . domElement . removeEventListener ( "touchstart" , touchstart , false ) ; this . domElement . removeEventListener ( "touchend" , touchend , false ) ; this . domElement . removeEventListener ( "touchmove" , touchmove , false ) ; document . removeEventListener ( "mousemove" , mousemove , false ) ; document . removeEventListener ( "mouseup" , mouseup , false ) ; window . removeEventListener ( "keydown" , keydown , false ) ; window . removeEventListener ( "keyup" , keyup , false ) ; } ; this . domElement . addEventListener ( "contextmenu" , contextmenu , false ) ; this . domElement . addEventListener ( "mousedown" , mousedown , false ) ; this . domElement . addEventListener ( "wheel" , mousewheel , false ) ; this . domElement . addEventListener ( "touchstart" , touchstart , false ) ; this . domElement . addEventListener ( "touchend" , touchend , false ) ; this . domElement . addEventListener ( "touchmove" , touchmove , false ) ; window . addEventListener ( "keydown" , keydown , false ) ; window . addEventListener ( "keyup" , keyup , false ) ; this . handleResize ( ) ; this . update ( ) ; } } export default TrackballControls ;