浏览代码

Merge pull request #7415 from RaananW/xr-changes-and-cleanup

XR - Small changes to API and cleanup
David Catuhe 5 年之前
父节点
当前提交
ee7dbbfef1

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

@@ -191,7 +191,7 @@
 - Teleportation allows selecting direction before teleporting when using thumbstick/touchpad. ([#7290](https://github.com/BabylonJS/Babylon.js/issues/7290)) ([RaananW](https://github.com/RaananW/))
 - It is now possible to force a certain profile type for the controllers ([#7348](https://github.com/BabylonJS/Babylon.js/issues/7375)) ([RaananW](https://github.com/RaananW/))
 - WebXR camera is initialized on the first frame ([#7389](https://github.com/BabylonJS/Babylon.js/issues/7389)) ([RaananW](https://github.com/RaananW/))
-- Selection has forcable gaze mode and touch-screen support ([#7395](https://github.com/BabylonJS/Babylon.js/issues/7395)) ([RaananW](https://github.com/RaananW/))
+- Selection has gaze mode (which can be forced) and touch-screen support ([#7395](https://github.com/BabylonJS/Babylon.js/issues/7395)) ([RaananW](https://github.com/RaananW/))
 
 ### Ray
 

+ 17 - 0
src/Cameras/XR/webXRCamera.ts

@@ -73,6 +73,23 @@ export class WebXRCamera extends FreeCamera {
         }
     }
 
+    /**
+     * Sets this camera's transformation based on a non-vr camera
+     * @param otherCamera the non-vr camera to copy the transformation from
+     * @param resetToBaseReferenceSpace should XR reset to the base reference space
+     */
+    public setTransformationFromNonVRCamera(otherCamera: Camera, resetToBaseReferenceSpace: boolean = true) {
+        const mat = otherCamera.computeWorldMatrix();
+        mat.decompose(undefined, this.rotationQuaternion, this.position);
+        // set the ground level
+        this.position.y = 0;
+        Quaternion.FromEulerAnglesToRef(0, this.rotationQuaternion.toEulerAngles().y, 0, this.rotationQuaternion);
+        this._firstFrame = true;
+        if (resetToBaseReferenceSpace) {
+            this._xrSessionManager.resetReferenceSpace();
+        }
+    }
+
     /** @hidden */
     public _updateForDualEyeDebugging(/*pupilDistance = 0.01*/) {
         // Create initial camera rigs

+ 21 - 19
src/Cameras/XR/webXRController.ts

@@ -9,6 +9,17 @@ import { WebXRMotionControllerManager } from './motionController/webXRMotionCont
 let idCount = 0;
 
 /**
+ * Configuration options for the WebXR controller creation
+ */
+export interface IWebXRControllerOptions {
+    /**
+     * Force a specific controller type for this controller.
+     * This can be used when creating your own profile or when testing different controllers
+     */
+    forceControllerProfile?: string;
+}
+
+/**
  * Represents an XR controller
  */
 export class WebXRController {
@@ -39,28 +50,28 @@ export class WebXRController {
     /**
      * Creates the controller
      * @see https://doc.babylonjs.com/how_to/webxr
-     * @param scene the scene which the controller should be associated to
+     * @param _scene the scene which the controller should be associated to
      * @param inputSource the underlying input source for the controller
-     * @param controllerProfile An optional controller profile for this input. This will override the xrInput profile.
+     * @param _options options for this controller creation
      */
     constructor(
-        private scene: Scene,
+        private _scene: Scene,
         /** The underlying input source for the controller  */
         public inputSource: XRInputSource,
-        controllerProfile?: string) {
-        this._uniqueId = `${idCount++}-${inputSource.targetRayMode}-${inputSource.handedness}`;
+        private _options: IWebXRControllerOptions = {}) {
+        this._uniqueId = `controller-${idCount++}-${inputSource.targetRayMode}-${inputSource.handedness}`;
 
-        this.pointer = new AbstractMesh("controllerPointer", scene);
+        this.pointer = new AbstractMesh(`${this._uniqueId}-pointer`, _scene);
         this.pointer.rotationQuaternion = new Quaternion();
 
         if (this.inputSource.gripSpace) {
-            this.grip = new AbstractMesh("controllerGrip", this.scene);
+            this.grip = new AbstractMesh(`${this._uniqueId}-grip`, this._scene);
             this.grip.rotationQuaternion = new Quaternion();
         }
 
         // for now only load motion controllers if gamepad available
         if (this.inputSource.gamepad) {
-            this.gamepadController = WebXRMotionControllerManager.GetMotionControllerWithXRInput(inputSource, scene, controllerProfile);
+            this.gamepadController = WebXRMotionControllerManager.GetMotionControllerWithXRInput(inputSource, _scene, this._options.forceControllerProfile);
             // if the model is loaded, do your thing
             this.gamepadController.onModelLoadedObservable.addOnce(() => {
                 this.gamepadController!.rootMesh!.parent = this.pointer;
@@ -87,7 +98,7 @@ export class WebXRController {
         if (pose) {
             this.pointer.position.copyFrom(<any>(pose.transform.position));
             this.pointer.rotationQuaternion!.copyFrom(<any>(pose.transform.orientation));
-            if (!this.scene.useRightHandedSystem) {
+            if (!this._scene.useRightHandedSystem) {
                 this.pointer.position.z *= -1;
                 this.pointer.rotationQuaternion!.z *= -1;
                 this.pointer.rotationQuaternion!.w *= -1;
@@ -100,7 +111,7 @@ export class WebXRController {
             if (pose) {
                 this.grip.position.copyFrom(<any>(pose.transform.position));
                 this.grip.rotationQuaternion!.copyFrom(<any>(pose.transform.orientation));
-                if (!this.scene.useRightHandedSystem) {
+                if (!this._scene.useRightHandedSystem) {
                     this.grip.position.z *= -1;
                     this.grip.rotationQuaternion!.z *= -1;
                     this.grip.rotationQuaternion!.w *= -1;
@@ -118,7 +129,6 @@ export class WebXRController {
      * @param result the resulting ray
      */
     public getWorldPointerRayToRef(result: Ray) {
-        // Force update to ensure picked point is synced with ray
         let worldMatrix = this.pointer.computeWorldMatrix();
         worldMatrix.decompose(undefined, this._tmpQuaternion, undefined);
         this._tmpVector.set(0, 0, 1);
@@ -129,14 +139,6 @@ export class WebXRController {
     }
 
     /**
-     * Get the scene associated with this controller
-     * @returns the scene object
-     */
-    public getScene() {
-        return this.scene;
-    }
-
-    /**
      * Disposes of the object
      */
     dispose() {

+ 1 - 1
src/Cameras/XR/webXRDefaultExperience.ts

@@ -95,7 +95,7 @@ export class WebXRDefaultExperience {
             }
 
             // Create the WebXR output target
-            result.renderTarget = result.baseExperience.sessionManager.getWebXRRenderTarget(xrHelper.onStateChangedObservable, options.outputCanvasOptions);
+            result.renderTarget = result.baseExperience.sessionManager.getWebXRRenderTarget(options.outputCanvasOptions);
 
             if (!options.disableDefaultUI) {
                 if (options.uiOptions) {

+ 3 - 7
src/Cameras/XR/webXRExperienceHelper.ts

@@ -1,7 +1,6 @@
 import { Nullable } from "../../types";
 import { Observable } from "../../Misc/observable";
 import { IDisposable, Scene } from "../../scene";
-import { Quaternion } from "../../Maths/math.vector";
 import { Camera } from "../../Cameras/camera";
 import { WebXRSessionManager } from "./webXRSessionManager";
 import { WebXRCamera } from "./webXRCamera";
@@ -161,17 +160,14 @@ export class WebXRExperienceHelper implements IDisposable {
     public dispose() {
         this.camera.dispose();
         this.onStateChangedObservable.clear();
+        this.onInitialXRPoseSetObservable.clear();
         this.sessionManager.dispose();
+        this.scene.activeCamera = this._nonVRCamera;
     }
 
     private _nonXRToXRCamera() {
         this.scene.activeCamera = this.camera;
-        const mat = this._nonVRCamera!.computeWorldMatrix();
-        mat.decompose(undefined, this.camera.rotationQuaternion, this.camera.position);
-        // set the ground level
-        this.camera.position.y = 0;
-        Quaternion.FromEulerAnglesToRef(0, this.camera.rotationQuaternion.toEulerAngles().y, 0, this.camera.rotationQuaternion);
-
+        this.camera.setTransformationFromNonVRCamera(this._nonVRCamera!);
         this.onInitialXRPoseSetObservable.notifyObservers(this.camera);
     }
 }

+ 4 - 1
src/Cameras/XR/webXRFeaturesManager.ts

@@ -270,7 +270,10 @@ export class WebXRFeaturesManager implements IDisposable {
      * dispose this features manager
      */
     dispose(): void {
-        this.getEnabledFeatures().forEach((feature) => this._features[feature].featureImplementation.dispose());
+        this.getEnabledFeatures().forEach((feature) => {
+            this.disableFeature(feature);
+            this._features[feature].featureImplementation.dispose();
+        });
     }
 
 }

+ 3 - 1
src/Cameras/XR/webXRInput.ts

@@ -84,7 +84,7 @@ export class WebXRInput implements IDisposable {
         let sources = this.controllers.map((c) => { return c.inputSource; });
         for (let input of addInputs) {
             if (sources.indexOf(input) === -1) {
-                let controller = new WebXRController(this.xrSessionManager.scene, input, this.options.forceInputProfile);
+                let controller = new WebXRController(this.xrSessionManager.scene, input, { forceControllerProfile: this.options.forceInputProfile });
                 this.controllers.push(controller);
                 if (!this.options.doNotLoadControllerMeshes && controller.gamepadController) {
                     controller.gamepadController.loadModel();
@@ -121,5 +121,7 @@ export class WebXRInput implements IDisposable {
         this.xrSessionManager.onXRFrameObservable.remove(this._frameObserver);
         this.xrSessionManager.onXRSessionInit.remove(this._sessionInitObserver);
         this.xrSessionManager.onXRSessionEnded.remove(this._sessionEndedObserver);
+        this.onControllerAddedObservable.clear();
+        this.onControllerRemovedObservable.clear();
     }
 }

+ 33 - 30
src/Cameras/XR/webXRManagedOutputCanvas.ts

@@ -1,7 +1,7 @@
 import { Nullable } from "../../types";
-import { Observable } from "../../Misc/observable";
 import { ThinEngine } from '../../Engines/thinEngine';
-import { WebXRState, WebXRRenderTarget } from "./webXRTypes";
+import { WebXRRenderTarget } from "./webXRTypes";
+import { WebXRSessionManager } from './webXRSessionManager';
 
 /**
  * COnfiguration object for WebXR output canvas
@@ -10,7 +10,7 @@ export class WebXRManagedOutputCanvasOptions {
     /**
      * Options for this XR Layer output
      */
-    public canvasOptions: XRWebGLLayerOptions;
+    public canvasOptions?: XRWebGLLayerOptions;
 
     /**
      * CSS styling for a newly created canvas (if not provided)
@@ -18,6 +18,12 @@ export class WebXRManagedOutputCanvasOptions {
     public newCanvasCssStyle?: string;
 
     /**
+     * An optional canvas in case you wish to create it yourself and provide it here.
+     * If not provided, a new canvas will be created
+     */
+    public canvasElement?: HTMLCanvasElement;
+
+    /**
      * Get the default values of the configuration object
      * @returns default values of this configuration object
      */
@@ -46,7 +52,7 @@ export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
     private _canvas: Nullable<HTMLCanvasElement> = null;
 
     /**
-     * xrpresent context of the canvas which can be used to display/mirror xr content
+     * Rendering context of the canvas which can be used to display/mirror xr content
      */
     public canvasContext: WebGLRenderingContext;
     /**
@@ -59,16 +65,16 @@ export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
      * @param xrSession xr session
      * @returns a promise that will resolve once the XR Layer has been created
      */
-    public initializeXRLayerAsync(xrSession: any) {
+    public initializeXRLayerAsync(xrSession: XRSession): Promise<XRWebGLLayer> {
 
         const createLayer = () => {
-            return this.xrLayer = new XRWebGLLayer(xrSession, this.canvasContext, this.configuration.canvasOptions);
+            return new XRWebGLLayer(xrSession, this.canvasContext, this._options.canvasOptions);
         };
 
         // support canvases without makeXRCompatible
         if (!(this.canvasContext as any).makeXRCompatible) {
             this.xrLayer = createLayer();
-            return Promise.resolve(true);
+            return Promise.resolve(this.xrLayer);
         }
 
         return (this.canvasContext as any).makeXRCompatible().then(() => {
@@ -79,29 +85,26 @@ export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
 
     /**
      * Initializes the canvas to be added/removed upon entering/exiting xr
-     * @param engine the Babylon engine
-     * @param canvas The canvas to be added/removed (If not specified a full screen canvas will be created)
-     * @param onStateChangedObservable the mechanism by which the canvas will be added/removed based on XR state
-     * @param configuration optional configuration for this canvas output. defaults will be used if not provided
+     * @param _xrSessionManager The XR Session manager
+     * @param _options optional configuration for this canvas output. defaults will be used if not provided
      */
-    constructor(engine: ThinEngine, canvas?: HTMLCanvasElement, onStateChangedObservable?: Observable<WebXRState>, private configuration: WebXRManagedOutputCanvasOptions = WebXRManagedOutputCanvasOptions.GetDefaults()) {
-        this._engine = engine;
-        if (!canvas) {
-            canvas = document.createElement('canvas');
-            canvas.style.cssText = this.configuration.newCanvasCssStyle || "position:absolute; bottom:0px;right:0px;";
-        }
-        this._setManagedOutputCanvas(canvas);
-
-        if (onStateChangedObservable) {
-            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 (stateInfo == WebXRState.NOT_IN_XR) {
-                    this._removeCanvas();
-                }
-            });
+    constructor(_xrSessionManager: WebXRSessionManager, private _options: WebXRManagedOutputCanvasOptions = WebXRManagedOutputCanvasOptions.GetDefaults()) {
+        this._engine = _xrSessionManager.scene.getEngine();
+        if (!_options.canvasElement) {
+            const canvas = document.createElement('canvas');
+            canvas.style.cssText = this._options.newCanvasCssStyle || "position:absolute; bottom:0px;right:0px;";
+            this._setManagedOutputCanvas(canvas);
+        } else {
+            this._setManagedOutputCanvas(_options.canvasElement);
         }
+
+        _xrSessionManager.onXRSessionInit.add(() => {
+            this._addCanvas();
+        });
+
+        _xrSessionManager.onXRSessionEnded.add(() => {
+            this._removeCanvas();
+        });
     }
     /**
      * Disposes of the object
@@ -118,9 +121,9 @@ export class WebXRManagedOutputCanvas implements WebXRRenderTarget {
             (this.canvasContext as any) = null;
         } else {
             this._canvas = canvas;
-            this.canvasContext = <any>this._canvas.getContext('webgl');
+            this.canvasContext = <any>this._canvas.getContext('webgl2');
             if (!this.canvasContext) {
-                this.canvasContext = <any>this._canvas.getContext('webgl2');
+                this.canvasContext = <any>this._canvas.getContext('webgl');
             }
         }
     }

+ 30 - 13
src/Cameras/XR/webXRSessionManager.ts

@@ -4,7 +4,7 @@ import { Nullable } from "../../types";
 import { IDisposable, Scene } from "../../scene";
 import { InternalTexture, InternalTextureSource } from "../../Materials/Textures/internalTexture";
 import { RenderTargetTexture } from "../../Materials/Textures/renderTargetTexture";
-import { WebXRRenderTarget, WebXRState } from './webXRTypes';
+import { WebXRRenderTarget } from './webXRTypes';
 import { WebXRManagedOutputCanvas, WebXRManagedOutputCanvasOptions } from './webXRManagedOutputCanvas';
 
 interface IRenderTargetProvider {
@@ -186,6 +186,13 @@ export class WebXRSessionManager implements IDisposable {
     }
 
     /**
+     * Resets the reference space to the one started the session
+     */
+    public resetReferenceSpace() {
+        this.referenceSpace = this.baseReferenceSpace;
+    }
+
+    /**
      * Updates the render state of the session
      * @param state state to set
      * @returns a promise that resolves once the render state has been updated
@@ -202,27 +209,28 @@ export class WebXRSessionManager implements IDisposable {
      * @returns a promise that will resolve once rendering has started
      */
     public startRenderingToXRAsync() {
+        const engine = this.scene.getEngine();
         // 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 = {
+        engine.customAnimationFrameRequester = {
             requestAnimationFrame: this.session.requestAnimationFrame.bind(this.session),
             renderFunction: (timestamp: number, xrFrame: Nullable<XRFrame>) => {
                 if (this._sessionEnded) {
                     return;
                 }
-                // Store the XR frame in the manager to be consumed by the XR camera to update pose
+                // Store the XR frame and timestamp in the session manager
                 this.currentFrame = xrFrame;
                 this.currentTimestamp = timestamp;
                 if (xrFrame) {
                     this.onXRFrameObservable.notifyObservers(xrFrame);
                     // only run the render loop if a frame exists
-                    this.scene.getEngine()._renderLoop();
+                    engine._renderLoop();
                 }
             }
         };
 
         if (this._xrNavigator.xr.native) {
             this._rttProvider = this._xrNavigator.xr.getNativeRenderTargetProvider(this.session, (width: number, height: number) => {
-                return this.scene.getEngine().createRenderTargetTexture({ width: width, height: height }, false);
+                return engine.createRenderTargetTexture({ width: width, height: height }, false);
             });
         } else {
             // Create render target texture from xr's webgl render target
@@ -230,8 +238,8 @@ export class WebXRSessionManager implements IDisposable {
         }
 
         // Stop window's animation frame and trigger sessions animation frame
-        if (window.cancelAnimationFrame) { window.cancelAnimationFrame(this.scene.getEngine()._frameHandler); }
-        this.scene.getEngine()._renderLoop();
+        if (window.cancelAnimationFrame) { window.cancelAnimationFrame(engine._frameHandler); }
+        engine._renderLoop();
         return Promise.resolve();
     }
 
@@ -272,12 +280,15 @@ export class WebXRSessionManager implements IDisposable {
      * @param options optional options to provide when creating a new render target
      * @returns a WebXR render target to which the session can render
      */
-    public getWebXRRenderTarget(onStateChangedObservable?: Observable<WebXRState>, options?: WebXRManagedOutputCanvasOptions): WebXRRenderTarget {
+    public getWebXRRenderTarget(options?: WebXRManagedOutputCanvasOptions): WebXRRenderTarget {
+        const engine = this.scene.getEngine();
         if (this._xrNavigator.xr.native) {
-            return this._xrNavigator.xr.getWebXRRenderTarget(this.scene.getEngine());
+            return this._xrNavigator.xr.getWebXRRenderTarget(engine);
         }
         else {
-            return new WebXRManagedOutputCanvas(this.scene.getEngine(), this.scene.getEngine().getRenderingCanvas() as HTMLCanvasElement, onStateChangedObservable!, options);
+            options = options || {};
+            options.canvasElement = engine.getRenderingCanvas() || undefined;
+            return new WebXRManagedOutputCanvas(this, options);
         }
     }
 
@@ -288,7 +299,7 @@ export class WebXRSessionManager implements IDisposable {
      * @param scene scene the new render target should be created for
      * @param baseLayer the webgl layer to create the render target for
      */
-    public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: Scene, baseLayer: XRWebGLLayer) {
+    public static _CreateRenderTargetTextureFromSession(_session: XRSession, scene: Scene, baseLayer: XRWebGLLayer) {
         if (!baseLayer) {
             throw "no layer";
         }
@@ -309,14 +320,20 @@ export class WebXRSessionManager implements IDisposable {
      * Disposes of the session manager
      */
     public dispose() {
+        // disposing without leaving XR? Exit XR first
+        if (!this._sessionEnded) {
+            this.exitXRAsync();
+        }
         this.onXRFrameObservable.clear();
         this.onXRSessionEnded.clear();
+        this.onXRReferenceSpaceChanged.clear();
+        this.onXRSessionInit.clear();
     }
 
     /**
-     * Gets a promise returning true when fullfiled if the given session mode is supported
+     * Returns a promise that resolves with a boolean indicating if the provided session mode is supported by this browser
      * @param sessionMode defines the session to test
-     * @returns a promise
+     * @returns a promise with boolean as final value
      */
     public static IsSessionSupportedAsync(sessionMode: XRSessionMode): Promise<boolean> {
         if (!(navigator as any).xr) {

+ 1 - 1
src/Cameras/XR/webXRTypes.ts

@@ -42,5 +42,5 @@ export interface WebXRRenderTarget extends IDisposable {
      * @param xrSession xr session
      * @returns a promise that will resolve once the XR Layer has been created
      */
-    initializeXRLayerAsync(xrSession: XRSession) : Promise<void>;
+    initializeXRLayerAsync(xrSession: XRSession) : Promise<XRWebGLLayer>;
 }