/ OrbitControls.js
OrbitControls.js
   1  ( function () {
   2  
   3  	// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
   4  	//
   5  	//    Orbit - left mouse / touch: one-finger move
   6  	//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
   7  	//    Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
   8  
   9  	const _changeEvent = {
  10  		type: 'change'
  11  	};
  12  	const _startEvent = {
  13  		type: 'start'
  14  	};
  15  	const _endEvent = {
  16  		type: 'end'
  17  	};
  18  
  19  	class OrbitControls extends THREE.EventDispatcher {
  20  
  21  		constructor( object, domElement ) {
  22  
  23  			super();
  24  			if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
  25  			if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
  26  			this.object = object;
  27  			this.domElement = domElement;
  28  			this.domElement.style.touchAction = 'none'; // disable touch scroll
  29  			// Set to false to disable this control
  30  
  31  			this.enabled = true; // "target" sets the location of focus, where the object orbits around
  32  
  33  			this.target = new THREE.Vector3(); // How far you can dolly in and out ( PerspectiveCamera only )
  34  
  35  			this.minDistance = 0;
  36  			this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only )
  37  
  38  			this.minZoom = 0;
  39  			this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits.
  40  			// Range is 0 to Math.PI radians.
  41  
  42  			this.minPolarAngle = 0; // radians
  43  
  44  			this.maxPolarAngle = Math.PI; // radians
  45  			// How far you can orbit horizontally, upper and lower limits.
  46  			// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
  47  
  48  			this.minAzimuthAngle = - Infinity; // radians
  49  
  50  			this.maxAzimuthAngle = Infinity; // radians
  51  			// Set to true to enable damping (inertia)
  52  			// If damping is enabled, you must call controls.update() in your animation loop
  53  
  54  			this.enableDamping = false;
  55  			this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
  56  			// Set to false to disable zooming
  57  
  58  			this.enableZoom = true;
  59  			this.zoomSpeed = 1.0; // Set to false to disable rotating
  60  
  61  			this.enableRotate = true;
  62  			this.rotateSpeed = 1.0; // Set to false to disable panning
  63  
  64  			this.enablePan = true;
  65  			this.panSpeed = 1.0;
  66  			this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
  67  
  68  			this.keyPanSpeed = 7.0; // pixels moved per arrow key push
  69  			// Set to true to automatically rotate around the target
  70  			// If auto-rotate is enabled, you must call controls.update() in your animation loop
  71  
  72  			this.autoRotate = false;
  73  			this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
  74  			// The four arrow keys
  75  
  76  			this.keys = {
  77  				LEFT: 'ArrowLeft',
  78  				UP: 'ArrowUp',
  79  				RIGHT: 'ArrowRight',
  80  				BOTTOM: 'ArrowDown'
  81  			}; // Mouse buttons
  82  
  83  			this.mouseButtons = {
  84  				LEFT: THREE.MOUSE.ROTATE,
  85  				MIDDLE: THREE.MOUSE.DOLLY,
  86  				RIGHT: THREE.MOUSE.PAN
  87  			}; // Touch fingers
  88  
  89  			this.touches = {
  90  				ONE: THREE.TOUCH.ROTATE,
  91  				TWO: THREE.TOUCH.DOLLY_PAN
  92  			}; // for reset
  93  
  94  			this.target0 = this.target.clone();
  95  			this.position0 = this.object.position.clone();
  96  			this.zoom0 = this.object.zoom; // the target DOM element for key events
  97  
  98  			this._domElementKeyEvents = null; //
  99  			// public methods
 100  			//
 101  
 102  			this.getPolarAngle = function () {
 103  
 104  				return spherical.phi;
 105  
 106  			};
 107  
 108  			this.getAzimuthalAngle = function () {
 109  
 110  				return spherical.theta;
 111  
 112  			};
 113  
 114  			this.getDistance = function () {
 115  
 116  				return this.object.position.distanceTo( this.target );
 117  
 118  			};
 119  
 120  			this.listenToKeyEvents = function ( domElement ) {
 121  
 122  				domElement.addEventListener( 'keydown', onKeyDown );
 123  				this._domElementKeyEvents = domElement;
 124  
 125  			};
 126  
 127  			this.saveState = function () {
 128  
 129  				scope.target0.copy( scope.target );
 130  				scope.position0.copy( scope.object.position );
 131  				scope.zoom0 = scope.object.zoom;
 132  
 133  			};
 134  
 135  			this.reset = function () {
 136  
 137  				scope.target.copy( scope.target0 );
 138  				scope.object.position.copy( scope.position0 );
 139  				scope.object.zoom = scope.zoom0;
 140  				scope.object.updateProjectionMatrix();
 141  				scope.dispatchEvent( _changeEvent );
 142  				scope.update();
 143  				state = STATE.NONE;
 144  
 145  			}; // this method is exposed, but perhaps it would be better if we can make it private...
 146  
 147  
 148  			this.update = function () {
 149  
 150  				const offset = new THREE.Vector3(); // so camera.up is the orbit axis
 151  
 152  				const quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
 153  				const quatInverse = quat.clone().invert();
 154  				const lastPosition = new THREE.Vector3();
 155  				const lastQuaternion = new THREE.Quaternion();
 156  				const twoPI = 2 * Math.PI;
 157  				return function update() {
 158  
 159  					const position = scope.object.position;
 160  					offset.copy( position ).sub( scope.target ); // rotate offset to "y-axis-is-up" space
 161  
 162  					offset.applyQuaternion( quat ); // angle from z-axis around y-axis
 163  
 164  					spherical.setFromVector3( offset );
 165  
 166  					if ( scope.autoRotate && state === STATE.NONE ) {
 167  
 168  						rotateLeft( getAutoRotationAngle() );
 169  
 170  					}
 171  
 172  					if ( scope.enableDamping ) {
 173  
 174  						spherical.theta += sphericalDelta.theta * scope.dampingFactor;
 175  						spherical.phi += sphericalDelta.phi * scope.dampingFactor;
 176  
 177  					} else {
 178  
 179  						spherical.theta += sphericalDelta.theta;
 180  						spherical.phi += sphericalDelta.phi;
 181  
 182  					} // restrict theta to be between desired limits
 183  
 184  
 185  					let min = scope.minAzimuthAngle;
 186  					let max = scope.maxAzimuthAngle;
 187  
 188  					if ( isFinite( min ) && isFinite( max ) ) {
 189  
 190  						if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
 191  						if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
 192  
 193  						if ( min <= max ) {
 194  
 195  							spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
 196  
 197  						} else {
 198  
 199  							spherical.theta = spherical.theta > ( min + max ) / 2 ? Math.max( min, spherical.theta ) : Math.min( max, spherical.theta );
 200  
 201  						}
 202  
 203  					} // restrict phi to be between desired limits
 204  
 205  
 206  					spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
 207  					spherical.makeSafe();
 208  					spherical.radius *= scale; // restrict radius to be between desired limits
 209  
 210  					spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); // move target to panned location
 211  
 212  					if ( scope.enableDamping === true ) {
 213  
 214  						scope.target.addScaledVector( panOffset, scope.dampingFactor );
 215  
 216  					} else {
 217  
 218  						scope.target.add( panOffset );
 219  
 220  					}
 221  
 222  					offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space
 223  
 224  					offset.applyQuaternion( quatInverse );
 225  					position.copy( scope.target ).add( offset );
 226  					scope.object.lookAt( scope.target );
 227  
 228  					if ( scope.enableDamping === true ) {
 229  
 230  						sphericalDelta.theta *= 1 - scope.dampingFactor;
 231  						sphericalDelta.phi *= 1 - scope.dampingFactor;
 232  						panOffset.multiplyScalar( 1 - scope.dampingFactor );
 233  
 234  					} else {
 235  
 236  						sphericalDelta.set( 0, 0, 0 );
 237  						panOffset.set( 0, 0, 0 );
 238  
 239  					}
 240  
 241  					scale = 1; // update condition is:
 242  					// min(camera displacement, camera rotation in radians)^2 > EPS
 243  					// using small-angle approximation cos(x/2) = 1 - x^2 / 8
 244  
 245  					if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
 246  
 247  						scope.dispatchEvent( _changeEvent );
 248  						lastPosition.copy( scope.object.position );
 249  						lastQuaternion.copy( scope.object.quaternion );
 250  						zoomChanged = false;
 251  						return true;
 252  
 253  					}
 254  
 255  					return false;
 256  
 257  				};
 258  
 259  			}();
 260  
 261  			this.dispose = function () {
 262  
 263  				scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
 264  				scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
 265  				scope.domElement.removeEventListener( 'pointercancel', onPointerCancel );
 266  				scope.domElement.removeEventListener( 'wheel', onMouseWheel );
 267  				scope.domElement.removeEventListener( 'pointermove', onPointerMove );
 268  				scope.domElement.removeEventListener( 'pointerup', onPointerUp );
 269  
 270  				if ( scope._domElementKeyEvents !== null ) {
 271  
 272  					scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
 273  
 274  				} //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
 275  
 276  			}; //
 277  			// internals
 278  			//
 279  
 280  
 281  			const scope = this;
 282  			const STATE = {
 283  				NONE: - 1,
 284  				ROTATE: 0,
 285  				DOLLY: 1,
 286  				PAN: 2,
 287  				TOUCH_ROTATE: 3,
 288  				TOUCH_PAN: 4,
 289  				TOUCH_DOLLY_PAN: 5,
 290  				TOUCH_DOLLY_ROTATE: 6
 291  			};
 292  			let state = STATE.NONE;
 293  			const EPS = 0.000001; // current position in spherical coordinates
 294  
 295  			const spherical = new THREE.Spherical();
 296  			const sphericalDelta = new THREE.Spherical();
 297  			let scale = 1;
 298  			const panOffset = new THREE.Vector3();
 299  			let zoomChanged = false;
 300  			const rotateStart = new THREE.Vector2();
 301  			const rotateEnd = new THREE.Vector2();
 302  			const rotateDelta = new THREE.Vector2();
 303  			const panStart = new THREE.Vector2();
 304  			const panEnd = new THREE.Vector2();
 305  			const panDelta = new THREE.Vector2();
 306  			const dollyStart = new THREE.Vector2();
 307  			const dollyEnd = new THREE.Vector2();
 308  			const dollyDelta = new THREE.Vector2();
 309  			const pointers = [];
 310  			const pointerPositions = {};
 311  
 312  			function getAutoRotationAngle() {
 313  
 314  				return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
 315  
 316  			}
 317  
 318  			function getZoomScale() {
 319  
 320  				return Math.pow( 0.95, scope.zoomSpeed );
 321  
 322  			}
 323  
 324  			function rotateLeft( angle ) {
 325  
 326  				sphericalDelta.theta -= angle;
 327  
 328  			}
 329  
 330  			function rotateUp( angle ) {
 331  
 332  				sphericalDelta.phi -= angle;
 333  
 334  			}
 335  
 336  			const panLeft = function () {
 337  
 338  				const v = new THREE.Vector3();
 339  				return function panLeft( distance, objectMatrix ) {
 340  
 341  					v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
 342  
 343  					v.multiplyScalar( - distance );
 344  					panOffset.add( v );
 345  
 346  				};
 347  
 348  			}();
 349  
 350  			const panUp = function () {
 351  
 352  				const v = new THREE.Vector3();
 353  				return function panUp( distance, objectMatrix ) {
 354  
 355  					if ( scope.screenSpacePanning === true ) {
 356  
 357  						v.setFromMatrixColumn( objectMatrix, 1 );
 358  
 359  					} else {
 360  
 361  						v.setFromMatrixColumn( objectMatrix, 0 );
 362  						v.crossVectors( scope.object.up, v );
 363  
 364  					}
 365  
 366  					v.multiplyScalar( distance );
 367  					panOffset.add( v );
 368  
 369  				};
 370  
 371  			}(); // deltaX and deltaY are in pixels; right and down are positive
 372  
 373  
 374  			const pan = function () {
 375  
 376  				const offset = new THREE.Vector3();
 377  				return function pan( deltaX, deltaY ) {
 378  
 379  					const element = scope.domElement;
 380  
 381  					if ( scope.object.isPerspectiveCamera ) {
 382  
 383  						// perspective
 384  						const position = scope.object.position;
 385  						offset.copy( position ).sub( scope.target );
 386  						let targetDistance = offset.length(); // half of the fov is center to top of screen
 387  
 388  						targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed
 389  
 390  						panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
 391  						panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
 392  
 393  					} else if ( scope.object.isOrthographicCamera ) {
 394  
 395  						// orthographic
 396  						panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
 397  						panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
 398  
 399  					} else {
 400  
 401  						// camera neither orthographic nor perspective
 402  						console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
 403  						scope.enablePan = false;
 404  
 405  					}
 406  
 407  				};
 408  
 409  			}();
 410  
 411  			function dollyOut( dollyScale ) {
 412  
 413  				if ( scope.object.isPerspectiveCamera ) {
 414  
 415  					scale /= dollyScale;
 416  
 417  				} else if ( scope.object.isOrthographicCamera ) {
 418  
 419  					scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
 420  					scope.object.updateProjectionMatrix();
 421  					zoomChanged = true;
 422  
 423  				} else {
 424  
 425  					console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
 426  					scope.enableZoom = false;
 427  
 428  				}
 429  
 430  			}
 431  
 432  			function dollyIn( dollyScale ) {
 433  
 434  				if ( scope.object.isPerspectiveCamera ) {
 435  
 436  					scale *= dollyScale;
 437  
 438  				} else if ( scope.object.isOrthographicCamera ) {
 439  
 440  					scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
 441  					scope.object.updateProjectionMatrix();
 442  					zoomChanged = true;
 443  
 444  				} else {
 445  
 446  					console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
 447  					scope.enableZoom = false;
 448  
 449  				}
 450  
 451  			} //
 452  			// event callbacks - update the object state
 453  			//
 454  
 455  
 456  			function handleMouseDownRotate( event ) {
 457  
 458  				rotateStart.set( event.clientX, event.clientY );
 459  
 460  			}
 461  
 462  			function handleMouseDownDolly( event ) {
 463  
 464  				dollyStart.set( event.clientX, event.clientY );
 465  
 466  			}
 467  
 468  			function handleMouseDownPan( event ) {
 469  
 470  				panStart.set( event.clientX, event.clientY );
 471  
 472  			}
 473  
 474  			function handleMouseMoveRotate( event ) {
 475  
 476  				rotateEnd.set( event.clientX, event.clientY );
 477  				rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
 478  				const element = scope.domElement;
 479  				rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
 480  
 481  				rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
 482  				rotateStart.copy( rotateEnd );
 483  				scope.update();
 484  
 485  			}
 486  
 487  			function handleMouseMoveDolly( event ) {
 488  
 489  				dollyEnd.set( event.clientX, event.clientY );
 490  				dollyDelta.subVectors( dollyEnd, dollyStart );
 491  
 492  				if ( dollyDelta.y > 0 ) {
 493  
 494  					dollyOut( getZoomScale() );
 495  
 496  				} else if ( dollyDelta.y < 0 ) {
 497  
 498  					dollyIn( getZoomScale() );
 499  
 500  				}
 501  
 502  				dollyStart.copy( dollyEnd );
 503  				scope.update();
 504  
 505  			}
 506  
 507  			function handleMouseMovePan( event ) {
 508  
 509  				panEnd.set( event.clientX, event.clientY );
 510  				panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
 511  				pan( panDelta.x, panDelta.y );
 512  				panStart.copy( panEnd );
 513  				scope.update();
 514  
 515  			}
 516  
 517  			function handleMouseWheel( event ) {
 518  
 519  				if ( event.deltaY < 0 ) {
 520  
 521  					dollyIn( getZoomScale() );
 522  
 523  				} else if ( event.deltaY > 0 ) {
 524  
 525  					dollyOut( getZoomScale() );
 526  
 527  				}
 528  
 529  				scope.update();
 530  
 531  			}
 532  
 533  			function handleKeyDown( event ) {
 534  
 535  				let needsUpdate = false;
 536  
 537  				switch ( event.code ) {
 538  
 539  					case scope.keys.UP:
 540  						pan( 0, scope.keyPanSpeed );
 541  						needsUpdate = true;
 542  						break;
 543  
 544  					case scope.keys.BOTTOM:
 545  						pan( 0, - scope.keyPanSpeed );
 546  						needsUpdate = true;
 547  						break;
 548  
 549  					case scope.keys.LEFT:
 550  						pan( scope.keyPanSpeed, 0 );
 551  						needsUpdate = true;
 552  						break;
 553  
 554  					case scope.keys.RIGHT:
 555  						pan( - scope.keyPanSpeed, 0 );
 556  						needsUpdate = true;
 557  						break;
 558  
 559  				}
 560  
 561  				if ( needsUpdate ) {
 562  
 563  					// prevent the browser from scrolling on cursor keys
 564  					event.preventDefault();
 565  					scope.update();
 566  
 567  				}
 568  
 569  			}
 570  
 571  			function handleTouchStartRotate() {
 572  
 573  				if ( pointers.length === 1 ) {
 574  
 575  					rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
 576  
 577  				} else {
 578  
 579  					const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
 580  					const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
 581  					rotateStart.set( x, y );
 582  
 583  				}
 584  
 585  			}
 586  
 587  			function handleTouchStartPan() {
 588  
 589  				if ( pointers.length === 1 ) {
 590  
 591  					panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
 592  
 593  				} else {
 594  
 595  					const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
 596  					const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
 597  					panStart.set( x, y );
 598  
 599  				}
 600  
 601  			}
 602  
 603  			function handleTouchStartDolly() {
 604  
 605  				const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX;
 606  				const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY;
 607  				const distance = Math.sqrt( dx * dx + dy * dy );
 608  				dollyStart.set( 0, distance );
 609  
 610  			}
 611  
 612  			function handleTouchStartDollyPan() {
 613  
 614  				if ( scope.enableZoom ) handleTouchStartDolly();
 615  				if ( scope.enablePan ) handleTouchStartPan();
 616  
 617  			}
 618  
 619  			function handleTouchStartDollyRotate() {
 620  
 621  				if ( scope.enableZoom ) handleTouchStartDolly();
 622  				if ( scope.enableRotate ) handleTouchStartRotate();
 623  
 624  			}
 625  
 626  			function handleTouchMoveRotate( event ) {
 627  
 628  				if ( pointers.length == 1 ) {
 629  
 630  					rotateEnd.set( event.pageX, event.pageY );
 631  
 632  				} else {
 633  
 634  					const position = getSecondPointerPosition( event );
 635  					const x = 0.5 * ( event.pageX + position.x );
 636  					const y = 0.5 * ( event.pageY + position.y );
 637  					rotateEnd.set( x, y );
 638  
 639  				}
 640  
 641  				rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
 642  				const element = scope.domElement;
 643  				rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
 644  
 645  				rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
 646  				rotateStart.copy( rotateEnd );
 647  
 648  			}
 649  
 650  			function handleTouchMovePan( event ) {
 651  
 652  				if ( pointers.length === 1 ) {
 653  
 654  					panEnd.set( event.pageX, event.pageY );
 655  
 656  				} else {
 657  
 658  					const position = getSecondPointerPosition( event );
 659  					const x = 0.5 * ( event.pageX + position.x );
 660  					const y = 0.5 * ( event.pageY + position.y );
 661  					panEnd.set( x, y );
 662  
 663  				}
 664  
 665  				panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
 666  				pan( panDelta.x, panDelta.y );
 667  				panStart.copy( panEnd );
 668  
 669  			}
 670  
 671  			function handleTouchMoveDolly( event ) {
 672  
 673  				const position = getSecondPointerPosition( event );
 674  				const dx = event.pageX - position.x;
 675  				const dy = event.pageY - position.y;
 676  				const distance = Math.sqrt( dx * dx + dy * dy );
 677  				dollyEnd.set( 0, distance );
 678  				dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
 679  				dollyOut( dollyDelta.y );
 680  				dollyStart.copy( dollyEnd );
 681  
 682  			}
 683  
 684  			function handleTouchMoveDollyPan( event ) {
 685  
 686  				if ( scope.enableZoom ) handleTouchMoveDolly( event );
 687  				if ( scope.enablePan ) handleTouchMovePan( event );
 688  
 689  			}
 690  
 691  			function handleTouchMoveDollyRotate( event ) {
 692  
 693  				if ( scope.enableZoom ) handleTouchMoveDolly( event );
 694  				if ( scope.enableRotate ) handleTouchMoveRotate( event );
 695  
 696  			} //
 697  			// event handlers - FSM: listen for events and reset state
 698  			//
 699  
 700  
 701  			function onPointerDown( event ) {
 702  
 703  				if ( scope.enabled === false ) return;
 704  
 705  				if ( pointers.length === 0 ) {
 706  
 707  					scope.domElement.setPointerCapture( event.pointerId );
 708  					scope.domElement.addEventListener( 'pointermove', onPointerMove );
 709  					scope.domElement.addEventListener( 'pointerup', onPointerUp );
 710  
 711  				} //
 712  
 713  
 714  				addPointer( event );
 715  
 716  				if ( event.pointerType === 'touch' ) {
 717  
 718  					onTouchStart( event );
 719  
 720  				} else {
 721  
 722  					onMouseDown( event );
 723  
 724  				}
 725  
 726  			}
 727  
 728  			function onPointerMove( event ) {
 729  
 730  				if ( scope.enabled === false ) return;
 731  
 732  				if ( event.pointerType === 'touch' ) {
 733  
 734  					onTouchMove( event );
 735  
 736  				} else {
 737  
 738  					onMouseMove( event );
 739  
 740  				}
 741  
 742  			}
 743  
 744  			function onPointerUp( event ) {
 745  
 746  				removePointer( event );
 747  
 748  				if ( pointers.length === 0 ) {
 749  
 750  					scope.domElement.releasePointerCapture( event.pointerId );
 751  					scope.domElement.removeEventListener( 'pointermove', onPointerMove );
 752  					scope.domElement.removeEventListener( 'pointerup', onPointerUp );
 753  
 754  				}
 755  
 756  				scope.dispatchEvent( _endEvent );
 757  				state = STATE.NONE;
 758  
 759  			}
 760  
 761  			function onPointerCancel( event ) {
 762  
 763  				removePointer( event );
 764  
 765  			}
 766  
 767  			function onMouseDown( event ) {
 768  
 769  				let mouseAction;
 770  
 771  				switch ( event.button ) {
 772  
 773  					case 0:
 774  						mouseAction = scope.mouseButtons.LEFT;
 775  						break;
 776  
 777  					case 1:
 778  						mouseAction = scope.mouseButtons.MIDDLE;
 779  						break;
 780  
 781  					case 2:
 782  						mouseAction = scope.mouseButtons.RIGHT;
 783  						break;
 784  
 785  					default:
 786  						mouseAction = - 1;
 787  
 788  				}
 789  
 790  				switch ( mouseAction ) {
 791  
 792  					case THREE.MOUSE.DOLLY:
 793  						if ( scope.enableZoom === false ) return;
 794  						handleMouseDownDolly( event );
 795  						state = STATE.DOLLY;
 796  						break;
 797  
 798  					case THREE.MOUSE.ROTATE:
 799  						if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
 800  
 801  							if ( scope.enablePan === false ) return;
 802  							handleMouseDownPan( event );
 803  							state = STATE.PAN;
 804  
 805  						} else {
 806  
 807  							if ( scope.enableRotate === false ) return;
 808  							handleMouseDownRotate( event );
 809  							state = STATE.ROTATE;
 810  
 811  						}
 812  
 813  						break;
 814  
 815  					case THREE.MOUSE.PAN:
 816  						if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
 817  
 818  							if ( scope.enableRotate === false ) return;
 819  							handleMouseDownRotate( event );
 820  							state = STATE.ROTATE;
 821  
 822  						} else {
 823  
 824  							if ( scope.enablePan === false ) return;
 825  							handleMouseDownPan( event );
 826  							state = STATE.PAN;
 827  
 828  						}
 829  
 830  						break;
 831  
 832  					default:
 833  						state = STATE.NONE;
 834  
 835  				}
 836  
 837  				if ( state !== STATE.NONE ) {
 838  
 839  					scope.dispatchEvent( _startEvent );
 840  
 841  				}
 842  
 843  			}
 844  
 845  			function onMouseMove( event ) {
 846  
 847  				if ( scope.enabled === false ) return;
 848  
 849  				switch ( state ) {
 850  
 851  					case STATE.ROTATE:
 852  						if ( scope.enableRotate === false ) return;
 853  						handleMouseMoveRotate( event );
 854  						break;
 855  
 856  					case STATE.DOLLY:
 857  						if ( scope.enableZoom === false ) return;
 858  						handleMouseMoveDolly( event );
 859  						break;
 860  
 861  					case STATE.PAN:
 862  						if ( scope.enablePan === false ) return;
 863  						handleMouseMovePan( event );
 864  						break;
 865  
 866  				}
 867  
 868  			}
 869  
 870  			function onMouseWheel( event ) {
 871  
 872  				if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return;
 873  				event.preventDefault();
 874  				scope.dispatchEvent( _startEvent );
 875  				handleMouseWheel( event );
 876  				scope.dispatchEvent( _endEvent );
 877  
 878  			}
 879  
 880  			function onKeyDown( event ) {
 881  
 882  				if ( scope.enabled === false || scope.enablePan === false ) return;
 883  				handleKeyDown( event );
 884  
 885  			}
 886  
 887  			function onTouchStart( event ) {
 888  
 889  				trackPointer( event );
 890  
 891  				switch ( pointers.length ) {
 892  
 893  					case 1:
 894  						switch ( scope.touches.ONE ) {
 895  
 896  							case THREE.TOUCH.ROTATE:
 897  								if ( scope.enableRotate === false ) return;
 898  								handleTouchStartRotate();
 899  								state = STATE.TOUCH_ROTATE;
 900  								break;
 901  
 902  							case THREE.TOUCH.PAN:
 903  								if ( scope.enablePan === false ) return;
 904  								handleTouchStartPan();
 905  								state = STATE.TOUCH_PAN;
 906  								break;
 907  
 908  							default:
 909  								state = STATE.NONE;
 910  
 911  						}
 912  
 913  						break;
 914  
 915  					case 2:
 916  						switch ( scope.touches.TWO ) {
 917  
 918  							case THREE.TOUCH.DOLLY_PAN:
 919  								if ( scope.enableZoom === false && scope.enablePan === false ) return;
 920  								handleTouchStartDollyPan();
 921  								state = STATE.TOUCH_DOLLY_PAN;
 922  								break;
 923  
 924  							case THREE.TOUCH.DOLLY_ROTATE:
 925  								if ( scope.enableZoom === false && scope.enableRotate === false ) return;
 926  								handleTouchStartDollyRotate();
 927  								state = STATE.TOUCH_DOLLY_ROTATE;
 928  								break;
 929  
 930  							default:
 931  								state = STATE.NONE;
 932  
 933  						}
 934  
 935  						break;
 936  
 937  					default:
 938  						state = STATE.NONE;
 939  
 940  				}
 941  
 942  				if ( state !== STATE.NONE ) {
 943  
 944  					scope.dispatchEvent( _startEvent );
 945  
 946  				}
 947  
 948  			}
 949  
 950  			function onTouchMove( event ) {
 951  
 952  				trackPointer( event );
 953  
 954  				switch ( state ) {
 955  
 956  					case STATE.TOUCH_ROTATE:
 957  						if ( scope.enableRotate === false ) return;
 958  						handleTouchMoveRotate( event );
 959  						scope.update();
 960  						break;
 961  
 962  					case STATE.TOUCH_PAN:
 963  						if ( scope.enablePan === false ) return;
 964  						handleTouchMovePan( event );
 965  						scope.update();
 966  						break;
 967  
 968  					case STATE.TOUCH_DOLLY_PAN:
 969  						if ( scope.enableZoom === false && scope.enablePan === false ) return;
 970  						handleTouchMoveDollyPan( event );
 971  						scope.update();
 972  						break;
 973  
 974  					case STATE.TOUCH_DOLLY_ROTATE:
 975  						if ( scope.enableZoom === false && scope.enableRotate === false ) return;
 976  						handleTouchMoveDollyRotate( event );
 977  						scope.update();
 978  						break;
 979  
 980  					default:
 981  						state = STATE.NONE;
 982  
 983  				}
 984  
 985  			}
 986  
 987  			function onContextMenu( event ) {
 988  
 989  				if ( scope.enabled === false ) return;
 990  				event.preventDefault();
 991  
 992  			}
 993  
 994  			function addPointer( event ) {
 995  
 996  				pointers.push( event );
 997  
 998  			}
 999  
1000  			function removePointer( event ) {
1001  
1002  				delete pointerPositions[ event.pointerId ];
1003  
1004  				for ( let i = 0; i < pointers.length; i ++ ) {
1005  
1006  					if ( pointers[ i ].pointerId == event.pointerId ) {
1007  
1008  						pointers.splice( i, 1 );
1009  						return;
1010  
1011  					}
1012  
1013  				}
1014  
1015  			}
1016  
1017  			function trackPointer( event ) {
1018  
1019  				let position = pointerPositions[ event.pointerId ];
1020  
1021  				if ( position === undefined ) {
1022  
1023  					position = new THREE.Vector2();
1024  					pointerPositions[ event.pointerId ] = position;
1025  
1026  				}
1027  
1028  				position.set( event.pageX, event.pageY );
1029  
1030  			}
1031  
1032  			function getSecondPointerPosition( event ) {
1033  
1034  				const pointer = event.pointerId === pointers[ 0 ].pointerId ? pointers[ 1 ] : pointers[ 0 ];
1035  				return pointerPositions[ pointer.pointerId ];
1036  
1037  			} //
1038  
1039  
1040  			scope.domElement.addEventListener( 'contextmenu', onContextMenu );
1041  			scope.domElement.addEventListener( 'pointerdown', onPointerDown );
1042  			scope.domElement.addEventListener( 'pointercancel', onPointerCancel );
1043  			scope.domElement.addEventListener( 'wheel', onMouseWheel, {
1044  				passive: false
1045  			} ); // force an update at start
1046  
1047  			this.update();
1048  
1049  		}
1050  
1051  	} // This set of controls performs orbiting, dollying (zooming), and panning.
1052  	// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
1053  	// This is very similar to OrbitControls, another set of touch behavior
1054  	//
1055  	//    Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
1056  	//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
1057  	//    Pan - left mouse, or arrow keys / touch: one-finger move
1058  
1059  
1060  	class MapControls extends OrbitControls {
1061  
1062  		constructor( object, domElement ) {
1063  
1064  			super( object, domElement );
1065  			this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
1066  
1067  			this.mouseButtons.LEFT = THREE.MOUSE.PAN;
1068  			this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
1069  			this.touches.ONE = THREE.TOUCH.PAN;
1070  			this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
1071  
1072  		}
1073  
1074  	}
1075  
1076  	THREE.MapControls = MapControls;
1077  	THREE.OrbitControls = OrbitControls;
1078  
1079  } )();