TrackballControls.js 11.3 KB
Newer Older
1
/**
2
 * @author Eberhard Graether / http://egraether.com/
3 4
 */

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

M
Mr.doob 已提交
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 16
	this.enabled = true;

17
	this.screen = { width: 0, height: 0, offsetLeft: 0, offsetTop: 0 };
18
	this.radius = ( this.screen.width + this.screen.height ) / 4;
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 41
	this.target = new THREE.Vector3();

	var lastPosition = new THREE.Vector3();
42

M
Mr.doob 已提交
43 44
	var _state = STATE.NONE,
	_prevState = STATE.NONE,
45

46 47
	_eye = new THREE.Vector3(),

48 49 50 51 52 53
	_rotateStart = new THREE.Vector3(),
	_rotateEnd = new THREE.Vector3(),

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

54 55 56
	_touchZoomDistanceStart = 0,
	_touchZoomDistanceEnd = 0,

57 58
	_panStart = new THREE.Vector2(),
	_panEnd = new THREE.Vector2();
59

W
WestLangley 已提交
60 61 62 63 64 65
	// for reset

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

M
Mr.doob 已提交
66 67 68 69
	// events

	var changeEvent = { type: 'change' };

70

71
	// methods
72

73
	this.handleResize = function () {
74 75 76 77
		var rect = this.domElement.getBoundingClientRect();
      
		this.screen.width = rect.width;
		this.screen.height = rect.height;
78

79 80
		this.screen.offsetLeft = rect.left;
		this.screen.offsetTop = rect.top;
81

82

83 84
	};

85
	this.handleEvent = function ( event ) {
86

87
		if ( typeof this[ event.type ] == 'function' ) {
88

89
			this[ event.type ]( event );
90

91
		}
92

93
	};
94

M
Mr.doob 已提交
95
	this.getMouseOnScreen = function ( clientX, clientY ) {
96

97
		return new THREE.Vector2(
98 99
			( clientX - _this.screen.offsetLeft ) / _this.screen.width,
			( clientY - _this.screen.offsetTop ) / _this.screen.height
100
		);
101

102
	};
103

M
Mr.doob 已提交
104
	this.getMouseProjectionOnBall = function ( clientX, clientY ) {
105

106
		var mouseOnBall = new THREE.Vector3(
107 108
			( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / (_this.screen.width*.5),
			( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / (_this.screen.height*.5),
109 110
			0.0
		);
111

112
		var length = mouseOnBall.length();
113

114 115 116 117 118 119 120
		if ( _this.noRoll ) {
			if (length < Math.SQRT1_2 ) {
				mouseOnBall.z = Math.sqrt( 1.0 - length*length );
			} else {
				mouseOnBall.z = .5 / length;
			}
		} else if ( length > 1.0 ) {
121

122
			mouseOnBall.normalize();
123

124
		} else {
125

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

128
		}
129

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

132
		var projection = _this.object.up.clone().setLength( mouseOnBall.y );
133 134
		projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) );
		projection.add( _eye.setLength( mouseOnBall.z ) );
135

136
		return projection;
137

138
	};
139

M
Mr.doob 已提交
140
	this.rotateCamera = function () {
141

142
		var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
143

144
		if ( angle ) {
145

146
			var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(),
147
				quaternion = new THREE.Quaternion();
148

149
			angle *= _this.rotateSpeed;
150

151
			quaternion.setFromAxisAngle( axis, -angle );
152

153 154
			_eye.applyQuaternion( quaternion );
			_this.object.up.applyQuaternion( quaternion );
155

156
			_rotateEnd.applyQuaternion( quaternion );
157

158
			if ( _this.staticMoving ) {
159

160
				_rotateStart.copy( _rotateEnd );
161 162

			} else {
163

164
				quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
165
				_rotateStart.applyQuaternion( quaternion );
166 167

			}
168

169
		}
170

171
	};
172

M
Mr.doob 已提交
173
	this.zoomCamera = function () {
174

175
		if ( _state === STATE.TOUCH_ZOOM ) {
176

177 178
			var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
			_touchZoomDistanceStart = _touchZoomDistanceEnd;
179
			_eye.multiplyScalar( factor );
180

181
		} else {
182

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

185
			if ( factor !== 1.0 && factor > 0.0 ) {
186

187 188 189 190 191 192 193 194 195 196 197
				_eye.multiplyScalar( factor );

				if ( _this.staticMoving ) {

					_zoomStart.copy( _zoomEnd );

				} else {

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

				}
198 199

			}
200 201 202

		}

203
	};
204

M
Mr.doob 已提交
205
	this.panCamera = function () {
206

207
		var mouseChange = _panEnd.clone().sub( _panStart );
208 209 210

		if ( mouseChange.lengthSq() ) {

211
			mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
212

213 214
			var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x );
			pan.add( _this.object.up.clone().setLength( mouseChange.y ) );
215

216 217
			_this.object.position.add( pan );
			_this.target.add( pan );
218

219
			if ( _this.staticMoving ) {
220

221
				_panStart = _panEnd;
222

223 224
			} else {

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

227 228 229
			}

		}
230

231
	};
232

M
Mr.doob 已提交
233
	this.checkDistances = function () {
234

235
		if ( !_this.noZoom || !_this.noPan ) {
236

237
			if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) {
238

239
				_this.object.position.setLength( _this.maxDistance );
240 241 242

			}

243
			if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
244

245
				_this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
246 247

			}
248 249 250

		}

251 252
	};

M
Mr.doob 已提交
253
	this.update = function () {
M
Mr.doob 已提交
254

255
		_eye.subVectors( _this.object.position, _this.target );
M
Mr.doob 已提交
256 257 258 259 260 261

		if ( !_this.noRotate ) {

			_this.rotateCamera();

		}
262

M
Mr.doob 已提交
263 264 265 266 267 268 269 270 271 272 273 274
		if ( !_this.noZoom ) {

			_this.zoomCamera();

		}

		if ( !_this.noPan ) {

			_this.panCamera();

		}

275
		_this.object.position.addVectors( _this.target, _eye );
M
Mr.doob 已提交
276 277 278 279 280

		_this.checkDistances();

		_this.object.lookAt( _this.target );

281
		if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {
282

M
Mr.doob 已提交
283
			_this.dispatchEvent( changeEvent );
M
Mr.doob 已提交
284 285 286 287 288 289 290

			lastPosition.copy( _this.object.position );

		}

	};

W
WestLangley 已提交
291 292 293 294 295 296 297 298 299 300 301 302 303
	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 );

304
		_this.dispatchEvent( changeEvent );
W
WestLangley 已提交
305 306 307 308 309

		lastPosition.copy( _this.object.position );

	};

310
	// listeners
311

312
	function keydown( event ) {
313

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

M
Mr.doob 已提交
316
		window.removeEventListener( 'keydown', keydown );
317

M
Mr.doob 已提交
318
		_prevState = _state;
319

320
		if ( _state !== STATE.NONE ) {
321

322
			return;
323

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

326
			_state = STATE.ROTATE;
327

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

330
			_state = STATE.ZOOM;
331

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

334
			_state = STATE.PAN;
335 336

		}
337

M
Mr.doob 已提交
338
	}
339

340
	function keyup( event ) {
341

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

M
Mr.doob 已提交
344
		_state = _prevState;
345

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

M
Mr.doob 已提交
348
	}
349

350
	function mousedown( event ) {
351

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

354 355
		event.preventDefault();
		event.stopPropagation();
356

357
		if ( _state === STATE.NONE ) {
358

359
			_state = event.button;
360

M
Mr.doob 已提交
361
		}
362

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

M
Mr.doob 已提交
365
			_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
366

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

M
Mr.doob 已提交
369
			_zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
370

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

M
Mr.doob 已提交
373
			_panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
374

375
		}
M
Mr.doob 已提交
376

M
Mr.doob 已提交
377 378
		document.addEventListener( 'mousemove', mousemove, false );
		document.addEventListener( 'mouseup', mouseup, false );
379

M
Mr.doob 已提交
380
	}
381

382
	function mousemove( event ) {
383

384 385 386 387
		if ( _this.enabled === false ) return;

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

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

391
			_rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
392

393
		} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
394

395
			_zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
396

397
		} else if ( _state === STATE.PAN && !_this.noPan ) {
398

399
			_panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
400

401
		}
402

M
Mr.doob 已提交
403
	}
404

405
	function mouseup( event ) {
406

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

409 410
		event.preventDefault();
		event.stopPropagation();
411

412
		_state = STATE.NONE;
413

M
Mr.doob 已提交
414 415
		document.removeEventListener( 'mousemove', mousemove );
		document.removeEventListener( 'mouseup', mouseup );
416

M
Mr.doob 已提交
417
	}
418

419 420
	function mousewheel( event ) {

421
		if ( _this.enabled === false ) return;
422 423 424 425 426 427

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

		var delta = 0;

428 429 430 431 432 433 434 435
		if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9

			delta = event.wheelDelta / 40;

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

			delta = - event.detail / 3;

436 437
		}

438
		_zoomStart.y += delta * 0.01;
439 440 441

	}

M
Mr.doob 已提交
442 443
	function touchstart( event ) {

444
		if ( _this.enabled === false ) return;
M
Mr.doob 已提交
445 446 447 448

		switch ( event.touches.length ) {

			case 1:
449
				_state = STATE.TOUCH_ROTATE;
M
Mr.doob 已提交
450 451
				_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
				break;
452

M
Mr.doob 已提交
453
			case 2:
454 455 456 457
				_state = STATE.TOUCH_ZOOM;
				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 已提交
458
				break;
459

M
Mr.doob 已提交
460
			case 3:
461
				_state = STATE.TOUCH_PAN;
M
Mr.doob 已提交
462 463 464
				_panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
				break;

465 466 467
			default:
				_state = STATE.NONE;

M
Mr.doob 已提交
468 469 470 471 472 473
		}

	}

	function touchmove( event ) {

474
		if ( _this.enabled === false ) return;
M
Mr.doob 已提交
475 476

		event.preventDefault();
477
		event.stopPropagation();
M
Mr.doob 已提交
478 479 480 481 482 483

		switch ( event.touches.length ) {

			case 1:
				_rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
				break;
484

M
Mr.doob 已提交
485
			case 2:
486 487 488
				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
				_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
M
Mr.doob 已提交
489
				break;
490

M
Mr.doob 已提交
491 492 493 494
			case 3:
				_panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
				break;

495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
			default:
				_state = STATE.NONE;

		}

	}

	function touchend( event ) {

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

		switch ( event.touches.length ) {

			case 1:
				_rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
				break;

			case 2:
				_touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
				break;

			case 3:
				_panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
				break;

M
Mr.doob 已提交
520 521
		}

522 523
		_state = STATE.NONE;

M
Mr.doob 已提交
524 525
	}

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

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

530
	this.domElement.addEventListener( 'mousewheel', mousewheel, false );
M
Mr.doob 已提交
531 532 533
	this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox

	this.domElement.addEventListener( 'touchstart', touchstart, false );
534
	this.domElement.addEventListener( 'touchend', touchend, false );
M
Mr.doob 已提交
535
	this.domElement.addEventListener( 'touchmove', touchmove, false );
M
Mr.doob 已提交
536

537 538
	window.addEventListener( 'keydown', keydown, false );
	window.addEventListener( 'keyup', keyup, false );
539

540 541
	this.handleResize();

542
};
543

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