UNPKG

26.6 kBJavaScriptView Raw
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 handleMouseUp() { // no-op
518 }
519
520 function handleMouseWheel( event ) {
521
522 if ( event.deltaY < 0 ) {
523
524 dollyIn( getZoomScale() );
525
526 } else if ( event.deltaY > 0 ) {
527
528 dollyOut( getZoomScale() );
529
530 }
531
532 scope.update();
533
534 }
535
536 function handleKeyDown( event ) {
537
538 let needsUpdate = false;
539
540 switch ( event.code ) {
541
542 case scope.keys.UP:
543 pan( 0, scope.keyPanSpeed );
544 needsUpdate = true;
545 break;
546
547 case scope.keys.BOTTOM:
548 pan( 0, - scope.keyPanSpeed );
549 needsUpdate = true;
550 break;
551
552 case scope.keys.LEFT:
553 pan( scope.keyPanSpeed, 0 );
554 needsUpdate = true;
555 break;
556
557 case scope.keys.RIGHT:
558 pan( - scope.keyPanSpeed, 0 );
559 needsUpdate = true;
560 break;
561
562 }
563
564 if ( needsUpdate ) {
565
566 // prevent the browser from scrolling on cursor keys
567 event.preventDefault();
568 scope.update();
569
570 }
571
572 }
573
574 function handleTouchStartRotate() {
575
576 if ( pointers.length === 1 ) {
577
578 rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
579
580 } else {
581
582 const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
583 const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
584 rotateStart.set( x, y );
585
586 }
587
588 }
589
590 function handleTouchStartPan() {
591
592 if ( pointers.length === 1 ) {
593
594 panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY );
595
596 } else {
597
598 const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX );
599 const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY );
600 panStart.set( x, y );
601
602 }
603
604 }
605
606 function handleTouchStartDolly() {
607
608 const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX;
609 const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY;
610 const distance = Math.sqrt( dx * dx + dy * dy );
611 dollyStart.set( 0, distance );
612
613 }
614
615 function handleTouchStartDollyPan() {
616
617 if ( scope.enableZoom ) handleTouchStartDolly();
618 if ( scope.enablePan ) handleTouchStartPan();
619
620 }
621
622 function handleTouchStartDollyRotate() {
623
624 if ( scope.enableZoom ) handleTouchStartDolly();
625 if ( scope.enableRotate ) handleTouchStartRotate();
626
627 }
628
629 function handleTouchMoveRotate( event ) {
630
631 if ( pointers.length == 1 ) {
632
633 rotateEnd.set( event.pageX, event.pageY );
634
635 } else {
636
637 const position = getSecondPointerPosition( event );
638 const x = 0.5 * ( event.pageX + position.x );
639 const y = 0.5 * ( event.pageY + position.y );
640 rotateEnd.set( x, y );
641
642 }
643
644 rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
645 const element = scope.domElement;
646 rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
647
648 rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
649 rotateStart.copy( rotateEnd );
650
651 }
652
653 function handleTouchMovePan( event ) {
654
655 if ( pointers.length === 1 ) {
656
657 panEnd.set( event.pageX, event.pageY );
658
659 } else {
660
661 const position = getSecondPointerPosition( event );
662 const x = 0.5 * ( event.pageX + position.x );
663 const y = 0.5 * ( event.pageY + position.y );
664 panEnd.set( x, y );
665
666 }
667
668 panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
669 pan( panDelta.x, panDelta.y );
670 panStart.copy( panEnd );
671
672 }
673
674 function handleTouchMoveDolly( event ) {
675
676 const position = getSecondPointerPosition( event );
677 const dx = event.pageX - position.x;
678 const dy = event.pageY - position.y;
679 const distance = Math.sqrt( dx * dx + dy * dy );
680 dollyEnd.set( 0, distance );
681 dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
682 dollyOut( dollyDelta.y );
683 dollyStart.copy( dollyEnd );
684
685 }
686
687 function handleTouchMoveDollyPan( event ) {
688
689 if ( scope.enableZoom ) handleTouchMoveDolly( event );
690 if ( scope.enablePan ) handleTouchMovePan( event );
691
692 }
693
694 function handleTouchMoveDollyRotate( event ) {
695
696 if ( scope.enableZoom ) handleTouchMoveDolly( event );
697 if ( scope.enableRotate ) handleTouchMoveRotate( event );
698
699 }
700
701 function handleTouchEnd() { // no-op
702 } //
703 // event handlers - FSM: listen for events and reset state
704 //
705
706
707 function onPointerDown( event ) {
708
709 if ( scope.enabled === false ) return;
710
711 if ( pointers.length === 0 ) {
712
713 scope.domElement.setPointerCapture( event.pointerId );
714 scope.domElement.addEventListener( 'pointermove', onPointerMove );
715 scope.domElement.addEventListener( 'pointerup', onPointerUp );
716
717 } //
718
719
720 addPointer( event );
721
722 if ( event.pointerType === 'touch' ) {
723
724 onTouchStart( event );
725
726 } else {
727
728 onMouseDown( event );
729
730 }
731
732 }
733
734 function onPointerMove( event ) {
735
736 if ( scope.enabled === false ) return;
737
738 if ( event.pointerType === 'touch' ) {
739
740 onTouchMove( event );
741
742 } else {
743
744 onMouseMove( event );
745
746 }
747
748 }
749
750 function onPointerUp( event ) {
751
752 if ( scope.enabled === false ) return;
753
754 if ( event.pointerType === 'touch' ) {
755
756 onTouchEnd();
757
758 } else {
759
760 onMouseUp( event );
761
762 }
763
764 removePointer( event ); //
765
766 if ( pointers.length === 0 ) {
767
768 scope.domElement.releasePointerCapture( event.pointerId );
769 scope.domElement.removeEventListener( 'pointermove', onPointerMove );
770 scope.domElement.removeEventListener( 'pointerup', onPointerUp );
771
772 }
773
774 }
775
776 function onPointerCancel( event ) {
777
778 removePointer( event );
779
780 }
781
782 function onMouseDown( event ) {
783
784 let mouseAction;
785
786 switch ( event.button ) {
787
788 case 0:
789 mouseAction = scope.mouseButtons.LEFT;
790 break;
791
792 case 1:
793 mouseAction = scope.mouseButtons.MIDDLE;
794 break;
795
796 case 2:
797 mouseAction = scope.mouseButtons.RIGHT;
798 break;
799
800 default:
801 mouseAction = - 1;
802
803 }
804
805 switch ( mouseAction ) {
806
807 case THREE.MOUSE.DOLLY:
808 if ( scope.enableZoom === false ) return;
809 handleMouseDownDolly( event );
810 state = STATE.DOLLY;
811 break;
812
813 case THREE.MOUSE.ROTATE:
814 if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
815
816 if ( scope.enablePan === false ) return;
817 handleMouseDownPan( event );
818 state = STATE.PAN;
819
820 } else {
821
822 if ( scope.enableRotate === false ) return;
823 handleMouseDownRotate( event );
824 state = STATE.ROTATE;
825
826 }
827
828 break;
829
830 case THREE.MOUSE.PAN:
831 if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
832
833 if ( scope.enableRotate === false ) return;
834 handleMouseDownRotate( event );
835 state = STATE.ROTATE;
836
837 } else {
838
839 if ( scope.enablePan === false ) return;
840 handleMouseDownPan( event );
841 state = STATE.PAN;
842
843 }
844
845 break;
846
847 default:
848 state = STATE.NONE;
849
850 }
851
852 if ( state !== STATE.NONE ) {
853
854 scope.dispatchEvent( _startEvent );
855
856 }
857
858 }
859
860 function onMouseMove( event ) {
861
862 if ( scope.enabled === false ) return;
863
864 switch ( state ) {
865
866 case STATE.ROTATE:
867 if ( scope.enableRotate === false ) return;
868 handleMouseMoveRotate( event );
869 break;
870
871 case STATE.DOLLY:
872 if ( scope.enableZoom === false ) return;
873 handleMouseMoveDolly( event );
874 break;
875
876 case STATE.PAN:
877 if ( scope.enablePan === false ) return;
878 handleMouseMovePan( event );
879 break;
880
881 }
882
883 }
884
885 function onMouseUp( event ) {
886
887 handleMouseUp( event );
888 scope.dispatchEvent( _endEvent );
889 state = STATE.NONE;
890
891 }
892
893 function onMouseWheel( event ) {
894
895 if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE && state !== STATE.ROTATE ) return;
896 event.preventDefault();
897 scope.dispatchEvent( _startEvent );
898 handleMouseWheel( event );
899 scope.dispatchEvent( _endEvent );
900
901 }
902
903 function onKeyDown( event ) {
904
905 if ( scope.enabled === false || scope.enablePan === false ) return;
906 handleKeyDown( event );
907
908 }
909
910 function onTouchStart( event ) {
911
912 trackPointer( event );
913
914 switch ( pointers.length ) {
915
916 case 1:
917 switch ( scope.touches.ONE ) {
918
919 case THREE.TOUCH.ROTATE:
920 if ( scope.enableRotate === false ) return;
921 handleTouchStartRotate();
922 state = STATE.TOUCH_ROTATE;
923 break;
924
925 case THREE.TOUCH.PAN:
926 if ( scope.enablePan === false ) return;
927 handleTouchStartPan();
928 state = STATE.TOUCH_PAN;
929 break;
930
931 default:
932 state = STATE.NONE;
933
934 }
935
936 break;
937
938 case 2:
939 switch ( scope.touches.TWO ) {
940
941 case THREE.TOUCH.DOLLY_PAN:
942 if ( scope.enableZoom === false && scope.enablePan === false ) return;
943 handleTouchStartDollyPan();
944 state = STATE.TOUCH_DOLLY_PAN;
945 break;
946
947 case THREE.TOUCH.DOLLY_ROTATE:
948 if ( scope.enableZoom === false && scope.enableRotate === false ) return;
949 handleTouchStartDollyRotate();
950 state = STATE.TOUCH_DOLLY_ROTATE;
951 break;
952
953 default:
954 state = STATE.NONE;
955
956 }
957
958 break;
959
960 default:
961 state = STATE.NONE;
962
963 }
964
965 if ( state !== STATE.NONE ) {
966
967 scope.dispatchEvent( _startEvent );
968
969 }
970
971 }
972
973 function onTouchMove( event ) {
974
975 trackPointer( event );
976
977 switch ( state ) {
978
979 case STATE.TOUCH_ROTATE:
980 if ( scope.enableRotate === false ) return;
981 handleTouchMoveRotate( event );
982 scope.update();
983 break;
984
985 case STATE.TOUCH_PAN:
986 if ( scope.enablePan === false ) return;
987 handleTouchMovePan( event );
988 scope.update();
989 break;
990
991 case STATE.TOUCH_DOLLY_PAN:
992 if ( scope.enableZoom === false && scope.enablePan === false ) return;
993 handleTouchMoveDollyPan( event );
994 scope.update();
995 break;
996
997 case STATE.TOUCH_DOLLY_ROTATE:
998 if ( scope.enableZoom === false && scope.enableRotate === false ) return;
999 handleTouchMoveDollyRotate( event );
1000 scope.update();
1001 break;
1002
1003 default:
1004 state = STATE.NONE;
1005
1006 }
1007
1008 }
1009
1010 function onTouchEnd( event ) {
1011
1012 handleTouchEnd( event );
1013 scope.dispatchEvent( _endEvent );
1014 state = STATE.NONE;
1015
1016 }
1017
1018 function onContextMenu( event ) {
1019
1020 if ( scope.enabled === false ) return;
1021 event.preventDefault();
1022
1023 }
1024
1025 function addPointer( event ) {
1026
1027 pointers.push( event );
1028
1029 }
1030
1031 function removePointer( event ) {
1032
1033 delete pointerPositions[ event.pointerId ];
1034
1035 for ( let i = 0; i < pointers.length; i ++ ) {
1036
1037 if ( pointers[ i ].pointerId == event.pointerId ) {
1038
1039 pointers.splice( i, 1 );
1040 return;
1041
1042 }
1043
1044 }
1045
1046 }
1047
1048 function trackPointer( event ) {
1049
1050 let position = pointerPositions[ event.pointerId ];
1051
1052 if ( position === undefined ) {
1053
1054 position = new THREE.Vector2();
1055 pointerPositions[ event.pointerId ] = position;
1056
1057 }
1058
1059 position.set( event.pageX, event.pageY );
1060
1061 }
1062
1063 function getSecondPointerPosition( event ) {
1064
1065 const pointer = event.pointerId === pointers[ 0 ].pointerId ? pointers[ 1 ] : pointers[ 0 ];
1066 return pointerPositions[ pointer.pointerId ];
1067
1068 } //
1069
1070
1071 scope.domElement.addEventListener( 'contextmenu', onContextMenu );
1072 scope.domElement.addEventListener( 'pointerdown', onPointerDown );
1073 scope.domElement.addEventListener( 'pointercancel', onPointerCancel );
1074 scope.domElement.addEventListener( 'wheel', onMouseWheel, {
1075 passive: false
1076 } ); // force an update at start
1077
1078 this.update();
1079
1080 }
1081
1082 } // This set of controls performs orbiting, dollying (zooming), and panning.
1083 // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
1084 // This is very similar to OrbitControls, another set of touch behavior
1085 //
1086 // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
1087 // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
1088 // Pan - left mouse, or arrow keys / touch: one-finger move
1089
1090
1091 class MapControls extends OrbitControls {
1092
1093 constructor( object, domElement ) {
1094
1095 super( object, domElement );
1096 this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
1097
1098 this.mouseButtons.LEFT = THREE.MOUSE.PAN;
1099 this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
1100 this.touches.ONE = THREE.TOUCH.PAN;
1101 this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
1102
1103 }
1104
1105 }
1106
1107 THREE.MapControls = MapControls;
1108 THREE.OrbitControls = OrbitControls;
1109
1110} )();