Przeglądaj źródła

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

David Catuhe 6 lat temu
rodzic
commit
7a9beb0648

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

@@ -24,6 +24,7 @@
 - Added startAndReleaseDragOnPointerEvents property to pointerDragBehavior which can be set to false for custom drag triggering ([TrevorDev](https://github.com/TrevorDev))
 - Effect renderer to render one or multiple shader effects to a texture ([TrevorDev](https://github.com/TrevorDev))
 - Added url parameters to web request modifiers ([PierreLeBlond](https://github.com/PierreLeBlond))
+- WebXR updated to spec as of June 27th ([TrevorDev](https://github.com/TrevorDev))
 
 ### Engine
 - Morph targets now can morph UV channel as well ([Deltakosh](https://github.com/deltakosh/))

+ 16 - 14
src/Cameras/XR/webXRCamera.ts

@@ -64,16 +64,16 @@ export class WebXRCamera extends FreeCamera {
      */
     public updateFromXRSessionManager(xrSessionManager: WebXRSessionManager) {
         // Ensure all frame data is available
-        if (!xrSessionManager._currentXRFrame || !xrSessionManager._currentXRFrame.getDevicePose) {
+        if (!xrSessionManager.currentFrame || !xrSessionManager.currentFrame.getViewerPose) {
             return false;
         }
-        var pose = xrSessionManager._currentXRFrame.getDevicePose(xrSessionManager._frameOfReference);
-        if (!pose || !pose.poseModelMatrix) {
+        var pose = xrSessionManager.currentFrame.getViewerPose(xrSessionManager.referenceSpace);
+        if (!pose || !pose.transform || !pose.transform.matrix) {
             return false;
         }
 
         // Update the parent cameras matrix
-        Matrix.FromFloat32ArrayToRefScaled(pose.poseModelMatrix, 0, 1, WebXRCamera._TmpMatrix);
+        Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, WebXRCamera._TmpMatrix);
         if (!this._scene.useRightHandedSystem) {
             WebXRCamera._TmpMatrix.toggleModelMatrixHandInPlace();
         }
@@ -83,10 +83,10 @@ export class WebXRCamera extends FreeCamera {
         this.computeWorldMatrix();
 
         // Update camera rigs
-        this._updateNumberOfRigCameras(xrSessionManager._currentXRFrame.views.length);
-        xrSessionManager._currentXRFrame.views.forEach((view, i) => {
+        this._updateNumberOfRigCameras(pose.views.length);
+        pose.views.forEach((view: any, i: number) => {
             // Update view/projection matrix
-            Matrix.FromFloat32ArrayToRefScaled(pose.getViewMatrix(view), 0, 1, this.rigCameras[i]._computedViewMatrix);
+            Matrix.FromFloat32ArrayToRefScaled(view.transform.matrix, 0, 1, this.rigCameras[i]._computedViewMatrix);
             Matrix.FromFloat32ArrayToRefScaled(view.projectionMatrix, 0, 1, this.rigCameras[i]._projectionMatrix);
             if (!this._scene.useRightHandedSystem) {
                 this.rigCameras[i]._computedViewMatrix.toggleModelMatrixHandInPlace();
@@ -94,13 +94,15 @@ export class WebXRCamera extends FreeCamera {
             }
 
             // Update viewport
-            var viewport = xrSessionManager._xrSession.baseLayer.getViewport(view);
-            var width = xrSessionManager._xrSession.baseLayer.framebufferWidth;
-            var height = xrSessionManager._xrSession.baseLayer.framebufferHeight;
-            this.rigCameras[i].viewport.width = viewport.width / width;
-            this.rigCameras[i].viewport.height = viewport.height / height;
-            this.rigCameras[i].viewport.x = viewport.x / width;
-            this.rigCameras[i].viewport.y = viewport.y / height;
+            if (xrSessionManager.session.renderState.baseLayer) {
+                var viewport = xrSessionManager.session.renderState.baseLayer.getViewport(view);
+                var width = xrSessionManager.session.renderState.baseLayer.framebufferWidth;
+                var height = xrSessionManager.session.renderState.baseLayer.framebufferHeight;
+                this.rigCameras[i].viewport.width = viewport.width / width;
+                this.rigCameras[i].viewport.height = viewport.height / height;
+                this.rigCameras[i].viewport.x = viewport.x / width;
+                this.rigCameras[i].viewport.y = viewport.y / height;
+            }
 
             // Set cameras to render to the session's render target
             this.rigCameras[i].outputRenderTarget = xrSessionManager._sessionRenderTargetTexture;

+ 13 - 16
src/Cameras/XR/webXREnterExitUI.ts

@@ -2,6 +2,7 @@ import { Nullable } from "../../types";
 import { Observable } from "../../Misc/observable";
 import { IDisposable, Scene } from "../../scene";
 import { WebXRExperienceHelper, WebXRState } from "./webXRExperienceHelper";
+import { WebXRManagedOutputCanvas } from '../XR/webXRManagedOutputCanvas';
 /**
  * Button which can be used to enter a different mode of XR
  */
@@ -9,13 +10,16 @@ export class WebXREnterExitUIButton {
     /**
      * Creates a WebXREnterExitUIButton
      * @param element button element
-     * @param initializationOptions XR initialization options for the button
+     * @param sessionMode XR initialization session mode
+     * @param referenceSpaceType the type of reference space to be used
      */
     constructor(
         /** button element */
         public element: HTMLElement,
         /** XR initialization options for the button */
-        public initializationOptions: XRSessionCreationOptions
+        public sessionMode: XRSessionMode,
+        /** Reference space type */
+        public referenceSpaceType: XRReferenceSpaceType
     ) { }
     /**
      * Overwritable function which can be used to update the button's visuals when the state changes
@@ -32,7 +36,7 @@ export class WebXREnterExitUIOptions {
     /**
      * Context to enter xr with
      */
-    outputCanvasContext?: Nullable<WebGLRenderingContext>;
+    webXRManagedOutputCanvas?: Nullable<WebXRManagedOutputCanvas>;
 
     /**
      * User provided buttons to enable/disable WebXR. The system will provide default if not set
@@ -64,7 +68,7 @@ export class WebXREnterExitUI implements IDisposable {
     public static CreateAsync(scene: Scene, helper: WebXRExperienceHelper, options: WebXREnterExitUIOptions): Promise<WebXREnterExitUI> {
         var ui = new WebXREnterExitUI(scene, options);
         var supportedPromises = ui._buttons.map((btn) => {
-            return helper.supportsSessionAsync(btn.initializationOptions);
+            return helper.sessionManager.supportsSessionAsync(btn.sessionMode);
         });
         helper.onStateChangedObservable.add((state) => {
             if (state == WebXRState.NOT_IN_XR) {
@@ -82,7 +86,9 @@ export class WebXREnterExitUI implements IDisposable {
                             return;
                         } else if (helper.state == WebXRState.NOT_IN_XR) {
                             ui._updateButtons(ui._buttons[i]);
-                            await helper.enterXRAsync(ui._buttons[i].initializationOptions, "eye-level");
+                            if (options.webXRManagedOutputCanvas) {
+                                await helper.enterXRAsync(ui._buttons[i].sessionMode, ui._buttons[i].referenceSpaceType, options.webXRManagedOutputCanvas);
+                            }
                         }
                     };
                 }
@@ -101,20 +107,11 @@ export class WebXREnterExitUI implements IDisposable {
             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(new WebXREnterExitUIButton(hmdBtn, { immersive: true, outputContext: options.outputCanvasContext }));
+            this._buttons.push(new WebXREnterExitUIButton(hmdBtn, "immersive-vr", "local-floor"));
             this._buttons[this._buttons.length - 1].update = function(activeButton: WebXREnterExitUIButton) {
                 this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
                 this.element.innerText = activeButton === this ? "EXIT" : "HMD";
             };
-
-            var windowBtn = document.createElement("button");
-            windowBtn.style.cssText = hmdBtn.style.cssText;
-            windowBtn.innerText = "Window";
-            this._buttons.push(new WebXREnterExitUIButton(windowBtn, { immersive: false, environmentIntegration: true, outputContext: options.outputCanvasContext }));
-            this._buttons[this._buttons.length - 1].update = function(activeButton: WebXREnterExitUIButton) {
-                this.element.style.display = (activeButton === null || activeButton === this) ? "" : "none";
-                this.element.innerText = activeButton === this ? "EXIT" : "Window";
-            };
             this._updateButtons(null);
         }
 
@@ -145,4 +142,4 @@ export class WebXREnterExitUI implements IDisposable {
         }
         this.activeButtonChangedObservable.clear();
     }
-}
+}

+ 27 - 37
src/Cameras/XR/webXRExperienceHelper.ts

@@ -3,10 +3,10 @@ import { Observable } from "../../Misc/observable";
 import { IDisposable, Scene } from "../../scene";
 import { Quaternion, Vector3 } from "../../Maths/math";
 import { AbstractMesh } from "../../Meshes/abstractMesh";
-import { Ray } from "../../Culling/ray";
 import { Camera } from "../../Cameras/camera";
 import { WebXRSessionManager } from "./webXRSessionManager";
 import { WebXRCamera } from "./webXRCamera";
+import { WebXRManagedOutputCanvas } from './webXRManagedOutputCanvas';
 /**
  * States of the webXR experience
  */
@@ -59,8 +59,8 @@ export class WebXRExperienceHelper implements IDisposable {
      */
     public onStateChangedObservable = new Observable<WebXRState>();
 
-    /** @hidden */
-    public _sessionManager: WebXRSessionManager;
+    /** Session manager used to keep track of xr session */
+    public sessionManager: WebXRSessionManager;
 
     private _nonVRCamera: Nullable<Camera> = null;
     private _originalSceneAutoClear = true;
@@ -74,7 +74,7 @@ export class WebXRExperienceHelper implements IDisposable {
      */
     public static CreateAsync(scene: Scene): Promise<WebXRExperienceHelper> {
         var helper = new WebXRExperienceHelper(scene);
-        return helper._sessionManager.initializeAsync().then(() => {
+        return helper.sessionManager.initializeAsync().then(() => {
             helper._supported = true;
             return helper;
         }).catch(() => {
@@ -88,7 +88,7 @@ export class WebXRExperienceHelper implements IDisposable {
      */
     private constructor(private scene: Scene) {
         this.camera = new WebXRCamera("", scene);
-        this._sessionManager = new WebXRSessionManager(scene);
+        this.sessionManager = new WebXRSessionManager(scene);
         this.container = new AbstractMesh("", scene);
         this.camera.parent = this.container;
     }
@@ -99,22 +99,30 @@ export class WebXRExperienceHelper implements IDisposable {
      */
     public exitXRAsync() {
         this._setState(WebXRState.EXITING_XR);
-        return this._sessionManager.exitXRAsync();
+        return this.sessionManager.exitXRAsync();
     }
 
     /**
      * 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
+     * @param referenceSpaceType frame of reference of the XR session
+     * @param outputCanvas the output canvas that will be used to enter XR mode
      * @returns promise that resolves after xr mode has entered
      */
-    public enterXRAsync(sessionCreationOptions: XRSessionCreationOptions, frameOfReference: string) {
+    public enterXRAsync(sessionCreationOptions: XRSessionMode, referenceSpaceType: XRReferenceSpaceType, outputCanvas: WebXRManagedOutputCanvas) {
         if (!this._supported) {
             throw "XR session not supported by this browser";
         }
         this._setState(WebXRState.ENTERING_XR);
-
-        return this._sessionManager.enterXRAsync(sessionCreationOptions, frameOfReference).then(() => {
+        return this.sessionManager.initializeSessionAsync(sessionCreationOptions).then(() => {
+            return this.sessionManager.setReferenceSpaceAsync(referenceSpaceType);
+        }).then(() => {
+            return outputCanvas.initializeXRLayerAsync(this.sessionManager.session);
+        }).then(() => {
+            return this.sessionManager.updateRenderStateAsync({baseLayer: outputCanvas.xrLayer});
+        }).then(() => {
+            return this.sessionManager.startRenderingToXRAsync();
+        }).then(() => {
             // Cache pre xr scene settings
             this._originalSceneAutoClear = this.scene.autoClear;
             this._nonVRCamera = this.scene.activeCamera;
@@ -123,11 +131,11 @@ export class WebXRExperienceHelper implements IDisposable {
             this.scene.autoClear = false;
             this.scene.activeCamera = this.camera;
 
-            this._sessionManager.onXRFrameObservable.add(() => {
-                this.camera.updateFromXRSessionManager(this._sessionManager);
+            this.sessionManager.onXRFrameObservable.add(() => {
+                this.camera.updateFromXRSessionManager(this.sessionManager);
             });
 
-            this._sessionManager.onXRSessionEnded.addOnce(() => {
+            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;
@@ -136,24 +144,18 @@ export class WebXRExperienceHelper implements IDisposable {
                 // Restore scene settings
                 this.scene.autoClear = this._originalSceneAutoClear;
                 this.scene.activeCamera = this._nonVRCamera;
-                this._sessionManager.onXRFrameObservable.clear();
+                this.sessionManager.onXRFrameObservable.clear();
 
                 this._setState(WebXRState.NOT_IN_XR);
             });
             this._setState(WebXRState.IN_XR);
+        }).catch((e: any) => {
+            console.log(e);
+            console.log(e.message);
         });
     }
 
     /**
-     * Fires a ray and returns the closest hit in the xr sessions enviornment, useful to place objects in AR
-     * @param ray ray to cast into the environment
-     * @returns Promise which resolves with a collision point in the environment if it exists
-     */
-    public environmentPointHitTestAsync(ray: Ray): Promise<Nullable<Vector3>> {
-        return this._sessionManager.environmentPointHitTestAsync(ray);
-    }
-
-    /**
      * Updates the global position of the camera by moving the camera's container
      * This should be used instead of modifying the camera's position as it will be overwritten by an xrSessions's update frame
      * @param position The desired global position of the camera
@@ -177,24 +179,12 @@ export class WebXRExperienceHelper implements IDisposable {
     }
 
     /**
-     * Checks if the creation options are supported by the xr session
-     * @param options creation options
-     * @returns true if supported
-     */
-    public supportsSessionAsync(options: XRSessionCreationOptions) {
-        if (!this._supported) {
-            return Promise.resolve(false);
-        }
-        return this._sessionManager.supportsSessionAsync(options);
-    }
-
-    /**
      * Disposes of the experience helper
      */
     public dispose() {
         this.camera.dispose();
         this.container.dispose();
         this.onStateChangedObservable.clear();
-        this._sessionManager.dispose();
+        this.sessionManager.dispose();
     }
-}
+}

+ 114 - 44
src/Cameras/XR/webXRInput.ts

@@ -1,9 +1,9 @@
 import { Nullable } from "../../types";
-import { Observer } from "../../Misc/observable";
-import { Matrix, Quaternion } from "../../Maths/math";
+import { Observer, Observable } from "../../Misc/observable";
 import { IDisposable, Scene } from "../../scene";
 import { AbstractMesh } from "../../Meshes/abstractMesh";
 import { WebXRExperienceHelper } from "./webXRExperienceHelper";
+import { Matrix, Quaternion } from '../../Maths/math';
 /**
  * Represents an XR input
  */
@@ -17,14 +17,69 @@ export class WebXRController {
      */
     public pointer: AbstractMesh;
 
+    private _tmpMatrix = new Matrix();
+
     /**
      * Creates the controller
      * @see https://doc.babylonjs.com/how_to/webxr
      * @param scene the scene which the controller should be associated to
+     * @param inputSource the underlying input source for the controller
+     * @param parentContainer parent that the controller meshes should be children of
      */
-    constructor(scene: Scene) {
+    constructor(
+        private scene: Scene,
+        /** The underlying input source for the controller  */
+        public inputSource: XRInputSource,
+        private parentContainer: Nullable<AbstractMesh> = null)
+    {
         this.pointer = new AbstractMesh("controllerPointer", scene);
+        if (parentContainer) {
+            parentContainer.addChild(this.pointer);
+
+        }
     }
+
+    /**
+     * Updates the controller pose based on the given XRFrame
+     * @param xrFrame xr frame to update the pose with
+     * @param referenceSpace reference space to use
+     */
+    public updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace) {
+        var pose = xrFrame.getPose(this.inputSource.targetRaySpace, referenceSpace);
+        if (pose) {
+            Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMatrix);
+            if (!this.pointer.getScene().useRightHandedSystem) {
+                this._tmpMatrix.toggleModelMatrixHandInPlace();
+            }
+            if (!this.pointer.rotationQuaternion) {
+                this.pointer.rotationQuaternion = new Quaternion();
+            }
+            this._tmpMatrix.decompose(this.pointer.scaling, this.pointer.rotationQuaternion!, this.pointer.position);
+        }
+
+        if (this.inputSource.gripSpace) {
+            if (!this.grip) {
+                this.grip = new AbstractMesh("controllerGrip", this.scene);
+                if (this.parentContainer) {
+                    this.parentContainer.addChild(this.grip);
+                }
+            }
+
+            var pose = xrFrame.getPose(this.inputSource.gripSpace, referenceSpace);
+            if (pose) {
+                Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMatrix);
+                if (!this.grip.getScene().useRightHandedSystem) {
+                    this._tmpMatrix.toggleModelMatrixHandInPlace();
+                }
+                if (!this.grip.rotationQuaternion) {
+                    this.grip.rotationQuaternion = new Quaternion();
+                }
+                this._tmpMatrix.decompose(this.grip.scaling, this.grip.rotationQuaternion!, this.grip.position);
+            }
+        }
+
+    }
+
     /**
      * Disposes of the object
      */
@@ -44,58 +99,73 @@ export class WebXRInput implements IDisposable {
      * XR controllers being tracked
      */
     public controllers: Array<WebXRController> = [];
-    private _tmpMatrix = new Matrix();
     private _frameObserver: Nullable<Observer<any>>;
+    /**
+     * Event when a controller has been connected/added
+     */
+    public onControllerAddedObservable = new Observable<WebXRController>();
+    /**
+     * Event when a controller has been removed/disconnected
+     */
+    public onControllerRemovedObservable = new Observable<WebXRController>();
 
     /**
      * Initializes the WebXRInput
      * @param helper experience helper which the input should be created for
      */
     public constructor(private helper: WebXRExperienceHelper) {
-        this._frameObserver = helper._sessionManager.onXRFrameObservable.add(() => {
-            if (!helper._sessionManager._currentXRFrame || !helper._sessionManager._currentXRFrame.getDevicePose) {
+        this._frameObserver = helper.sessionManager.onXRFrameObservable.add(() => {
+            if (!helper.sessionManager.currentFrame) {
                 return;
             }
 
-            var xrFrame = helper._sessionManager._currentXRFrame;
-            var inputSources = helper._sessionManager._xrSession.getInputSources();
-
-            inputSources.forEach((input, i) => {
-                let inputPose = xrFrame.getInputPose(input, helper._sessionManager._frameOfReference);
-                if (inputPose) {
-                    if (this.controllers.length <= i) {
-                        this.controllers.push(new WebXRController(helper.container.getScene()));
-                    }
-                    var controller = this.controllers[i];
-
-                    // Manage the grip if it exists
-                    if (inputPose.gripMatrix) {
-                        if (!controller.grip) {
-                            controller.grip = new AbstractMesh("controllerGrip", helper.container.getScene());
-                        }
-                        Matrix.FromFloat32ArrayToRefScaled(inputPose.gripMatrix, 0, 1, this._tmpMatrix);
-                        if (!controller.grip.getScene().useRightHandedSystem) {
-                            this._tmpMatrix.toggleModelMatrixHandInPlace();
-                        }
-                        if (!controller.grip.rotationQuaternion) {
-                            controller.grip.rotationQuaternion = new Quaternion();
-                        }
-                        this._tmpMatrix.decompose(controller.grip.scaling, controller.grip.rotationQuaternion, controller.grip.position);
-                    }
-
-                    // Manager pointer of controller
-                    Matrix.FromFloat32ArrayToRefScaled(inputPose.targetRay.transformMatrix, 0, 1, this._tmpMatrix);
-                    if (!controller.pointer.getScene().useRightHandedSystem) {
-                        this._tmpMatrix.toggleModelMatrixHandInPlace();
-                    }
-                    if (!controller.pointer.rotationQuaternion) {
-                        controller.pointer.rotationQuaternion = new Quaternion();
-                    }
-                    this._tmpMatrix.decompose(controller.pointer.scaling, controller.pointer.rotationQuaternion, controller.pointer.position);
-                }
+            // Start listing to input add/remove event
+            if (this.controllers.length == 0 && helper.sessionManager.session.inputSources) {
+                this._addAndRemoveControllers(helper.sessionManager.session.inputSources, []);
+                helper.sessionManager.session.addEventListener("inputsourceschange", this._onInputSourcesChange);
+            }
+
+            // Update controller pose info
+            this.controllers.forEach((controller) => {
+                controller.updateFromXRFrame(helper.sessionManager.currentFrame!, helper.sessionManager.referenceSpace);
             });
+
+        });
+    }
+
+    private _onInputSourcesChange = (event: XRInputSourceChangeEvent) => {
+        this._addAndRemoveControllers(event.added, event.removed);
+    }
+
+    private _addAndRemoveControllers(addInputs: Array<XRInputSource>, removeInputs: Array<XRInputSource>) {
+        // Add controllers if they don't already exist
+        var sources = this.controllers.map((c) => {return c.inputSource; });
+        addInputs.forEach((input) => {
+            if (sources.indexOf(input) === -1) {
+                var controller = new WebXRController(this.helper.camera._scene, input, this.helper.container);
+                this.controllers.push(controller);
+                this.onControllerAddedObservable.notifyObservers(controller);
+            }
+        });
+
+        // Remove and dispose of controllers to be disposed
+        var keepControllers: Array<WebXRController> = [];
+        var removedControllers: Array<WebXRController> = [];
+        this.controllers.forEach((c) => {
+            if (removeInputs.indexOf(c.inputSource) === -1) {
+                keepControllers.push(c);
+            }else {
+                removedControllers.push(c);
+            }
         });
+        this.controllers = keepControllers;
+        removedControllers.forEach((c) => {
+            this.onControllerRemovedObservable.notifyObservers(c);
+            c.dispose();
+        });
+
     }
+
     /**
      * Disposes of the object
      */
@@ -103,6 +173,6 @@ export class WebXRInput implements IDisposable {
         this.controllers.forEach((c) => {
             c.dispose();
         });
-        this.helper._sessionManager.onXRFrameObservable.remove(this._frameObserver);
+        this.helper.sessionManager.onXRFrameObservable.remove(this._frameObserver);
     }
-}
+}

+ 27 - 8
src/Cameras/XR/webXRManagedOutputCanvas.ts

@@ -9,16 +9,32 @@ export class WebXRManagedOutputCanvas implements IDisposable {
     /**
      * xrpresent context of the canvas which can be used to display/mirror xr content
      */
-    public canvasContext: Nullable<WebGLRenderingContext> = null;
+    public canvasContext: WebGLRenderingContext;
+    /**
+     * xr layer for the canvas
+     */
+    public xrLayer: Nullable<XRWebGLLayer> = null;
+
+    /**
+     * Initializes the xr layer for the session
+     * @param xrSession xr session
+     * @returns a promise that will resolve once the XR Layer has been created
+     */
+    public initializeXRLayerAsync(xrSession: any) {
+        return (this.canvasContext as any).makeXRCompatible().then(() => {
+            this.xrLayer = new XRWebGLLayer(xrSession, this.canvasContext);
+        });
+    }
+
     /**
      * 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) {
+    constructor(private 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: #000000;";
+            canvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:90%;height:100%;background-color: #000000;";
         }
         this._setManagedOutputCanvas(canvas);
         helper.onStateChangedObservable.add((stateInfo) => {
@@ -42,22 +58,25 @@ export class WebXRManagedOutputCanvas implements IDisposable {
         this._removeCanvas();
         if (!canvas) {
             this._canvas = null;
-            this.canvasContext = null;
+            (this.canvasContext as any) = null;
         } else {
             this._canvas = canvas;
-            this.canvasContext = <any>this._canvas.getContext('xrpresent');
+            this.canvasContext = <any>this._canvas.getContext('webgl');
+            if (!this.canvasContext) {
+                this.canvasContext = <any>this._canvas.getContext('webgl2');
+            }
         }
     }
 
     private _addCanvas() {
-        if (this._canvas) {
+        if (this._canvas && this._canvas !== this.helper.container.getScene().getEngine().getRenderingCanvas()) {
             document.body.appendChild(this._canvas);
         }
     }
 
     private _removeCanvas() {
-        if (this._canvas && document.body.contains(this._canvas)) {
+        if (this._canvas && document.body.contains(this._canvas) && this._canvas !== this.helper.container.getScene().getEngine().getRenderingCanvas()) {
             document.body.removeChild(this._canvas);
         }
     }
-}
+}

+ 89 - 89
src/Cameras/XR/webXRSessionManager.ts

@@ -2,12 +2,10 @@ import { Logger } from "../../Misc/logger";
 import { Observable } from "../../Misc/observable";
 import { Nullable } from "../../types";
 import { IDisposable, Scene } from "../../scene";
-import { Vector3, Matrix } from "../../Maths/math";
 import { InternalTexture } from "../../Materials/Textures/internalTexture";
 import { RenderTargetTexture } from "../../Materials/Textures/renderTargetTexture";
-import { Ray } from "../../Culling/ray";
 /**
- * Manages an XRSession
+ * Manages an XRSession to work with Babylon's engine
  * @see https://doc.babylonjs.com/how_to/webxr
  */
 export class WebXRSessionManager implements IDisposable {
@@ -20,17 +18,25 @@ export class WebXRSessionManager implements IDisposable {
      */
     public onXRSessionEnded: Observable<any> = new Observable<any>();
 
-    /** @hidden */
-    public _xrSession: XRSession;
-    /** @hidden */
-    public _frameOfReference: XRFrameOfReference;
+    /**
+     * Underlying xr session
+     */
+    public session: XRSession;
+
+    /**
+     * Type of reference space used when creating the session
+     */
+    public referenceSpace: XRReferenceSpace;
+
     /** @hidden */
     public _sessionRenderTargetTexture: Nullable<RenderTargetTexture> = null;
-    /** @hidden */
-    public _currentXRFrame: Nullable<XRFrame>;
+
+    /**
+     * Current XR frame
+     */
+    public currentFrame: Nullable<XRFrame>;
     private _xrNavigator: any;
-    private _xrDevice: XRDevice;
-    private _tmpMatrix = new Matrix();
+    private baseLayer: Nullable<XRWebGLLayer> = null;
 
     /**
      * Constructs a WebXRSessionManager, this must be initialized within a user action before usage
@@ -52,26 +58,20 @@ export class WebXRSessionManager implements IDisposable {
         if (!this._xrNavigator.xr) {
             return Promise.reject("webXR not supported by this browser");
         }
-        // Request the webXR device
-        return this._xrNavigator.xr.requestDevice().then((device: XRDevice) => {
-            this._xrDevice = device;
-            return (<any>this.scene.getEngine()._gl).setCompatibleXRDevice(this._xrDevice);
-        });
+        return Promise.resolve();
     }
 
     /**
-     * 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
+     * Initializes an xr session
+     * @param xrSessionMode mode to initialize
+     * @returns a promise which will resolve once the session has been initialized
      */
-    public enterXRAsync(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: string): Promise<void> {
-        // initialize session
-        return this._xrDevice.requestSession(sessionCreationOptions).then((session: XRSession) => {
-            this._xrSession = session;
+    public initializeSessionAsync(xrSessionMode: XRSessionMode) {
+        return this._xrNavigator.xr.requestSession(xrSessionMode).then((session: XRSession) => {
+            this.session = 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", () => {
+            this.session.addEventListener("end", () => {
                 // Remove render target texture and notify frame obervers
                 this._sessionRenderTargetTexture = null;
 
@@ -83,83 +83,80 @@ export class WebXRSessionManager implements IDisposable {
                 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) => {
-            this._frameOfReference = frameOfRef;
-            // 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: 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();
+    /**
+     * Sets the reference space on the xr session
+     * @param referenceSpace space to set
+     * @returns a promise that will resolve once the reference space has been set
+     */
+    public setReferenceSpaceAsync(referenceSpace: XRReferenceSpaceType) {
+        return this.session.requestReferenceSpace(referenceSpace).then((referenceSpace: XRReferenceSpace) => {
+            this.referenceSpace = referenceSpace;
         });
     }
 
     /**
-     * Stops the xrSession and restores the renderloop
-     * @returns Promise which resolves after it exits XR
+     * Updates the render state of the session
+     * @param state state to set
+     * @returns a promise that resolves once the render state has been updated
      */
-    public exitXRAsync() {
-        return this._xrSession.end();
+    public updateRenderStateAsync(state: any) {
+        if (state.baseLayer) {
+            this.baseLayer = state.baseLayer;
+        }
+        return this.session.updateRenderState(state);
     }
 
     /**
-     * Fires a ray and returns the closest hit in the xr sessions enviornment, useful to place objects in AR
-     * @param ray ray to cast into the environment
-     * @returns Promise which resolves with a collision point in the environment if it exists
+     * Starts rendering to the xr layer
+     * @returns a promise that will resolve once rendering has started
      */
-    public environmentPointHitTestAsync(ray: Ray): Promise<Nullable<Vector3>> {
-        return new Promise((res) => {
-            // Compute left handed inputs to request hit test
-            var origin = new Float32Array([ray.origin.x, ray.origin.y, ray.origin.z]);
-            var direction = new Float32Array([ray.direction.x, ray.direction.y, ray.direction.z]);
-            if (!this.scene.useRightHandedSystem) {
-                origin[2] *= -1;
-                direction[2] *= -1;
+    public startRenderingToXRAsync() {
+        // 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.session.requestAnimationFrame.bind(this.session),
+            renderFunction: (timestamp: number, xrFrame: Nullable<XRFrame>) => {
+                // Store the XR frame in the manager to be consumed by the XR camera to update pose
+                this.currentFrame = xrFrame;
+                this.onXRFrameObservable.notifyObservers(null);
+                this.scene.getEngine()._renderLoop();
             }
+        };
+        // Create render target texture from xr's webgl render target
+        this._sessionRenderTargetTexture = WebXRSessionManager._CreateRenderTargetTextureFromSession(this.session, this.scene, this.baseLayer!);
+
+        // Stop window's animation frame and trigger sessions animation frame
+        window.cancelAnimationFrame(this.scene.getEngine()._frameHandler);
+        this.scene.getEngine()._renderLoop();
+        return Promise.resolve();
+    }
 
-            // Fire hittest
-            this._xrSession.requestHitTest(origin, direction, this._frameOfReference)
-                .then((hits: any) => {
-                    if (hits.length > 0) {
-                        Matrix.FromFloat32ArrayToRefScaled(hits[0].hitMatrix, 0, 1.0, this._tmpMatrix);
-                        var hitPoint = this._tmpMatrix.getTranslation();
-                        if (!this.scene.useRightHandedSystem) {
-                            hitPoint.z *= -1;
-                        }
-                        res(hitPoint);
-                    } else {
-                        res(null);
-                    }
-                }).catch(() => {
-                    res(null);
-                });
-        });
+    /**
+     * Stops the xrSession and restores the renderloop
+     * @returns Promise which resolves after it exits XR
+     */
+    public exitXRAsync() {
+        return this.session.end();
     }
 
     /**
      * Checks if a session would be supported for the creation options specified
-     * @param options creation options to check if they are supported
+     * @param sessionMode session mode to check if supported eg. immersive-vr
      * @returns true if supported
      */
-    public supportsSessionAsync(options: XRSessionCreationOptions) {
-        return this._xrDevice.supportsSession(options).then(() => {
-            return true;
-        }).catch(() => {
-            return false;
-        });
+    public supportsSessionAsync(sessionMode: XRSessionMode) {
+        if (!(navigator as any).xr || !(navigator as any).xr.supportsSession) {
+            return Promise.resolve(false);
+        }else {
+            return (navigator as any).xr.supportsSession(sessionMode).then(() => {
+                return Promise.resolve(true);
+            }).catch((e: any) => {
+                Logger.Warn(e);
+                return Promise.resolve(false);
+            });
+        }
     }
 
     /**
@@ -168,12 +165,15 @@ export class WebXRSessionManager implements IDisposable {
      * @param session session to create render target for
      * @param scene scene the new render target should be created for
      */
-    public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: Scene) {
+    public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: Scene, baseLayer: XRWebGLLayer) {
+        if (!baseLayer) {
+            throw "no layer";
+        }
         // Create internal texture
         var internalTexture = new InternalTexture(scene.getEngine(), InternalTexture.DATASOURCE_UNKNOWN, true);
-        internalTexture.width = session.baseLayer.framebufferWidth;
-        internalTexture.height = session.baseLayer.framebufferHeight;
-        internalTexture._framebuffer = session.baseLayer.framebuffer;
+        internalTexture.width = baseLayer.framebufferWidth;
+        internalTexture.height = baseLayer.framebufferHeight;
+        internalTexture._framebuffer = baseLayer.framebuffer;
 
         // Create render target texture from the internal texture
         var renderTargetTexture = new RenderTargetTexture("XR renderTargetTexture", { width: internalTexture.width, height: internalTexture.height }, scene, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true);
@@ -189,4 +189,4 @@ export class WebXRSessionManager implements IDisposable {
         this.onXRFrameObservable.clear();
         this.onXRSessionEnded.clear();
     }
-}
+}

+ 1 - 1
src/Helpers/sceneHelpers.ts

@@ -213,7 +213,7 @@ Scene.prototype.createDefaultVRExperience = function(webVROptions: VRExperienceH
 Scene.prototype.createDefaultXRExperienceAsync = function(): Promise<WebXRExperienceHelper> {
     return WebXRExperienceHelper.CreateAsync(this).then((helper) => {
         var outputCanvas = new WebXRManagedOutputCanvas(helper);
-        return WebXREnterExitUI.CreateAsync(this, helper, { outputCanvasContext: outputCanvas.canvasContext })
+        return WebXREnterExitUI.CreateAsync(this, helper, { webXRManagedOutputCanvas: outputCanvas })
             .then((ui) => {
                 new WebXRInput(helper);
                 return helper;

+ 106 - 30
src/LibDeclarations/webxr.d.ts

@@ -1,41 +1,117 @@
-interface XRDevice {
-    requestSession(options: XRSessionCreationOptions): Promise<XRSession>;
-    supportsSession(options: XRSessionCreationOptions): Promise<void>;
+type XRSessionMode =
+    | "inline"
+    | "immersive-vr"
+    | "immersive-ar";
+
+type XRReferenceSpaceType =
+    | "viewer"
+    | "local"
+    | "local-floor"
+    | "bounded-floor"
+    | "unbounded";
+
+type XREnvironmentBlendMode =
+    | "opaque"
+    | "additive"
+    | "alpha-blend";
+
+type XRVisibilityState =
+    | "visible"
+    | "visible-blurred"
+    | "hidden";
+
+type XRHandedness =
+    | "none"
+    | "left"
+    | "right";
+
+type XRTargetRayMode =
+    | "gaze"
+    | "tracked-pointer"
+    | "screen";
+
+type XREye =
+    | "none"
+    | "left"
+    | "right";
+
+interface XRSpace extends EventTarget {
+
 }
-interface XRSession {
-    getInputSources(): Array<any>;
-    baseLayer: XRWebGLLayer;
-    requestFrameOfReference(type: string): Promise<void>;
-    requestHitTest(origin: Float32Array, direction: Float32Array, frameOfReference: any): any;
-    end(): Promise<void>;
-    requestAnimationFrame: Function;
-    addEventListener: Function;
+
+interface XRRenderState {
+    depthNear: number ;
+    depthFar: number ;
+    inlineVerticalFieldOfView: number | undefined;
+    baseLayer: XRWebGLLayer | undefined;
 }
-interface XRSessionCreationOptions {
-    outputContext?: WebGLRenderingContext | null;
-    immersive?: boolean;
-    environmentIntegration?: boolean;
+
+interface XRInputSource {
+    handedness: XRHandedness;
+    targetRayMode: XRTargetRayMode;
+    targetRaySpace: XRSpace;
+    gripSpace: XRSpace | undefined;
+    gamepad: Gamepad | undefined;
+    profiles: Array<string>;
 }
-interface XRLayer {
-    getViewport: Function;
-    framebufferWidth: number;
-    framebufferHeight: number;
+
+interface XRSession {
+    addEventListener: Function;
+    requestReferenceSpace(type: XRReferenceSpaceType): Promise<XRReferenceSpace>;
+    updateRenderState(XRRenderStateInit: any): Promise<void>;
+    requestAnimationFrame: Function;
+    end(): Promise<void>;
+    renderState: XRRenderState;
+    inputSources: Array<XRInputSource>;
+
 }
-interface XRView {
-    projectionMatrix: Float32Array;
+
+interface XRReferenceSpace extends XRSpace {
+    getOffsetReferenceSpace(originOffset: XRRigidTransform): XRReferenceSpace;
+    onreset: any;
 }
+
 interface XRFrame {
-    getDevicePose: Function;
-    getInputPose: Function;
-    views: Array<XRView>;
-    baseLayer: XRLayer;
+    session: XRSession;
+    getViewerPose(referenceSpace: XRReferenceSpace): XRViewerPose | undefined;
+    getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 }
-interface XRFrameOfReference {
+
+interface XRViewerPose extends XRPose {
+    views: Array<XRView>;
 }
-interface XRWebGLLayer extends XRLayer {
-    framebuffer: WebGLFramebuffer;
+
+interface XRPose {
+    transform: XRRigidTransform;
+    emulatedPosition: boolean;
 }
+
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
-    new(session: XRSession, context?: WebGLRenderingContext): XRWebGLLayer;
-};
+    new(session: XRSession, context: WebGLRenderingContext | undefined): XRWebGLLayer;
+};
+interface XRWebGLLayer {
+    framebuffer: WebGLFramebuffer;
+    framebufferWidth: number;
+    framebufferHeight: number;
+    getViewport: Function;
+}
+
+interface XRRigidTransform {
+    position: DOMPointReadOnly;
+    orientation: DOMPointReadOnly;
+    matrix: Float32Array;
+    inverse: XRRigidTransform;
+}
+
+interface XRView {
+    eye: XREye;
+    projectionMatrix: Float32Array;
+    transform: XRRigidTransform;
+}
+
+interface XRInputSourceChangeEvent {
+    session: XRSession;
+    removed: Array<XRInputSource>;
+    added: Array<XRInputSource>;
+}

+ 6 - 0
src/scene.ts

@@ -3523,10 +3523,16 @@ export class Scene extends AbstractScene implements IAnimatable {
 
             this._intermediateRendering = false;
 
+            // Need to bind if sub-camera has an outputRenderTarget eg. for webXR
+            if (this.activeCamera && this.activeCamera.outputRenderTarget) {
+                needRebind = true;
+            }
+
             // Restore framebuffer after rendering to targets
             if (needRebind) {
                 this._bindFrameBuffer();
             }
+
         }
 
         this.onAfterRenderTargetsRenderObservable.notifyObservers(this);