Browse Source

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David Catuhe 6 years ago
parent
commit
be714bf7d1

+ 2 - 1
Tools/Gulp/config.json

@@ -1299,7 +1299,8 @@
                 "../../src/Cameras/VR/babylon.vrDeviceOrientationGamepadCamera.js",
                 "../../src/Cameras/VR/babylon.vrExperienceHelper.js",
                 "../../src/Cameras/XR/babylon.webXRCamera.js",
-                "../../src/Cameras/XR/babylon.webXRSessionManager.js"
+                "../../src/Cameras/XR/babylon.webXRSessionManager.js",
+                "../../src/Cameras/XR/babylon.webXRExperienceHelper.js"
             ],
             "dependUpon": [
                 "core",

+ 1 - 0
dist/preview release/what's new.md

@@ -10,6 +10,7 @@
   - camera customDefaultRenderTarget to allow cameras to render to a custom render target (eg. xr framebuffer) instead of the canvas ([TrevorDev](https://github.com/TrevorDev))
   - webXR camera which can be updated by a webXRSession ([TrevorDev](https://github.com/TrevorDev))
   - webXRSessionManager to bridge xrSession to babylon's engine/camera ([TrevorDev](https://github.com/TrevorDev))
+  - webXRExperienceHelper to setup a default XR experience ([TrevorDev](https://github.com/TrevorDev))
 
 ## Updates
 

+ 4 - 2
src/Cameras/XR/babylon.webXRCamera.ts

@@ -51,15 +51,16 @@ module BABYLON {
         /**
          * Updates the cameras position from the current pose information of the  XR session
          * @param xrSessionManager the session containing pose information
+         * @returns true if the camera has been updated, false if the session did not contain pose or frame data
          */
         public updateFromXRSessionManager(xrSessionManager: WebXRSessionManager) {
             // Ensure all frame data is available
             if (!xrSessionManager._currentXRFrame || !xrSessionManager._currentXRFrame.getDevicePose) {
-                return;
+                return false;
             }
             var pose = xrSessionManager._currentXRFrame.getDevicePose(xrSessionManager._frameOfReference);
             if (!pose || !pose.poseModelMatrix) {
-                return;
+                return false;
             }
 
             // Update the parent cameras matrix
@@ -95,6 +96,7 @@ module BABYLON {
                 // Set cameras to render to the session's render target
                 this.rigCameras[i].outputRenderTarget = xrSessionManager._sessionRenderTargetTexture;
             });
+            return true;
         }
     }
 }

+ 145 - 0
src/Cameras/XR/babylon.webXRExperienceHelper.ts

@@ -0,0 +1,145 @@
+module BABYLON {
+    /**
+     * States of the webXR experience
+     */
+    export enum WebXRState {
+        /**
+         * Transitioning to/from being in XR mode
+         */
+        TRANSITION,
+        /**
+         * In XR mode and presenting
+         */
+        IN_XR,
+        /**
+         * Not entered XR mode
+         */
+        NOT_IN_XR
+    }
+    /**
+     * Helper class used to enable XR
+     * @see https://doc.babylonjs.com/how_to/webxr
+     */
+    export class WebXRExperienceHelper implements IDisposable {
+        /**
+         * Container which stores the xr camera and controllers as children. This can be used to move the camera/user as the camera's position is updated by the xr device
+         */
+        public container: AbstractMesh;
+        /**
+         * Camera used to render xr content
+         */
+        public camera: WebXRCamera;
+
+        /**
+         * The current state of the XR experience (eg. transitioning, in XR or not in XR)
+         */
+        public state: WebXRState = WebXRState.NOT_IN_XR;
+
+        /**
+         * Fires when the state of the experience helper has changed
+         */
+        public onStateChangedObservable = new Observable<WebXRState>();
+
+        private _sessionManager: WebXRSessionManager;
+
+        private _nonVRCamera: Nullable<Camera> = null;
+        private _originalSceneAutoClear = true;
+
+        private _outputCanvas: HTMLCanvasElement;
+        private _outputCanvasContext: WebGLRenderingContext;
+
+        /**
+         * Creates a WebXRExperienceHelper
+         * @param scene The scene the helper should be created in
+         */
+        constructor(private scene: BABYLON.Scene) {
+            this.camera = new BABYLON.WebXRCamera("", scene);
+            this._sessionManager = new BABYLON.WebXRSessionManager(scene);
+            this.container = new AbstractMesh("", scene);
+            this._sessionManager.initialize();
+        }
+
+        /**
+         * Exits XR mode and returns the scene to its original state
+         * @returns promise that resolves after xr mode has exited
+         */
+        public exitXR() {
+            this.state = WebXRState.TRANSITION;
+            this.onStateChangedObservable.notifyObservers(this.state);
+            return this._sessionManager.exitXR();
+        }
+
+        /**
+         * Enters XR mode (This must be done within a user interaction in most browsers eg. button click)
+         * @param sessionCreationOptions options for the XR session
+         * @param frameOfReference frame of reference of the XR session
+         * @returns promise that resolves after xr mode has entered
+         */
+        public enterXR(sessionCreationOptions: XRSessionCreationOptions, frameOfReference: string) {
+            this.state = WebXRState.TRANSITION;
+            this.onStateChangedObservable.notifyObservers(this.state);
+
+            this._createCanvas();
+            if (!sessionCreationOptions.outputContext) {
+                sessionCreationOptions.outputContext = this._outputCanvasContext;
+            }
+
+            return this._sessionManager.enterXR(sessionCreationOptions, frameOfReference).then(() => {
+                // Cache pre xr scene settings
+                this._originalSceneAutoClear = this.scene.autoClear;
+                this._nonVRCamera = this.scene.activeCamera;
+
+                // Overwrite current scene settings
+                this.scene.autoClear = false;
+                this.scene.activeCamera = this.camera;
+
+                this._sessionManager.onXRFrameObservable.add(() => {
+                    this.camera.updateFromXRSessionManager(this._sessionManager);
+                });
+
+                this._sessionManager.onXRSessionEnded.addOnce(() => {
+                    // Reset camera rigs output render target to ensure sessions render target is not drawn after it ends
+                    this.camera.rigCameras.forEach((c) => {
+                        c.outputRenderTarget = null;
+                    });
+
+                    // Restore scene settings
+                    this.scene.autoClear = this._originalSceneAutoClear;
+                    this.scene.activeCamera = this._nonVRCamera;
+                    this._sessionManager.onXRFrameObservable.clear();
+                    this._removeCanvas();
+
+                    this.state = WebXRState.NOT_IN_XR;
+                    this.onStateChangedObservable.notifyObservers(this.state);
+                });
+                this.state = WebXRState.IN_XR;
+                this.onStateChangedObservable.notifyObservers(this.state);
+            });
+        }
+
+        /**
+         * Disposes of the experience helper
+         */
+        public dispose() {
+            this.camera.dispose();
+            this.container.dispose();
+            this._removeCanvas();
+            this.onStateChangedObservable.clear();
+            this._sessionManager.dispose();
+        }
+
+        // create canvas used to mirror/vr xr content in fullscreen
+        private _createCanvas() {
+            this._removeCanvas();
+            this._outputCanvas = document.createElement('canvas');
+            this._outputCanvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #48989e;";
+            document.body.appendChild(this._outputCanvas);
+            this._outputCanvasContext = <any>this._outputCanvas.getContext('xrpresent');
+        }
+        private _removeCanvas() {
+            if (this._outputCanvas && document.body.contains(this._outputCanvas)) {
+                document.body.removeChild(this._outputCanvas);
+            }
+        }
+    }
+}

+ 46 - 17
src/Cameras/XR/babylon.webXRSessionManager.ts

@@ -3,18 +3,27 @@ module BABYLON {
      * Manages an XRSession
      * @see https://doc.babylonjs.com/how_to/webxr
      */
-    export class WebXRSessionManager {
-        private _xrNavigator: any;
-        private _xrDevice: XRDevice;
-        private _tmpMatrix = new BABYLON.Matrix();
+    export class WebXRSessionManager implements IDisposable {
+        /**
+         * Fires every time a new xrFrame arrives which can be used to update the camera
+         */
+        public onXRFrameObservable: Observable<any> = new BABYLON.Observable<any>();
+        /**
+         * Fires when the xr session is ended either by the device or manually done
+         */
+        public onXRSessionEnded: Observable<any> = new BABYLON.Observable<any>();
+
         /** @hidden */
         public _xrSession: XRSession;
         /** @hidden */
         public _frameOfReference: XRFrameOfReference;
         /** @hidden */
-        public _sessionRenderTargetTexture: RenderTargetTexture;
+        public _sessionRenderTargetTexture: Nullable<RenderTargetTexture> = null;
         /** @hidden */
-        public _currentXRFrame: XRFrame;
+        public _currentXRFrame: Nullable<XRFrame>;
+        private _xrNavigator: any;
+        private _xrDevice: XRDevice;
+        private _tmpMatrix = new BABYLON.Matrix();
 
         /**
          * Constructs a WebXRSessionManager, this must be initialized within a user action before usage
@@ -48,10 +57,25 @@ module BABYLON {
          * @param frameOfReferenceType option to configure how the xr pose is expressed
          * @returns Promise which resolves after it enters XR
          */
-        public enterXR(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: XRFrameOfReferenceType): Promise<void> {
+        public enterXR(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: string): Promise<void> {
             // initialize session
             return this._xrDevice.requestSession(sessionCreationOptions).then((session: XRSession) => {
                 this._xrSession = session;
+
+                // handle when the session is ended (By calling session.end or device ends its own session eg. pressing home button on phone)
+                this._xrSession.addEventListener("end", () => {
+                    // Remove render target texture and notify frame obervers
+                    this._sessionRenderTargetTexture = null;
+
+                    // Restore frame buffer to avoid clear on xr framebuffer after session end
+                    this.scene.getEngine().restoreDefaultFramebuffer();
+
+                    // Need to restart render loop as after the session is ended the last request for new frame will never call callback
+                    this.scene.getEngine().customAnimationFrameRequester = null;
+                    this.onXRSessionEnded.notifyObservers(null);
+                    this.scene.getEngine()._renderLoop();
+                }, {once : true});
+
                 this._xrSession.baseLayer = new XRWebGLLayer(this._xrSession, this.scene.getEngine()._gl);
                 return this._xrSession.requestFrameOfReference(frameOfReferenceType);
             }).then((frameOfRef: any) => {
@@ -59,14 +83,19 @@ module BABYLON {
                 // Tell the engine's render loop to be driven by the xr session's refresh rate and provide xr pose information
                 this.scene.getEngine().customAnimationFrameRequester = {
                     requestAnimationFrame: this._xrSession.requestAnimationFrame.bind(this._xrSession),
-                    renderFunction: (timestamp: number, xrFrame: XRFrame) => {
+                    renderFunction: (timestamp: number, xrFrame: Nullable<XRFrame>) => {
                         // Store the XR frame in the manager to be consumed by the XR camera to update pose
                         this._currentXRFrame = xrFrame;
+                        this.onXRFrameObservable.notifyObservers(null);
                         this.scene.getEngine()._renderLoop();
                     }
                 };
                 // Create render target texture from xr's webgl render target
                 this._sessionRenderTargetTexture = WebXRSessionManager._CreateRenderTargetTextureFromSession(this._xrSession, this.scene);
+
+                // Stop window's animation frame and trigger sessions animation frame
+                window.cancelAnimationFrame(this.scene.getEngine()._frameHandler);
+                this.scene.getEngine()._renderLoop();
             });
         }
 
@@ -75,15 +104,7 @@ module BABYLON {
          * @returns Promise which resolves after it exits XR
          */
         public exitXR() {
-            return new Promise((res) => {
-                this.scene.getEngine().customAnimationFrameRequester = null;
-                this._xrSession.end();
-                // Restore frame buffer to avoid clear on xr framebuffer after session end
-                this.scene.getEngine().restoreDefaultFramebuffer();
-                // Need to restart render loop as after calling session.end the last request for new frame will never call callback
-                this.scene.getEngine()._renderLoop();
-                res();
-            });
+            return this._xrSession.end();
         }
 
         /**
@@ -139,5 +160,13 @@ module BABYLON {
 
              return renderTargetTexture;
         }
+
+        /**
+         * Disposes of the session manager
+         */
+        public dispose() {
+            this.onXRFrameObservable.clear();
+            this.onXRSessionEnded.clear();
+        }
     }
 }

+ 2 - 1
src/Engine/babylon.engine.ts

@@ -867,7 +867,8 @@ module BABYLON {
         private _emptyCubeTexture: Nullable<InternalTexture>;
         private _emptyTexture3D: Nullable<InternalTexture>;
 
-        private _frameHandler: number;
+        /** @hidden */
+        public _frameHandler: number;
 
         private _nextFreeTextureSlots = new Array<number>();
         private _maxSimultaneousTextures = 0;

+ 5 - 8
src/babylon.mixins.ts

@@ -184,12 +184,14 @@ interface XRDevice {
 }
 interface XRSession {
     baseLayer: XRWebGLLayer;
-    requestFrameOfReference(type: XRFrameOfReferenceType): Promise<void>;
+    requestFrameOfReference(type: string): Promise<void>;
     requestHitTest(origin: Float32Array, direction: Float32Array, frameOfReference: any): any;
-    end(): void;
+    end(): Promise<void>;
     requestAnimationFrame: Function;
+    addEventListener: Function;
 }
 interface XRSessionCreationOptions {
+    outputContext: WebGLRenderingContext;
 }
 interface XRLayer {
     getViewport: Function;
@@ -212,9 +214,4 @@ interface XRWebGLLayer extends XRLayer {
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
     new(session: XRSession, context?: WebGLRenderingContext): XRWebGLLayer;
-};
-enum XRFrameOfReferenceType {
-    "head-model",
-    "eye-level",
-    "stage",
-}
+};