API Docs for: 1.0.2
Show:

File: GIScene\Control\PanOrbitZoomCenter.js

/**
 * Control to examine Objects and move around on a terrain. Rotates the Camera around a center, zooms in and out, pans the camera and moves to a new rotation center on double click. 
 *
 * @namespace GIScene
 * @class Control.PanOrbitZoomCenter
 * @constructor
 * @extends GIScene.Control
 * @param {THREE.Camera} object The camera object to be used with this control
 * @param {DOMElement} [domElement] The DOM Element to which the mouse events will be added
 * 
 * @author qiao / https://github.com/qiao
 * @author mrdoob / http://mrdoob.com
 * @author alteredq / http://alteredqualia.com/
 * modified by m.auer
 */

GIScene.Control.PanOrbitZoomCenter = function ( object, domElement ) {
	
	GIScene.Control.call( this );

	this.object = object;
	this.domElement = ( domElement !== undefined ) ? domElement : document;

	// API

	this.center = null;
	
	this.userZoom = true;
	this.userZoomSpeed = 1.0;

	this.userRotate = true;
	this.userRotateSpeed = 1.0;

	this.autoRotate = false;
	this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60

	// mca
	this.userPan = true;
	this.userPanSpeed = 0.825;

	
	// internals

	var scope = this;

	var EPS = 0.000001;
	var PIXELS_PER_ROUND = 1800;

	var rotateStart = new THREE.Vector2();
	var rotateEnd = new THREE.Vector2();
	var rotateDelta = new THREE.Vector2();

	var zoomStart = new THREE.Vector2();
	var zoomEnd = new THREE.Vector2();
	var zoomDelta = new THREE.Vector2();

	//mca
	var _eye 	= new THREE.Vector3(),
	_panStart 	= new THREE.Vector2(),
	_panEnd 	= new THREE.Vector2(),
	mouse 		= new THREE.Vector3(),
	projector 	= new THREE.Projector();

	//mca end
	var phiDelta = 0;
	var thetaDelta = 0;
	var scale = 1;

	var lastPosition = new THREE.Vector3();

	var STATE = { NONE : -1, ROTATE : 0, ZOOM : 1, PAN: 2 }; //mca 0=left, 1=middle or both, 2=right
	var state = STATE.NONE;

	// events

	var changeEvent = { type: 'change' };


	this.rotateLeft = function ( angle ) {

		if ( angle === undefined ) {

			angle = getAutoRotationAngle();

		}

		thetaDelta -= angle;

	};

	this.rotateRight = function ( angle ) {

		if ( angle === undefined ) {

			angle = getAutoRotationAngle();

		}

		thetaDelta += angle;

	};

	this.rotateUp = function ( angle ) {

		if ( angle === undefined ) {

			angle = getAutoRotationAngle();

		}

		phiDelta -= angle;

	};

	this.rotateDown = function ( angle ) {

		if ( angle === undefined ) {

			angle = getAutoRotationAngle();

		}

		phiDelta += angle;

	};

	this.zoomIn = function ( zoomScale ) {

		if ( zoomScale === undefined ) {

			zoomScale = getZoomScale();

		}

		scale /= zoomScale;

	};

	this.zoomOut = function ( zoomScale ) {

		if ( zoomScale === undefined ) {

			zoomScale = getZoomScale();

		}

		scale *= zoomScale;

	};
	
	//mca inserted from TrackballControls _this-->scope
	this.panCamera = function () {

		var mouseChange = _panEnd.clone().sub( _panStart );
		
		if ( mouseChange.lengthSq() ) {

			mouseChange.multiplyScalar( _eye.length() * scope.userPanSpeed );  //panSpeed

			// var pan = _eye.clone().cross( scope.object.up ).setLength( mouseChange.x );
			// pan.add( scope.object.up.clone().setLength( mouseChange.y ) );
			var pan = _eye.clone().cross( scope.object.up ).setLength( mouseChange.x );
			pan.sub( _eye.clone().cross( _eye.clone().cross( scope.object.up ) ).setLength( mouseChange.y ) ); //mca: don't pan along y axis, pan perpendicular to eye vector
			

			scope.object.position.add( pan );
			var oldCenter = scope.center.clone();
			scope.center.add( pan ); //target-->center

			_panStart = _panEnd;

		}

	};

	this.update = function () {
		
		if (state != STATE.PAN) {
		
			var position = this.object.position;
			var offset = position.clone().sub( this.center );
	
			// angle from z-axis around y-axis
	
			var theta = Math.atan2( offset.x, offset.z );
	
			// angle from y-axis
	
			var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
	
			if ( this.autoRotate ) {
	
				this.rotateLeft( getAutoRotationAngle() );
	
			}
	
			theta += thetaDelta;
			phi += phiDelta;
	
			// restrict phi to be betwee EPS and PI-EPS
	
			phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
	
			var radius = offset.length();
			offset.x = radius * Math.sin( phi ) * Math.sin( theta );
			offset.y = radius * Math.cos( phi );
			offset.z = radius * Math.sin( phi ) * Math.cos( theta );
			//zoom perspective
			offset.multiplyScalar( scale );
			//zoom ortho; mca
			if(this.object.inOrthographicMode){
				this.object.toOrthographic();
			}
			
			
			position.copy( this.center ).add( offset );
	
			this.object.lookAt( this.center );
	
			thetaDelta = 0;
			phiDelta = 0;
			scale = 1;
		}
		//mca PAN target --> center
		else {
			_eye.copy( scope.object.position ).sub( scope.center );
			scope.panCamera();
			scope.object.position.addVectors( scope.center, _eye );
			//scope.object.lookAt( scope.center );
		}
		//mca end

		if ( lastPosition.distanceTo( this.object.position ) > 0 ) {
			/**
			 *@event change 
			 */
			this.dispatchEvent( changeEvent );
			lastPosition.copy( this.object.position );
		}

	}.bind(this);

	function getAutoRotationAngle() {

		return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;

	}

	function getZoomScale() {

		return Math.pow( 0.95, scope.userZoomSpeed );

	}

	function onMouseDown( event ) {
		/**
		 *@event beforechange 
		 */
		scope.dispatchEvent({type:'beforechange'});
		
		if ( !scope.userRotate ) return;

		event.preventDefault();

		if ( event.button === STATE.ROTATE ){	//|| event.button === 2 ) {

			state = STATE.ROTATE;

			rotateStart.set( event.clientX, event.clientY );

		} else if ( event.button === STATE.ZOOM ) {

			state = STATE.ZOOM;

			zoomStart.set( event.clientX, event.clientY );

		} 
		//mca
		else if (event.button === STATE.PAN) {
			state = STATE.PAN;
			_panStart = _panEnd = new THREE.Vector2(event.clientX, event.clientY).multiplyScalar(1/500);
		}

		document.addEventListener( 'mousemove', onMouseMove, false );
		document.addEventListener( 'mouseup', onMouseUp, false );

	}

	function onMouseMove( event ) {

		event.preventDefault();
		
		
		
		if ( state === STATE.ROTATE ) {

			rotateEnd.set( event.clientX, event.clientY );
			rotateDelta.subVectors( rotateEnd, rotateStart );

			scope.rotateLeft( 2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed );
			scope.rotateUp( 2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed );

			rotateStart.copy( rotateEnd );

		} else if ( state === STATE.ZOOM ) {

			zoomEnd.set( event.clientX, event.clientY );
			zoomDelta.subVectors( zoomEnd, zoomStart );

			if ( zoomDelta.y > 0 ) {

				scope.zoomIn();

			} else {

				scope.zoomOut();

			}

			zoomStart.copy( zoomEnd );

		}
		//mca
		else if ( state === STATE.PAN ) {
			_panEnd = new THREE.Vector2(event.clientX, event.clientY).multiplyScalar(1/500);
		};

	}

	function onMouseUp( event ) {

		if ( ! scope.userRotate ) return;

		document.removeEventListener( 'mousemove', onMouseMove, false );
		document.removeEventListener( 'mouseup', onMouseUp, false );

		state = STATE.NONE;
		/**
		 *@event afterchange 
		 */
		scope.dispatchEvent({type:'afterchange'});
	}

	function onMouseWheel( event ) {

		if ( ! scope.userZoom ) return;

		if ( ( event.wheelDelta && event.wheelDelta > 0 ) || ( event.detail && event.detail > 0 ) ) {

			scope.zoomOut();

		} else {

			scope.zoomIn();

		}

	}
	
	var onDoubleClick = function( event ){
		event.preventDefault();
		//get mouse ScreenCoords
		var viewPortCoords = GIScene.Utils.getViewportCoordsFromDOMEvent(this.domElement,event);
		
		//only set if down and up coords are the same	
		mouse.set(viewPortCoords.x, viewPortCoords.y, 1);
		var ray = projector.pickingRay(mouse, this.object);
		var pickables = this.scene.root.getDescendants().filter(function (e,i,a){return e.geometry;}); //@TODO currently all scene objects are pickable 
		var pickResults = ray.intersectObjects(pickables);
		
		var pickResult;
		for(var i=0; i < pickResults.length; i++){
			if(pickResults[i].object.geometry && pickResults[i].object.visible){
				pickResult = pickResults[i];
				break;
			};
		};
		if(pickResult){
			// this.deactivate();
			var _onCenter = function(event) {
				this.scene.removeEventListener("center", _onCenter);
				// this.activate();
				this.scene.addEventListener("beforeRender", this.update);
			}.bind(this);
			this.scene.addEventListener("center", _onCenter);
			this.scene.removeEventListener("beforeRender", this.update);
			this.scene.setCenter(pickResult.point,this.object.position.clone().sub(this.center),500);
			
		}	
	}.bind(this);
	
	var onCenter = function(event){
		  
		this.center = event.content.center;
		
	}.bind(this);
	
	var onChange = function(event){
		
		this.scene.center = event.target.center.clone();
		this.object.target.position.setZ(-this.object.position.distanceTo(event.target.center));
		
	};
	
	/**
	 * Activates this Control
	 * 
	 * @method activate
	 *  
	 */
	this.activate = function(){
		if(this.isActive) return;
		
		this.center = this.scene.center.clone();//new THREE.Vector3();
		this.object.up.set(0,1,0);
		
		var dir= new THREE.Vector3(0,0,-1); //cameras default direction
		dir.applyQuaternion(this.object.quaternion); //cameras actual direction
		dir.normalize();
		
		//if(this.center.distanceTo(this.object.position)< 0.01){this.object.position.add(new THREE.Vector3(0,0,0.01));}
		if(this.center.distanceTo(this.object.position)< 0.01){this.object.position.sub(dir.multiplyScalar(0.01));}
		
		this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
		this.domElement.addEventListener( 'mousedown', onMouseDown, false );
		this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
		this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); //Firefox
		this.domElement.addEventListener( 'dblclick', onDoubleClick, false ); 
		this.addEventListener('change', onChange);
		this.scene.addEventListener('center', onCenter);
		//hang in render loop
		this.scene.addEventListener("beforeRender", this.update);
		
		//call super class method
		GIScene.Control.prototype.activate.call(this);
	};
	
	
	/**
	 * Deactivates this Control
	 * 
	 * @method deactivate
	 *  
	 */
	this.deactivate = function(){
		if(!this.isActive) return;
		
		this.domElement.removeEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
		this.domElement.removeEventListener( 'mousedown', onMouseDown, false );
		this.domElement.removeEventListener( 'mousewheel', onMouseWheel, false );
		this.domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel, false ); //Firefox
		this.domElement.removeEventListener( 'dblclick', onDoubleClick, false ); 
		this.removeEventListener('change', onChange);
		this.scene.removeEventListener('center', onCenter);
		//remove from render loop
		this.scene.removeEventListener("beforeRender", this.update);
		
		//call super class method
		GIScene.Control.prototype.deactivate.call(this);
	};
	
};


GIScene.Control.PanOrbitZoomCenter.prototype = Object.create( GIScene.Control.prototype );