Quellcode durchsuchen

experience helper now has an xr fallback

Raanan Weber vor 5 Jahren
Ursprung
Commit
31d00c9d08
1 geänderte Dateien mit 163 neuen und 4 gelöschten Zeilen
  1. 163 4
      src/Cameras/VR/vrExperienceHelper.ts

+ 163 - 4
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 {
@@ -425,7 +431,7 @@ export class VRExperienceHelper {
      * Observable raised when a new mesh is selected based on meshSelectionPredicate.
      * This observable will provide the mesh and the controller used to select the mesh
      */
-    public onMeshSelectedWithController = new Observable<{mesh: AbstractMesh, controller: WebVRController}>();
+    public onMeshSelectedWithController = new Observable<{ mesh: AbstractMesh, controller: WebVRController }>();
 
     /**
      * Observable raised when a new mesh is picked based on meshSelectionPredicate
@@ -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) {
@@ -944,7 +1055,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() {
@@ -1006,6 +1117,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);
@@ -1098,6 +1213,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 {
@@ -1318,6 +1437,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;
             }
@@ -2039,7 +2194,7 @@ export class VRExperienceHelper {
                         this.onNewMeshSelected.notifyObservers(hit.pickedMesh);
                         let gazerAsControllerGazer = gazer as VRExperienceHelperControllerGazer;
                         if (gazerAsControllerGazer.webVRController) {
-                            this.onMeshSelectedWithController.notifyObservers({mesh: hit.pickedMesh, controller: gazerAsControllerGazer.webVRController});
+                            this.onMeshSelectedWithController.notifyObservers({ mesh: hit.pickedMesh, controller: gazerAsControllerGazer.webVRController });
                         }
                     }
                     catch (err) {
@@ -2125,7 +2280,7 @@ export class VRExperienceHelper {
         if (this._vrDeviceOrientationCamera) {
             this._vrDeviceOrientationCamera.dispose();
         }
-        if (!this._useCustomVRButton  && this._btnVR && this._btnVR.parentNode) {
+        if (!this._useCustomVRButton && this._btnVR && this._btnVR.parentNode) {
             document.body.removeChild(this._btnVR);
         }
 
@@ -2147,6 +2302,10 @@ export class VRExperienceHelper {
             this._teleportationTarget.dispose();
         }
 
+        if (this.xr) {
+            this.xr.dispose();
+        }
+
         this._floorMeshesCollection = [];
 
         document.removeEventListener("keydown", this._onKeyDown);