threejs/viewer: TrackballControls.js

File TrackballControls.js, 11.3 KB (added by Leon Kos, 11 years ago)

Krogelni kontrolnik za vrtenje modela

Line 
1/**
2 * @author Eberhard Graether / http://egraether.com/
3 */
4
5THREE.TrackballControls = function ( object, domElement ) {
6
7        var _this = this;
8        var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 };
9
10        this.object = object;
11        this.domElement = ( domElement !== undefined ) ? domElement : document;
12
13        // API
14
15        this.enabled = true;
16
17        this.screen = { left: 0, top: 0, width: 0, height: 0 };
18
19        this.rotateSpeed = 1.0;
20        this.zoomSpeed = 1.2;
21        this.panSpeed = 0.3;
22
23        this.noRotate = false;
24        this.noZoom = false;
25        this.noPan = false;
26        this.noRoll = false;
27
28        this.staticMoving = false;
29        this.dynamicDampingFactor = 0.2;
30
31        this.minDistance = 0;
32        this.maxDistance = Infinity;
33
34        this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
35
36        // internals
37
38        this.target = new THREE.Vector3();
39
40        var lastPosition = new THREE.Vector3();
41
42        var _state = STATE.NONE,
43        _prevState = STATE.NONE,
44
45        _eye = new THREE.Vector3(),
46
47        _rotateStart = new THREE.Vector3(),
48        _rotateEnd = new THREE.Vector3(),
49
50        _zoomStart = new THREE.Vector2(),
51        _zoomEnd = new THREE.Vector2(),
52
53        _touchZoomDistanceStart = 0,
54        _touchZoomDistanceEnd = 0,
55
56        _panStart = new THREE.Vector2(),
57        _panEnd = new THREE.Vector2();
58
59        // for reset
60
61        this.target0 = this.target.clone();
62        this.position0 = this.object.position.clone();
63        this.up0 = this.object.up.clone();
64
65        // events
66
67        var changeEvent = { type: 'change' };
68
69
70        // methods
71
72        this.handleResize = function () {
73
74                if ( this.domElement === document ) {
75
76                        this.screen.left = 0;
77                        this.screen.top = 0;
78                        this.screen.width = window.innerWidth;
79                        this.screen.height = window.innerHeight;
80
81                } else {
82
83                        this.screen = this.domElement.getBoundingClientRect();
84
85                }
86
87        };
88
89        this.handleEvent = function ( event ) {
90
91                if ( typeof this[ event.type ] == 'function' ) {
92
93                        this[ event.type ]( event );
94
95                }
96
97        };
98
99        this.getMouseOnScreen = function ( clientX, clientY ) {
100
101                return new THREE.Vector2(
102                        ( clientX - _this.screen.left ) / _this.screen.width,
103                        ( clientY - _this.screen.top ) / _this.screen.height
104                );
105
106        };
107
108        this.getMouseProjectionOnBall = function ( clientX, clientY ) {
109
110                var mouseOnBall = new THREE.Vector3(
111                        ( clientX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5),
112                        ( _this.screen.height * 0.5 + _this.screen.top - clientY ) / (_this.screen.height*.5),
113                        0.0
114                );
115
116                var length = mouseOnBall.length();
117
118                if ( _this.noRoll ) {
119
120                        if ( length < Math.SQRT1_2 ) {
121
122                                mouseOnBall.z = Math.sqrt( 1.0 - length*length );
123
124                        } else {
125
126                                mouseOnBall.z = .5 / length;
127                               
128                        }
129
130                } else if ( length > 1.0 ) {
131
132                        mouseOnBall.normalize();
133
134                } else {
135
136                        mouseOnBall.z = Math.sqrt( 1.0 - length * length );
137
138                }
139
140                _eye.copy( _this.object.position ).sub( _this.target );
141
142                var projection = _this.object.up.clone().setLength( mouseOnBall.y );
143                projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) );
144                projection.add( _eye.setLength( mouseOnBall.z ) );
145
146                return projection;
147
148        };
149
150        this.rotateCamera = function () {
151
152                var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
153
154                if ( angle ) {
155
156                        var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(),
157                                quaternion = new THREE.Quaternion();
158
159                        angle *= _this.rotateSpeed;
160
161                        quaternion.setFromAxisAngle( axis, -angle );
162
163                        _eye.applyQuaternion( quaternion );
164                        _this.object.up.applyQuaternion( quaternion );
165
166                        _rotateEnd.applyQuaternion( quaternion );
167
168                        if ( _this.staticMoving ) {
169
170                                _rotateStart.copy( _rotateEnd );
171
172                        } else {
173
174                                quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
175                                _rotateStart.applyQuaternion( quaternion );
176
177                        }
178
179                }
180
181        };
182
183        this.zoomCamera = function () {
184
185                if ( _state === STATE.TOUCH_ZOOM ) {
186
187                        var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
188                        _touchZoomDistanceStart = _touchZoomDistanceEnd;
189                        _eye.multiplyScalar( factor );
190
191                } else {
192
193                        var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
194
195                        if ( factor !== 1.0 && factor > 0.0 ) {
196
197                                _eye.multiplyScalar( factor );
198
199                                if ( _this.staticMoving ) {
200
201                                        _zoomStart.copy( _zoomEnd );
202
203                                } else {
204
205                                        _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
206
207                                }
208
209                        }
210
211                }
212
213        };
214
215        this.panCamera = function () {
216
217                var mouseChange = _panEnd.clone().sub( _panStart );
218
219                if ( mouseChange.lengthSq() ) {
220
221                        mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
222
223                        var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x );
224                        pan.add( _this.object.up.clone().setLength( mouseChange.y ) );
225
226                        _this.object.position.add( pan );
227                        _this.target.add( pan );
228
229                        if ( _this.staticMoving ) {
230
231                                _panStart = _panEnd;
232
233                        } else {
234
235                                _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
236
237                        }
238
239                }
240
241        };
242
243        this.checkDistances = function () {
244
245                if ( !_this.noZoom || !_this.noPan ) {
246
247                        if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
248
249                                _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
250
251                        }
252
253                        if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
254
255                                _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
256
257                        }
258
259                }
260
261        };
262
263        this.update = function () {
264
265                _eye.subVectors( _this.object.position, _this.target );
266
267                if ( !_this.noRotate ) {
268
269                        _this.rotateCamera();
270
271                }
272
273                if ( !_this.noZoom ) {
274
275                        _this.zoomCamera();
276
277                }
278
279                if ( !_this.noPan ) {
280
281                        _this.panCamera();
282
283                }
284
285                _this.object.position.addVectors( _this.target, _eye );
286
287                _this.checkDistances();
288
289                _this.object.lookAt( _this.target );
290
291                if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {
292
293                        _this.dispatchEvent( changeEvent );
294
295                        lastPosition.copy( _this.object.position );
296
297                }
298
299        };
300
301        this.reset = function () {
302
303                _state = STATE.NONE;
304                _prevState = STATE.NONE;
305
306                _this.target.copy( _this.target0 );
307                _this.object.position.copy( _this.position0 );
308                _this.object.up.copy( _this.up0 );
309
310                _eye.subVectors( _this.object.position, _this.target );
311
312                _this.object.lookAt( _this.target );
313
314                _this.dispatchEvent( changeEvent );
315
316                lastPosition.copy( _this.object.position );
317
318        };
319
320        // listeners
321
322        function keydown( event ) {
323
324                if ( _this.enabled === false ) return;
325
326                window.removeEventListener( 'keydown', keydown );
327
328                _prevState = _state;
329
330                if ( _state !== STATE.NONE ) {
331
332                        return;
333
334                } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {
335
336                        _state = STATE.ROTATE;
337
338                } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {
339
340                        _state = STATE.ZOOM;
341
342                } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {
343
344                        _state = STATE.PAN;
345
346                }
347
348        }
349
350        function keyup( event ) {
351
352                if ( _this.enabled === false ) return;
353
354                _state = _prevState;
355
356                window.addEventListener( 'keydown', keydown, false );
357
358        }
359
360        function mousedown( event ) {
361
362                if ( _this.enabled === false ) return;
363
364                event.preventDefault();
365                event.stopPropagation();
366
367                if ( _state === STATE.NONE ) {
368
369                        _state = event.button;
370
371                }
372
373                if ( _state === STATE.ROTATE && !_this.noRotate ) {
374
375                        _rotateStart = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
376                        _rotateEnd.copy(_rotateStart)
377
378                } else if ( _state === STATE.ZOOM && !_this.noZoom ) {
379
380                        _zoomStart = _this.getMouseOnScreen( event.clientX, event.clientY );
381                        _zoomEnd.copy(_zoomStart);
382
383                } else if ( _state === STATE.PAN && !_this.noPan ) {
384
385                        _panStart = _this.getMouseOnScreen( event.clientX, event.clientY );
386                        _panEnd.copy(_panStart)
387
388                }
389
390                document.addEventListener( 'mousemove', mousemove, false );
391                document.addEventListener( 'mouseup', mouseup, false );
392
393        }
394
395        function mousemove( event ) {
396
397                if ( _this.enabled === false ) return;
398
399                event.preventDefault();
400                event.stopPropagation();
401
402                if ( _state === STATE.ROTATE && !_this.noRotate ) {
403
404                        _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
405
406                } else if ( _state === STATE.ZOOM && !_this.noZoom ) {
407
408                        _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
409
410                } else if ( _state === STATE.PAN && !_this.noPan ) {
411
412                        _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
413
414                }
415
416        }
417
418        function mouseup( event ) {
419
420                if ( _this.enabled === false ) return;
421
422                event.preventDefault();
423                event.stopPropagation();
424
425                _state = STATE.NONE;
426
427                document.removeEventListener( 'mousemove', mousemove );
428                document.removeEventListener( 'mouseup', mouseup );
429
430        }
431
432        function mousewheel( event ) {
433
434                if ( _this.enabled === false ) return;
435
436                event.preventDefault();
437                event.stopPropagation();
438
439                var delta = 0;
440
441                if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
442
443                        delta = event.wheelDelta / 40;
444
445                } else if ( event.detail ) { // Firefox
446
447                        delta = - event.detail / 3;
448
449                }
450
451                _zoomStart.y += delta * 0.01;
452
453        }
454
455        function touchstart( event ) {
456
457                if ( _this.enabled === false ) return;
458
459                switch ( event.touches.length ) {
460
461                        case 1:
462                                _state = STATE.TOUCH_ROTATE;
463                                _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
464                                break;
465
466                        case 2:
467                                _state = STATE.TOUCH_ZOOM;
468                                var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
469                                var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
470                                _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
471                                break;
472
473                        case 3:
474                                _state = STATE.TOUCH_PAN;
475                                _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
476                                break;
477
478                        default:
479                                _state = STATE.NONE;
480
481                }
482
483        }
484
485        function touchmove( event ) {
486
487                if ( _this.enabled === false ) return;
488
489                event.preventDefault();
490                event.stopPropagation();
491
492                switch ( event.touches.length ) {
493
494                        case 1:
495                                _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
496                                break;
497
498                        case 2:
499                                var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
500                                var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
501                                _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
502                                break;
503
504                        case 3:
505                                _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
506                                break;
507
508                        default:
509                                _state = STATE.NONE;
510
511                }
512
513        }
514
515        function touchend( event ) {
516
517                if ( _this.enabled === false ) return;
518
519                switch ( event.touches.length ) {
520
521                        case 1:
522                                _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
523                                break;
524
525                        case 2:
526                                _touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
527                                break;
528
529                        case 3:
530                                _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
531                                break;
532
533                }
534
535                _state = STATE.NONE;
536
537        }
538
539        this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
540
541        this.domElement.addEventListener( 'mousedown', mousedown, false );
542
543        this.domElement.addEventListener( 'mousewheel', mousewheel, false );
544        this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
545
546        this.domElement.addEventListener( 'touchstart', touchstart, false );
547        this.domElement.addEventListener( 'touchend', touchend, false );
548        this.domElement.addEventListener( 'touchmove', touchmove, false );
549
550        window.addEventListener( 'keydown', keydown, false );
551        window.addEventListener( 'keyup', keyup, false );
552
553        this.handleResize();
554
555};
556
557THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );