فهرست منبع

With Greggman's changes

Folded in this code
https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
Also added documentation.
Eric Haines 12 سال پیش
والد
کامیت
352ed93bab
1فایلهای تغییر یافته به همراه298 افزوده شده و 91 حذف شده
  1. 298 91
      examples/js/controls/OrbitControls.js

+ 298 - 91
examples/js/controls/OrbitControls.js

@@ -3,7 +3,23 @@
  * @author mrdoob / http://mrdoob.com
  * @author alteredq / http://alteredqualia.com/
  * @author WestLangley / http://github.com/WestLangley
+ * @author erich666 / http://erichaines.com
  */
+/*global THREE, console */
+
+// This set of controls performs orbiting, dollying (zooming), and panning. It maintains
+// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
+// supported.
+//
+//    Orbit - left mouse / touch: one finger move
+//    Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
+//    Pan - right mouse, or arrow keys / touch: three finter swipe
+//
+// This is a drop-in replacement for (most) TrackballControls used in examples.
+// That is, include this js file and wherever you see:
+//    	controls = new THREE.TrackballControls( camera );
+//      controls.target.z = 150;
+// Simple substitute "OrbitControls" and the control should work as-is.
 
 THREE.OrbitControls = function ( object, domElement ) {
 
@@ -12,52 +28,72 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 	// API
 
+	// Set to false to disable this control
 	this.enabled = true;
 
-	this.center = new THREE.Vector3();
-
-	this.userZoom = true;
-	this.userZoomSpeed = 1.0;
+	// "target" sets the location of focus, where the control orbits around
+	// and where it pans with respect to.
+	this.target = new THREE.Vector3();
+	// center is old, deprecated; use "target" instead
+	this.center = this.target;
+
+	// This option actually enables dollying in and out; left as "zoom" for
+	// backwards compatibility
+	this.noZoom = false;
+	this.zoomSpeed = 1.0;
+	// Limits to how far you can dolly in and out
+	this.minDistance = 0;
+	this.maxDistance = Infinity;
 
-	this.userRotate = true;
-	this.userRotateSpeed = 1.0;
+	// Set to true to disable this control
+	this.noRotate = false;
+	this.rotateSpeed = 1.0;
 
-	this.userPan = true;
-	this.userPanSpeed = 2.0;
+	// Set to true to disable this control
+	this.noPan = false;
+	this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
 
+	// Set to true to automatically rotate around the target
 	this.autoRotate = false;
 	this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
 
+	// How far you can orbit vertically, upper and lower limits.
+	// Range is 0 to Math.PI radians.
 	this.minPolarAngle = 0; // radians
 	this.maxPolarAngle = Math.PI; // radians
 
-	this.minDistance = 0;
-	this.maxDistance = Infinity;
-
+	// Set to true to disable use of the keys
+	this.noKeys = false;
+	// The four arrow keys
 	this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
 
+	////////////
 	// 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();
+	var panStart = new THREE.Vector2();
+	var panEnd = new THREE.Vector2();
+	var panDelta = new THREE.Vector2();
+
+	var dollyStart = new THREE.Vector2();
+	var dollyEnd = new THREE.Vector2();
+	var dollyDelta = new THREE.Vector2();
 
 	var phiDelta = 0;
 	var thetaDelta = 0;
 	var scale = 1;
+	var pan = new THREE.Vector3();
 
 	var lastPosition = new THREE.Vector3();
 
-	var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 };
+	var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
 	var state = STATE.NONE;
 
 	// events
@@ -77,18 +113,6 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 	};
 
-	this.rotateRight = function ( angle ) {
-
-		if ( angle === undefined ) {
-
-			angle = getAutoRotationAngle();
-
-		}
-
-		thetaDelta += angle;
-
-	};
-
 	this.rotateUp = function ( angle ) {
 
 		if ( angle === undefined ) {
@@ -101,56 +125,89 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 	};
 
-	this.rotateDown = function ( angle ) {
+	// pass in distance in world space to move left
+	this.panLeft = function ( distance ) {
 
-		if ( angle === undefined ) {
+		var panOffset = new THREE.Vector3();
+		var te = this.object.matrix.elements;
+		// get X column of matrix
+		panOffset.set( te[0], te[1], te[2] );
+		panOffset.multiplyScalar(-distance);
+		
+		pan.add( panOffset );
 
-			angle = getAutoRotationAngle();
-
-		}
+	};
 
-		phiDelta += angle;
+	// pass in distance in world space to move up
+	this.panUp = function ( distance ) {
 
+		var panOffset = new THREE.Vector3();
+		var te = this.object.matrix.elements;
+		// get Y column of matrix
+		panOffset.set( te[4], te[5], te[6] );
+		panOffset.multiplyScalar(distance);
+		
+		pan.add( panOffset );
+	};
+	
+	// main entry point; pass in Vector2 of change desired in pixel space,
+	// right and down are positive
+	this.pan = function ( delta ) {
+
+		if ( scope.object.fov !== undefined )
+		{
+			// perspective
+			var position = scope.object.position;
+			var offset = position.clone().sub( scope.target );
+			var targetDistance = offset.length();
+
+			// half of the fov is center to top of screen
+			targetDistance *= Math.tan( (scope.object.fov/2) * Math.PI / 180.0 );
+			// we actually don't use screenWidth, since perspective camera is fixed to screen height
+			scope.panLeft( 2 * delta.x * targetDistance / scope.domElement.height );
+			scope.panUp( 2 * delta.y * targetDistance / scope.domElement.height );
+		}
+		else if ( scope.object.top !== undefined )
+		{
+			// orthographic
+			scope.panLeft( delta.x * (scope.object.right - scope.object.left) / scope.domElement.width );
+			scope.panUp( delta.y * (scope.object.top - scope.object.bottom) / scope.domElement.height );
+		}
+		else
+		{
+			// camera neither orthographic or perspective - warn user
+			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+		}
 	};
 
-	this.zoomIn = function ( zoomScale ) {
+	this.dollyIn = function ( dollyScale ) {
 
-		if ( zoomScale === undefined ) {
+		if ( dollyScale === undefined ) {
 
-			zoomScale = getZoomScale();
+			dollyScale = getZoomScale();
 
 		}
 
-		scale /= zoomScale;
+		scale /= dollyScale;
 
 	};
 
-	this.zoomOut = function ( zoomScale ) {
+	this.dollyOut = function ( dollyScale ) {
 
-		if ( zoomScale === undefined ) {
+		if ( dollyScale === undefined ) {
 
-			zoomScale = getZoomScale();
+			dollyScale = getZoomScale();
 
 		}
 
-		scale *= zoomScale;
-
-	};
-
-	this.pan = function ( distance ) {
-
-		distance.transformDirection( this.object.matrix );
-		distance.multiplyScalar( scope.userPanSpeed );
-
-		this.object.position.add( distance );
-		this.center.add( distance );
+		scale *= dollyScale;
 
 	};
 
 	this.update = function () {
 
 		var position = this.object.position;
-		var offset = position.clone().sub( this.center );
+		var offset = position.clone().sub( this.target );
 
 		// angle from z-axis around y-axis
 
@@ -179,18 +236,22 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 		// restrict radius to be between desired limits
 		radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
+		
+		// move target to panned location
+		this.target.add( pan );
 
 		offset.x = radius * Math.sin( phi ) * Math.sin( theta );
 		offset.y = radius * Math.cos( phi );
 		offset.z = radius * Math.sin( phi ) * Math.cos( theta );
 
-		position.copy( this.center ).add( offset );
+		position.copy( this.target ).add( offset );
 
-		this.object.lookAt( this.center );
+		this.object.lookAt( this.target );
 
 		thetaDelta = 0;
 		phiDelta = 0;
 		scale = 1;
+		pan.set(0,0,0);
 
 		if ( lastPosition.distanceTo( this.object.position ) > 0 ) {
 
@@ -211,91 +272,104 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 	function getZoomScale() {
 
-		return Math.pow( 0.95, scope.userZoomSpeed );
+		return Math.pow( 0.95, scope.zoomSpeed );
 
 	}
 
 	function onMouseDown( event ) {
 
-		if ( scope.enabled === false ) return;
-		if ( scope.userRotate === false ) return;
-
+		if ( scope.enabled === false ) { return; }
 		event.preventDefault();
 
 		if ( event.button === 0 ) {
+			if ( scope.noRotate === true ) { return; }
 
 			state = STATE.ROTATE;
 
 			rotateStart.set( event.clientX, event.clientY );
 
 		} else if ( event.button === 1 ) {
+			if ( scope.noZoom === true ) { return; }
 
-			state = STATE.ZOOM;
+			state = STATE.DOLLY;
 
-			zoomStart.set( event.clientX, event.clientY );
+			dollyStart.set( event.clientX, event.clientY );
 
 		} else if ( event.button === 2 ) {
+			if ( scope.noPan === true ) { return; }
 
 			state = STATE.PAN;
 
+			panStart.set( event.clientX, event.clientY );
+
 		}
 
-		document.addEventListener( 'mousemove', onMouseMove, false );
-		document.addEventListener( 'mouseup', onMouseUp, false );
+		// Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
+		scope.domElement.addEventListener( 'mousemove', onMouseMove, false );
+		scope.domElement.addEventListener( 'mouseup', onMouseUp, false );
 
 	}
 
 	function onMouseMove( event ) {
 
-		if ( scope.enabled === false ) return;
+		if ( scope.enabled === false ) { return; }
 
 		event.preventDefault();
 
 		if ( state === STATE.ROTATE ) {
+			if ( scope.noRotate === true ) { return; }
 
 			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 );
+			// rotating across whole screen goes 360 degrees around
+			scope.rotateLeft( 2 * Math.PI * rotateDelta.x / scope.domElement.width * scope.rotateSpeed );
+			// rotating up and down along whole screen attempts to go 360, but limited to 180
+			scope.rotateUp( 2 * Math.PI * rotateDelta.y / scope.domElement.height * scope.rotateSpeed );
 
 			rotateStart.copy( rotateEnd );
 
-		} else if ( state === STATE.ZOOM ) {
+		} else if ( state === STATE.DOLLY ) {
+			if ( scope.noZoom === true ) { return; }
 
-			zoomEnd.set( event.clientX, event.clientY );
-			zoomDelta.subVectors( zoomEnd, zoomStart );
+			dollyEnd.set( event.clientX, event.clientY );
+			dollyDelta.subVectors( dollyEnd, dollyStart );
 
-			if ( zoomDelta.y > 0 ) {
+			if ( dollyDelta.y > 0 ) {
 
-				scope.zoomIn();
+				scope.dollyIn();
 
 			} else {
 
-				scope.zoomOut();
+				scope.dollyOut();
 
 			}
 
-			zoomStart.copy( zoomEnd );
+			dollyStart.copy( dollyEnd );
 
 		} else if ( state === STATE.PAN ) {
+			if ( scope.noPan === true ) { return; }
 
-			var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
-			var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
+			panEnd.set( event.clientX, event.clientY );
+			panDelta.subVectors( panEnd, panStart );
+			
+			scope.pan( panDelta );
 
-			scope.pan( new THREE.Vector3( - movementX, movementY, 0 ) );
+			panStart.copy( panEnd );
 
 		}
 
+		// Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
+		scope.update();
 	}
 
-	function onMouseUp( event ) {
+	function onMouseUp( /* event */ ) {
 
-		if ( scope.enabled === false ) return;
-		if ( scope.userRotate === false ) return;
+		if ( scope.enabled === false ) { return; }
 
-		document.removeEventListener( 'mousemove', onMouseMove, false );
-		document.removeEventListener( 'mouseup', onMouseUp, false );
+		// Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
+		scope.domElement.removeEventListener( 'mousemove', onMouseMove, false );
+		scope.domElement.removeEventListener( 'mouseup', onMouseUp, false );
 
 		state = STATE.NONE;
 
@@ -303,8 +377,8 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 	function onMouseWheel( event ) {
 
-		if ( scope.enabled === false ) return;
-		if ( scope.userZoom === false ) return;
+		if ( scope.enabled === false ) { return; }
+		if ( scope.noZoom === true ) { return; }
 
 		var delta = 0;
 
@@ -320,11 +394,11 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 		if ( delta > 0 ) {
 
-			scope.zoomOut();
+			scope.dollyOut();
 
 		} else {
 
-			scope.zoomIn();
+			scope.dollyIn();
 
 		}
 
@@ -332,33 +406,166 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 	function onKeyDown( event ) {
 
-		if ( scope.enabled === false ) return;
-		if ( scope.userPan === false ) return;
+		if ( scope.enabled === false ) { return; }
+		if ( scope.noKeys === true ) { return; }
+		if ( scope.noPan === true ) { return; }
 
+		// pan a pixel - I guess for precise positioning?
+		// Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
+		var needUpdate = false;
 		switch ( event.keyCode ) {
 
 			case scope.keys.UP:
-				scope.pan( new THREE.Vector3( 0, 1, 0 ) );
+				scope.pan( new THREE.Vector2( 0, scope.keyPanSpeed ) );
+				needUpdate = true;
 				break;
 			case scope.keys.BOTTOM:
-				scope.pan( new THREE.Vector3( 0, - 1, 0 ) );
+				scope.pan( new THREE.Vector2( 0, -scope.keyPanSpeed ) );
+				needUpdate = true;
 				break;
 			case scope.keys.LEFT:
-				scope.pan( new THREE.Vector3( - 1, 0, 0 ) );
+				scope.pan( new THREE.Vector2( scope.keyPanSpeed, 0 ) );
+				needUpdate = true;
 				break;
 			case scope.keys.RIGHT:
-				scope.pan( new THREE.Vector3( 1, 0, 0 ) );
+				scope.pan( new THREE.Vector2( -scope.keyPanSpeed, 0 ) );
+				needUpdate = true;
 				break;
 		}
 
+		// Greggman fix: https://github.com/greggman/three.js/commit/fde9f9917d6d8381f06bf22cdff766029d1761be
+		if ( needUpdate ) {
+
+			scope.update();
+
+		}
+
+	}
+	
+	function touchstart( event ) {
+
+		if ( scope.enabled === false ) { return; }
+
+		switch ( event.touches.length ) {
+
+			case 1:	// one-fingered touch: rotate
+				if ( scope.noRotate === true ) { return; }
+
+				state = STATE.TOUCH_ROTATE;
+
+				rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
+
+			case 2:	// two-fingered touch: dolly
+				if ( scope.noZoom === true ) { return; }
+
+				state = STATE.TOUCH_DOLLY;
+
+				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+				var distance = Math.sqrt( dx * dx + dy * dy );
+				dollyStart.set( 0, distance );
+				break;
+
+			case 3: // three-fingered touch: pan
+				if ( scope.noPan === true ) { return; }
+
+				state = STATE.TOUCH_PAN;
+
+				panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				break;
+
+			default:
+				state = STATE.NONE;
+
+		}
+	}
+
+	function touchmove( event ) {
+
+		if ( scope.enabled === false ) { return; }
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		switch ( event.touches.length ) {
+
+			case 1: // one-fingered touch: rotate
+				if ( scope.noRotate === true ) { return; }
+				if ( state !== STATE.TOUCH_ROTATE ) { return; }
+
+				rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				rotateDelta.subVectors( rotateEnd, rotateStart );
+
+				// rotating across whole screen goes 360 degrees around
+				scope.rotateLeft( 2 * Math.PI * rotateDelta.x / scope.domElement.width * scope.rotateSpeed );
+				// rotating up and down along whole screen attempts to go 360, but limited to 180
+				scope.rotateUp( 2 * Math.PI * rotateDelta.y / scope.domElement.height * scope.rotateSpeed );
+
+				rotateStart.copy( rotateEnd );
+				break;
+
+			case 2: // two-fingered touch: dolly
+				if ( scope.noZoom === true ) { return; }
+				if ( state !== STATE.TOUCH_DOLLY ) { return; }
+
+				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+				var distance = Math.sqrt( dx * dx + dy * dy );
+
+				dollyEnd.set( 0, distance );
+				dollyDelta.subVectors( dollyEnd, dollyStart );
+
+				if ( dollyDelta.y > 0 ) {
+
+					scope.dollyOut();
+
+				} else {
+
+					scope.dollyIn();
+
+				}
+
+				dollyStart.copy( dollyEnd );
+				break;
+
+			case 3: // three-fingered touch: pan
+				if ( scope.noPan === true ) { return; }
+				if ( state !== STATE.TOUCH_PAN ) { return; }
+
+				panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+				panDelta.subVectors( panEnd, panStart );
+				
+				scope.pan( panDelta );
+
+				panStart.copy( panEnd );
+				break;
+
+			default:
+				state = STATE.NONE;
+
+		}
+
+	}
+
+	function touchend( /* event */ ) {
+
+		if ( scope.enabled === false ) { return; }
+
+		state = STATE.NONE;
 	}
 
 	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( 'keydown', onKeyDown, false );
 
+	this.domElement.addEventListener( 'touchstart', touchstart, false );
+	this.domElement.addEventListener( 'touchend', touchend, false );
+	this.domElement.addEventListener( 'touchmove', touchmove, false );
+
 };
 
 THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );