TrackballControls.js 12.8 KB
Newer Older
1
/**
2
 * @author Eberhard Graether / http://egraether.com/
M
Mark Lundin 已提交
3
 * @author Mark Lundin 	/ http://mark-lundin.com
4 5
 */

6
THREE.TrackballControls = function ( object, domElement ) {
7

M
Mr.doob 已提交
8
	var _this = this;
M
Mr.doob 已提交
9
	var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
10

11
	this.object = object;
12
	this.domElement = ( domElement !== undefined ) ? domElement : document;
13

14
	// API
15

16 17
	this.enabled = true;

18
	this.screen = { left: 0, top: 0, width: 0, height: 0 };
19

20 21 22
	this.rotateSpeed = 1.0;
	this.zoomSpeed = 1.2;
	this.panSpeed = 0.3;
23

24
	this.noRotate = false;
25 26
	this.noZoom = false;
	this.noPan = false;
27
	this.noRoll = false;
28

29 30
	this.staticMoving = false;
	this.dynamicDampingFactor = 0.2;
31

32 33
	this.minDistance = 0;
	this.maxDistance = Infinity;
34

35
	this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
36

37
	// internals
38

M
Mr.doob 已提交
39 40
	this.target = new THREE.Vector3();

41 42
	var EPS = 0.000001;

M
Mr.doob 已提交
43
	var lastPosition = new THREE.Vector3();
44

M
Mr.doob 已提交
45 46
	var _state = STATE.NONE,
	_prevState = STATE.NONE,
47

48 49
	_eye = new THREE.Vector3(),

50 51 52 53 54 55
	_rotateStart = new THREE.Vector3(),
	_rotateEnd = new THREE.Vector3(),

	_zoomStart = new THREE.Vector2(),
	_zoomEnd = new THREE.Vector2(),

56 57 58
	_touchZoomDistanceStart = 0,
	_touchZoomDistanceEnd = 0,

59 60
	_panStart = new THREE.Vector2(),
	_panEnd = new THREE.Vector2();
61

W
WestLangley 已提交
62 63 64 65 66 67
	// for reset

	this.target0 = this.target.clone();
	this.position0 = this.object.position.clone();
	this.up0 = this.object.up.clone();

M
Mr.doob 已提交
68 69 70
	// events

	var changeEvent = { type: 'change' };
71 72
	var startEvent = { type: 'start'};
	var endEvent = { type: 'end'};
M
Mr.doob 已提交
73

74

75
	// methods
76

77 78
	this.handleResize = function () {

79
		if ( this.domElement === document ) {
80

81 82 83 84 85 86 87
			this.screen.left = 0;
			this.screen.top = 0;
			this.screen.width = window.innerWidth;
			this.screen.height = window.innerHeight;

		} else {

88
			var box = this.domElement.getBoundingClientRect();
89
			// adjustments come from similar code in the jquery offset() function
90 91 92 93 94
			var d = this.domElement.ownerDocument.documentElement;
			this.screen.left = box.left + window.pageXOffset - d.clientLeft;
			this.screen.top = box.top + window.pageYOffset - d.clientTop;
			this.screen.width = box.width;
			this.screen.height = box.height;
95 96

		}
97

98 99
	};

100
	this.handleEvent = function ( event ) {
101

102
		if ( typeof this[ event.type ] == 'function' ) {
103

104
			this[ event.type ]( event );
105

106
		}
107

108
	};
109

M
Mr.doob 已提交
110
	var getMouseOnScreen = ( function () {
111

112
		var vector = new THREE.Vector2();
113

M
Mr.doob 已提交
114 115 116 117 118 119 120 121
		return function ( pageX, pageY ) {

			vector.set(
				( pageX - _this.screen.left ) / _this.screen.width,
				( pageY - _this.screen.top ) / _this.screen.height
			);

			return vector;
122

M
Mr.doob 已提交
123
		};
124

M
Mr.doob 已提交
125
	}() );
126

M
Mr.doob 已提交
127
	var getMouseProjectionOnBall = ( function () {
128

M
Mr.doob 已提交
129 130 131 132 133
		var vector = new THREE.Vector3();
		var objectUp = new THREE.Vector3();
		var mouseOnBall = new THREE.Vector3();

		return function ( pageX, pageY ) {
134

M
Mark Lundin 已提交
135
			mouseOnBall.set(
136 137
				( pageX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5),
				( _this.screen.height * 0.5 + _this.screen.top - pageY ) / (_this.screen.height*.5),
138 139
				0.0
			);
140

141
			var length = mouseOnBall.length();
142

143
			if ( _this.noRoll ) {
144

145
				if ( length < Math.SQRT1_2 ) {
146

147
					mouseOnBall.z = Math.sqrt( 1.0 - length*length );
148

149 150 151 152 153 154 155
				} else {

					mouseOnBall.z = .5 / length;
					
				}

			} else if ( length > 1.0 ) {
156

157
				mouseOnBall.normalize();
158

159 160 161 162 163 164 165 166
			} else {

				mouseOnBall.z = Math.sqrt( 1.0 - length * length );

			}

			_eye.copy( _this.object.position ).sub( _this.target );

M
Mr.doob 已提交
167 168 169
			vector.copy( _this.object.up ).setLength( mouseOnBall.y )
			vector.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) );
			vector.add( _eye.setLength( mouseOnBall.z ) );
170

M
Mr.doob 已提交
171
			return vector;
172

M
Mr.doob 已提交
173 174 175
		};

	}() );
176

177
	this.rotateCamera = (function(){
178

179 180
		var axis = new THREE.Vector3(),
			quaternion = new THREE.Quaternion();
181

182

183
		return function () {
184

185
			var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
186

187
			if ( angle ) {
188

189
				axis.crossVectors( _rotateStart, _rotateEnd ).normalize();
190

191
				angle *= _this.rotateSpeed;
192

193
				quaternion.setFromAxisAngle( axis, -angle );
194

195 196
				_eye.applyQuaternion( quaternion );
				_this.object.up.applyQuaternion( quaternion );
197

198
				_rotateEnd.applyQuaternion( quaternion );
199

200
				if ( _this.staticMoving ) {
201

202
					_rotateStart.copy( _rotateEnd );
203

204
				} else {
205

206 207
					quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
					_rotateStart.applyQuaternion( quaternion );
208

209
				}
210

211
			}
212
		}
213

214
	}());
215

M
Mr.doob 已提交
216
	this.zoomCamera = function () {
217

M
Mr.doob 已提交
218
		if ( _state === STATE.TOUCH_ZOOM_PAN ) {
219

220 221
			var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
			_touchZoomDistanceStart = _touchZoomDistanceEnd;
222
			_eye.multiplyScalar( factor );
223

224
		} else {
225

226
			var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
227

228
			if ( factor !== 1.0 && factor > 0.0 ) {
229

230 231 232 233 234 235 236 237 238 239 240
				_eye.multiplyScalar( factor );

				if ( _this.staticMoving ) {

					_zoomStart.copy( _zoomEnd );

				} else {

					_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;

				}
241 242

			}
243 244 245

		}

246
	};
247

248
	this.panCamera = (function(){
249

250 251 252
		var mouseChange = new THREE.Vector2(),
			objectUp = new THREE.Vector3(),
			pan = new THREE.Vector3();
253

254
		return function () {
255

256
			mouseChange.copy( _panEnd ).sub( _panStart );
257

258
			if ( mouseChange.lengthSq() ) {
259

260
				mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
261

262 263
				pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
				pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
264

265 266
				_this.object.position.add( pan );
				_this.target.add( pan );
267

268
				if ( _this.staticMoving ) {
269

270
					_panStart.copy( _panEnd );
271

272 273 274
				} else {

					_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
275

276 277 278
				}

			}
279
		}
280

281
	}());
282

M
Mr.doob 已提交
283
	this.checkDistances = function () {
284

285
		if ( !_this.noZoom || !_this.noPan ) {
286

287
			if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
288

289
				_this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
290 291 292

			}

293
			if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
294

295
				_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
296 297

			}
298 299 300

		}

301 302
	};

M
Mr.doob 已提交
303
	this.update = function () {
M
Mr.doob 已提交
304

305
		_eye.subVectors( _this.object.position, _this.target );
M
Mr.doob 已提交
306 307 308 309 310 311

		if ( !_this.noRotate ) {

			_this.rotateCamera();

		}
312

M
Mr.doob 已提交
313 314 315 316 317 318 319 320 321 322 323 324
		if ( !_this.noZoom ) {

			_this.zoomCamera();

		}

		if ( !_this.noPan ) {

			_this.panCamera();

		}

325
		_this.object.position.addVectors( _this.target, _eye );
M
Mr.doob 已提交
326 327 328 329 330

		_this.checkDistances();

		_this.object.lookAt( _this.target );

331
		if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) {
332

M
Mr.doob 已提交
333
			_this.dispatchEvent( changeEvent );
M
Mr.doob 已提交
334 335 336 337 338 339 340

			lastPosition.copy( _this.object.position );

		}

	};

W
WestLangley 已提交
341 342 343 344 345 346 347 348 349 350 351 352 353
	this.reset = function () {

		_state = STATE.NONE;
		_prevState = STATE.NONE;

		_this.target.copy( _this.target0 );
		_this.object.position.copy( _this.position0 );
		_this.object.up.copy( _this.up0 );

		_eye.subVectors( _this.object.position, _this.target );

		_this.object.lookAt( _this.target );

354
		_this.dispatchEvent( changeEvent );
W
WestLangley 已提交
355 356 357 358 359

		lastPosition.copy( _this.object.position );

	};

360
	// listeners
361

362
	function keydown( event ) {
363

364
		if ( _this.enabled === false ) return;
365

M
Mr.doob 已提交
366
		window.removeEventListener( 'keydown', keydown );
367

M
Mr.doob 已提交
368
		_prevState = _state;
369

370
		if ( _state !== STATE.NONE ) {
371

372
			return;
373

374
		} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {
375

376
			_state = STATE.ROTATE;
377

378
		} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {
379

380
			_state = STATE.ZOOM;
381

382
		} else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {
383

384
			_state = STATE.PAN;
385 386

		}
387

M
Mr.doob 已提交
388
	}
389

390
	function keyup( event ) {
391

392
		if ( _this.enabled === false ) return;
393

M
Mr.doob 已提交
394
		_state = _prevState;
395

M
Mr.doob 已提交
396
		window.addEventListener( 'keydown', keydown, false );
397

M
Mr.doob 已提交
398
	}
399

400
	function mousedown( event ) {
401

402
		if ( _this.enabled === false ) return;
403

404 405
		event.preventDefault();
		event.stopPropagation();
406

407
		if ( _state === STATE.NONE ) {
408

409
			_state = event.button;
410

M
Mr.doob 已提交
411
		}
412

M
Mr.doob 已提交
413
		if ( _state === STATE.ROTATE && !_this.noRotate ) {
414

M
Mr.doob 已提交
415 416
			_rotateStart.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
			_rotateEnd.copy( _rotateStart );
417

M
Mr.doob 已提交
418
		} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
419

M
Mr.doob 已提交
420
			_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
G
gero3 已提交
421
			_zoomEnd.copy(_zoomStart);
422

M
Mr.doob 已提交
423
		} else if ( _state === STATE.PAN && !_this.noPan ) {
424

M
Mr.doob 已提交
425
			_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
G
gero3 已提交
426
			_panEnd.copy(_panStart)
427

428
		}
M
Mr.doob 已提交
429

M
Mr.doob 已提交
430 431
		document.addEventListener( 'mousemove', mousemove, false );
		document.addEventListener( 'mouseup', mouseup, false );
432

M
Mr.doob 已提交
433
		_this.dispatchEvent( startEvent );
434

M
Mr.doob 已提交
435
	}
436

437
	function mousemove( event ) {
438

439 440 441 442
		if ( _this.enabled === false ) return;

		event.preventDefault();
		event.stopPropagation();
443

M
Mr.doob 已提交
444
		if ( _state === STATE.ROTATE && !_this.noRotate ) {
445

M
Mr.doob 已提交
446
			_rotateEnd.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
447

448
		} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
449

M
Mr.doob 已提交
450
			_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
451

452
		} else if ( _state === STATE.PAN && !_this.noPan ) {
453

M
Mr.doob 已提交
454
			_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
455

456
		}
457

M
Mr.doob 已提交
458
	}
459

460
	function mouseup( event ) {
461

462
		if ( _this.enabled === false ) return;
463

464 465
		event.preventDefault();
		event.stopPropagation();
466

467
		_state = STATE.NONE;
468

M
Mr.doob 已提交
469 470
		document.removeEventListener( 'mousemove', mousemove );
		document.removeEventListener( 'mouseup', mouseup );
471
		_this.dispatchEvent( endEvent );
472

M
Mr.doob 已提交
473
	}
474

475 476
	function mousewheel( event ) {

477
		if ( _this.enabled === false ) return;
478 479 480 481 482 483

		event.preventDefault();
		event.stopPropagation();

		var delta = 0;

484 485 486 487 488 489 490 491
		if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9

			delta = event.wheelDelta / 40;

		} else if ( event.detail ) { // Firefox

			delta = - event.detail / 3;

492 493
		}

494
		_zoomStart.y += delta * 0.01;
495 496
		_this.dispatchEvent( startEvent );
		_this.dispatchEvent( endEvent );
497 498 499

	}

M
Mr.doob 已提交
500 501
	function touchstart( event ) {

502
		if ( _this.enabled === false ) return;
M
Mr.doob 已提交
503 504 505 506

		switch ( event.touches.length ) {

			case 1:
507
				_state = STATE.TOUCH_ROTATE;
M
Mr.doob 已提交
508 509
				_rotateStart.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
				_rotateEnd.copy( _rotateStart );
M
Mr.doob 已提交
510
				break;
511

M
Mr.doob 已提交
512
			case 2:
M
Mr.doob 已提交
513
				_state = STATE.TOUCH_ZOOM_PAN;
514 515 516
				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
				_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
M
Mr.doob 已提交
517 518 519

				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
520
				_panStart.copy( getMouseOnScreen( x, y ) );
M
Mr.doob 已提交
521
				_panEnd.copy( _panStart );
M
Mr.doob 已提交
522 523
				break;

524 525 526
			default:
				_state = STATE.NONE;

M
Mr.doob 已提交
527
		}
528 529
		_this.dispatchEvent( startEvent );

M
Mr.doob 已提交
530 531 532 533 534

	}

	function touchmove( event ) {

535
		if ( _this.enabled === false ) return;
M
Mr.doob 已提交
536 537

		event.preventDefault();
538
		event.stopPropagation();
M
Mr.doob 已提交
539 540 541 542

		switch ( event.touches.length ) {

			case 1:
M
Mr.doob 已提交
543
				_rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
M
Mr.doob 已提交
544
				break;
545

M
Mr.doob 已提交
546
			case 2:
547 548
				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
549
				_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
M
Mr.doob 已提交
550 551 552 553

				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
				_panEnd.copy( getMouseOnScreen( x, y ) );
M
Mr.doob 已提交
554 555
				break;

556 557 558 559 560 561 562 563 564 565 566 567 568 569
			default:
				_state = STATE.NONE;

		}

	}

	function touchend( event ) {

		if ( _this.enabled === false ) return;

		switch ( event.touches.length ) {

			case 1:
M
Mr.doob 已提交
570 571
				_rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
				_rotateStart.copy( _rotateEnd );
572 573 574 575
				break;

			case 2:
				_touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
M
Mr.doob 已提交
576

577
				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
M
Mr.doob 已提交
578 579 580
				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
				_panEnd.copy( getMouseOnScreen( x, y ) );
				_panStart.copy( _panEnd );
581 582
				break;

M
Mr.doob 已提交
583 584
		}

585
		_state = STATE.NONE;
586
		_this.dispatchEvent( endEvent );
587

M
Mr.doob 已提交
588 589
	}

590
	this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
591

592
	this.domElement.addEventListener( 'mousedown', mousedown, false );
M
Mr.doob 已提交
593

594
	this.domElement.addEventListener( 'mousewheel', mousewheel, false );
M
Mr.doob 已提交
595 596 597
	this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox

	this.domElement.addEventListener( 'touchstart', touchstart, false );
598
	this.domElement.addEventListener( 'touchend', touchend, false );
M
Mr.doob 已提交
599
	this.domElement.addEventListener( 'touchmove', touchmove, false );
M
Mr.doob 已提交
600

601 602
	window.addEventListener( 'keydown', keydown, false );
	window.addEventListener( 'keyup', keyup, false );
603

604 605
	this.handleResize();

606 607 608
	// force an update at start
	this.update();

609
};
610

M
Mr.doob 已提交
611
THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
612
THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;