瀏覽代碼

Adjusting the WebVR architecture to fit the new WebVR API

The engine is now responsible to search for new devices, if WebVR is
enabled in the browser.
If WebVR is enabled (and a device was chosen (attachControl), the
fullscreen and request animation frame will be triggered by the
vrDisplay device.
Users with no WebVR enabled can still use the API in the same way.
Raanan Weber 9 年之前
父節點
當前提交
bb3fc341ec
共有 4 個文件被更改,包括 185 次插入88 次删除
  1. 47 44
      src/Cameras/VR/babylon.webVRCamera.ts
  2. 30 17
      src/Tools/babylon.tools.ts
  3. 107 26
      src/babylon.engine.ts
  4. 1 1
      src/babylon.mixins.ts

+ 47 - 44
src/Cameras/VR/babylon.webVRCamera.ts

@@ -1,60 +1,46 @@
 declare var HMDVRDevice;
-declare var PositionSensorVRDevice;
+declare var VRDisplay;
 
 module BABYLON {
+
+    export interface WebVROptions {
+        trackPosition?: boolean; //update the camera's position
+        displayName?: string; //if there are more than one VRDisplays.
+    }
+
     export class WebVRFreeCamera extends FreeCamera {
-        public _hmdDevice = null;
-        public _sensorDevice = null;
+        public _vrDevice = null;
         private _cacheState = null;
-        public _vrEnabled = false;
+        private _vrEnabled = false;
+
+        private _oldSize: BABYLON.Size;
+        private _oldHardwareScaleFactor: number;
 
         private _initialQuaternion: Quaternion;
         private _quaternionCache: Quaternion;
 
-        constructor(name: string, position: Vector3, scene: Scene, compensateDistortion = true, vrCameraMetrics: VRCameraMetrics = VRCameraMetrics.GetDefault()) {
+        constructor(name: string, position: Vector3, scene: Scene, compensateDistortion = true, vrCameraMetrics: VRCameraMetrics = VRCameraMetrics.GetDefault(), private webVROptions: WebVROptions = {}) {
             super(name, position, scene);
 
             vrCameraMetrics.compensateDistortion = compensateDistortion;
             this.setCameraRigMode(Camera.RIG_MODE_VR, { vrCameraMetrics: vrCameraMetrics });
 
-            this._getWebVRDevices = this._getWebVRDevices.bind(this);
+            //this._getWebVRDevices = this._getWebVRDevices.bind(this);
+            if (!this.getEngine().vrDisplaysPromise) {
+                Tools.Error("WebVR is not enabled on your browser");
+            }
 
             this.rotationQuaternion = new Quaternion();
             this._quaternionCache = new Quaternion();
         }
 
-        private _getWebVRDevices(devices: Array<any>): void {
-            var size = devices.length;
-            var i = 0;
-
-            // Reset devices.
-            this._sensorDevice = null;
-            this._hmdDevice = null;
-
-            // Search for a HmdDevice.
-            while (i < size && this._hmdDevice === null) {
-                if (devices[i] instanceof HMDVRDevice) {
-                    this._hmdDevice = devices[i];
-                }
-                i++;
-            }
-
-            i = 0;
-
-            while (i < size && this._sensorDevice === null) {
-                if (devices[i] instanceof PositionSensorVRDevice && (!this._hmdDevice || devices[i].hardwareUnitId === this._hmdDevice.hardwareUnitId)) {
-                    this._sensorDevice = devices[i];
-                }
-                i++;
-            }
-
-            this._vrEnabled = this._sensorDevice && this._hmdDevice ? true : false;
-        }
-
         public _checkInputs(): void {
             if (this._vrEnabled) {
-                this._cacheState = this._sensorDevice.getState();
-                this.rotationQuaternion.copyFrom(this._cacheState.orientation);
+                this._cacheState = this._vrDevice.getPose();
+                this.rotationQuaternion.copyFromFloats(this._cacheState.orientation[0], this._cacheState.orientation[1], this._cacheState.orientation[2], this._cacheState.orientation[3]);
+                if (this.webVROptions.trackPosition) {
+                    this.position.copyFromFloats(this._cacheState.position[0], this._cacheState.position[1], -this._cacheState.position[2]);
+                }
                 //Flip in XY plane
                 this.rotationQuaternion.z *= -1;
                 this.rotationQuaternion.w *= -1;
@@ -72,22 +58,39 @@ module BABYLON {
 
             noPreventDefault = Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
 
-            if (navigator.getVRDevices) {
-                navigator.getVRDevices().then(this._getWebVRDevices);
-            }
-            else if (navigator.mozGetVRDevices) {
-                navigator.mozGetVRDevices(this._getWebVRDevices);
-            }
+            //TODO get the metrics updated using the device's eye parameters!
+
+            //sanity check. if no WebVR enabled.
+            this.getEngine().vrDisplaysPromise && this.getEngine().vrDisplaysPromise.then((devices) => {
+                if (devices.length > 0) {
+                    this._vrEnabled = true;
+                    if (this.webVROptions.displayName) {
+                        devices.some(device => {
+                            if (device.displayName === this.webVROptions.displayName) {
+                                this.getEngine().enableVR(device);
+                                return true;
+                            } else {
+                                return false;
+                            }
+                        })
+                    } else {
+                        //choose the first one
+                        this.getEngine().enableVR(devices[0]);
+                    }
+                }
+            })
         }
 
         public detachControl(element: HTMLElement): void {
             super.detachControl(element);
             this._vrEnabled = false;
+            this.getEngine().disableVR();
         }
 
         public requestVRFullscreen(requestPointerlock: boolean) {
-            if (!this._hmdDevice) return;
-            this.getEngine().switchFullscreen(requestPointerlock, { vrDisplay: this._hmdDevice })
+            //Backwards comp.
+            Tools.Warn("requestVRFullscreen is deprecated. Use engine.switchFullscreen() instead")
+            this.getEngine().switchFullscreen(requestPointerlock);
         }
 
         public getTypeName(): string {

+ 30 - 17
src/Tools/babylon.tools.ts

@@ -251,30 +251,43 @@
             return eventPrefix;
         }
 
-        public static QueueNewFrame(func): void {
-            if (window.requestAnimationFrame)
-                window.requestAnimationFrame(func);
-            else if (window.msRequestAnimationFrame)
-                window.msRequestAnimationFrame(func);
-            else if (window.webkitRequestAnimationFrame)
-                window.webkitRequestAnimationFrame(func);
-            else if (window.mozRequestAnimationFrame)
-                window.mozRequestAnimationFrame(func);
-            else if (window.oRequestAnimationFrame)
-                window.oRequestAnimationFrame(func);
+        /**
+         * @param func - the function to be called
+         * @param requester - the object that will request the next frame. Falls back to window.
+         */
+        public static QueueNewFrame(func, requester: any = window): void {
+            if (requester.requestAnimationFrame)
+                requester.requestAnimationFrame(func);
+            else if (requester.msRequestAnimationFrame)
+                requester.msRequestAnimationFrame(func);
+            else if (requester.webkitRequestAnimationFrame)
+                requester.webkitRequestAnimationFrame(func);
+            else if (requester.mozRequestAnimationFrame)
+                requester.mozRequestAnimationFrame(func);
+            else if (requester.oRequestAnimationFrame)
+                requester.oRequestAnimationFrame(func);
             else {
                 window.setTimeout(func, 16);
             }
         }
 
-        public static RequestFullscreen(element, options?: any): void {
-            var requestFunction = element.requestFullscreen || element.msRequestFullscreen || element.webkitRequestFullscreen || element.mozRequestFullScreen;
-            if (!requestFunction) return;
-            requestFunction.call(element, options);
+        public static RequestFullscreen(element, vrDisplay?): void {
+            //WebVR?
+            if (vrDisplay) {
+                vrDisplay.requestPresent([{ source: element }]);
+            } else {
+                var requestFunction = element.requestFullscreen || element.msRequestFullscreen || element.webkitRequestFullscreen || element.mozRequestFullScreen;
+                if (!requestFunction) return;
+                requestFunction.call(element);
+            }
         }
 
-        public static ExitFullscreen(): void {
-            if (document.exitFullscreen) {
+        public static ExitFullscreen(vrDisplay?): void {
+            //WebVR?
+            if (vrDisplay) {
+                vrDisplay.exitPresent();
+            }
+            else if (document.exitFullscreen) {
                 document.exitFullscreen();
             }
             else if (document.mozCancelFullScreen) {

+ 107 - 26
src/babylon.engine.ts

@@ -184,8 +184,8 @@
         public textureLOD: boolean;
         public drawBuffersExtension;
     }
-    
-    export interface EngineOptions extends WebGLContextAttributes{
+
+    export interface EngineOptions extends WebGLContextAttributes {
         limitDeviceRatio?: number;
     }
 
@@ -311,6 +311,17 @@
         public enableOfflineSupport = true;
         public scenes = new Array<Scene>();
 
+        //WebVR 
+
+        //The new WebVR uses promises.
+        //this promise resolves with the current devices available.
+        public vrDisplaysPromise;
+
+        private _vrDisplays;
+        private _vrDisplayEnabled;
+        private _oldSize: BABYLON.Size;
+        private _oldHardwareScaleFactor: number;
+
         // Private Members
         public _gl: WebGLRenderingContext;
         private _renderingCanvas: HTMLCanvasElement;
@@ -328,7 +339,7 @@
         private _caps: EngineCapabilities;
         private _pointerLockRequested: boolean;
         private _alphaTest: boolean;
-        private _isStencilEnable: boolean; 
+        private _isStencilEnable: boolean;
 
         private _loadingScreen: ILoadingScreen;
 
@@ -382,7 +393,7 @@
 
         private _externalData: StringDictionary<Object>;
         private _bindedRenderFunction: any;
-        
+
         /**
          * @constructor
          * @param {HTMLCanvasElement} canvas - the canvas to be used for rendering
@@ -396,7 +407,7 @@
 
             options = options || {};
 
-            if(antialias != null){
+            if (antialias != null) {
                 options.antialias = antialias;
             }
 
@@ -552,6 +563,9 @@
             //default loading screen
             this._loadingScreen = new DefaultLoadingScreen(this._renderingCanvas);
 
+            //Load WebVR Devices
+            this._getVRDisplays();
+
             Tools.Log("Babylon.js engine (v" + Engine.Version + ") launched");
         }
 
@@ -698,11 +712,11 @@
         public setStencilFunction(stencilFunc: number) {
             this._stencilState.stencilFunc = stencilFunc;
         }
-        
+
         public setStencilFunctionReference(reference: number) {
             this._stencilState.stencilFuncRef = reference;
         }
-        
+
         public setStencilFunctionMask(mask: number) {
             this._stencilState.stencilFuncMask = mask;
         }
@@ -770,7 +784,7 @@
 
             if (this._activeRenderLoops.length > 0) {
                 // Register new frame
-                Tools.QueueNewFrame(this._bindedRenderFunction);
+                Tools.QueueNewFrame(this._bindedRenderFunction, this._getFrameRequester());
             } else {
                 this._renderingQueueLaunched = false;
             }
@@ -803,19 +817,19 @@
          * @param {boolean} requestPointerLock - should a pointer lock be requested from the user
          * @param {any} options - an options object to be sent to the requestFullscreen function
          */
-        public switchFullscreen(requestPointerLock: boolean, options?: any): void {
+        public switchFullscreen(requestPointerLock: boolean): void {
             if (this.isFullscreen) {
-                Tools.ExitFullscreen();
+                Tools.ExitFullscreen(this._vrDisplayEnabled);
             } else {
                 this._pointerLockRequested = requestPointerLock;
-                Tools.RequestFullscreen(this._renderingCanvas, options);
+                Tools.RequestFullscreen(this._renderingCanvas, this._vrDisplayEnabled);
             }
         }
 
-        public clear(color: any, backBuffer: boolean, depth: boolean, stencil:boolean = false): void {
+        public clear(color: any, backBuffer: boolean, depth: boolean, stencil: boolean = false): void {
             this.applyStates();
 
-            var mode = 0;            
+            var mode = 0;
             if (backBuffer) {
                 this._gl.clearColor(color.r, color.g, color.b, color.a !== undefined ? color.a : 1.0);
                 mode |= this._gl.COLOR_BUFFER_BIT;
@@ -892,6 +906,11 @@
 
         public endFrame(): void {
             //this.flushFramebuffer();
+
+            //submit frame to the vr device, if enabled
+            if(this._vrDisplayEnabled && this._vrDisplayEnabled.isPresenting) {
+                this._vrDisplayEnabled.submitFrame()
+            }
         }
 
         /**
@@ -935,14 +954,73 @@
             }
         }
 
+
+        //WebVR functions
+
+        public enableVR(vrDevice) {
+            this._vrDisplayEnabled = vrDevice;
+            window.addEventListener('vrdisplaypresentchange', this._onVRFullScreenTriggered, false);
+        }
+
+        public disableVR() {
+            if (this._vrDisplayEnabled) {
+                this._vrDisplayEnabled = null;
+                window.removeEventListener('vrdisplaypresentchange', this._onVRFullScreenTriggered, false);
+            }
+        }
+
+        private _onVRFullScreenTriggered = () => {
+            if (this._vrDisplayEnabled && this._vrDisplayEnabled.isPresenting) {
+                //get the old size before we change
+                this._oldSize = new BABYLON.Size(this.getRenderWidth(), this.getRenderHeight());
+                this._oldHardwareScaleFactor = this.getHardwareScalingLevel();
+
+                //get the width and height, change the render size
+                var leftEye = this._vrDisplayEnabled.getEyeParameters('left');
+                var width, height;
+                this.setHardwareScalingLevel(1);
+                this.setSize(leftEye.renderWidth * 2, leftEye.renderHeight);
+
+            } else {
+                this.setHardwareScalingLevel(this._oldHardwareScaleFactor);
+                this.setSize(this._oldSize.width, this._oldSize.height);
+            }
+        }
+
+        private _getFrameRequester() {
+            if (this._vrDisplayEnabled && this._vrDisplayEnabled.isPresenting) {
+                return this._vrDisplayEnabled;
+            } else {
+                return window;
+            }
+        }
+
+        private _getVRDisplays() {
+            var getWebVRDevices = (devices: Array<any>) => {
+                var size = devices.length;
+                var i = 0;
+
+                this._vrDisplays = devices.filter(function (device) {
+                    return devices[i] instanceof VRDisplay;
+                });
+
+                return this._vrDisplays;
+            }
+
+            //using a key due to typescript
+            if (navigator.getVRDisplays) {
+                this.vrDisplaysPromise = navigator.getVRDisplays().then(getWebVRDevices);
+            }
+        }
+
         public bindFramebuffer(texture: WebGLTexture, faceIndex?: number, requiredWidth?: number, requiredHeight?: number): void {
             this._currentRenderTarget = texture;
             this.bindUnboundFramebuffer(texture._framebuffer);
             var gl = this._gl;
             if (texture.isCube) {
-                
+
                 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, texture, 0);
-            } 
+            }
 
             gl.viewport(0, 0, requiredWidth || texture._width, requiredHeight || texture._height);
 
@@ -1134,12 +1212,12 @@
                 var offset = 0;
                 for (var index = 0; index < attributesCount; index++) {
 
-                    if(index < vertexDeclaration.length){
+                    if (index < vertexDeclaration.length) {
 
                         var order = effect.getAttributeLocation(index);
 
                         if (order >= 0) {
-                            if(!this._vertexAttribArraysEnabled[order]){
+                            if (!this._vertexAttribArraysEnabled[order]) {
                                 this._gl.enableVertexAttribArray(order);
                                 this._vertexAttribArraysEnabled[order] = true;
                             }
@@ -1148,11 +1226,11 @@
 
                         offset += vertexDeclaration[index] * 4;
 
-                    }else{
+                    } else {
 
                         //disable effect attributes that have no data
                         var order = effect.getAttributeLocation(index);
-                        if(this._vertexAttribArraysEnabled[order]){
+                        if (this._vertexAttribArraysEnabled[order]) {
                             this._gl.disableVertexAttribArray(order);
                             this._vertexAttribArraysEnabled[order] = false;
                         }
@@ -1183,14 +1261,14 @@
                         var vertexBuffer = vertexBuffers[attributes[index]];
 
                         if (!vertexBuffer) {
-                            if(this._vertexAttribArraysEnabled[order]){
+                            if (this._vertexAttribArraysEnabled[order]) {
                                 this._gl.disableVertexAttribArray(order);
                                 this._vertexAttribArraysEnabled[order] = false;
                             }
                             continue;
                         }
 
-                        if(!this._vertexAttribArraysEnabled[order]){
+                        if (!this._vertexAttribArraysEnabled[order]) {
                             this._gl.enableVertexAttribArray(order);
                             this._vertexAttribArraysEnabled[order] = true;
                         }
@@ -1269,7 +1347,7 @@
                 for (let i = 0; i < offsetLocations.length; i++) {
                     let ai = <InstancingAttributeInfo>offsetLocations[i];
 
-                    if(!this._vertexAttribArraysEnabled[ai.index]){
+                    if (!this._vertexAttribArraysEnabled[ai.index]) {
                         this._gl.enableVertexAttribArray(ai.index);
                         this._vertexAttribArraysEnabled[ai.index] = true;
                     }
@@ -1283,7 +1361,7 @@
                 for (let index = 0; index < 4; index++) {
                     let offsetLocation = <number>offsetLocations[index];
 
-                    if(!this._vertexAttribArraysEnabled[offsetLocation]){
+                    if (!this._vertexAttribArraysEnabled[offsetLocation]) {
                         this._gl.enableVertexAttribArray(offsetLocation);
                         this._vertexAttribArraysEnabled[offsetLocation] = true;
                     }
@@ -2104,7 +2182,7 @@
             this.bindUnboundFramebuffer(framebuffer);
 
             // Manage attachments
-            if (generateStencilBuffer) {                
+            if (generateStencilBuffer) {
                 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
             }
             else if (generateDepthBuffer) {
@@ -2134,7 +2212,7 @@
             texture.references = 1;
             texture.samplingMode = samplingMode;
             texture.type = type;
-            
+
             this.resetTextureCache();
 
             this._loadedTexturesCache.push(texture);
@@ -2201,7 +2279,7 @@
             this.bindUnboundFramebuffer(framebuffer);
 
             // Manage attachments
-            if (generateStencilBuffer) {                
+            if (generateStencilBuffer) {
                 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
             }
             else if (generateDepthBuffer) {
@@ -2768,6 +2846,9 @@
 
             this._gl = null;
 
+            //WebVR
+            this.disableVR();
+
             // Events
             window.removeEventListener("blur", this._onBlur);
             window.removeEventListener("focus", this._onFocus);

+ 1 - 1
src/babylon.mixins.ts

@@ -97,7 +97,7 @@ interface MSStyleCSSProperties {
 }
 
 interface Navigator {
-    getVRDevices: () => any;
+    getVRDisplays: () => any;
     mozGetVRDevices: (any: any) => any;
     isCocoonJS: boolean;
 }