فهرست منبع

[XR] Features manager optional/required features and compatibility check (#8592)

* Enhancing the feature manager compatibility check

* extend the configuration object

* adjusted to the new architecture

* what's new

* nu and docs
Raanan Weber 5 سال پیش
والد
کامیت
224af4f9c1

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

@@ -156,6 +156,7 @@
 - WebXR anchors feature implemented ([#7917](https://github.com/BabylonJS/Babylon.js/issues/7917)) ([RaananW](https://github.com/RaananW))
 - Canvas is being resized when entering XR ([RaananW](https://github.com/RaananW))
 - All camera view matrices are now calculated by Babylon to support left and right handed systems ([RaananW](https://github.com/RaananW))
+- WebXR Features Manager now has the ability to check if a feature can be enabled, and set native features optional or required ([RaananW](https://github.com/RaananW))
 
 ### Collisions
 

+ 15 - 0
src/XR/features/WebXRAbstractFeature.ts

@@ -21,6 +21,11 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
     public disableAutoAttach: boolean = false;
 
     /**
+     * The name of the native xr feature name (like anchor, hit-test, or hand-tracking)
+     */
+    public xrNativeFeatureName: string = '';
+
+    /**
      * Construct a new (abstract) WebXR feature
      * @param _xrSessionManager the xr session manager for this feature
      */
@@ -81,6 +86,16 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
     }
 
     /**
+     * This function will be executed during before enabling the feature and can be used to not-allow enabling it.
+     * Note that at this point the session has NOT started, so this is purely checking if the browser supports it
+     *
+     * @returns whether or not the feature is compatible in this environment
+     */
+    public isCompatible(): boolean {
+        return true;
+    }
+
+    /**
      * This is used to register callbacks that will automatically be removed when detach is called.
      * @param observable the observable to which the observer will be attached
      * @param callback the callback to register

+ 5 - 2
src/XR/features/WebXRAnchorSystem.ts

@@ -119,6 +119,7 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
      */
     constructor(_xrSessionManager: WebXRSessionManager, private _options: IWebXRAnchorSystemOptions = {}) {
         super(_xrSessionManager);
+        this.xrNativeFeatureName = "anchors";
     }
 
     private _tmpVector = new Vector3();
@@ -154,7 +155,8 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
         // the matrix that we'll use
         const m = new XRRigidTransform({ x: this._tmpVector.x, y: this._tmpVector.y, z: this._tmpVector.z }, { x: this._tmpQuaternion.x, y: this._tmpQuaternion.y, z: this._tmpQuaternion.z, w: this._tmpQuaternion.w });
         if (!hitTestResult.xrHitResult.createAnchor) {
-            throw new Error('Anchors not enabled in this browsed. Add "anchors" to optional features');
+            this.detach();
+            throw new Error('Anchors not enabled in this environment/browser');
         } else {
             try {
                 return hitTestResult.xrHitResult.createAnchor(m);
@@ -322,7 +324,7 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
         return <IWebXRAnchor>anchor;
     }
 
-    private async _createAnchorAtTransformation(xrTransformation: XRRigidTransform, xrFrame: XRFrame) {
+    private async _createAnchorAtTransformation(xrTransformation: XRRigidTransform, xrFrame: XRFrame): Promise<XRAnchor> {
         if (xrFrame.createAnchor) {
             try {
                 return xrFrame.createAnchor(xrTransformation, this._referenceSpaceForFrameAnchors ?? this._xrSessionManager.referenceSpace);
@@ -330,6 +332,7 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
                 throw new Error(error);
             }
         } else {
+            this.detach();
             throw new Error("Anchors are not enabled in your browser");
         }
     }

+ 7 - 1
src/XR/features/WebXRHitTest.ts

@@ -135,7 +135,8 @@ export class WebXRHitTest extends WebXRAbstractFeature implements IWebXRHitTestF
         public readonly options: IWebXRHitTestOptions = {}
     ) {
         super(_xrSessionManager);
-        Tools.Warn("Hit test is an experimental and unstable feature. make sure you enable optionalFeatures when creating the XR session");
+        this.xrNativeFeatureName = "hit-test";
+        Tools.Warn("Hit test is an experimental and unstable feature.");
     }
 
     /**
@@ -149,6 +150,11 @@ export class WebXRHitTest extends WebXRAbstractFeature implements IWebXRHitTestF
             return false;
         }
 
+        // Feature enabled, but not available
+        if (!this._xrSessionManager.session.requestHitTestSource) {
+            return false;
+        }
+
         if (!this.options.disablePermanentHitTest) {
             if (this._xrSessionManager.referenceSpace) {
                 this.initHitTestSource(this._xrSessionManager.referenceSpace);

+ 3 - 0
src/XR/features/WebXRHitTestLegacy.ts

@@ -4,6 +4,7 @@ import { Observable } from "../../Misc/observable";
 import { Vector3, Matrix } from "../../Maths/math.vector";
 import { TransformNode } from "../../Meshes/transformNode";
 import { WebXRAbstractFeature } from "./WebXRAbstractFeature";
+import { Tools } from "../../Misc/tools";
 
 // the plugin is registered at the end of the file
 
@@ -90,6 +91,8 @@ export class WebXRHitTestLegacy extends WebXRAbstractFeature implements IWebXRHi
         public readonly options: IWebXRLegacyHitTestOptions = {}
     ) {
         super(_xrSessionManager);
+        this.xrNativeFeatureName = "hit-test";
+        Tools.Warn("A newer version of this plugin is available");
     }
 
     /**

+ 1 - 0
src/XR/webXRExperienceHelper.ts

@@ -106,6 +106,7 @@ export class WebXRExperienceHelper implements IDisposable {
             sessionCreationOptions.optionalFeatures = sessionCreationOptions.optionalFeatures || [];
             sessionCreationOptions.optionalFeatures.push(referenceSpaceType);
         }
+        this.featuresManager.extendXRSessionInitObject(sessionCreationOptions);
         // we currently recommend "unbounded" space in AR (#7959)
         if (sessionMode === "immersive-ar" && referenceSpaceType !== "unbounded") {
             Logger.Warn("We recommend using 'unbounded' reference space type when using 'immersive-ar' session mode");

+ 72 - 15
src/XR/webXRFeaturesManager.ts

@@ -1,5 +1,6 @@
 import { WebXRSessionManager } from "./webXRSessionManager";
 import { IDisposable } from "../scene";
+import { Tools } from "../Misc/tools";
 
 /**
  * Defining the interface required for a (webxr) feature
@@ -29,6 +30,19 @@ export interface IWebXRFeature extends IDisposable {
      * @returns true if successful.
      */
     detach(): boolean;
+
+    /**
+     * This function will be executed during before enabling the feature and can be used to not-allow enabling it.
+     * Note that at this point the session has NOT started, so this is purely checking if the browser supports it
+     *
+     * @returns whether or not the feature is compatible in this environment
+     */
+    isCompatible(): boolean;
+
+    /**
+     * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
+     */
+    xrNativeFeatureName?: string;
 }
 
 /**
@@ -90,6 +104,7 @@ export class WebXRFeaturesManager implements IDisposable {
             featureImplementation: IWebXRFeature;
             version: number;
             enabled: boolean;
+            required: boolean;
         };
     } = {};
 
@@ -255,9 +270,10 @@ export class WebXRFeaturesManager implements IDisposable {
      * @param version optional version to load. if not provided the latest version will be enabled
      * @param moduleOptions options provided to the module. Ses the module documentation / constructor
      * @param attachIfPossible if set to true (default) the feature will be automatically attached, if it is currently possible
+     * @param required is this feature required to the app. If set to true the session init will fail if the feature is not available.
      * @returns a new constructed feature or throws an error if feature not found.
      */
-    public enableFeature(featureName: string | { Name: string }, version: number | string = "latest", moduleOptions: any = {}, attachIfPossible: boolean = true): IWebXRFeature {
+    public enableFeature(featureName: string | { Name: string }, version: number | string = "latest", moduleOptions: any = {}, attachIfPossible: boolean = true, required: boolean = true): IWebXRFeature {
         const name = typeof featureName === "string" ? featureName : featureName.Name;
         let versionToLoad = 0;
         if (typeof version === "string") {
@@ -291,24 +307,35 @@ export class WebXRFeaturesManager implements IDisposable {
             this.disableFeature(name);
         }
 
-        this._features[name] = {
-            featureImplementation: constructFunction(),
-            enabled: true,
-            version: versionToLoad,
-        };
+        const constructed = constructFunction();
+        if (constructed.isCompatible()) {
+            this._features[name] = {
+                featureImplementation: constructed,
+                enabled: true,
+                version: versionToLoad,
+                required,
+            };
 
-        if (attachIfPossible) {
-            // if session started already, request and enable
-            if (this._xrSessionManager.session && !feature.featureImplementation.attached) {
-                // enable feature
-                this.attachFeature(name);
+            if (attachIfPossible) {
+                // if session started already, request and enable
+                if (this._xrSessionManager.session && !feature.featureImplementation.attached) {
+                    // enable feature
+                    this.attachFeature(name);
+                }
+            } else {
+                // disable auto-attach when session starts
+                this._features[name].featureImplementation.disableAutoAttach = true;
             }
+
+            return this._features[name].featureImplementation;
         } else {
-            // disable auto-attach when session starts
-            this._features[name].featureImplementation.disableAutoAttach = true;
+            if (required) {
+                throw new Error("required feature not compatible");
+            } else {
+                Tools.Warn(`Feature ${name} not compatible with the current environment/browser and was not enabled.`);
+                return constructed;
+            }
         }
-
-        return this._features[name].featureImplementation;
     }
 
     /**
@@ -327,4 +354,34 @@ export class WebXRFeaturesManager implements IDisposable {
     public getEnabledFeatures() {
         return Object.keys(this._features);
     }
+
+    /**
+     * This function will exten the session creation configuration object with enabled features.
+     * If, for example, the anchors feature is enabled, it will be automatically added to the optional or required features list,
+     * according to the defined "required" variable, provided during enableFeature call
+     * @param xrSessionInit the xr Session init object to extend
+     *
+     * @returns an extended XRSessionInit object
+     */
+    public extendXRSessionInitObject(xrSessionInit: XRSessionInit): XRSessionInit {
+        const enabledFeatures = this.getEnabledFeatures();
+        enabledFeatures.forEach((featureName) => {
+            const feature = this._features[featureName];
+            const nativeName = feature.featureImplementation.xrNativeFeatureName;
+            if (nativeName) {
+                if (feature.required) {
+                    xrSessionInit.requiredFeatures = xrSessionInit.requiredFeatures || [];
+                    if (xrSessionInit.requiredFeatures.indexOf(nativeName) === -1) {
+                        xrSessionInit.requiredFeatures.push(nativeName);
+                    }
+                } else {
+                    xrSessionInit.optionalFeatures = xrSessionInit.optionalFeatures || [];
+                    if (xrSessionInit.optionalFeatures.indexOf(nativeName) === -1) {
+                        xrSessionInit.optionalFeatures.push(nativeName);
+                    }
+                }
+            }
+        });
+        return xrSessionInit;
+    }
 }