TrackballControls class
class TrackballControls extends EventEmitter { int _state, _prevState; Object3D object; Element domElement; bool enabled; Math.Rectangle screen; num rotateSpeed, zoomSpeed, panSpeed; bool noRotate, noZoom, noPan, noRoll; bool staticMoving; num dynamicDampingFactor; num minDistance, maxDistance; List keys; Vector3 target; Vector3 _eye; Vector3 _rotateStart, _rotateEnd; Vector2 _zoomStart, _zoomEnd; Vector2 _panStart, _panEnd; Vector3 lastPosition; StreamSubscription<MouseEvent> mouseMoveStream; StreamSubscription<MouseEvent> mouseUpStream; StreamSubscription<KeyboardEvent> keydownStream; EventEmitterEvent changeEvent; TrackballControls( this.object, [Element domElement] ) { this.domElement = ( domElement != null) ? domElement : document; // API enabled = true; screen = new Math.Rectangle( 0, 0, 0, 0 ); rotateSpeed = 1.0; zoomSpeed = 1.2; panSpeed = 0.3; noRotate = false; noZoom = false; noPan = false; noRoll = false; staticMoving = false; dynamicDampingFactor = 0.2; minDistance = 0; maxDistance = double.INFINITY; keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; // internals target = new Vector3.zero(); lastPosition = new Vector3.zero(); _state = STATE.NONE; _prevState = STATE.NONE; _eye = new Vector3.zero(); _rotateStart = new Vector3.zero(); _rotateEnd = new Vector3.zero(); _zoomStart = new Vector2.zero(); _zoomEnd = new Vector2.zero(); _panStart = new Vector2.zero(); _panEnd = new Vector2.zero(); changeEvent = new EventEmitterEvent(type: 'change'); domElement ..onContextMenu.listen(( event ) => event.preventDefault()) ..onMouseDown.listen(mousedown) ..onMouseWheel.listen(mousewheel) ..onTouchStart.listen(touchstart) ..onTouchEnd.listen(touchstart) ..onTouchMove.listen(touchmove); //this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox keydownStream = window.onKeyDown.listen(keydown); window.onKeyUp.listen(keyup); handleResize(); } // methods handleResize () { if ( domElement == document ) { screen = new Math.Rectangle(0, 0, window.innerWidth, window.innerHeight); } else { screen = domElement.getBoundingClientRect(); } } handleEvent( event ) { dispatchEvent(event); } getMouseOnScreen( clientX, clientY ) => new Vector2( ( clientX - screen.left ) / screen.width, ( clientY - screen.top ) / screen.height ); getMouseProjectionOnBall( clientX, clientY ) { var mouseOnBall = new Vector3( ( clientX - screen.width * 0.5 - screen.left ) / ( screen.width * 0.5 ), ( screen.height * 0.5 + screen.top - clientY ) / ( screen.height * 0.5 ), 0.0 ); var length = mouseOnBall.length; if ( noRoll ) { if ( length < Math.SQRT1_2 ) { mouseOnBall.z = Math.sqrt( 1.0 - length * length ); } else { mouseOnBall.z = 0.5 / length; } } else if ( length > 1.0 ) { mouseOnBall.normalize(); } else { mouseOnBall.z = Math.sqrt( 1.0 - length * length ); } _eye.setFrom(object.position ).sub( target ); Vector3 projection = object.up.clone().normalize().scale( mouseOnBall.y ); projection.add( object.up.cross( _eye ).normalize().scale( mouseOnBall.x ) ); projection.add( _eye.normalize().scale( mouseOnBall.z ) ); return projection; } rotateCamera() { var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length / _rotateEnd.length ); if ( !angle.isNaN && angle != 0) { Vector3 axis = _rotateStart.cross(_rotateEnd ).normalize(); Quaternion quaternion = new Quaternion.identity(); angle *= rotateSpeed; quaternion.setAxisAngle( axis, angle ); quaternion.rotate( _eye ); quaternion.rotate( object.up ); quaternion.rotate( _rotateEnd ); if ( staticMoving ) { _rotateStart.setFrom( _rotateEnd ); } else { quaternion.setAxisAngle( axis, -angle * ( dynamicDampingFactor - 1.0 ) ); quaternion.rotate( _rotateStart ); } } } zoomCamera() { var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * zoomSpeed; if ( factor != 1.0 && factor > 0.0 ) { _eye.scale( factor ); if ( staticMoving ) { _zoomStart.setFrom(_zoomEnd); } else { _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; } } } panCamera() { Vector2 mouseChange = _panEnd - _panStart; if ( mouseChange.length != 0.0) { mouseChange.scale( _eye.length * panSpeed ); Vector3 pan = _eye.cross( object.up ).normalize().scale( mouseChange.x ); pan += object.up.clone().normalize().scale( mouseChange.y ); object.position.add( pan ); target.add( pan ); if ( staticMoving ) { _panStart = _panEnd; } else { _panStart += (_panEnd - _panStart).scale(dynamicDampingFactor); } } } checkDistances() { if ( !noZoom || !noPan ) { if ( object.position.length2 > maxDistance * maxDistance ) { object.position.normalize().scale( maxDistance ); } if ( _eye.length2 < minDistance * minDistance ) { object.position = target + _eye.normalize().scale(minDistance); } } } update() { _eye.setFrom( object.position ).sub( target ); if ( !noRotate ) { rotateCamera(); } if ( !noZoom ) { zoomCamera(); } if ( !noPan ) { panCamera(); } object.position = target + _eye; checkDistances(); object.lookAt( target ); // distanceToSquared if ( (lastPosition - object.position).length2 > 0.0 ) { // dispatchEvent( changeEvent ); lastPosition.setFrom( object.position ); } } // listeners keydown( KeyboardEvent event ) { if ( !enabled ) return; keydownStream.cancel(); _prevState = _state; if ( _state != STATE.NONE ) { return; } else if ( event.keyCode == keys[ STATE.ROTATE ] && !noRotate ) { _state = STATE.ROTATE; } else if ( event.keyCode == keys[ STATE.ZOOM ] && !noZoom ) { _state = STATE.ZOOM; } else if ( event.keyCode == keys[ STATE.PAN ] && !noPan ) { _state = STATE.PAN; } } keyup( KeyboardEvent event ) { if ( !enabled ) { return; } _state = _prevState; keydownStream = window.onKeyDown.listen( keydown ); } mousedown( MouseEvent event ) { if ( !enabled ) { return; } event.preventDefault(); event.stopPropagation(); if ( _state == STATE.NONE ) { _state = event.button; } if ( _state == STATE.ROTATE && !noRotate ) { _rotateStart = getMouseProjectionOnBall( event.client.x, event.client.y ); _rotateEnd = _rotateStart; } else if ( _state == STATE.ZOOM && !noZoom ) { _zoomStart = getMouseOnScreen( event.client.x, event.client.y ); _zoomEnd = _zoomStart; } else if ( _state == STATE.PAN && !noPan ) { _panStart = getMouseOnScreen( event.client.x, event.client.y ); _panEnd = _panStart; } mouseMoveStream = document.onMouseMove.listen( mousemove ); mouseUpStream = document.onMouseUp.listen( mouseup ); } mousemove( MouseEvent event ) { if ( !enabled ) { return; } if ( _state == STATE.ROTATE && !noRotate ) { _rotateEnd = getMouseProjectionOnBall( event.client.x, event.client.y ); } else if ( _state == STATE.ZOOM && !noZoom ) { _zoomEnd = getMouseOnScreen( event.client.x, event.client.y ); } else if ( _state == STATE.PAN && !noPan ) { _panEnd = getMouseOnScreen( event.client.x, event.client.y ); } } mouseup( MouseEvent event ) { if ( !enabled ) { return; } event.preventDefault(); event.stopPropagation(); _state = STATE.NONE; mouseMoveStream.cancel(); mouseUpStream.cancel(); } mousewheel( WheelEvent event ) { if ( !enabled ) { return; } event.preventDefault(); event.stopPropagation(); var delta = 0; // TODO(nelsonsilva) - check this! if ( event.deltaY != 0 ) { // WebKit / Opera / Explorer 9 delta = event.deltaY / 40; } else if ( event.detail != 0 ) { // Firefox delta = - event.detail / 3; } _zoomStart.y += ( 1 / delta ) * 0.05; } touchstart( TouchEvent event ) { if ( !enabled ) { return; } event.preventDefault(); switch ( event.touches.length ) { case 1: _rotateStart = _rotateEnd = getMouseProjectionOnBall( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; case 2: _zoomStart = _zoomEnd = getMouseOnScreen( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; case 3: _panStart = _panEnd = getMouseOnScreen( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; } } touchmove( TouchEvent event ) { if ( !enabled ) { return; } event.preventDefault(); switch ( event.touches.length ) { case 1: _rotateEnd = getMouseProjectionOnBall( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; case 2: _zoomEnd = getMouseOnScreen( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; case 3: _panEnd = getMouseOnScreen( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; } } }
Extends
EventEmitter > TrackballControls
Constructors
new TrackballControls(Object3D object, [Element domElement]) #
Creates a new Object instance.
Object instances have no meaningful state, and are only useful through their identity. An Object instance is equal to itself only.
docs inherited from Object
TrackballControls( this.object, [Element domElement] ) { this.domElement = ( domElement != null) ? domElement : document; // API enabled = true; screen = new Math.Rectangle( 0, 0, 0, 0 ); rotateSpeed = 1.0; zoomSpeed = 1.2; panSpeed = 0.3; noRotate = false; noZoom = false; noPan = false; noRoll = false; staticMoving = false; dynamicDampingFactor = 0.2; minDistance = 0; maxDistance = double.INFINITY; keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; // internals target = new Vector3.zero(); lastPosition = new Vector3.zero(); _state = STATE.NONE; _prevState = STATE.NONE; _eye = new Vector3.zero(); _rotateStart = new Vector3.zero(); _rotateEnd = new Vector3.zero(); _zoomStart = new Vector2.zero(); _zoomEnd = new Vector2.zero(); _panStart = new Vector2.zero(); _panEnd = new Vector2.zero(); changeEvent = new EventEmitterEvent(type: 'change'); domElement ..onContextMenu.listen(( event ) => event.preventDefault()) ..onMouseDown.listen(mousedown) ..onMouseWheel.listen(mousewheel) ..onTouchStart.listen(touchstart) ..onTouchEnd.listen(touchstart) ..onTouchMove.listen(touchmove); //this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox keydownStream = window.onKeyDown.listen(keydown); window.onKeyUp.listen(keyup); handleResize(); }
Properties
EventEmitterEvent changeEvent #
EventEmitterEvent changeEvent
StreamSubscription<KeyboardEvent> keydownStream #
StreamSubscription<KeyboardEvent> keydownStream
StreamSubscription<MouseEvent> mouseMoveStream #
StreamSubscription<MouseEvent> mouseMoveStream
StreamSubscription<MouseEvent> mouseUpStream #
StreamSubscription<MouseEvent> mouseUpStream
Methods
dynamic addEventListener(type, listener) #
inherited from EventEmitter
addEventListener( type, listener ) { if ( listeners[ type ] == null ) { listeners[ type ] = []; } if ( listeners[ type ].indexOf( listener ) == - 1 ) { listeners[ type ].add( listener ); } }
dynamic checkDistances() #
checkDistances() { if ( !noZoom || !noPan ) { if ( object.position.length2 > maxDistance * maxDistance ) { object.position.normalize().scale( maxDistance ); } if ( _eye.length2 < minDistance * minDistance ) { object.position = target + _eye.normalize().scale(minDistance); } } }
dynamic dispatchEvent(event) #
inherited from EventEmitter
dispatchEvent( event ) { if ( listeners[ event.type ] != null ) { listeners[ event.type ].forEach((listener) => listener( event )); } }
dynamic getMouseOnScreen(clientX, clientY) #
getMouseOnScreen( clientX, clientY ) => new Vector2( ( clientX - screen.left ) / screen.width, ( clientY - screen.top ) / screen.height );
dynamic getMouseProjectionOnBall(clientX, clientY) #
getMouseProjectionOnBall( clientX, clientY ) { var mouseOnBall = new Vector3( ( clientX - screen.width * 0.5 - screen.left ) / ( screen.width * 0.5 ), ( screen.height * 0.5 + screen.top - clientY ) / ( screen.height * 0.5 ), 0.0 ); var length = mouseOnBall.length; if ( noRoll ) { if ( length < Math.SQRT1_2 ) { mouseOnBall.z = Math.sqrt( 1.0 - length * length ); } else { mouseOnBall.z = 0.5 / length; } } else if ( length > 1.0 ) { mouseOnBall.normalize(); } else { mouseOnBall.z = Math.sqrt( 1.0 - length * length ); } _eye.setFrom(object.position ).sub( target ); Vector3 projection = object.up.clone().normalize().scale( mouseOnBall.y ); projection.add( object.up.cross( _eye ).normalize().scale( mouseOnBall.x ) ); projection.add( _eye.normalize().scale( mouseOnBall.z ) ); return projection; }
dynamic handleEvent(event) #
handleEvent( event ) { dispatchEvent(event); }
dynamic handleResize() #
handleResize () { if ( domElement == document ) { screen = new Math.Rectangle(0, 0, window.innerWidth, window.innerHeight); } else { screen = domElement.getBoundingClientRect(); } }
dynamic keydown(KeyboardEvent event) #
keydown( KeyboardEvent event ) { if ( !enabled ) return; keydownStream.cancel(); _prevState = _state; if ( _state != STATE.NONE ) { return; } else if ( event.keyCode == keys[ STATE.ROTATE ] && !noRotate ) { _state = STATE.ROTATE; } else if ( event.keyCode == keys[ STATE.ZOOM ] && !noZoom ) { _state = STATE.ZOOM; } else if ( event.keyCode == keys[ STATE.PAN ] && !noPan ) { _state = STATE.PAN; } }
dynamic keyup(KeyboardEvent event) #
keyup( KeyboardEvent event ) { if ( !enabled ) { return; } _state = _prevState; keydownStream = window.onKeyDown.listen( keydown ); }
dynamic mousedown(MouseEvent event) #
mousedown( MouseEvent event ) { if ( !enabled ) { return; } event.preventDefault(); event.stopPropagation(); if ( _state == STATE.NONE ) { _state = event.button; } if ( _state == STATE.ROTATE && !noRotate ) { _rotateStart = getMouseProjectionOnBall( event.client.x, event.client.y ); _rotateEnd = _rotateStart; } else if ( _state == STATE.ZOOM && !noZoom ) { _zoomStart = getMouseOnScreen( event.client.x, event.client.y ); _zoomEnd = _zoomStart; } else if ( _state == STATE.PAN && !noPan ) { _panStart = getMouseOnScreen( event.client.x, event.client.y ); _panEnd = _panStart; } mouseMoveStream = document.onMouseMove.listen( mousemove ); mouseUpStream = document.onMouseUp.listen( mouseup ); }
dynamic mousemove(MouseEvent event) #
mousemove( MouseEvent event ) { if ( !enabled ) { return; } if ( _state == STATE.ROTATE && !noRotate ) { _rotateEnd = getMouseProjectionOnBall( event.client.x, event.client.y ); } else if ( _state == STATE.ZOOM && !noZoom ) { _zoomEnd = getMouseOnScreen( event.client.x, event.client.y ); } else if ( _state == STATE.PAN && !noPan ) { _panEnd = getMouseOnScreen( event.client.x, event.client.y ); } }
dynamic mouseup(MouseEvent event) #
mouseup( MouseEvent event ) { if ( !enabled ) { return; } event.preventDefault(); event.stopPropagation(); _state = STATE.NONE; mouseMoveStream.cancel(); mouseUpStream.cancel(); }
dynamic mousewheel(WheelEvent event) #
mousewheel( WheelEvent event ) { if ( !enabled ) { return; } event.preventDefault(); event.stopPropagation(); var delta = 0; // TODO(nelsonsilva) - check this! if ( event.deltaY != 0 ) { // WebKit / Opera / Explorer 9 delta = event.deltaY / 40; } else if ( event.detail != 0 ) { // Firefox delta = - event.detail / 3; } _zoomStart.y += ( 1 / delta ) * 0.05; }
dynamic panCamera() #
panCamera() { Vector2 mouseChange = _panEnd - _panStart; if ( mouseChange.length != 0.0) { mouseChange.scale( _eye.length * panSpeed ); Vector3 pan = _eye.cross( object.up ).normalize().scale( mouseChange.x ); pan += object.up.clone().normalize().scale( mouseChange.y ); object.position.add( pan ); target.add( pan ); if ( staticMoving ) { _panStart = _panEnd; } else { _panStart += (_panEnd - _panStart).scale(dynamicDampingFactor); } } }
dynamic removeEventListener(type, listener) #
inherited from EventEmitter
removeEventListener ( type, listener ) { var index = listeners[ type ].indexOf( listener ); if ( index != - 1 ) { listeners[ type ].removeAt( index ); } }
dynamic rotateCamera() #
rotateCamera() { var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length / _rotateEnd.length ); if ( !angle.isNaN && angle != 0) { Vector3 axis = _rotateStart.cross(_rotateEnd ).normalize(); Quaternion quaternion = new Quaternion.identity(); angle *= rotateSpeed; quaternion.setAxisAngle( axis, angle ); quaternion.rotate( _eye ); quaternion.rotate( object.up ); quaternion.rotate( _rotateEnd ); if ( staticMoving ) { _rotateStart.setFrom( _rotateEnd ); } else { quaternion.setAxisAngle( axis, -angle * ( dynamicDampingFactor - 1.0 ) ); quaternion.rotate( _rotateStart ); } } }
dynamic touchmove(TouchEvent event) #
touchmove( TouchEvent event ) { if ( !enabled ) { return; } event.preventDefault(); switch ( event.touches.length ) { case 1: _rotateEnd = getMouseProjectionOnBall( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; case 2: _zoomEnd = getMouseOnScreen( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; case 3: _panEnd = getMouseOnScreen( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; } }
dynamic touchstart(TouchEvent event) #
touchstart( TouchEvent event ) { if ( !enabled ) { return; } event.preventDefault(); switch ( event.touches.length ) { case 1: _rotateStart = _rotateEnd = getMouseProjectionOnBall( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; case 2: _zoomStart = _zoomEnd = getMouseOnScreen( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; case 3: _panStart = _panEnd = getMouseOnScreen( event.touches[ 0 ].page.x, event.touches[ 0 ].page.y ); break; } }
dynamic update() #
update() { _eye.setFrom( object.position ).sub( target ); if ( !noRotate ) { rotateCamera(); } if ( !noZoom ) { zoomCamera(); } if ( !noPan ) { panCamera(); } object.position = target + _eye; checkDistances(); object.lookAt( target ); // distanceToSquared if ( (lastPosition - object.position).length2 > 0.0 ) { // dispatchEvent( changeEvent ); lastPosition.setFrom( object.position ); } }
dynamic zoomCamera() #
zoomCamera() { var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * zoomSpeed; if ( factor != 1.0 && factor > 0.0 ) { _eye.scale( factor ); if ( staticMoving ) { _zoomStart.setFrom(_zoomEnd); } else { _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; } } }