Bläddra i källkod

Merge pull request #7181 from RaananW/vr-to-xr

XR in VRExperienceHelper
David Catuhe 5 år sedan
förälder
incheckning
2b09271d83

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

@@ -156,6 +156,7 @@
 - UI Button has options to set different session mode and reference type ([RaananW](https://github.com/RaananW/))
 - Added option to change the teleportation duration in the VRExperienceHelper class ([https://github.com/LeoRodz](https://github.com/LeoRodz))
 - Added support to teleport the camera at constant speed in the VRExperienceHelper class ([https://github.com/LeoRodz](https://github.com/LeoRodz))
+- VRExperienceHelper has now an XR fallback to force XR usage (Beta) ([RaananW](https://github.com/RaananW/))
 
 ### Ray
 

+ 160 - 1
src/Cameras/VR/vrExperienceHelper.ts

@@ -36,6 +36,10 @@ import "../../Meshes/Builders/cylinderBuilder";
 import "../../Gamepads/gamepadSceneComponent";
 import "../../Animations/animatable";
 import { Axis } from '../../Maths/math.axis';
+import { WebXRSessionManager } from '../XR/webXRSessionManager';
+import { WebXRDefaultExperience } from '../XR/webXRDefaultExperience';
+import { WebXRState } from '../XR/webXRTypes';
+import { WebXRControllerTeleportation } from '../XR/webXRControllerTeleportation';
 
 /**
  * Options to modify the vr teleportation behavior.
@@ -87,6 +91,8 @@ export interface VRExperienceHelperOptions extends WebVROptions {
      * Distortion metrics for the fallback vrDeviceOrientationCamera (default: VRCameraMetrics.Default)
      */
     vrDeviceOrientationCameraMetrics?: VRCameraMetrics;
+
+    useXR?: boolean;
 }
 
 class VRExperienceHelperGazer implements IDisposable {
@@ -672,6 +678,19 @@ export class VRExperienceHelper {
      */
     public requestPointerLockOnFullScreen = true;
 
+    // XR
+
+    /**
+     * If asking to force XR, this will be populated with the default xr experience
+     */
+    public xr: WebXRDefaultExperience;
+
+    /**
+     * Was the XR test done already. If this is true AND this.xr exists, xr is initialized.
+     * If this is true and no this.xr, xr exists but is not supported, using WebVR.
+     */
+    public xrTestDone: boolean = false;
+
     /**
      * Instantiates a VRExperienceHelper.
      * Helps to quickly add VR support to an existing scene.
@@ -750,6 +769,98 @@ export class VRExperienceHelper {
             this._existingCamera = this._scene.activeCamera;
         }
 
+        if (this.webVROptions.useXR && (navigator as any).xr) {
+            // force-check XR session support
+            WebXRSessionManager.IsSessionSupported("immersive-vr").then((supported) => {
+                if (supported) {
+                    Logger.Log("Using WebXR. It is recommended to use the WebXRDefaultExperience directly");
+                    // it is possible to use XR, let's do it!
+                    scene.createDefaultXRExperienceAsync({
+                        floorMeshes: webVROptions.floorMeshes || []
+                    }).then((xr) => {
+                        this.xr = xr;
+                        // connect observables
+                        this.xrTestDone = true;
+
+                        this._cameraGazer = new VRExperienceHelperCameraGazer(() => { return this.xr.baseExperience.camera; }, scene);
+
+                        this.xr.baseExperience.onStateChangedObservable.add((state) => {
+                            // support for entering / exiting
+                            switch (state) {
+                                case WebXRState.ENTERING_XR:
+                                    this.onEnteringVRObservable.notifyObservers(this);
+                                    if (this._interactionsEnabled) {
+                                        this._scene.registerBeforeRender(this.beforeRender);
+                                    }
+                                    if (this._displayLaserPointer) {
+                                        [this._leftController, this._rightController].forEach((controller) => {
+                                            if (controller) {
+                                                controller._activatePointer();
+                                            }
+                                        });
+                                    }
+                                    break;
+                                case WebXRState.EXITING_XR:
+                                    this.onExitingVRObservable.notifyObservers(this);
+                                    if (this._interactionsEnabled) {
+                                        this._scene.unregisterBeforeRender(this.beforeRender);
+                                        this._cameraGazer._gazeTracker.isVisible = false;
+                                        if (this._leftController) {
+                                            this._leftController._gazeTracker.isVisible = false;
+                                        }
+                                        if (this._rightController) {
+                                            this._rightController._gazeTracker.isVisible = false;
+                                        }
+                                    }
+
+                                    // resize to update width and height when exiting vr exits fullscreen
+                                    this._scene.getEngine().resize();
+
+                                    [this._leftController, this._rightController].forEach((controller) => {
+                                        if (controller) {
+                                            controller._deactivatePointer();
+                                        }
+                                    });
+                                    break;
+                                case WebXRState.IN_XR:
+                                    this._hasEnteredVR = true;
+                                    break;
+                                case WebXRState.NOT_IN_XR:
+                                    this._hasEnteredVR = false;
+                                    break;
+                            }
+                        });
+
+                        this.xr.input.onControllerAddedObservable.add((controller) => {
+                            var webVRController = controller.gamepadController;
+                            if (webVRController) {
+                                var localController = new VRExperienceHelperControllerGazer(webVRController, this._scene, this._cameraGazer._gazeTracker);
+
+                                if (controller.inputSource.handedness === "right" || (this._leftController && this._leftController.webVRController != webVRController)) {
+                                    this._rightController = localController;
+                                } else {
+                                    this._leftController = localController;
+                                }
+
+                                this._tryEnableInteractionOnController(localController);
+                            }
+                        });
+                    });
+                } else {
+                    // XR not supported (thou exists), continue WebVR init
+                    this.completeVRInit(scene, webVROptions);
+                }
+            });
+        } else {
+            // no XR, continue init synchronous
+            this.completeVRInit(scene, webVROptions);
+        }
+
+    }
+
+    private completeVRInit(scene: Scene,
+        webVROptions: VRExperienceHelperOptions): void {
+        this.xrTestDone = true;
         // Create VR cameras
         if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
             if (webVROptions.useMultiview) {
@@ -945,7 +1056,7 @@ export class VRExperienceHelper {
      * Gets a value indicating if we are currently in VR mode.
      */
     public get isInVRMode(): boolean {
-        return this._webVRpresenting || this._fullscreenVRpresenting;
+        return (this.xr && this.webVROptions.useXR && this.xr.baseExperience.state === WebXRState.IN_XR) || (this._webVRpresenting || this._fullscreenVRpresenting);
     }
 
     private onVrDisplayPresentChange() {
@@ -1007,6 +1118,10 @@ export class VRExperienceHelper {
      * Otherwise, will use the fullscreen API.
      */
     public enterVR() {
+        if (this.xr) {
+            this.xr.baseExperience.enterXRAsync("immersive-vr", "local-floor", this.xr.renderTarget);
+            return;
+        }
         if (this.onEnteringVRObservable) {
             try {
                 this.onEnteringVRObservable.notifyObservers(this);
@@ -1099,6 +1214,10 @@ export class VRExperienceHelper {
      * Attempt to exit VR, or fullscreen.
      */
     public exitVR() {
+        if (this.xr) {
+            this.xr.baseExperience.exitXRAsync();
+            return;
+        }
         if (this._hasEnteredVR) {
             if (this.onExitingVRObservable) {
                 try {
@@ -1319,6 +1438,42 @@ export class VRExperienceHelper {
 
             this.enableInteractions();
 
+            if (this.webVROptions.useXR && (vrTeleportationOptions.floorMeshes || vrTeleportationOptions.floorMeshName)) {
+                const floorMeshes: AbstractMesh[] = vrTeleportationOptions.floorMeshes || [];
+                if (!floorMeshes.length) {
+                    const floorMesh = this._scene.getMeshByName(vrTeleportationOptions.floorMeshName!);
+                    if (floorMesh) {
+                        floorMeshes.push(floorMesh);
+                    }
+                }
+                if (this.xr) {
+                    this.xr.teleportation = new WebXRControllerTeleportation(this.xr.input, floorMeshes);
+                    return;
+                } else if (!this.xrTestDone) {
+                    const waitForXr = () => {
+                        if (this.xrTestDone) {
+                            this._scene.unregisterBeforeRender(waitForXr);
+                            if (this.xr) {
+                                this.xr.teleportation = new WebXRControllerTeleportation(this.xr.input, floorMeshes);
+                            } else {
+                                this.enableTeleportation(vrTeleportationOptions);
+                            }
+                        }
+                    };
+                    this._scene.registerBeforeRender(waitForXr);
+                    return;
+                }
+            }
+
+            if (this.xr && vrTeleportationOptions.floorMeshes) {
+                this.xr.teleportation = new WebXRControllerTeleportation(this.xr.input, vrTeleportationOptions.floorMeshes);
+                return;
+            } else {
+                if (this.webVROptions.useXR && !this.xrTestDone) {
+
+                }
+            }
+
             if (vrTeleportationOptions.floorMeshName) {
                 this._floorMeshName = vrTeleportationOptions.floorMeshName;
             }
@@ -2148,6 +2303,10 @@ export class VRExperienceHelper {
             this._teleportationTarget.dispose();
         }
 
+        if (this.xr) {
+            this.xr.dispose();
+        }
+
         this._floorMeshesCollection = [];
 
         document.removeEventListener("keydown", this._onKeyDown);

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

@@ -21,7 +21,7 @@ export class WebXRDefaultExperienceOptions {
     /**
      * Enable or disable default UI to enter XR
      */
-    public disableDefaultUI: boolean;
+    public disableDefaultUI?: boolean;
 
     /**
      * optional configuration for the output canvas

+ 19 - 15
src/Cameras/XR/webXRSessionManager.ts

@@ -208,21 +208,7 @@ export class WebXRSessionManager implements IDisposable {
      * @returns true if supported
      */
     public supportsSessionAsync(sessionMode: XRSessionMode) {
-        if (!(navigator as any).xr) {
-            return Promise.resolve(false);
-        }
-        // When the specs are final, remove supportsSession!
-        const functionToUse = (navigator as any).xr.isSessionSupported || (navigator as any).xr.supportsSession;
-        if (!functionToUse) {
-            return Promise.resolve(false);
-        } else {
-            return functionToUse.call((navigator as any).xr, sessionMode).then(() => {
-                return Promise.resolve(true);
-            }).catch((e: any) => {
-                Logger.Warn(e);
-                return Promise.resolve(false);
-            });
-        }
+        return WebXRSessionManager.IsSessionSupported(sessionMode);
     }
 
     /**
@@ -270,4 +256,22 @@ export class WebXRSessionManager implements IDisposable {
         this.onXRFrameObservable.clear();
         this.onXRSessionEnded.clear();
     }
+
+    public static IsSessionSupported(sessionMode: XRSessionMode): Promise<boolean> {
+        if (!(navigator as any).xr) {
+            return Promise.resolve(false);
+        }
+        // When the specs are final, remove supportsSession!
+        const functionToUse = (navigator as any).xr.isSessionSupported || (navigator as any).xr.supportsSession;
+        if (!functionToUse) {
+            return Promise.resolve(false);
+        } else {
+            return functionToUse.call((navigator as any).xr, sessionMode).then(() => {
+                return Promise.resolve(true);
+            }).catch((e: any) => {
+                Logger.Warn(e);
+                return Promise.resolve(false);
+            });
+        }
+    }
 }