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