PageSourceSearch

https://static.studyladder.co.uk/cdn/games/0.1701/three/examples/jsm/controls/OrbitControls.js

js studyladder.co.uk collected 2026-09-24 11:31:04 UTC 25,338 bytes, 1,247 lines download raw bytes

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.