Jelajahi Sumber

plane detector feature

Raanan Weber 5 tahun lalu
induk
melakukan
b6205f93b0
1 mengubah file dengan 172 tambahan dan 0 penghapusan
  1. 172 0
      src/Cameras/XR/features/WebXRPlaneDetector.ts

+ 172 - 0
src/Cameras/XR/features/WebXRPlaneDetector.ts

@@ -0,0 +1,172 @@
+import { WebXRFeaturesManager, WebXRFeature } from '../webXRFeaturesManager';
+import { TransformNode } from '../../../Meshes/transformNode';
+import { WebXRSessionManager } from '../webXRSessionManager';
+import { Observable, Observer } from '../../../Misc/observable';
+import { Vector3, Matrix } from '../../../Maths/math.vector';
+import { Nullable } from '../../../types';
+
+const Name = "xr-plane-detector";
+//register the plugin
+WebXRFeaturesManager.AddWebXRFeature(Name, (xrSessionManager, options) => {
+    return () => new WebXRPlaneDetector(xrSessionManager, options);
+});
+
+export interface WebXRPlaneDetectorOptions {
+    worldParentNode?: TransformNode;
+    // coordinatesSpace?: Space;
+}
+
+export interface WebXRPlane {
+    id: number;
+    xrPlane: XRPlane;
+    polygonDefinition: Array<Vector3>;
+    transformationMatrix: Matrix;
+}
+
+let planeIdProvider = 0;
+
+export class WebXRPlaneDetector implements WebXRFeature {
+
+    public static Name = Name;
+
+    public onPlaneAddedObservable: Observable<WebXRPlane> = new Observable();
+    public onPlaneRemovedObservable: Observable<WebXRPlane> = new Observable();
+    public onPlaneUpdatedObservable: Observable<WebXRPlane> = new Observable();
+
+    private _enabled: boolean = false;
+    private _attached: boolean = false;
+    private _detectedPlanes: Array<WebXRPlane> = [];
+    private _lastFrameDetected: XRPlaneSet = new Set();
+    private _observerTracked: Nullable<Observer<XRFrame>>;
+
+    constructor(private xrSessionManager: WebXRSessionManager, private options: WebXRPlaneDetectorOptions = {}) {
+        if (this.xrSessionManager.session) {
+            this.xrSessionManager.session.updateWorldTrackingState({ planeDetectionState: { enabled: true } });
+            this._enabled = true;
+        } else {
+            this.xrSessionManager.onXRSessionInit.addOnce(() => {
+                this.xrSessionManager.session.updateWorldTrackingState({ planeDetectionState: { enabled: true } });
+                this._enabled = true;
+            });
+        }
+    }
+
+    attach(): boolean {
+
+        this._observerTracked = this.xrSessionManager.onXRFrameObservable.add(() => {
+            const frame = this.xrSessionManager.currentFrame;
+            if (!this._attached || !this._enabled || !frame) { return; }
+            // const timestamp = this.xrSessionManager.currentTimestamp;
+
+            const detectedPlanes = frame.worldInformation.detectedPlanes;
+            if (detectedPlanes && detectedPlanes.size) {
+                this._detectedPlanes.filter((plane) => !detectedPlanes.has(plane.xrPlane)).map((plane) => {
+                    const index = this._detectedPlanes.indexOf(plane);
+                    this._detectedPlanes.splice(index, 1);
+                    this.onPlaneRemovedObservable.notifyObservers(plane);
+                });
+                // now check for new ones
+                detectedPlanes.forEach((xrPlane) => {
+                    if (!this._lastFrameDetected.has(xrPlane)) {
+                        const newPlane: Partial<WebXRPlane> = {
+                            id: planeIdProvider++,
+                            xrPlane: xrPlane,
+                            polygonDefinition: []
+                        };
+                        const plane = this.updatePlaneWithXRPlane(xrPlane, newPlane, frame);
+                        this._detectedPlanes.push(plane);
+                        this.onPlaneAddedObservable.notifyObservers(plane);
+                    } else {
+                        // updated?
+                        if (xrPlane.lastChangedTime === this.xrSessionManager.currentTimestamp) {
+                            let index = this.findIndexInPlaneArray(xrPlane);
+                            const plane = this._detectedPlanes[index];
+                            this.updatePlaneWithXRPlane(xrPlane, plane, frame);
+                            this.onPlaneUpdatedObservable.notifyObservers(plane);
+                        }
+                    }
+                });
+                this._lastFrameDetected = detectedPlanes;
+            }
+        });
+
+        this._attached = true;
+        return true;
+    }
+    detach(): boolean {
+        this._attached = false;
+
+        if (this._observerTracked) {
+            this.xrSessionManager.onXRFrameObservable.remove(this._observerTracked);
+        }
+
+        return true;
+    }
+
+    dispose(): void {
+        this.detach();
+        this.onPlaneAddedObservable.clear();
+        this.onPlaneRemovedObservable.clear();
+        this.onPlaneUpdatedObservable.clear();
+    }
+
+    private updatePlaneWithXRPlane(xrPlane: XRPlane, plane: Partial<WebXRPlane>, xrFrame: XRFrame): WebXRPlane {
+        plane.polygonDefinition = xrPlane.polygon.map((xrPoint) => {
+            const rightHandedSystem = this.xrSessionManager.scene.useRightHandedSystem ? 1 : -1;
+            return new Vector3(xrPoint.x, xrPoint.y, xrPoint.z * rightHandedSystem);
+        });
+        // matrix
+        const pose = xrFrame.getPose(xrPlane.planeSpace, this.xrSessionManager.referenceSpace);
+        if (pose) {
+            const mat = plane.transformationMatrix || new Matrix();
+            Matrix.FromArrayToRef(pose.transform.matrix, 0, mat);
+            if (!this.xrSessionManager.scene.useRightHandedSystem) {
+                mat.toggleModelMatrixHandInPlace();
+            }
+            plane.transformationMatrix = mat;
+            //if (this.options.coordinatesSpace === Space.WORLD) {
+            if (!this.options.worldParentNode) {
+                // Logger.Warn("Please provide a world parent node to apply world transformation");
+            } else {
+                mat.multiplyToRef(this.options.worldParentNode.getWorldMatrix(), mat);
+            }
+            //}
+        }
+        /*if (!this.options.dontCreatePolygon) {
+            let mat;
+            if (plane.mesh) {
+                mat = plane.mesh.material;
+                plane.mesh.dispose(false, false);
+            } //else {
+            plane.polygonDefinition.push(plane.polygonDefinition[0]);
+            plane.mesh = TubeBuilder.CreateTube("tube", { path: plane.polygonDefinition, radius: 0.01, sideOrientation: Mesh.FRONTSIDE, updatable: true }, this.xrSessionManager.scene);
+            //}
+            if (!mat) {
+                mat = new StandardMaterial("mat", this.xrSessionManager.scene);
+                mat.alpha = 0.5;
+                (<StandardMaterial>mat).diffuseColor = Color3.Random();
+            }
+            plane.mesh.material = mat;
+
+            plane.mesh.rotationQuaternion = new Quaternion();
+            plane.transformMatrix!.decompose(plane.mesh.scaling, plane.mesh.rotationQuaternion, plane.mesh.position);
+            if (this.options.worldParentNode) {
+                plane.mesh.parent = this.options.worldParentNode;
+            }
+        }*/
+        return <WebXRPlane>plane;
+    }
+
+    /**
+     * avoiding using Array.find for global support.
+     * @param xrPlane the plane to find in the array
+     */
+    private findIndexInPlaneArray(xrPlane: XRPlane) {
+        for (let i = 0; i < this._detectedPlanes.length; ++i) {
+            if (this._detectedPlanes[i].xrPlane === xrPlane) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}