Parcourir la source

the new anchor system

Raanan Weber il y a 5 ans
Parent
commit
c6ffa0072e
1 fichiers modifiés avec 155 ajouts et 90 suppressions
  1. 155 90
      src/XR/features/WebXRAnchorSystem.ts

+ 155 - 90
src/XR/features/WebXRAnchorSystem.ts

@@ -1,29 +1,26 @@
-import { WebXRFeatureName } from '../webXRFeaturesManager';
+import { WebXRFeatureName, WebXRFeaturesManager } from '../webXRFeaturesManager';
 import { WebXRSessionManager } from '../webXRSessionManager';
 import { Observable } from '../../Misc/observable';
-import { Matrix } from '../../Maths/math.vector';
+import { Matrix, Vector3, Quaternion } from '../../Maths/math.vector';
 import { TransformNode } from '../../Meshes/transformNode';
-import { WebXRPlaneDetector } from './WebXRPlaneDetector';
-import { WebXRHitTestLegacy } from './WebXRHitTestLegacy';
 import { WebXRAbstractFeature } from './WebXRAbstractFeature';
+import { IWebXRHitResult } from './WebXRHitTest';
+import { Tools } from '../../Misc/tools';
 
 /**
  * Configuration options of the anchor system
  */
 export interface IWebXRAnchorSystemOptions {
     /**
-     * Should a new anchor be added every time a select event is triggered
-     */
-    addAnchorOnSelect?: boolean;
-    /**
-     * should the anchor system use plane detection.
-     * If set to true, the plane-detection feature should be set using setPlaneDetector
-     */
-    usePlaneDetection?: boolean;
-    /**
      * a node that will be used to convert local to world coordinates
      */
     worldParentNode?: TransformNode;
+
+    /**
+     * If set to true a reference of the created anchors will be kept until the next session starts
+     * If not defined, anchors will be removed from the array when the feature is detached or the session ended.
+     */
+    doNotRemoveAnchorsOnSessionEnded?: boolean;
 }
 
 /**
@@ -42,45 +39,33 @@ export interface IWebXRAnchor {
      * The native anchor object
      */
     xrAnchor: XRAnchor;
+
+    /**
+     * if defined, this object will be constantly updated by the anchor's position and rotation
+     */
+    attachedNode?: TransformNode;
+}
+
+interface IWebXRFutureAnchor {
+    resolve: (xrAnchor: XRAnchor) => void;
+    reject: (msg?: string) => void;
+    xrTransformation: XRRigidTransform;
 }
 
 let anchorIdProvider = 0;
 
 /**
- * An implementation of the anchor system of WebXR.
- * Note that the current documented implementation is not available in any browser. Future implementations
- * will use the frame to create an anchor and not the session or a detected plane
+ * An implementation of the anchor system for WebXR.
  * For further information see https://github.com/immersive-web/anchors/
  */
 export class WebXRAnchorSystem extends WebXRAbstractFeature {
-    private _enabled: boolean = false;
-    private _hitTestModule: WebXRHitTestLegacy;
     private _lastFrameDetected: XRAnchorSet = new Set();
-    private _onSelect = (event: XRInputSourceEvent) => {
-        if (!this._options.addAnchorOnSelect) {
-            return;
-        }
-        const onResults = (results: XRHitResult[]) => {
-            if (results.length) {
-                const hitResult = results[0];
-                const transform = new XRRigidTransform(hitResult.hitMatrix);
-                // find the plane on which to add.
-                this.addAnchorAtRigidTransformation(transform);
-            }
-        };
 
-        // avoid the hit-test, if the hit-test module is defined
-        if (this._hitTestModule && !this._hitTestModule.options.testOnPointerDownOnly) {
-            onResults(this._hitTestModule.lastNativeXRHitResults);
-        }
-        WebXRHitTestLegacy.XRHitTestWithSelectEvent(event, this._xrSessionManager.referenceSpace).then(onResults);
+    private _trackedAnchors: Array<IWebXRAnchor> = [];
 
-        // API will soon change, will need to use the plane
-        this._planeDetector;
-    }
+    private _referenceSpaceForFrameAnchors: XRReferenceSpace;
 
-    private _planeDetector: WebXRPlaneDetector;
-    private _trackedAnchors: Array<IWebXRAnchor> = [];
+    private _futureAnchors: IWebXRFutureAnchor[] = [];
 
     /**
      * The module's name
@@ -108,6 +93,14 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
     public onAnchorUpdatedObservable: Observable<IWebXRAnchor> = new Observable();
 
     /**
+     * Set the reference space to use for anchor creation, when not using a hit test.
+     * Will default to the session's reference space if not defined
+     */
+    public set referenceSpaceForFrameAnchors(referenceSpace: XRReferenceSpace) {
+        this._referenceSpaceForFrameAnchors = referenceSpace;
+    }
+
+    /**
      * constructs a new anchor system
      * @param _xrSessionManager an instance of WebXRSessionManager
      * @param _options configuration object for this feature
@@ -116,32 +109,79 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
         super(_xrSessionManager);
     }
 
+    private _tmpVector = new Vector3();
+    private _tmpQuaternion = new Quaternion();
+
+    private _populateTmpTransformation(position: Vector3, rotationQuaternion: Quaternion) {
+        this._tmpVector.copyFrom(position);
+        this._tmpQuaternion.copyFrom(rotationQuaternion);
+        if (!this._xrSessionManager.scene.useRightHandedSystem) {
+            this._tmpVector.z *= -1;
+            this._tmpQuaternion.z *= -1;
+            this._tmpQuaternion.w *= -1;
+        }
+        return {
+            position: this._tmpVector,
+            rotationQuaternion: this._tmpQuaternion
+        };
+    }
+
     /**
-     * Add anchor at a specific XR point.
+     * Create a new anchor point using a hit test result at a specific point in the scene
+     * An anchor is tracked only after it is added to the trackerAnchors in xrFrame. The promise returned here does not yet guaranty that.
+     * Use onAnchorAddedObservable to get newly added anchors if you require tracking guaranty.
      *
-     * @param xrRigidTransformation xr-coordinates where a new anchor should be added
-     * @param anchorCreator the object o use to create an anchor with. either a session or a plane
-     * @returns a promise the fulfills when the anchor was created
+     * @param hitTestResult The hit test result to use for this anchor creation
+     * @param position an optional position offset for this anchor
+     * @param rotationQuaternion an optional rotation offset for this anchor
+     * @returns A promise that fulfills when the XR anchor was registered in the system (but not necessarily added to the tracked anchors)
      */
-    public addAnchorAtRigidTransformation(xrRigidTransformation: XRRigidTransform, anchorCreator?: XRAnchorCreator): Promise<XRAnchor> {
-        const creator = anchorCreator || this._xrSessionManager.session;
-        return creator.createAnchor(xrRigidTransformation, this._xrSessionManager.referenceSpace);
+    public async addAnchorPointUsingHitTestResultAsync(hitTestResult: IWebXRHitResult, position: Vector3 = new Vector3(), rotationQuaternion: Quaternion = new Quaternion()): Promise<XRAnchor> {
+        // convert to XR space (right handed) if needed
+        this._populateTmpTransformation(position, rotationQuaternion);
+        // the matrix that we'll use
+        const m = new XRRigidTransform({...this._tmpVector}, {...this._tmpQuaternion});
+        if (!hitTestResult.xrHitResult.createAnchor) {
+            throw new Error('Anchors not enabled in this browsed. Add "anchors" to optional features');
+        } else {
+            try {
+                return hitTestResult.xrHitResult.createAnchor(m);
+            }
+            catch (error) {
+                throw new Error(error);
+            }
+        }
     }
 
     /**
-     * attach this feature
-     * Will usually be called by the features manager
+     * Add a new anchor at a specific position and rotation
+     * This function will add a new anchor per default in the next available frame. Unless forced, the createAnchor function
+     * will be called in the next xrFrame loop to make sure that the anchor can be created correctly.
+     * An anchor is tracked only after it is added to the trackerAnchors in xrFrame. The promise returned here does not yet guaranty that.
+     * Use onAnchorAddedObservable to get newly added anchors if you require tracking guaranty.
      *
-     * @returns true if successful.
+     * @param position the position in which to add an anchor
+     * @param rotationQuaternion an optional rotation for the anchor transformation
+     * @param forceCreateInCurrentFrame force the creation of this anchor in the current frame. Must be called inside xrFrame loop!
+     * @returns A promise that fulfills when the XR anchor was registered in the system (but not necessarily added to the tracked anchors)
      */
-    public attach(): boolean {
-        if (!super.attach()) {
-            return false;
-        }
-        if (this._options.addAnchorOnSelect) {
-            this._xrSessionManager.session.addEventListener('select', this._onSelect, false);
+    public addAnchorAtPositionAndRotationAsync(position: Vector3, rotationQuaternion: Quaternion = new Quaternion(), forceCreateInCurrentFrame = false): Promise<XRAnchor> {
+        // convert to XR space (right handed) if needed
+        this._populateTmpTransformation(position, rotationQuaternion);
+        // the matrix that we'll use
+        const xrTransformation = new XRRigidTransform({...this._tmpVector}, {...this._tmpQuaternion});
+        if (forceCreateInCurrentFrame && this.attached && this._xrSessionManager.currentFrame) {
+            return this._createAnchorAtTransformation(xrTransformation, this._xrSessionManager.currentFrame);
+        } else {
+            // add the transformation to the future anchors list
+            return new Promise<XRAnchor>((resolve, reject) => {
+                this._futureAnchors.push({
+                    xrTransformation,
+                    resolve,
+                    reject
+                });
+            });
         }
-        return true;
     }
 
     /**
@@ -155,7 +195,14 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
             return false;
         }
 
-        this._xrSessionManager.session.removeEventListener('select', this._onSelect);
+        if (!this._options.doNotRemoveAnchorsOnSessionEnded) {
+            while (this._trackedAnchors.length) {
+                const toRemove = this._trackedAnchors.pop();
+                if (toRemove) {
+                    this.onAnchorRemovedObservable.notifyObservers(toRemove);
+                }
+            }
+        }
 
         return true;
     }
@@ -170,33 +217,20 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
         this.onAnchorUpdatedObservable.clear();
     }
 
-    /**
-     * If set, it will improve performance by using the current hit-test results instead of executing a new hit-test
-     * @param hitTestModule the hit-test module to use.
-     */
-    public setHitTestModule(hitTestModule: WebXRHitTestLegacy) {
-        this._hitTestModule = hitTestModule;
-    }
-
-    /**
-     * set the plane detector to use in order to create anchors from frames
-     * @param planeDetector the plane-detector module to use
-     * @param enable enable plane-anchors. default is true
-     */
-    public setPlaneDetector(planeDetector: WebXRPlaneDetector, enable: boolean = true) {
-        this._planeDetector = planeDetector;
-        this._options.usePlaneDetection = enable;
-    }
-
     protected _onXRFrame(frame: XRFrame) {
-        if (!this.attached || !this._enabled || !frame) { return; }
+        if (!this.attached || !frame) { return; }
 
         const trackedAnchors = frame.trackedAnchors;
-        if (trackedAnchors && trackedAnchors.size) {
-            this._trackedAnchors.filter((anchor) => !trackedAnchors.has(anchor.xrAnchor)).map((anchor) => {
+        if (trackedAnchors) {
+            const toRemove = this._trackedAnchors.filter((anchor) => !trackedAnchors.has(anchor.xrAnchor)).map((anchor) => {
                 const index = this._trackedAnchors.indexOf(anchor);
-                this._trackedAnchors.splice(index, 1);
+                return index;
+            });
+            let idxTracker = 0;
+            toRemove.forEach((index) => {
+                const anchor = this._trackedAnchors.splice(index - idxTracker, 1)[0];
                 this.onAnchorRemovedObservable.notifyObservers(anchor);
+                idxTracker--;
             });
             // now check for new ones
             trackedAnchors.forEach((xrAnchor) => {
@@ -205,21 +239,39 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
                         id: anchorIdProvider++,
                         xrAnchor: xrAnchor
                     };
-                    const plane = this._updateAnchorWithXRFrame(xrAnchor, newAnchor, frame);
-                    this._trackedAnchors.push(plane);
-                    this.onAnchorAddedObservable.notifyObservers(plane);
+                    const anchor = this._updateAnchorWithXRFrame(xrAnchor, newAnchor, frame);
+                    this._trackedAnchors.push(anchor);
+                    this.onAnchorAddedObservable.notifyObservers(anchor);
                 } else {
-                    // updated?
-                    if (xrAnchor.lastChangedTime === this._xrSessionManager.currentTimestamp) {
-                        let index = this._findIndexInAnchorArray(xrAnchor);
-                        const anchor = this._trackedAnchors[index];
+                    let index = this._findIndexInAnchorArray(xrAnchor);
+                    const anchor = this._trackedAnchors[index];
+                    try {
+                        // anchors update every frame
                         this._updateAnchorWithXRFrame(xrAnchor, anchor, frame);
+                        if (anchor.attachedNode) {
+                            anchor.attachedNode.rotationQuaternion = anchor.attachedNode.rotationQuaternion || new Quaternion();
+                            anchor.transformationMatrix.decompose(anchor.attachedNode.scaling, anchor.attachedNode.rotationQuaternion, anchor.attachedNode.position);
+                        }
                         this.onAnchorUpdatedObservable.notifyObservers(anchor);
+                    } catch (e) {
+                        Tools.Warn(`Anchor could not be updated`);
                     }
                 }
             });
             this._lastFrameDetected = trackedAnchors;
         }
+
+        // process future anchors
+        while (this._futureAnchors.length) {
+            const futureAnchor = this._futureAnchors.pop();
+            if (!futureAnchor) {
+                return;
+            }
+            if (!frame.createAnchor) {
+                futureAnchor.reject('Anchors not enabled in this browser');
+            }
+            this._createAnchorAtTransformation(futureAnchor.xrTransformation, frame).then(futureAnchor.resolve, futureAnchor.reject);
+        }
     }
 
     /**
@@ -254,9 +306,22 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature {
 
         return <IWebXRAnchor>anchor;
     }
+
+    private async _createAnchorAtTransformation(xrTransformation: XRRigidTransform, xrFrame: XRFrame) {
+        if (xrFrame.createAnchor) {
+            try {
+                return xrFrame.createAnchor(xrTransformation, this._referenceSpaceForFrameAnchors ?? this._xrSessionManager.referenceSpace);
+            }
+            catch (error) {
+                throw new Error(error);
+            }
+        } else {
+            throw new Error('Anchors are not enabled in your browser');
+        }
+    }
 }
 
 // register the plugin
-// WebXRFeaturesManager.AddWebXRFeature(WebXRAnchorSystem.Name, (xrSessionManager, options) => {
-//     return () => new WebXRAnchorSystem(xrSessionManager, options);
-// }, WebXRAnchorSystem.Version);
+WebXRFeaturesManager.AddWebXRFeature(WebXRAnchorSystem.Name, (xrSessionManager, options) => {
+    return () => new WebXRAnchorSystem(xrSessionManager, options);
+}, WebXRAnchorSystem.Version);