UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

201 lines 8.31 kB
import { WebXRFeaturesManager, WebXRFeatureName } from "../webXRFeaturesManager.js"; import { Observable } from "../../Misc/observable.js"; import { Vector3, Matrix } from "../../Maths/math.vector.js"; import { WebXRAbstractFeature } from "./WebXRAbstractFeature.js"; let PlaneIdProvider = 0; /** * The plane detector is used to detect planes in the real world when in AR * For more information see https://github.com/immersive-web/real-world-geometry/ */ export class WebXRPlaneDetector extends WebXRAbstractFeature { /** * construct a new Plane Detector * @param _xrSessionManager an instance of xr Session manager * @param _options configuration to use when constructing this feature */ constructor(_xrSessionManager, _options = {}) { super(_xrSessionManager); this._options = _options; this._detectedPlanes = []; this._enabled = false; this._lastFrameDetected = new Set(); /** * Observers registered here will be executed when a new plane was added to the session */ this.onPlaneAddedObservable = new Observable(); /** * Observers registered here will be executed when a plane is no longer detected in the session */ this.onPlaneRemovedObservable = new Observable(); /** * Observers registered here will be executed when an existing plane updates (for example - expanded) * This can execute N times every frame */ this.onPlaneUpdatedObservable = new Observable(); this.xrNativeFeatureName = "plane-detection"; if (this._xrSessionManager.session) { this._init(); } else { this._xrSessionManager.onXRSessionInit.addOnce(() => { this._init(); }); } } /** * detach this feature. * Will usually be called by the features manager * * @returns true if successful. */ detach() { if (!super.detach()) { return false; } if (!this._options.doNotRemovePlanesOnSessionEnded) { while (this._detectedPlanes.length) { const toRemove = this._detectedPlanes.pop(); if (toRemove) { this.onPlaneRemovedObservable.notifyObservers(toRemove); } } } return true; } /** * Dispose this feature and all of the resources attached */ dispose() { super.dispose(); this.onPlaneAddedObservable.clear(); this.onPlaneRemovedObservable.clear(); this.onPlaneUpdatedObservable.clear(); } /** * Check if the needed objects are defined. * This does not mean that the feature is enabled, but that the objects needed are well defined. * @returns true if the initial compatibility test passed */ isCompatible() { return typeof XRPlane !== "undefined"; } /** * Enable room capture mode. * When enabled and supported by the system, * the detectedPlanes array will be populated with the detected room boundaries * @see https://immersive-web.github.io/real-world-geometry/plane-detection.html#dom-xrsession-initiateroomcapture * @returns true if plane detection is enabled and supported. Will reject if not supported. */ // eslint-disable-next-line @typescript-eslint/naming-convention async initiateRoomCapture() { if (this._xrSessionManager.session.initiateRoomCapture) { return await this._xrSessionManager.session.initiateRoomCapture(); } throw "initiateRoomCapture is not supported on this session"; } _onXRFrame(frame) { if (!this.attached || !this._enabled || !frame) { return; } const detectedPlanes = frame.detectedPlanes || frame.worldInformation?.detectedPlanes; if (detectedPlanes) { // remove all planes that are not currently detected in the frame for (let planeIdx = 0; planeIdx < this._detectedPlanes.length; planeIdx++) { const plane = this._detectedPlanes[planeIdx]; if (!detectedPlanes.has(plane.xrPlane)) { this._detectedPlanes.splice(planeIdx--, 1); this.onPlaneRemovedObservable.notifyObservers(plane); } } // now check for new ones detectedPlanes.forEach((xrPlane) => { if (!this._lastFrameDetected.has(xrPlane)) { const newPlane = { 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) { const index = this._findIndexInPlaneArray(xrPlane); const plane = this._detectedPlanes[index]; this._updatePlaneWithXRPlane(xrPlane, plane, frame); this.onPlaneUpdatedObservable.notifyObservers(plane); } } }); this._lastFrameDetected = detectedPlanes; } } _init() { const internalInit = () => { this._enabled = true; if (this._detectedPlanes.length) { this._detectedPlanes.length = 0; } }; // Only supported by BabylonNative if (!!this._xrSessionManager.isNative && !!this._options.preferredDetectorOptions && !!this._xrSessionManager.session.trySetPreferredPlaneDetectorOptions) { this._xrSessionManager.session.trySetPreferredPlaneDetectorOptions(this._options.preferredDetectorOptions); } if (!this._xrSessionManager.session.updateWorldTrackingState) { internalInit(); return; } this._xrSessionManager.session.updateWorldTrackingState({ planeDetectionState: { enabled: true } }); internalInit(); } _updatePlaneWithXRPlane(xrPlane, plane, xrFrame) { 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.worldParentNode) { mat.multiplyToRef(this._options.worldParentNode.getWorldMatrix(), mat); } } return plane; } /** * avoiding using Array.find for global support. * @param xrPlane the plane to find in the array * @returns the index of the plane in the array or -1 if not found */ _findIndexInPlaneArray(xrPlane) { for (let i = 0; i < this._detectedPlanes.length; ++i) { if (this._detectedPlanes[i].xrPlane === xrPlane) { return i; } } return -1; } } /** * The module's name */ WebXRPlaneDetector.Name = WebXRFeatureName.PLANE_DETECTION; /** * The (Babylon) version of this module. * This is an integer representing the implementation version. * This number does not correspond to the WebXR specs version */ WebXRPlaneDetector.Version = 1; //register the plugin WebXRFeaturesManager.AddWebXRFeature(WebXRPlaneDetector.Name, (xrSessionManager, options) => { return () => new WebXRPlaneDetector(xrSessionManager, options); }, WebXRPlaneDetector.Version); //# sourceMappingURL=WebXRPlaneDetector.js.map