HenryPL commited on
Commit
6a5c4bb
·
verified ·
1 Parent(s): 301ac80

v12: OrbitControls.js

Browse files
Files changed (1) hide show
  1. OrbitControls.js +1110 -0
OrbitControls.js ADDED
@@ -0,0 +1,1110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ) 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
+ } )();