source: other-projects/nz-flag-design/trunk/render-3d/Flag_files/OrbitControls.js@ 29721

Last change on this file since 29721 was 29686, checked in by bmt11, 9 years ago
File size: 15.4 KB
Line 
1/**
2 * @author qiao / https://github.com/qiao
3 * @author mrdoob / http://mrdoob.com
4 * @author alteredq / http://alteredqualia.com/
5 * @author WestLangley / http://github.com/WestLangley
6 * @author erich666 / http://erichaines.com
7 */
8/*global THREE, console */
9
10// This set of controls performs orbiting, dollying (zooming), and panning. It maintains
11// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
12// supported.
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 arrow keys / touch: three finter swipe
17//
18// This is a drop-in replacement for (most) TrackballControls used in examples.
19// That is, include this js file and wherever you see:
20// controls = new THREE.TrackballControls( camera );
21// controls.target.z = 150;
22// Simple substitute "OrbitControls" and the control should work as-is.
23
24THREE.OrbitControls = function ( object, domElement ) {
25
26 this.object = object;
27 this.domElement = ( domElement !== undefined ) ? domElement : document;
28
29 // API
30
31 // Set to false to disable this control
32 this.enabled = true;
33
34 // "target" sets the location of focus, where the control orbits around
35 // and where it pans with respect to.
36 this.target = new THREE.Vector3();
37
38 // center is old, deprecated; use "target" instead
39 this.center = this.target;
40
41 // This option actually enables dollying in and out; left as "zoom" for
42 // backwards compatibility
43 this.noZoom = false;
44 this.zoomSpeed = 1.0;
45
46 // Limits to how far you can dolly in and out
47 this.minDistance = 1000;
48 this.maxDistance = 9000;
49
50 // Set to true to disable this control
51 this.noRotate = false;
52 this.rotateSpeed = 0.8;
53
54 // Set to true to disable this control
55 this.noPan = false;
56 this.keyPanSpeed = 70.0; // pixels moved per arrow key push
57
58 // Set to true to automatically rotate around the target
59 this.autoRotate = false;
60 this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
61
62 // How far you can orbit vertically, upper and lower limits.
63 // Range is 0 to Math.PI radians.
64 this.minPolarAngle = Math.PI/12; // radians
65 this.maxPolarAngle = Math.PI/2; // radians
66
67 // How far you can orbit horizontally, upper and lower limits.
68 // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
69 this.minAzimuthAngle = - Infinity; // radians
70 this.maxAzimuthAngle = Infinity; // radians
71
72 // Set to true to disable use of the keys
73 this.noKeys = true;
74
75 // The four arrow keys
76 this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
77
78 // Mouse buttons
79 this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
80
81 ////////////
82 // internals
83
84 var scope = this;
85
86 var EPS = 0.000001;
87
88 var rotateStart = new THREE.Vector2();
89 var rotateEnd = new THREE.Vector2();
90 var rotateDelta = new THREE.Vector2();
91
92 var panStart = new THREE.Vector2();
93 var panEnd = new THREE.Vector2();
94 var panDelta = new THREE.Vector2();
95 var panOffset = new THREE.Vector3();
96
97 var offset = new THREE.Vector3();
98
99 var dollyStart = new THREE.Vector2();
100 var dollyEnd = new THREE.Vector2();
101 var dollyDelta = new THREE.Vector2();
102
103 var phiDelta = 0;
104 var thetaDelta = 0;
105 var scale = 1;
106 var pan = new THREE.Vector3();
107
108 var lastPosition = new THREE.Vector3();
109 var lastQuaternion = new THREE.Quaternion();
110
111 var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
112
113 var state = STATE.NONE;
114
115 // for reset
116
117 this.target0 = this.target.clone();
118 this.position0 = this.object.position.clone();
119
120 // so camera.up is the orbit axis
121
122 var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
123 var quatInverse = quat.clone().inverse();
124
125 // events
126
127 var changeEvent = { type: 'change' };
128 var startEvent = { type: 'start'};
129 var endEvent = { type: 'end'};
130
131 this.rotateLeft = function ( angle ) {
132
133 if ( angle === undefined ) {
134
135 angle = getAutoRotationAngle();
136
137 }
138
139 thetaDelta -= angle;
140
141 };
142
143 this.rotateUp = function ( angle ) {
144
145 if ( angle === undefined ) {
146
147 angle = getAutoRotationAngle();
148
149 }
150
151 phiDelta -= angle;
152
153 };
154
155 // pass in distance in world space to move left
156 this.panLeft = function ( distance ) {
157
158 var te = this.object.matrix.elements;
159
160 // get X column of matrix
161 panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] );
162 panOffset.multiplyScalar( - distance );
163
164 pan.add( panOffset );
165
166 };
167
168 // pass in distance in world space to move up
169 this.panUp = function ( distance ) {
170
171 var te = this.object.matrix.elements;
172
173 // get Y column of matrix
174 panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] );
175 panOffset.multiplyScalar( distance );
176
177 pan.add( panOffset );
178
179 };
180
181 // pass in x,y of change desired in pixel space,
182 // right and down are positive
183 this.pan = function ( deltaX, deltaY ) {
184
185 var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
186
187 if ( scope.object.fov !== undefined ) {
188
189 // perspective
190 var position = scope.object.position;
191 var offset = position.clone().sub( scope.target );
192 var targetDistance = offset.length();
193
194 // half of the fov is center to top of screen
195 targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
196
197 // we actually don't use screenWidth, since perspective camera is fixed to screen height
198 scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight );
199 scope.panUp( 2 * deltaY * targetDistance / element.clientHeight );
200
201 } else if ( scope.object.top !== undefined ) {
202
203 // orthographic
204 scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth );
205 scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight );
206
207 } else {
208
209 // camera neither orthographic or perspective
210 console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
211
212 }
213
214 };
215
216 this.dollyIn = function ( dollyScale ) {
217
218 if ( dollyScale === undefined ) {
219
220 dollyScale = getZoomScale();
221
222 }
223
224 scale /= dollyScale;
225
226 };
227
228 this.dollyOut = function ( dollyScale ) {
229
230 if ( dollyScale === undefined ) {
231
232 dollyScale = getZoomScale();
233
234 }
235
236 scale *= dollyScale;
237
238 };
239
240 this.update = function () {
241
242 var position = this.object.position;
243
244 offset.copy( position ).sub( this.target );
245
246 // rotate offset to "y-axis-is-up" space
247 offset.applyQuaternion( quat );
248
249 // angle from z-axis around y-axis
250
251 var theta = Math.atan2( offset.x, offset.z );
252
253 // angle from y-axis
254
255 var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
256
257 if ( this.autoRotate ) {
258
259 this.rotateLeft( getAutoRotationAngle() );
260
261 }
262
263 theta += thetaDelta;
264 phi += phiDelta;
265
266 // restrict theta to be between desired limits
267 theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) );
268
269 // restrict phi to be between desired limits
270 phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
271
272 // restrict phi to be betwee EPS and PI-EPS
273 phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
274
275 var radius = offset.length() * scale;
276
277 // restrict radius to be between desired limits
278 radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
279
280 // move target to panned location
281 this.target.add( pan );
282
283 offset.x = radius * Math.sin( phi ) * Math.sin( theta );
284 offset.y = radius * Math.cos( phi );
285 offset.z = radius * Math.sin( phi ) * Math.cos( theta );
286
287 // rotate offset back to "camera-up-vector-is-up" space
288 offset.applyQuaternion( quatInverse );
289
290 position.copy( this.target ).add( offset );
291
292 this.object.lookAt( this.target );
293
294 thetaDelta = 0;
295 phiDelta = 0;
296 scale = 1;
297 pan.set( 0, 0, 0 );
298
299 // update condition is:
300 // min(camera displacement, camera rotation in radians)^2 > EPS
301 // using small-angle approximation cos(x/2) = 1 - x^2 / 8
302
303 if ( lastPosition.distanceToSquared( this.object.position ) > EPS
304 || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) {
305
306 this.dispatchEvent( changeEvent );
307
308 lastPosition.copy( this.object.position );
309 lastQuaternion.copy (this.object.quaternion );
310
311 }
312
313 };
314
315
316 this.reset = function () {
317
318 state = STATE.NONE;
319
320 this.target.copy( this.target0 );
321 this.object.position.copy( this.position0 );
322
323 this.update();
324
325 };
326
327 function getAutoRotationAngle() {
328
329 return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
330
331 }
332
333 function getZoomScale() {
334
335 return Math.pow( 0.95, scope.zoomSpeed );
336
337 }
338
339 function onMouseDown( event ) {
340
341 if ( scope.enabled === false ) return;
342 event.preventDefault();
343
344 if ( event.button === scope.mouseButtons.ORBIT ) {
345 if ( scope.noRotate === true ) return;
346
347 state = STATE.ROTATE;
348
349 rotateStart.set( event.clientX, event.clientY );
350
351 } else if ( event.button === scope.mouseButtons.ZOOM ) {
352 if ( scope.noZoom === true ) return;
353
354 state = STATE.DOLLY;
355
356 dollyStart.set( event.clientX, event.clientY );
357
358 } else if ( event.button === scope.mouseButtons.PAN ) {
359 if ( scope.noPan === true ) return;
360
361 state = STATE.PAN;
362
363 panStart.set( event.clientX, event.clientY );
364
365 }
366
367 document.addEventListener( 'mousemove', onMouseMove, false );
368 document.addEventListener( 'mouseup', onMouseUp, false );
369 scope.dispatchEvent( startEvent );
370
371 }
372
373 function onMouseMove( event ) {
374
375 if ( scope.enabled === false ) return;
376
377 event.preventDefault();
378
379 var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
380
381 if ( state === STATE.ROTATE ) {
382
383 if ( scope.noRotate === true ) return;
384
385 rotateEnd.set( event.clientX, event.clientY );
386 rotateDelta.subVectors( rotateEnd, rotateStart );
387
388 // rotating across whole screen goes 360 degrees around
389 scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
390
391 // rotating up and down along whole screen attempts to go 360, but limited to 180
392 scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
393
394 rotateStart.copy( rotateEnd );
395
396 } else if ( state === STATE.DOLLY ) {
397
398 if ( scope.noZoom === true ) return;
399
400 dollyEnd.set( event.clientX, event.clientY );
401 dollyDelta.subVectors( dollyEnd, dollyStart );
402
403 if ( dollyDelta.y > 0 ) {
404
405 scope.dollyIn();
406
407 } else {
408
409 scope.dollyOut();
410
411 }
412
413 dollyStart.copy( dollyEnd );
414
415 } else if ( state === STATE.PAN ) {
416
417 if ( scope.noPan === true ) return;
418
419 panEnd.set( event.clientX, event.clientY );
420 panDelta.subVectors( panEnd, panStart );
421
422 scope.pan( panDelta.x, panDelta.y );
423
424 panStart.copy( panEnd );
425
426 }
427
428 scope.update();
429
430 }
431
432 function onMouseUp( /* event */ ) {
433
434 if ( scope.enabled === false ) return;
435
436 document.removeEventListener( 'mousemove', onMouseMove, false );
437 document.removeEventListener( 'mouseup', onMouseUp, false );
438 scope.dispatchEvent( endEvent );
439 state = STATE.NONE;
440
441 }
442
443 function onMouseWheel( event ) {
444
445 if ( scope.enabled === false || scope.noZoom === true ) return;
446
447 event.preventDefault();
448 event.stopPropagation();
449
450 var delta = 0;
451
452 if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9
453
454 delta = event.wheelDelta;
455
456 } else if ( event.detail !== undefined ) { // Firefox
457
458 delta = - event.detail;
459
460 }
461
462 if ( delta > 0 ) {
463
464 scope.dollyOut();
465
466 } else {
467
468 scope.dollyIn();
469
470 }
471
472 scope.update();
473 scope.dispatchEvent( startEvent );
474 scope.dispatchEvent( endEvent );
475
476 }
477
478 function onKeyDown( event ) {
479
480 if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return;
481
482 switch ( event.keyCode ) {
483
484 case scope.keys.UP:
485 scope.pan( 0, scope.keyPanSpeed );
486 scope.update();
487 break;
488
489 case scope.keys.BOTTOM:
490 scope.pan( 0, - scope.keyPanSpeed );
491 scope.update();
492 break;
493
494 case scope.keys.LEFT:
495 scope.pan( scope.keyPanSpeed, 0 );
496 scope.update();
497 break;
498
499 case scope.keys.RIGHT:
500 scope.pan( - scope.keyPanSpeed, 0 );
501 scope.update();
502 break;
503
504 }
505
506 }
507
508 function touchstart( event ) {
509
510 if ( scope.enabled === false ) return;
511
512 switch ( event.touches.length ) {
513
514 case 1: // one-fingered touch: rotate
515
516 if ( scope.noRotate === true ) return;
517
518 state = STATE.TOUCH_ROTATE;
519
520 rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
521 break;
522
523 case 2: // two-fingered touch: dolly
524
525 if ( scope.noZoom === true ) return;
526
527 state = STATE.TOUCH_DOLLY;
528
529 var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
530 var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
531 var distance = Math.sqrt( dx * dx + dy * dy );
532 dollyStart.set( 0, distance );
533 break;
534
535 case 3: // three-fingered touch: pan
536
537 if ( scope.noPan === true ) return;
538
539 state = STATE.TOUCH_PAN;
540
541 panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
542 break;
543
544 default:
545
546 state = STATE.NONE;
547
548 }
549
550 scope.dispatchEvent( startEvent );
551
552 }
553
554 function touchmove( event ) {
555
556 if ( scope.enabled === false ) return;
557
558 event.preventDefault();
559 event.stopPropagation();
560
561 var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
562
563 switch ( event.touches.length ) {
564
565 case 1: // one-fingered touch: rotate
566
567 if ( scope.noRotate === true ) return;
568 if ( state !== STATE.TOUCH_ROTATE ) return;
569
570 rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
571 rotateDelta.subVectors( rotateEnd, rotateStart );
572
573 // rotating across whole screen goes 360 degrees around
574 scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
575 // rotating up and down along whole screen attempts to go 360, but limited to 180
576 scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
577
578 rotateStart.copy( rotateEnd );
579
580 scope.update();
581 break;
582
583 case 2: // two-fingered touch: dolly
584
585 if ( scope.noZoom === true ) return;
586 if ( state !== STATE.TOUCH_DOLLY ) return;
587
588 var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
589 var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
590 var distance = Math.sqrt( dx * dx + dy * dy );
591
592 dollyEnd.set( 0, distance );
593 dollyDelta.subVectors( dollyEnd, dollyStart );
594
595 if ( dollyDelta.y > 0 ) {
596
597 scope.dollyOut();
598
599 } else {
600
601 scope.dollyIn();
602
603 }
604
605 dollyStart.copy( dollyEnd );
606
607 scope.update();
608 break;
609
610 case 3: // three-fingered touch: pan
611
612 if ( scope.noPan === true ) return;
613 if ( state !== STATE.TOUCH_PAN ) return;
614
615 panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
616 panDelta.subVectors( panEnd, panStart );
617
618 scope.pan( panDelta.x, panDelta.y );
619
620 panStart.copy( panEnd );
621
622 scope.update();
623 break;
624
625 default:
626
627 state = STATE.NONE;
628
629 }
630
631 }
632
633 function touchend( /* event */ ) {
634
635 if ( scope.enabled === false ) return;
636
637 scope.dispatchEvent( endEvent );
638 state = STATE.NONE;
639
640 }
641
642 this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
643 this.domElement.addEventListener( 'mousedown', onMouseDown, false );
644 this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
645 this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
646
647 this.domElement.addEventListener( 'touchstart', touchstart, false );
648 this.domElement.addEventListener( 'touchend', touchend, false );
649 this.domElement.addEventListener( 'touchmove', touchmove, false );
650
651 window.addEventListener( 'keydown', onKeyDown, false );
652
653 // force an update at start
654 this.update();
655
656};
657
658THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
Note: See TracBrowser for help on using the repository browser.