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