|
@@ -1,5 +1,6 @@
|
|
|
/**
|
|
|
* @author dmarcos / https://github.com/dmarcos
|
|
|
+ * @author mrdoob / http://mrdoob.com
|
|
|
*
|
|
|
* It handles stereo rendering
|
|
|
* If mozGetVRDevices and getVRDevices APIs are not available it gracefuly falls back to a
|
|
@@ -22,184 +23,189 @@
|
|
|
*/
|
|
|
THREE.VREffect = function ( renderer, done ) {
|
|
|
|
|
|
- var cameraLeft = new THREE.PerspectiveCamera();
|
|
|
- var cameraRight = new THREE.PerspectiveCamera();
|
|
|
+ if ( !navigator.mozGetVRDevices && !navigator.getVRDevices ) {
|
|
|
|
|
|
- this._renderer = renderer;
|
|
|
+ if ( done ) done( 'Your browser is not VR Ready' );
|
|
|
|
|
|
- this._init = function() {
|
|
|
- var self = this;
|
|
|
- if ( !navigator.mozGetVRDevices && !navigator.getVRDevices ) {
|
|
|
- if ( done ) {
|
|
|
- done( 'Your browser is not VR Ready' );
|
|
|
- }
|
|
|
- return;
|
|
|
- }
|
|
|
- if ( navigator.getVRDevices ) {
|
|
|
- navigator.getVRDevices().then( gotVRDevices );
|
|
|
- } else {
|
|
|
- navigator.mozGetVRDevices( gotVRDevices );
|
|
|
- }
|
|
|
- function gotVRDevices( devices ) {
|
|
|
- var vrHMD;
|
|
|
- for ( var i = 0; i < devices.length; i ++ ) {
|
|
|
- if ( devices[i] instanceof HMDVRDevice ) {
|
|
|
- vrHMD = devices[i];
|
|
|
- self._vrHMD = vrHMD;
|
|
|
-
|
|
|
- if ( vrHMD.getEyeParameters !== undefined ) {
|
|
|
- var leftEyeParams = vrHMD.getEyeParameters( 'left' );
|
|
|
- var rightEyeParams = vrHMD.getEyeParameters( 'right' );
|
|
|
- self.leftEyeTranslation = leftEyeParams.eyeTranslation;
|
|
|
- self.rightEyeTranslation = rightEyeParams.eyeTranslation;
|
|
|
- self.leftEyeFOV = leftEyeParams.recommendedFieldOfView;
|
|
|
- self.rightEyeFOV = rightEyeParams.recommendedFieldOfView;
|
|
|
- } else {
|
|
|
- // TODO: This is an older code path and not spec compliant.
|
|
|
- // It should be removed at some point in the near future.
|
|
|
- self.leftEyeTranslation = vrHMD.getEyeTranslation( 'left' );
|
|
|
- self.rightEyeTranslation = vrHMD.getEyeTranslation( 'right' );
|
|
|
- self.leftEyeFOV = vrHMD.getRecommendedEyeFieldOfView( 'left' );
|
|
|
- self.rightEyeFOV = vrHMD.getRecommendedEyeFieldOfView( 'right' );
|
|
|
- }
|
|
|
-
|
|
|
- break; // We keep the first we encounter
|
|
|
- }
|
|
|
- }
|
|
|
- if ( done ) {
|
|
|
- if ( !vrHMD ) {
|
|
|
- done( 'HMD not available' );
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
+ }
|
|
|
|
|
|
- this._init();
|
|
|
+ // init
|
|
|
|
|
|
- this.render = function ( scene, camera ) {
|
|
|
- var renderer = this._renderer;
|
|
|
- var vrHMD = this._vrHMD;
|
|
|
- // VR render mode if HMD is available
|
|
|
- if ( vrHMD ) {
|
|
|
- this.renderStereo.apply( this, arguments );
|
|
|
- return;
|
|
|
- }
|
|
|
- // Regular render mode if not HMD
|
|
|
- if ( scene instanceof Array ) scene = scene[ 0 ];
|
|
|
- renderer.render.apply( this._renderer, arguments );
|
|
|
- };
|
|
|
+ var vrHMD;
|
|
|
+ var leftEyeTranslation, leftEyeFOV;
|
|
|
+ var rightEyeTranslation, rightEyeFOV;
|
|
|
|
|
|
- this.renderStereo = function( scene, camera, renderTarget, forceClear ) {
|
|
|
+ function gotVRDevices( devices ) {
|
|
|
|
|
|
- var sceneLeft, sceneRight;
|
|
|
+ for ( var i = 0; i < devices.length; i ++ ) {
|
|
|
|
|
|
- if ( scene instanceof Array ) {
|
|
|
+ if ( devices[ i ] instanceof HMDVRDevice ) {
|
|
|
|
|
|
- sceneLeft = scene[ 0 ];
|
|
|
- sceneRight = scene[ 1 ];
|
|
|
+ vrHMD = devices[ i ];
|
|
|
|
|
|
- } else {
|
|
|
+ if ( vrHMD.getEyeParameters !== undefined ) {
|
|
|
+
|
|
|
+ var leftEyeParams = vrHMD.getEyeParameters( 'left' );
|
|
|
+ var rightEyeParams = vrHMD.getEyeParameters( 'right' );
|
|
|
+
|
|
|
+ leftEyeTranslation = leftEyeParams.eyeTranslation;
|
|
|
+ rightEyeTranslation = rightEyeParams.eyeTranslation;
|
|
|
+ leftEyeFOV = leftEyeParams.recommendedFieldOfView;
|
|
|
+ rightEyeFOV = rightEyeParams.recommendedFieldOfView;
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ // TODO: This is an older code path and not spec compliant.
|
|
|
+ // It should be removed at some point in the near future.
|
|
|
+ leftEyeTranslation = vrHMD.getEyeTranslation( 'left' );
|
|
|
+ rightEyeTranslation = vrHMD.getEyeTranslation( 'right' );
|
|
|
+ leftEyeFOV = vrHMD.getRecommendedEyeFieldOfView( 'left' );
|
|
|
+ rightEyeFOV = vrHMD.getRecommendedEyeFieldOfView( 'right' );
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
- sceneLeft = scene;
|
|
|
- sceneRight = scene;
|
|
|
+ break; // We keep the first we encounter
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
- var leftEyeTranslation = this.leftEyeTranslation;
|
|
|
- var rightEyeTranslation = this.rightEyeTranslation;
|
|
|
- var renderer = this._renderer;
|
|
|
- var rendererSize = renderer.getSize();
|
|
|
- rendererSize.width /= 2;
|
|
|
+ if ( vrHMD === undefined ) {
|
|
|
|
|
|
- renderer.enableScissorTest( true );
|
|
|
- renderer.clear();
|
|
|
+ if ( done ) done( 'HMD not available' );
|
|
|
|
|
|
- if ( camera.parent === undefined ) {
|
|
|
- camera.updateMatrixWorld();
|
|
|
}
|
|
|
|
|
|
- cameraLeft.projectionMatrix = this.FovToProjection( this.leftEyeFOV, true, camera.near, camera.far );
|
|
|
- cameraRight.projectionMatrix = this.FovToProjection( this.rightEyeFOV, true, camera.near, camera.far );
|
|
|
+ }
|
|
|
|
|
|
- camera.matrixWorld.decompose( cameraLeft.position, cameraLeft.quaternion, cameraLeft.scale );
|
|
|
- camera.matrixWorld.decompose( cameraRight.position, cameraRight.quaternion, cameraRight.scale );
|
|
|
+ if ( navigator.getVRDevices ) {
|
|
|
|
|
|
- cameraLeft.translateX( leftEyeTranslation.x );
|
|
|
- cameraRight.translateX( rightEyeTranslation.x );
|
|
|
+ navigator.getVRDevices().then( gotVRDevices );
|
|
|
|
|
|
- // render left eye
|
|
|
- renderer.setViewport( 0, 0, rendererSize.width, rendererSize.height );
|
|
|
- renderer.setScissor( 0, 0, rendererSize.width, rendererSize.height );
|
|
|
- renderer.render( sceneLeft, cameraLeft );
|
|
|
+ } else if ( navigator.mozGetVRDevices ) {
|
|
|
|
|
|
- // render right eye
|
|
|
- renderer.setViewport( rendererSize.width, 0, rendererSize.width, rendererSize.height );
|
|
|
- renderer.setScissor( rendererSize.width, 0, rendererSize.width, rendererSize.height );
|
|
|
- renderer.render( sceneRight, cameraRight );
|
|
|
+ navigator.mozGetVRDevices( gotVRDevices );
|
|
|
|
|
|
- renderer.enableScissorTest( false );
|
|
|
+ }
|
|
|
|
|
|
- };
|
|
|
+ //
|
|
|
|
|
|
this.setSize = function( width, height ) {
|
|
|
+
|
|
|
renderer.setSize( width, height );
|
|
|
+
|
|
|
};
|
|
|
|
|
|
- this.setFullScreen = function( enable ) {
|
|
|
- var renderer = this._renderer;
|
|
|
- var vrHMD = this._vrHMD;
|
|
|
- var canvasOriginalSize = this._canvasOriginalSize;
|
|
|
- if (!vrHMD) {
|
|
|
- return;
|
|
|
- }
|
|
|
- // If state doesn't change we do nothing
|
|
|
- if ( enable === this._fullScreen ) {
|
|
|
- return;
|
|
|
- }
|
|
|
- this._fullScreen = !!enable;
|
|
|
+ // fullscreen
|
|
|
|
|
|
- // VR Mode disabled
|
|
|
- if ( !enable ) {
|
|
|
- // Restores canvas original size
|
|
|
- renderer.setSize( canvasOriginalSize.width, canvasOriginalSize.height );
|
|
|
- return;
|
|
|
- }
|
|
|
- // VR Mode enabled
|
|
|
- this._canvasOriginalSize = renderer.getSize();
|
|
|
- this.startFullscreen();
|
|
|
- };
|
|
|
+ var isFullscreen = false;
|
|
|
+
|
|
|
+ var canvas = renderer.domElement;
|
|
|
+ var fullscreenchange = canvas.mozRequestFullScreen ? 'mozfullscreenchange' : 'webkitfullscreenchange';
|
|
|
+
|
|
|
+ document.addEventListener( fullscreenchange, function ( event ) {
|
|
|
+
|
|
|
+ isFullscreen = document.mozFullScreenElement || document.webkitFullscreenElement;
|
|
|
+
|
|
|
+ }, false );
|
|
|
+
|
|
|
+ this.setFullScreen = function ( boolean ) {
|
|
|
+
|
|
|
+ if ( vrHMD === undefined ) return;
|
|
|
+ if ( isFullscreen === boolean ) return;
|
|
|
|
|
|
- this.startFullscreen = function() {
|
|
|
- var self = this;
|
|
|
- var renderer = this._renderer;
|
|
|
- var vrHMD = this._vrHMD;
|
|
|
- var canvas = renderer.domElement;
|
|
|
- var fullScreenChange =
|
|
|
- canvas.mozRequestFullScreen ? 'mozfullscreenchange' : 'webkitfullscreenchange';
|
|
|
-
|
|
|
- document.addEventListener( fullScreenChange, onFullScreenChanged, false );
|
|
|
- function onFullScreenChanged() {
|
|
|
- if ( !document.mozFullScreenElement && !document.webkitFullscreenElement ) {
|
|
|
- self.setFullScreen( false );
|
|
|
- }
|
|
|
- }
|
|
|
if ( canvas.mozRequestFullScreen ) {
|
|
|
+
|
|
|
canvas.mozRequestFullScreen( { vrDisplay: vrHMD } );
|
|
|
+
|
|
|
} else if ( canvas.webkitRequestFullscreen ) {
|
|
|
+
|
|
|
canvas.webkitRequestFullscreen( { vrDisplay: vrHMD } );
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ // render
|
|
|
+
|
|
|
+ var cameraLeft = new THREE.PerspectiveCamera();
|
|
|
+ var cameraRight = new THREE.PerspectiveCamera();
|
|
|
+
|
|
|
+ this.render = function ( scene, camera ) {
|
|
|
+
|
|
|
+ if ( vrHMD ) {
|
|
|
+
|
|
|
+ var sceneLeft, sceneRight;
|
|
|
+
|
|
|
+ if ( scene instanceof Array ) {
|
|
|
+
|
|
|
+ sceneLeft = scene[ 0 ];
|
|
|
+ sceneRight = scene[ 1 ];
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ sceneLeft = scene;
|
|
|
+ sceneRight = scene;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ var size = renderer.getSize();
|
|
|
+ size.width /= 2;
|
|
|
+
|
|
|
+ renderer.enableScissorTest( true );
|
|
|
+ renderer.clear();
|
|
|
+
|
|
|
+ if ( camera.parent === undefined ) {
|
|
|
+ camera.updateMatrixWorld();
|
|
|
+ }
|
|
|
+
|
|
|
+ cameraLeft.projectionMatrix = fovToProjection( leftEyeFOV, true, camera.near, camera.far );
|
|
|
+ cameraRight.projectionMatrix = fovToProjection( rightEyeFOV, true, camera.near, camera.far );
|
|
|
+
|
|
|
+ camera.matrixWorld.decompose( cameraLeft.position, cameraLeft.quaternion, cameraLeft.scale );
|
|
|
+ camera.matrixWorld.decompose( cameraRight.position, cameraRight.quaternion, cameraRight.scale );
|
|
|
+
|
|
|
+ cameraLeft.translateX( leftEyeTranslation.x );
|
|
|
+ cameraRight.translateX( rightEyeTranslation.x );
|
|
|
+
|
|
|
+ // render left eye
|
|
|
+ renderer.setViewport( 0, 0, size.width, size.height );
|
|
|
+ renderer.setScissor( 0, 0, size.width, size.height );
|
|
|
+ renderer.render( sceneLeft, cameraLeft );
|
|
|
+
|
|
|
+ // render right eye
|
|
|
+ renderer.setViewport( size.width, 0, size.width, size.height );
|
|
|
+ renderer.setScissor( size.width, 0, size.width, size.height );
|
|
|
+ renderer.render( sceneRight, cameraRight );
|
|
|
+
|
|
|
+ renderer.enableScissorTest( false );
|
|
|
+
|
|
|
+ return;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Regular render mode if not HMD
|
|
|
+
|
|
|
+ if ( scene instanceof Array ) scene = scene[ 0 ];
|
|
|
+
|
|
|
+ renderer.render( scene, camera );
|
|
|
+
|
|
|
};
|
|
|
|
|
|
- this.FovToNDCScaleOffset = function( fov ) {
|
|
|
+ //
|
|
|
+
|
|
|
+ function fovToNDCScaleOffset( fov ) {
|
|
|
+
|
|
|
var pxscale = 2.0 / (fov.leftTan + fov.rightTan);
|
|
|
var pxoffset = (fov.leftTan - fov.rightTan) * pxscale * 0.5;
|
|
|
var pyscale = 2.0 / (fov.upTan + fov.downTan);
|
|
|
var pyoffset = (fov.upTan - fov.downTan) * pyscale * 0.5;
|
|
|
return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] };
|
|
|
- };
|
|
|
|
|
|
- this.FovPortToProjection = function( fov, rightHanded /* = true */, zNear /* = 0.01 */, zFar /* = 10000.0 */ )
|
|
|
- {
|
|
|
+ }
|
|
|
+
|
|
|
+ function fovPortToProjection( fov, rightHanded, zNear, zFar ) {
|
|
|
+
|
|
|
rightHanded = rightHanded === undefined ? true : rightHanded;
|
|
|
zNear = zNear === undefined ? 0.01 : zNear;
|
|
|
zFar = zFar === undefined ? 10000.0 : zFar;
|
|
@@ -211,7 +217,7 @@ THREE.VREffect = function ( renderer, done ) {
|
|
|
var m = mobj.elements;
|
|
|
|
|
|
// and with scale/offset info for normalized device coords
|
|
|
- var scaleAndOffset = this.FovToNDCScaleOffset(fov);
|
|
|
+ var scaleAndOffset = fovToNDCScaleOffset(fov);
|
|
|
|
|
|
// X result, map clip edges to [-w,+w]
|
|
|
m[0 * 4 + 0] = scaleAndOffset.scale[0];
|
|
@@ -242,17 +248,21 @@ THREE.VREffect = function ( renderer, done ) {
|
|
|
mobj.transpose();
|
|
|
|
|
|
return mobj;
|
|
|
- };
|
|
|
+ }
|
|
|
+
|
|
|
+ function fovToProjection( fov, rightHanded, zNear, zFar ) {
|
|
|
+
|
|
|
+ var DEG2RAD = Math.PI / 180.0;
|
|
|
|
|
|
- this.FovToProjection = function( fov, rightHanded /* = true */, zNear /* = 0.01 */, zFar /* = 10000.0 */ )
|
|
|
- {
|
|
|
var fovPort = {
|
|
|
- upTan: Math.tan(fov.upDegrees * Math.PI / 180.0),
|
|
|
- downTan: Math.tan(fov.downDegrees * Math.PI / 180.0),
|
|
|
- leftTan: Math.tan(fov.leftDegrees * Math.PI / 180.0),
|
|
|
- rightTan: Math.tan(fov.rightDegrees * Math.PI / 180.0)
|
|
|
+ upTan: Math.tan( fov.upDegrees * DEG2RAD ),
|
|
|
+ downTan: Math.tan( fov.downDegrees * DEG2RAD ),
|
|
|
+ leftTan: Math.tan( fov.leftDegrees * DEG2RAD ),
|
|
|
+ rightTan: Math.tan( fov.rightDegrees * DEG2RAD )
|
|
|
};
|
|
|
- return this.FovPortToProjection(fovPort, rightHanded, zNear, zFar);
|
|
|
- };
|
|
|
+
|
|
|
+ return fovPortToProjection( fovPort, rightHanded, zNear, zFar );
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
};
|