WebXRManager.js 9.8 KB
Newer Older
F
Firepal 已提交
1
/**
M
Mr.doob 已提交
2 3 4
 * @author mrdoob / http://mrdoob.com/
 */

M
Mr.doob 已提交
5
import { ArrayCamera } from '../../cameras/ArrayCamera.js';
6
import { EventDispatcher } from '../../core/EventDispatcher.js';
M
Mr.doob 已提交
7
import { PerspectiveCamera } from '../../cameras/PerspectiveCamera.js';
M
Mr.doob 已提交
8 9
import { Vector3 } from '../../math/Vector3.js';
import { Vector4 } from '../../math/Vector4.js';
M
Mr.doob 已提交
10
import { WebGLAnimation } from '../webgl/WebGLAnimation.js';
11
import { WebXRController } from './WebXRController.js';
M
Mr.doob 已提交
12

13
function WebXRManager( renderer, gl ) {
M
Mr.doob 已提交
14

15 16
	var scope = this;

M
Mr.doob 已提交
17 18
	var session = null;

F
Firepal 已提交
19
	var framebufferScaleFactor = 1.0;
M
Cleanup  
Morgan Villedieu 已提交
20

21 22
	var referenceSpace = null;
	var referenceSpaceType = 'local-floor';
M
Mr.doob 已提交
23 24

	var pose = null;
25 26

	var controllers = [];
27
	var inputSourcesMap = new Map();
M
Mr.doob 已提交
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

	//

	var cameraL = new PerspectiveCamera();
	cameraL.layers.enable( 1 );
	cameraL.viewport = new Vector4();

	var cameraR = new PerspectiveCamera();
	cameraR.layers.enable( 2 );
	cameraR.viewport = new Vector4();

	var cameraVR = new ArrayCamera( [ cameraL, cameraR ] );
	cameraVR.layers.enable( 1 );
	cameraVR.layers.enable( 2 );

43 44 45
	var _currentDepthNear = null;
	var _currentDepthFar = null;

M
Mr.doob 已提交
46 47 48 49
	//

	this.enabled = false;

50 51
	this.isPresenting = false;

52 53 54 55 56 57
	this.getController = function ( id ) {

		var controller = controllers[ id ];

		if ( controller === undefined ) {

58
			controller = new WebXRController();
59 60 61 62
			controllers[ id ] = controller;

		}

63
		return controller.getTargetRaySpace();
64

65 66 67 68 69 70 71 72
	};

	this.getControllerGrip = function ( id ) {

		var controller = controllers[ id ];

		if ( controller === undefined ) {

73
			controller = new WebXRController();
74 75 76 77
			controllers[ id ] = controller;

		}

78
		return controller.getGripSpace();
79 80 81

	};

82
	//
M
Mr.doob 已提交
83

84
	function onSessionEvent( event ) {
M
Mr.doob 已提交
85

86
		var controller = inputSourcesMap.get( event.inputSource );
M
Mr.doob 已提交
87

88
		if ( controller ) {
M
Mr.doob 已提交
89

90
			controller.dispatchEvent( { type: event.type } );
91

92
		}
93 94 95

	}

M
Mr.doob 已提交
96
	function onSessionEnd() {
97

98 99
		inputSourcesMap.forEach( function ( controller, inputSource ) {

100
			controller.disconnect( inputSource );
101 102 103 104 105 106 107

		} );

		inputSourcesMap.clear();

		//

108
		renderer.setFramebuffer( null );
M
Mr.doob 已提交
109
		renderer.setRenderTarget( renderer.getRenderTarget() ); // Hack #15830
110 111
		animation.stop();

112 113
		scope.isPresenting = false;

114 115
		scope.dispatchEvent( { type: 'sessionend' } );

116 117
	}

118
	function onRequestReferenceSpace( value ) {
119

120
		referenceSpace = value;
121 122 123 124

		animation.setContext( session );
		animation.start();

125 126
		scope.isPresenting = true;

127
		scope.dispatchEvent( { type: 'sessionstart' } );
128

129 130
	}

F
Firepal 已提交
131
	this.setFramebufferScaleFactor = function ( value ) {
132

F
Firepal 已提交
133
		framebufferScaleFactor = value;
F
Firepal 已提交
134

F
Firepal 已提交
135
		// Warn if function is used while presenting
F
Firepal 已提交
136 137 138 139
		if ( scope.isPresenting == true ) {

			console.warn( "WebXRManager: Cannot change framebuffer scale while presenting VR content" );

F
Firepal 已提交
140
		}
M
Cleanup  
Morgan Villedieu 已提交
141

142 143
	};

144
	this.setReferenceSpaceType = function ( value ) {
145

146
		referenceSpaceType = value;
147 148 149

	};

150 151 152 153 154 155
	this.getReferenceSpace = function () {

		return referenceSpace;

	};

156 157 158 159 160 161
	this.getSession = function () {

		return session;

	};

M
Mr.doob 已提交
162
	this.setSession = function ( value ) {
M
Mr.doob 已提交
163

M
Mr.doob 已提交
164
		session = value;
M
Mr.doob 已提交
165 166 167

		if ( session !== null ) {

168 169 170
			session.addEventListener( 'select', onSessionEvent );
			session.addEventListener( 'selectstart', onSessionEvent );
			session.addEventListener( 'selectend', onSessionEvent );
171 172 173
			session.addEventListener( 'squeeze', onSessionEvent );
			session.addEventListener( 'squeezestart', onSessionEvent );
			session.addEventListener( 'squeezeend', onSessionEvent );
174
			session.addEventListener( 'end', onSessionEnd );
M
Mr.doob 已提交
175

M
Mr.doob 已提交
176 177 178 179 180 181
			var attributes = gl.getContextAttributes();

			var layerInit = {
				antialias: attributes.antialias,
				alpha: attributes.alpha,
				depth: attributes.depth,
F
Firepal 已提交
182 183
				stencil: attributes.stencil,
				framebufferScaleFactor: framebufferScaleFactor
M
Mr.doob 已提交
184 185
			};

M
Mugen87 已提交
186
			// eslint-disable-next-line no-undef
M
Mr.doob 已提交
187 188 189
			var baseLayer = new XRWebGLLayer( session, gl, layerInit );

			session.updateRenderState( { baseLayer: baseLayer } );
M
Cleanup  
Morgan Villedieu 已提交
190

191
			session.requestReferenceSpace( referenceSpaceType ).then( onRequestReferenceSpace );
M
Mr.doob 已提交
192

M
Cleanup  
Morgan Villedieu 已提交
193 194
			//

195
			session.addEventListener( 'inputsourceschange', updateInputSources );
196

197
		}
198

199
	};
200

201 202 203 204 205
	function updateInputSources( event ) {

		var inputSources = session.inputSources;

		// Assign inputSources to available controllers
206

207
		for ( var i = 0; i < controllers.length; i ++ ) {
208

209
			inputSourcesMap.set( inputSources[ i ], controllers[ i ] );
210

M
Mr.doob 已提交
211 212
		}

213
		// Notify disconnected
214

215
		for ( var i = 0; i < event.removed.length; i ++ ) {
216

217 218
			var inputSource = event.removed[ i ];
			var controller = inputSourcesMap.get( inputSource );
219

220
			if ( controller ) {
221

222
				controller.dispatchEvent( { type: 'disconnected', data: inputSource } );
223 224 225 226 227 228 229 230 231 232 233 234
				inputSourcesMap.delete( inputSource );

			}

		}

		// Notify connected

		for ( var i = 0; i < event.added.length; i ++ ) {

			var inputSource = event.added[ i ];
			var controller = inputSourcesMap.get( inputSource );
235

236 237
			if ( controller ) {

238
				controller.dispatchEvent( { type: 'connected', data: inputSource } );
239 240

			}
241 242 243 244 245 246

		}

	}

	//
M
Mr.doob 已提交
247

M
Mr.doob 已提交
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
	var cameraLPos = new Vector3();
	var cameraRPos = new Vector3();

	/**
	 * @author jsantell / https://www.jsantell.com/
	 *
	 * Assumes 2 cameras that are parallel and share an X-axis, and that
	 * the cameras' projection and world matrices have already been set.
	 * And that near and far planes are identical for both cameras.
	 * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
	 */
	function setProjectionFromUnion( camera, cameraL, cameraR ) {

		cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
		cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );

		var ipd = cameraLPos.distanceTo( cameraRPos );

		var projL = cameraL.projectionMatrix.elements;
		var projR = cameraR.projectionMatrix.elements;

		// VR systems will have identical far and near planes, and
		// most likely identical top and bottom frustum extents.
		// Use the left camera for these values.
		var near = projL[ 14 ] / ( projL[ 10 ] - 1 );
		var far = projL[ 14 ] / ( projL[ 10 ] + 1 );
		var topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
		var bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];

		var leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
		var rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
		var left = near * leftFov;
		var right = near * rightFov;

		// Calculate the new camera's position offset from the
		// left camera. xOffset should be roughly half `ipd`.
		var zOffset = ipd / ( - leftFov + rightFov );
		var xOffset = zOffset * - leftFov;

		// TODO: Better way to apply this offset?
		cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale );
		camera.translateX( xOffset );
		camera.translateZ( zOffset );
		camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale );
		camera.matrixWorldInverse.getInverse( camera.matrixWorld );

		// Find the union of the frustum values of the cameras and scale
		// the values so that the near plane's position does not change in world space,
		// although must now be relative to the new union camera.
		var near2 = near + zOffset;
		var far2 = far + zOffset;
		var left2 = left - xOffset;
		var right2 = right + ( ipd - xOffset );
		var top2 = topFov * far / far2 * near2;
		var bottom2 = bottomFov * far / far2 * near2;

		camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );

	}

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
	function updateCamera( camera, parent ) {

		if ( parent === null ) {

			camera.matrixWorld.copy( camera.matrix );

		} else {

			camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );

		}

		camera.matrixWorldInverse.getInverse( camera.matrixWorld );

	}

M
Mr.doob 已提交
324 325
	this.getCamera = function ( camera ) {

326 327
		cameraVR.near = cameraR.near = cameraL.near = camera.near;
		cameraVR.far = cameraR.far = cameraL.far = camera.far;
328

329
		if ( _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far ) {
M
Mr.doob 已提交
330

331
			// Note that the new renderState won't apply until the next frame. See #18320
332 333 334 335 336 337 338 339 340 341

			session.updateRenderState( {
				depthNear: cameraVR.near,
				depthFar: cameraVR.far
			} );

			_currentDepthNear = cameraVR.near;
			_currentDepthFar = cameraVR.far;

		}
342

343 344
		var parent = camera.parent;
		var cameras = cameraVR.cameras;
345

346
		updateCamera( cameraVR, parent );
347

348
		for ( var i = 0; i < cameras.length; i ++ ) {
349

350
			updateCamera( cameras[ i ], parent );
351

352
		}
353

354
		// update camera and its children
355

356
		camera.matrixWorld.copy( cameraVR.matrixWorld );
357

358
		var children = camera.children;
359

360
		for ( var i = 0, l = children.length; i < l; i ++ ) {
361

362
			children[ i ].updateMatrixWorld( true );
363 364 365

		}

366 367 368
		setProjectionFromUnion( cameraVR, cameraL, cameraR );

		return cameraVR;
M
Mr.doob 已提交
369 370 371

	};

M
Mr.doob 已提交
372
	// Animation Loop
M
Mr.doob 已提交
373

M
Mr.doob 已提交
374
	var onAnimationFrameCallback = null;
M
Mr.doob 已提交
375

M
Mr.doob 已提交
376
	function onAnimationFrame( time, frame ) {
M
Mr.doob 已提交
377

378
		pose = frame.getViewerPose( referenceSpace );
M
Mr.doob 已提交
379

380
		if ( pose !== null ) {
381

382
			var views = pose.views;
M
Cleanup  
Morgan Villedieu 已提交
383
			var baseLayer = session.renderState.baseLayer;
384

M
Mr.doob 已提交
385
			renderer.setFramebuffer( baseLayer.framebuffer );
386

M
Mr.doob 已提交
387
			for ( var i = 0; i < views.length; i ++ ) {
M
Mr.doob 已提交
388

M
Mr.doob 已提交
389
				var view = views[ i ];
M
Mr.doob 已提交
390
				var viewport = baseLayer.getViewport( view );
M
Mr.doob 已提交
391

M
Mr.doob 已提交
392
				var camera = cameraVR.cameras[ i ];
M
Mr.doob 已提交
393
				camera.matrix.fromArray( view.transform.matrix );
M
Mr.doob 已提交
394 395
				camera.projectionMatrix.fromArray( view.projectionMatrix );
				camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height );
M
Mr.doob 已提交
396

M
Mr.doob 已提交
397
				if ( i === 0 ) {
M
Mr.doob 已提交
398

M
Mr.doob 已提交
399
					cameraVR.matrix.copy( camera.matrix );
M
Mr.doob 已提交
400

M
Mr.doob 已提交
401
				}
M
Mr.doob 已提交
402 403 404

			}

M
Mr.doob 已提交
405
		}
M
Mr.doob 已提交
406

407 408
		//

409 410
		var inputSources = session.inputSources;

411 412 413
		for ( var i = 0; i < controllers.length; i ++ ) {

			var controller = controllers[ i ];
414
			var inputSource = inputSources[ i ];
415

416
			controller.update( inputSource, frame, referenceSpace );
417

418 419
		}

420
		if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );
M
Mr.doob 已提交
421 422

	}
M
Mr.doob 已提交
423

M
Mr.doob 已提交
424 425 426 427 428 429
	var animation = new WebGLAnimation();
	animation.setAnimationLoop( onAnimationFrame );

	this.setAnimationLoop = function ( callback ) {

		onAnimationFrameCallback = callback;
M
Mr.doob 已提交
430 431 432

	};

M
Mr.doob 已提交
433 434
	this.dispose = function () {};

M
Mr.doob 已提交
435 436
}

437 438
Object.assign( WebXRManager.prototype, EventDispatcher.prototype );

M
Mr.doob 已提交
439
export { WebXRManager };