فهرست منبع

Merge pull request #7540 from RaananW/api-addons

XR changes and bug-fixes
David Catuhe 5 سال پیش
والد
کامیت
c0e54898b8

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

@@ -205,6 +205,7 @@
 - Laser pointers can be excluded from lighting influence so that they are always visible in both WebXR and WebVR ([#7323](https://github.com/BabylonJS/Babylon.js/issues/7323)) ([RaananW](https://github.com/RaananW/))
 - Full support for the online motion controller repository ([#7323](https://github.com/BabylonJS/Babylon.js/issues/7323)) ([RaananW](https://github.com/RaananW/))
 - New feature - XR Controller physics impostor ([RaananW](https://github.com/RaananW/))
+- Teleportation between different ground levels in WebXR is enabled ([RaananW](https://github.com/RaananW/))
 
 ### Ray
 

+ 3 - 3
src/XR/features/WebXRControllerPhysics.ts

@@ -31,7 +31,7 @@ export class IWebXRControllerPhysicsOptions {
          */
         impostorType?: number;
         /**
-         * the size of the impostor
+         * the size of the impostor. Defaults to 10cm
          */
         impostorSize?: number | { width: number, height: number, depth: number };
         /**
@@ -184,7 +184,7 @@ export class WebXRControllerPhysics extends WebXRAbstractFeature {
             Logger.Warn("physics engine not enabled, skipped. Please add this controller manually.");
         }
         if (this._options.physicsProperties!.useControllerMesh) {
-            xrController.onMotionControllerProfileLoaded.addOnce((motionController) => {
+            xrController.onMotionControllerInitObservable.addOnce((motionController) => {
                 motionController.onModelLoadedObservable.addOnce(() => {
                     const impostor = new PhysicsImpostor(motionController.rootMesh!, PhysicsImpostor.MeshImpostor, {
                         mass: 0,
@@ -201,7 +201,7 @@ export class WebXRControllerPhysics extends WebXRAbstractFeature {
             });
         } else {
             const impostorType: number = this._options.physicsProperties!.impostorType || PhysicsImpostor.SphereImpostor;
-            const impostorSize: number | { width: number, height: number, depth: number } = this._options.physicsProperties!.impostorSize || 0.08;
+            const impostorSize: number | { width: number, height: number, depth: number } = this._options.physicsProperties!.impostorSize || 0.1;
             const impostorMesh = SphereBuilder.CreateSphere('impostor-mesh-' + xrController.uniqueId, {
                 diameterX: typeof impostorSize === 'number' ? impostorSize : impostorSize.width,
                 diameterY: typeof impostorSize === 'number' ? impostorSize : impostorSize.height,

+ 1 - 1
src/XR/features/WebXRControllerPointerSelection.ts

@@ -357,7 +357,7 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
     }
 
     private _attachTrackedPointerRayMode(xrController: WebXRController) {
-        xrController.onMotionControllerProfileLoaded.add((motionController) => {
+        xrController.onMotionControllerInitObservable.add((motionController) => {
             if (this._options.forceGazeMode) {
                 return this._attachGazeMode(xrController);
             }

+ 13 - 7
src/XR/features/WebXRControllerTeleportation.ts

@@ -267,18 +267,24 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
                 let hitPossible = false;
                 // first check if direct ray possible
                 controllerData.xrController.getWorldPointerRayToRef(this._tmpRay);
+                // pick grounds that are LOWER only. upper will use parabolic path
                 let pick = scene.pickWithRay(this._tmpRay, (o) => {
-                    return this._floorMeshes.indexOf(o) !== -1;
+                    const index = this._floorMeshes.indexOf(o);
+                    if (index === -1) { return false; }
+                    return (this._floorMeshes[index].absolutePosition.y < this._options.xrInput.xrCamera.position.y);
                 });
                 if (pick && pick.pickedPoint) {
                     hitPossible = true;
                     this.setTargetMeshPosition(pick.pickedPoint);
                     this.setTargetMeshVisibility(true);
-                    this.showParabolicPath(pick);
+                    this._showParabolicPath(pick);
                 } else {
                     if (this.parabolicRayEnabled) {
+                        // radius compensation according to pointer rotation around X
+                        const xRotation = controllerData.xrController.pointer.rotationQuaternion!.toEulerAngles().x;
+                        const compensation = (1 + ((Math.PI / 2) - Math.abs(xRotation)));
                         // check parabolic ray
-                        const radius = this.parabolicCheckRadius;
+                        const radius = this.parabolicCheckRadius * compensation;
                         this._tmpRay.origin.addToRef(this._tmpRay.direction.scale(radius * 2), this._tmpVector);
                         this._tmpVector.y = this._tmpRay.origin.y;
                         this._tmpRay.origin.addInPlace(this._tmpRay.direction.scale(radius));
@@ -292,7 +298,7 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
                             hitPossible = true;
                             this.setTargetMeshPosition(pick.pickedPoint);
                             this.setTargetMeshVisibility(true);
-                            this.showParabolicPath(pick);
+                            this._showParabolicPath(pick);
                         }
                     }
                 }
@@ -326,7 +332,7 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         };
         const controllerData = this._controllers[xrController.uniqueId];
         // motion controller support
-        xrController.onMotionControllerProfileLoaded.addOnce(() => {
+        xrController.onMotionControllerInitObservable.addOnce(() => {
             if (xrController.motionController) {
                 const movementController = xrController.motionController.getComponentOfType(WebXRControllerComponent.THUMBSTICK) || xrController.motionController.getComponentOfType(WebXRControllerComponent.TOUCHPAD);
                 if (!movementController || this._options.useMainComponentOnly) {
@@ -444,7 +450,7 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
         this._currentTeleportationControllerId = "";
         // do the movement forward here
         if (this._options.teleportationTargetMesh && this._options.teleportationTargetMesh.isVisible) {
-            const height = this._options.xrInput.xrCamera.position.y - this._options.teleportationTargetMesh.position.y;
+            const height = this._options.xrInput.xrCamera.realWorldHeight;
             this._options.xrInput.xrCamera.position.copyFrom(this._options.teleportationTargetMesh.position);
             this._options.xrInput.xrCamera.position.y += height;
             this._options.xrInput.xrCamera.rotationQuaternion.multiplyInPlace(Quaternion.FromEulerAngles(0, controllerData.teleportationState.currentRotation, 0));
@@ -578,7 +584,7 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
 
     private _quadraticBezierCurve: AbstractMesh;
 
-    private showParabolicPath(pickInfo: PickingInfo) {
+    private _showParabolicPath(pickInfo: PickingInfo) {
         if (!pickInfo.pickedPoint) { return; }
 
         const controllerData = this._controllers[this._currentTeleportationControllerId];

+ 25 - 0
src/XR/webXRCamera.ts

@@ -24,6 +24,21 @@ export class WebXRCamera extends FreeCamera {
     private _xrInvPositionCache: Vector3 = new Vector3();
     private _xrInvQuaternionCache = Quaternion.Identity();
 
+    private _realWorldHeight: number = 0;
+
+    /**
+     * Prevent the camera from calculating the real-world height
+     * If you are not using the user's height disable this for better performance
+     */
+    public disableRealWorldHeightCalculation: boolean = false;
+
+    /**
+     * Return the user's height, unrelated to the current ground.
+     */
+    public get realWorldHeight(): number {
+        return this._realWorldHeight;
+    }
+
     /**
      * Creates a new webXRCamera, this should only be set at the camera after it has been updated by the xrSessionManager
      * @param name the name of the camera
@@ -177,6 +192,16 @@ export class WebXRCamera extends FreeCamera {
 
     private _updateFromXRSession() {
 
+        // user height
+        if (!this.disableRealWorldHeightCalculation) {
+            const basePose = this._xrSessionManager.currentFrame && this._xrSessionManager.currentFrame.getViewerPose(this._xrSessionManager.baseReferenceSpace);
+            if (basePose && basePose.transform) {
+                this._realWorldHeight = basePose.transform.position.y;
+            }
+        } else {
+            this._realWorldHeight = 0;
+        }
+
         const pose = this._xrSessionManager.currentFrame && this._xrSessionManager.currentFrame.getViewerPose(this._xrSessionManager.referenceSpace);
 
         if (!pose) {

+ 19 - 7
src/XR/webXRController.ts

@@ -52,12 +52,21 @@ export class WebXRController {
     /**
      * Observers registered here will trigger when a motion controller profile was assigned to this xr controller
      */
-    public onMotionControllerProfileLoaded = new Observable<WebXRAbstractMotionController>();
+    public onMotionControllerInitObservable = new Observable<WebXRAbstractMotionController>();
 
     /**
-     * Event that fires when the controller is removed/disposed
+     * Will be triggered when the mesh associated with the motion controller is done loading.
+     * It is also possible that this will never trigger (!) if no mesh was loaded, or if the developer decides to load a different mesh
+     * A shortened version of controller -> motion controller -> on mesh loaded.
      */
-    public onDisposeObservable = new Observable<{}>();
+    public onMeshLoadedObservable = new Observable<AbstractMesh>();
+
+    /**
+     * Event that fires when the controller is removed/disposed.
+     * The object provided as event data is this controller, after associated assets were disposed.
+     * uniqueId is still available.
+     */
+    public onDisposeObservable = new Observable<WebXRController>();
 
     private _tmpQuaternion = new Quaternion();
     private _tmpVector = new Vector3();
@@ -85,15 +94,16 @@ export class WebXRController {
             this.grip.rotationQuaternion = new Quaternion();
         }
 
-        // for now only load motion controllers if gamepad available
+        // for now only load motion controllers if gamepad object available
         if (this.inputSource.gamepad) {
             WebXRMotionControllerManager.GetMotionControllerWithXRInput(inputSource, _scene, this._options.forceControllerProfile).then((motionController) => {
                 this.motionController = motionController;
-                this.onMotionControllerProfileLoaded.notifyObservers(motionController);
+                this.onMotionControllerInitObservable.notifyObservers(motionController);
                 // should the model be loaded?
                 if (!this._options.doNotLoadControllerMesh) {
                     this.motionController.loadModel().then((success) => {
                         if (success) {
+                            this.onMeshLoadedObservable.notifyObservers(this.motionController!.rootMesh!);
                             this.motionController!.rootMesh!.parent = this.grip || this.pointer;
                             this.motionController!.disableAnimation = !!this._options.disableMotionControllerAnimation;
                         }
@@ -172,8 +182,10 @@ export class WebXRController {
         if (this.motionController) {
             this.motionController.dispose();
         }
-        this.onMotionControllerProfileLoaded.clear();
         this.pointer.dispose();
-        this.onDisposeObservable.notifyObservers({});
+        this.onMotionControllerInitObservable.clear();
+        this.onMeshLoadedObservable.clear();
+        this.onDisposeObservable.notifyObservers(this);
+        this.onDisposeObservable.clear();
     }
 }

+ 7 - 2
src/XR/webXRDefaultExperience.ts

@@ -49,6 +49,11 @@ export class WebXRDefaultExperienceOptions {
      * Mainly used in AR
      */
     public ignoreNativeCameraTransformation?: boolean;
+
+    /**
+     * When loading teleportation and pointer select, use stable versions instead of latest.
+     */
+    public useStablePlugins?: boolean;
 }
 
 /**
@@ -99,13 +104,13 @@ export class WebXRDefaultExperience {
 
             // Add controller support
             result.input = new WebXRInput(xrHelper.sessionManager, xrHelper.camera, options.inputOptions);
-            result.pointerSelection = <WebXRControllerPointerSelection>result.baseExperience.featuresManager.enableFeature(WebXRControllerPointerSelection.Name, "latest", {
+            result.pointerSelection = <WebXRControllerPointerSelection>result.baseExperience.featuresManager.enableFeature(WebXRControllerPointerSelection.Name, options.useStablePlugins ? "stable" : "latest", {
                 xrInput: result.input
             });
 
             // Add default teleportation, including rotation
             if (!options.disableTeleportation) {
-                result.teleportation = <WebXRMotionControllerTeleportation>result.baseExperience.featuresManager.enableFeature(WebXRMotionControllerTeleportation.Name, "latest", {
+                result.teleportation = <WebXRMotionControllerTeleportation>result.baseExperience.featuresManager.enableFeature(WebXRMotionControllerTeleportation.Name, options.useStablePlugins ? "stable" : "latest", {
                     floorMeshes: options.floorMeshes,
                     xrInput: result.input
                 });