瀏覽代碼

Merge pull request #5361 from TrevorDev/webXREnterExitUI

Web xr enter exit ui
David Catuhe 6 年之前
父節點
當前提交
d41443a445

+ 3 - 1
Tools/Gulp/config.json

@@ -1300,7 +1300,9 @@
                 "../../src/Cameras/VR/babylon.vrExperienceHelper.js",
                 "../../src/Cameras/XR/babylon.webXRCamera.js",
                 "../../src/Cameras/XR/babylon.webXRSessionManager.js",
-                "../../src/Cameras/XR/babylon.webXRExperienceHelper.js"
+                "../../src/Cameras/XR/babylon.webXRExperienceHelper.js",
+                "../../src/Cameras/XR/babylon.webXREnterExitUI.js",
+                "../../src/Cameras/XR/babylon.webXRManagedOutputCanvas.js"
             ],
             "dependUpon": [
                 "core",

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

@@ -11,6 +11,7 @@
   - 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))
+  - WebXREnterExitUI and WebXRManagedOutputCanvas classes to configure the XR experience ([TrevorDev](https://github.com/TrevorDev))
 
 ## Updates
 

+ 82 - 0
src/Cameras/XR/babylon.webXREnterExitUI.ts

@@ -0,0 +1,82 @@
+module BABYLON {
+    /**
+     * Options to create the webXR UI
+     */
+    export class WebXREnterExitUIOptions {
+        /**
+         * Context to enter xr with
+         */
+        outputCanvasContext?: Nullable<WebGLRenderingContext>;
+        customButtons?: Array<{element: HTMLElement, initializationOptions: XRSessionCreationOptions}>;
+    }
+    /**
+     * UI to allow the user to enter/exit XR mode
+     */
+    export class WebXREnterExitUI implements IDisposable {
+        private _overlay: HTMLDivElement;
+        private _buttons: Array<{element: HTMLElement, initializationOptions: XRSessionCreationOptions}> = [];
+        /**
+         * Creates UI to allow the user to enter/exit XR mode
+         * @param scene the scene to add the ui to
+         * @param helper the xr experience helper to enter/exit xr with
+         * @param options options to configure the UI
+         * @returns the created ui
+         */
+        public static CreateAsync(scene: BABYLON.Scene, helper: WebXRExperienceHelper, options: WebXREnterExitUIOptions) {
+            var ui = new WebXREnterExitUI(scene, options);
+            var supportedPromises = ui._buttons.map((btn) => {
+                return helper.supportsSession(btn.initializationOptions);
+            });
+            return Promise.all(supportedPromises).then((results) => {
+                results.forEach((supported, i) => {
+                    if (supported) {
+                        ui._overlay.appendChild(ui._buttons[i].element);
+                        ui._buttons[i].element.onclick = async() => {
+                            if (helper.state == BABYLON.WebXRState.IN_XR) {
+                                await helper.exitXR();
+                                return;
+                            }else if (helper.state == BABYLON.WebXRState.NOT_IN_XR) {
+                                await helper.enterXR(ui._buttons[i].initializationOptions, "eye-level");
+                            }
+                        };
+                    }
+                });
+            });
+        }
+        private constructor(private scene: BABYLON.Scene, options: WebXREnterExitUIOptions) {
+            this._overlay = document.createElement("div");
+            this._overlay.style.cssText = "z-index:11;position: absolute; right: 20px;bottom: 50px;";
+
+            if (options.customButtons) {
+                this._buttons = options.customButtons;
+            }else {
+                var hmdBtn = document.createElement("button");
+                hmdBtn.style.cssText = "color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-repeat:no-repeat; background-position: center; outline: none;";
+                hmdBtn.innerText = "HMD";
+                this._buttons.push({element: hmdBtn, initializationOptions: {immersive: true}});
+
+                var windowBtn = document.createElement("button");
+                windowBtn.style.cssText = hmdBtn.style.cssText;
+                windowBtn.innerText = "Window";
+                this._buttons.push({element: windowBtn, initializationOptions: {immersive: false, environmentIntegration: true, outputContext: options.outputCanvasContext}});
+            }
+
+            var renderCanvas = scene.getEngine().getRenderingCanvas();
+            if (renderCanvas && renderCanvas.parentNode) {
+                renderCanvas.parentNode.appendChild(this._overlay);
+                scene.onDisposeObservable.addOnce(() => {
+                    this.dispose();
+                });
+            }
+        }
+        /**
+         * Disposes of the object
+         */
+        dispose() {
+            var renderCanvas = this.scene.getEngine().getRenderingCanvas();
+            if (renderCanvas && renderCanvas.parentNode && renderCanvas.parentNode.contains(this._overlay)) {
+                renderCanvas.parentNode.removeChild(this._overlay);
+            }
+        }
+    }
+}

+ 44 - 35
src/Cameras/XR/babylon.webXRExperienceHelper.ts

@@ -4,9 +4,13 @@ module BABYLON {
      */
     export enum WebXRState {
         /**
-         * Transitioning to/from being in XR mode
+         * Transitioning to being in XR mode
          */
-        TRANSITION,
+        ENTERING_XR,
+        /**
+         * Transitioning to non XR mode
+         */
+        EXITING_XR,
         /**
          * In XR mode and presenting
          */
@@ -35,6 +39,11 @@ module BABYLON {
          */
         public state: WebXRState = WebXRState.NOT_IN_XR;
 
+        private _setState(val: WebXRState) {
+            this.state = val;
+            this.onStateChangedObservable.notifyObservers(this.state);
+        }
+
         /**
          * Fires when the state of the experience helper has changed
          */
@@ -45,18 +54,31 @@ module BABYLON {
         private _nonVRCamera: Nullable<Camera> = null;
         private _originalSceneAutoClear = true;
 
-        private _outputCanvas: HTMLCanvasElement;
-        private _outputCanvasContext: WebGLRenderingContext;
+        private _supported = false;
+
+        /**
+         * Creates the experience helper
+         * @param scene the scene to attach the experience helper to
+         * @returns a promise for the experience helper
+         */
+        public static CreateAsync(scene: BABYLON.Scene): Promise<WebXRExperienceHelper> {
+            var helper = new WebXRExperienceHelper(scene);
+            return helper._sessionManager.initialize().then(() => {
+                helper._supported = true;
+                return helper;
+            }).catch(() => {
+                return helper;
+            });
+        }
 
         /**
          * Creates a WebXRExperienceHelper
          * @param scene The scene the helper should be created in
          */
-        constructor(private scene: BABYLON.Scene) {
+        private 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();
         }
 
         /**
@@ -64,8 +86,7 @@ module BABYLON {
          * @returns promise that resolves after xr mode has exited
          */
         public exitXR() {
-            this.state = WebXRState.TRANSITION;
-            this.onStateChangedObservable.notifyObservers(this.state);
+            this._setState(WebXRState.EXITING_XR);
             return this._sessionManager.exitXR();
         }
 
@@ -76,13 +97,7 @@ module BABYLON {
          * @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;
-            }
+            this._setState(WebXRState.ENTERING_XR);
 
             return this._sessionManager.enterXR(sessionCreationOptions, frameOfReference).then(() => {
                 // Cache pre xr scene settings
@@ -107,39 +122,33 @@ module BABYLON {
                     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._setState(WebXRState.NOT_IN_XR);
                 });
-                this.state = WebXRState.IN_XR;
-                this.onStateChangedObservable.notifyObservers(this.state);
+                this._setState(WebXRState.IN_XR);
             });
         }
 
         /**
+         * Checks if the creation options are supported by the xr session
+         * @param options creation options
+         * @returns true if supported
+         */
+        public supportsSession(options: XRSessionCreationOptions) {
+            if (!this._supported) {
+                return Promise.resolve(false);
+            }
+            return this._sessionManager.supportsSession(options);
+        }
+
+        /**
          * 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);
-            }
-        }
     }
 }

+ 62 - 0
src/Cameras/XR/babylon.webXRManagedOutputCanvas.ts

@@ -0,0 +1,62 @@
+module BABYLON {
+    /**
+     * Creates a canvas that is added/removed from the webpage when entering/exiting XR
+     */
+    export class WebXRManagedOutputCanvas implements IDisposable {
+        private _canvas: Nullable<HTMLCanvasElement> = null;
+        /**
+         * xrpresent context of the canvas which can be used to display/mirror xr content
+         */
+        public canvasContext: Nullable<WebGLRenderingContext> = null;
+        /**
+         * Initializes the canvas to be added/removed upon entering/exiting xr
+         * @param helper the xr experience helper used to trigger adding/removing of the canvas
+         * @param canvas The canvas to be added/removed (If not specified a full screen canvas will be created)
+         */
+        public constructor(helper: WebXRExperienceHelper, canvas?: HTMLCanvasElement) {
+            if (!canvas) {
+                canvas = document.createElement('canvas');
+                canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #48989e;";
+            }
+            this._setManagedOutputCanvas(canvas);
+            helper.onStateChangedObservable.add((stateInfo) => {
+                if (stateInfo == WebXRState.ENTERING_XR) {
+                    // The canvas is added to the screen before entering XR because currently the xr session must be initialized while the canvas is added render properly
+                    this._addCanvas();
+                }else if (helper.state == WebXRState.NOT_IN_XR) {
+                    this._removeCanvas();
+                }
+            });
+        }
+        /**
+         * Disposes of the object
+         */
+        public dispose() {
+            this._removeCanvas();
+            this._setManagedOutputCanvas(null);
+        }
+
+        private _setManagedOutputCanvas(canvas: Nullable<HTMLCanvasElement>) {
+            this._removeCanvas();
+            if (!canvas) {
+                this._canvas = null;
+                this.canvasContext = null;
+            }else {
+                this._canvas = canvas;
+                this.canvasContext = <any>this._canvas.getContext('xrpresent');
+            }
+        }
+
+        private _addCanvas() {
+            if (this._canvas) {
+                document.body.appendChild(this._canvas);
+            }
+        }
+
+        private _removeCanvas() {
+            if (this._canvas && document.body.contains(this._canvas)) {
+                document.body.removeChild(this._canvas);
+            }
+        }
+    }
+}

+ 15 - 2
src/Cameras/XR/babylon.webXRSessionManager.ts

@@ -34,7 +34,7 @@ module BABYLON {
         }
 
         /**
-         * Initializes the manager, this must be done with a user action (eg. button click event)
+         * Initializes the manager
          * After initialization enterXR can be called to start an XR session
          * @returns Promise which resolves after it is initialized
          */
@@ -52,7 +52,7 @@ module BABYLON {
         }
 
         /**
-         * Enters XR with the desired XR session options
+         * Enters XR with the desired XR session options, this must be done with a user action (eg. button click event)
          * @param sessionCreationOptions xr options to create the session with
          * @param frameOfReferenceType option to configure how the xr pose is expressed
          * @returns Promise which resolves after it enters XR
@@ -142,6 +142,19 @@ module BABYLON {
         }
 
         /**
+         * Checks if a session would be supported for the creation options specified
+         * @param options creation options to check if they are supported
+         * @returns true if supported
+         */
+        public supportsSession(options: XRSessionCreationOptions) {
+            return this._xrDevice.supportsSession(options).then(() => {
+                return true;
+            }).catch((e) => {
+                return false;
+            });
+        }
+
+        /**
          * @hidden
          * Converts the render layer of xrSession to a render target
          * @param session session to create render target for

+ 14 - 2
src/Helpers/babylon.sceneHelpers.ts

@@ -52,6 +52,13 @@ module BABYLON {
          * @returns a new VREXperienceHelper
          */
         createDefaultVRExperience(webVROptions?: VRExperienceHelperOptions): VRExperienceHelper;
+
+        /**
+         * Creates a new XREXperienceHelper
+         * @see http://doc.babylonjs.com/how_to/webxr
+         * @returns a promise for a new XREXperienceHelper
+         */
+        createDefaultXRExperienceAsync(): Promise<WebXRExperienceHelper>;
     }
 
     Scene.prototype.createDefaultLight = function(replace = false): void {
@@ -170,7 +177,12 @@ module BABYLON {
         return null;
     };
 
-    Scene.prototype.createDefaultVRExperience = function(webVROptions: VRExperienceHelperOptions = {}): VRExperienceHelper {
-        return new VRExperienceHelper(this, webVROptions);
+    Scene.prototype.createDefaultXRExperienceAsync = function(): Promise<BABYLON.WebXRExperienceHelper> {
+        return BABYLON.WebXRExperienceHelper.CreateAsync(this).then((helper) => {
+            var outputCanvas = new BABYLON.WebXRManagedOutputCanvas(helper);
+            return BABYLON.WebXREnterExitUI.CreateAsync(this, helper, {outputCanvasContext: outputCanvas.canvasContext}).then((ui) => {
+                return helper;
+            });
+        });
     };
 }

+ 4 - 1
src/babylon.mixins.ts

@@ -181,6 +181,7 @@ interface WebGLUniformLocation {
 // WebXR
 interface XRDevice {
     requestSession(options: XRSessionCreationOptions): Promise<XRSession>;
+    supportsSession(options: XRSessionCreationOptions): Promise<void>;
 }
 interface XRSession {
     baseLayer: XRWebGLLayer;
@@ -191,7 +192,9 @@ interface XRSession {
     addEventListener: Function;
 }
 interface XRSessionCreationOptions {
-    outputContext: WebGLRenderingContext;
+    outputContext?: WebGLRenderingContext | null;
+    immersive?: boolean;
+    environmentIntegration?: boolean;
 }
 interface XRLayer {
     getViewport: Function;