Pārlūkot izejas kodu

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David Catuhe 5 gadi atpakaļ
vecāks
revīzija
837cf2c3dc

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

@@ -193,7 +193,7 @@
 - WebXR teleportation can now be disabled after initialized or before created ([RaananW](https://github.com/RaananW/))
 - New Features Manager for WebXR features ([RaananW](https://github.com/RaananW/))
 - New features - Plane detection, Hit Test, Background remover ([RaananW](https://github.com/RaananW/))
-- Camera's API is Babylon-conform (position, rotationQuaternion, world matrix, direction etc') ([#7239](https://github.com/BabylonJS/Babylon.js/issues/7239)) ([RaananW](https://github.com/RaananW/))
+- XR Camera's API is Babylon-conform (position, rotationQuaternion, world matrix, direction etc') ([#7239](https://github.com/BabylonJS/Babylon.js/issues/7239)) ([RaananW](https://github.com/RaananW/))
 - XR Input now using standard profiles and completely separated from the gamepad class ([#7348](https://github.com/BabylonJS/Babylon.js/issues/7348)) ([RaananW](https://github.com/RaananW/))
 - Teleportation and controller selection are now WebXR features. ([#7290](https://github.com/BabylonJS/Babylon.js/issues/7290)) ([RaananW](https://github.com/RaananW/))
 - Teleportation allows selecting direction before teleporting when using thumbstick or touchpad. ([#7290](https://github.com/BabylonJS/Babylon.js/issues/7290)) ([RaananW](https://github.com/RaananW/))

+ 25 - 33
loaders/src/glTF/glTFFileLoader.ts

@@ -486,7 +486,10 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
                     readAsync: (byteOffset: number, byteLength: number) => {
                         return new Promise<ArrayBufferView>((resolve, reject) => {
                             fileRequests.push(scene._requestFile(url, (data, webRequest) => {
-                                dataBuffer.byteLength = Number(webRequest!.getResponseHeader("Content-Range")!.split("/")[1]);
+                                const contentRange = webRequest!.getResponseHeader("Content-Range");
+                                if (contentRange) {
+                                    dataBuffer.byteLength = Number(contentRange.split("/")[1]);
+                                }
                                 resolve(new Uint8Array(data as ArrayBuffer));
                             }, onProgress, true, true, (error) => {
                                 reject(error);
@@ -727,18 +730,18 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
             }
 
             const length = dataReader.readUint32();
-            if (length !== dataReader.buffer.byteLength) {
+            if (dataReader.buffer.byteLength != 0 && length !== dataReader.buffer.byteLength) {
                 throw new Error(`Length in header does not match actual data length: ${length} != ${dataReader.buffer.byteLength}`);
             }
 
             let unpacked: Promise<IGLTFLoaderData>;
             switch (version) {
                 case 1: {
-                    unpacked = this._unpackBinaryV1Async(dataReader);
+                    unpacked = this._unpackBinaryV1Async(dataReader, length);
                     break;
                 }
                 case 2: {
-                    unpacked = this._unpackBinaryV2Async(dataReader);
+                    unpacked = this._unpackBinaryV2Async(dataReader, length);
                     break;
                 }
                 default: {
@@ -752,7 +755,7 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
         });
     }
 
-    private _unpackBinaryV1Async(dataReader: DataReader): Promise<IGLTFLoaderData> {
+    private _unpackBinaryV1Async(dataReader: DataReader, length: number): Promise<IGLTFLoaderData> {
         const ContentFormat = {
             JSON: 0
         };
@@ -764,7 +767,7 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
             throw new Error(`Unexpected content format: ${contentFormat}`);
         }
 
-        const bodyLength = dataReader.buffer.byteLength - dataReader.byteOffset;
+        const bodyLength = length - dataReader.byteOffset;
 
         const data: IGLTFLoaderData = { json: this._parseJson(dataReader.readString(contentLength)), bin: null };
         if (bodyLength !== 0) {
@@ -778,37 +781,26 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
         return Promise.resolve(data);
     }
 
-    private _unpackBinaryV2Async(dataReader: DataReader): Promise<IGLTFLoaderData> {
+    private _unpackBinaryV2Async(dataReader: DataReader, length: number): Promise<IGLTFLoaderData> {
         const ChunkFormat = {
             JSON: 0x4E4F534A,
             BIN: 0x004E4942
         };
 
-        // Read the JSON chunk header.
-        const chunkLength = dataReader.readUint32();
-        const chunkFormat = dataReader.readUint32();
-        if (chunkFormat !== ChunkFormat.JSON) {
-            throw new Error("First chunk format is not JSON");
-        }
+        const data: IGLTFLoaderData = { json: {}, bin: null };
 
-        // Bail if there are no other chunks.
-        if (dataReader.byteOffset + chunkLength === dataReader.buffer.byteLength) {
-            return dataReader.loadAsync(chunkLength).then(() => {
-                return { json: this._parseJson(dataReader.readString(chunkLength)), bin: null };
-            });
-        }
+        const readAsync = (): Promise<IGLTFLoaderData> => {
+            const chunkLength = dataReader.readUint32();
+            const chunkFormat = dataReader.readUint32();
 
-        // Read the JSON chunk and the length and type of the next chunk.
-        return dataReader.loadAsync(chunkLength + 8).then(() => {
-            const data: IGLTFLoaderData = { json: this._parseJson(dataReader.readString(chunkLength)), bin: null };
-
-            const readAsync = (): Promise<IGLTFLoaderData> => {
-                const chunkLength = dataReader.readUint32();
-                const chunkFormat = dataReader.readUint32();
+            const finalChunk = (dataReader.byteOffset + chunkLength + 8 > length);
 
+            // Read the chunk and (if available) the length and type of the next chunk.
+            return dataReader.loadAsync(finalChunk ? chunkLength : chunkLength + 8).then(() => {
                 switch (chunkFormat) {
                     case ChunkFormat.JSON: {
-                        throw new Error("Unexpected JSON chunk");
+                        data.json = this._parseJson(dataReader.readString(chunkLength));
+                        break;
                     }
                     case ChunkFormat.BIN: {
                         const startByteOffset = dataReader.byteOffset;
@@ -826,15 +818,15 @@ export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISc
                     }
                 }
 
-                if (dataReader.byteOffset !== dataReader.buffer.byteLength) {
-                    return dataReader.loadAsync(8).then(readAsync);
+                if (finalChunk) {
+                    return data;
                 }
 
-                return Promise.resolve(data);
-            };
+                return readAsync();
+            });
+        };
 
-            return readAsync();
-        });
+        return readAsync();
     }
 
     private static _parseVersion(version: string): Nullable<{ major: number, minor: number }> {

+ 19 - 7
src/Cameras/XR/features/WebXRAbstractFeature.ts

@@ -15,7 +15,6 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
      * @param _xrSessionManager the xr session manager for this feature
      */
     constructor(protected _xrSessionManager: WebXRSessionManager) {
-
     }
 
     private _attached: boolean = false;
@@ -32,14 +31,28 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
     }
 
     /**
+     * Should auto-attach be disabled?
+     */
+    public disableAutoAttach: boolean = false;
+
+    /**
      * attach this feature
      *
+     * @param force should attachment be forced (even when already attached)
      * @returns true if successful, false is failed or already attached
      */
-    public attach(): boolean {
-        if (this.attached) {
-            return false;
+    public attach(force?: boolean): boolean {
+        if (!force) {
+            if (this.attached) {
+                return false;
+            }
+        } else {
+            if (this.attached) {
+                // detach first, to be sure
+                this.detach();
+            }
         }
+
         this._attached = true;
         this._addNewAttachObserver(this._xrSessionManager.onXRFrameObservable, (frame) => this._onXRFrame(frame));
         return true;
@@ -52,6 +65,7 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
      */
     public detach(): boolean {
         if (!this._attached) {
+            this.disableAutoAttach = true;
             return false;
         }
         this._attached = false;
@@ -72,9 +86,7 @@ export abstract class WebXRAbstractFeature implements IWebXRFeature {
      * This function will not execute after the feature is detached.
      * @param _xrFrame the current frame
      */
-    protected _onXRFrame(_xrFrame: XRFrame): void {
-        // no-op
-    }
+    protected abstract _onXRFrame(_xrFrame: XRFrame): void;
 
     /**
      * This is used to register callbacks that will automatically be removed when detach is called.

+ 6 - 8
src/Cameras/XR/features/WebXRAnchorSystem.ts

@@ -1,4 +1,4 @@
-import { IWebXRFeature, WebXRFeaturesManager } from '../webXRFeaturesManager';
+import { WebXRFeatureName } from '../webXRFeaturesManager';
 import { WebXRSessionManager } from '../webXRSessionManager';
 import { Observable } from '../../../Misc/observable';
 import { Matrix } from '../../../Maths/math.vector';
@@ -7,8 +7,6 @@ import { WebXRPlaneDetector } from './WebXRPlaneDetector';
 import { WebXRHitTestLegacy } from './WebXRHitTestLegacy';
 import { WebXRAbstractFeature } from './WebXRAbstractFeature';
 
-const Name = "xr-anchor-system";
-
 /**
  * Configuration options of the anchor system
  */
@@ -54,12 +52,12 @@ let anchorIdProvider = 0;
  * will use the frame to create an anchor and not the session or a detected plane
  * For further information see https://github.com/immersive-web/anchors/
  */
-export class WebXRAnchorSystem extends WebXRAbstractFeature implements IWebXRFeature {
+export class WebXRAnchorSystem extends WebXRAbstractFeature {
 
     /**
      * The module's name
      */
-    public static readonly Name = Name;
+    public static readonly Name = WebXRFeatureName.ANCHOR_SYSTEM;
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.
@@ -262,6 +260,6 @@ export class WebXRAnchorSystem extends WebXRAbstractFeature implements IWebXRFea
 }
 
 //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);

+ 7 - 5
src/Cameras/XR/features/WebXRBackgroundRemover.ts

@@ -1,11 +1,9 @@
-import { WebXRFeaturesManager, IWebXRFeature } from "../webXRFeaturesManager";
+import { WebXRFeaturesManager, WebXRFeatureName } from "../webXRFeaturesManager";
 import { WebXRSessionManager } from '../webXRSessionManager';
 import { AbstractMesh } from '../../../Meshes/abstractMesh';
 import { Observable } from '../../../Misc/observable';
 import { WebXRAbstractFeature } from './WebXRAbstractFeature';
 
-const Name = "xr-background-remover";
-
 /**
  * Options interface for the background remover plugin
  */
@@ -37,12 +35,12 @@ export interface IWebXRBackgroundRemoverOptions {
 /**
  * A module that will automatically disable background meshes when entering AR and will enable them when leaving AR.
  */
-export class WebXRBackgroundRemover extends WebXRAbstractFeature implements IWebXRFeature {
+export class WebXRBackgroundRemover extends WebXRAbstractFeature {
 
     /**
      * The module's name
      */
-    public static readonly Name = Name;
+    public static readonly Name = WebXRFeatureName.BACKGROUND_REMOVER;
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.
@@ -128,6 +126,10 @@ export class WebXRBackgroundRemover extends WebXRAbstractFeature implements IWeb
         super.dispose();
         this.onBackgroundStateChangedObservable.clear();
     }
+
+    protected _onXRFrame(_xrFrame: XRFrame) {
+        // no-op
+    }
 }
 
 //register the plugin

+ 3 - 5
src/Cameras/XR/features/WebXRControllerPointerSelection.ts

@@ -1,4 +1,4 @@
-import { WebXRFeaturesManager, IWebXRFeature } from "../webXRFeaturesManager";
+import { WebXRFeaturesManager, WebXRFeatureName } from "../webXRFeaturesManager";
 import { WebXRSessionManager } from '../webXRSessionManager';
 import { AbstractMesh } from '../../../Meshes/abstractMesh';
 import { Observer } from '../../../Misc/observable';
@@ -17,8 +17,6 @@ import { Ray } from '../../../Culling/ray';
 import { PickingInfo } from '../../../Collisions/pickingInfo';
 import { WebXRAbstractFeature } from './WebXRAbstractFeature';
 
-const Name = "xr-controller-pointer-selection";
-
 /**
  * Options interface for the pointer selection module
  */
@@ -62,12 +60,12 @@ export interface IWebXRControllerPointerSelectionOptions {
 /**
  * A module that will enable pointer selection for motion controllers of XR Input Sources
  */
-export class WebXRControllerPointerSelection extends WebXRAbstractFeature implements IWebXRFeature {
+export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
 
     /**
      * The module's name
      */
-    public static readonly Name = Name;
+    public static readonly Name = WebXRFeatureName.POINTER_SELECTION;
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.

+ 3 - 5
src/Cameras/XR/features/WebXRControllerTeleportation.ts

@@ -1,4 +1,4 @@
-import { IWebXRFeature, WebXRFeaturesManager } from '../webXRFeaturesManager';
+import { IWebXRFeature, WebXRFeaturesManager, WebXRFeatureName } from '../webXRFeaturesManager';
 import { Observer } from '../../../Misc/observable';
 import { WebXRSessionManager } from '../webXRSessionManager';
 import { Nullable } from '../../../types';
@@ -22,8 +22,6 @@ import { Curve3 } from '../../../Maths/math.path';
 import { LinesBuilder } from '../../../Meshes/Builders/linesBuilder';
 import { WebXRAbstractFeature } from './WebXRAbstractFeature';
 
-const Name = "xr-controller-teleportation";
-
 /**
  * The options container for the teleportation module
  */
@@ -85,11 +83,11 @@ export interface IWebXRTeleportationOptions {
  * When enabled and attached, the feature will allow a user to move aroundand rotate in the scene using
  * the input of the attached controllers.
  */
-export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature implements IWebXRFeature {
+export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
     /**
      * The module's name
      */
-    public static readonly Name = Name;
+    public static readonly Name = WebXRFeatureName.TELEPORTATION;
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.

+ 3 - 8
src/Cameras/XR/features/WebXRHitTestLegacy.ts

@@ -1,15 +1,10 @@
-import { IWebXRFeature, WebXRFeaturesManager } from '../webXRFeaturesManager';
+import { WebXRFeaturesManager, WebXRFeatureName } from '../webXRFeaturesManager';
 import { WebXRSessionManager } from '../webXRSessionManager';
 import { Observable } from '../../../Misc/observable';
 import { Vector3, Matrix } from '../../../Maths/math.vector';
 import { TransformNode } from '../../../Meshes/transformNode';
 import { WebXRAbstractFeature } from './WebXRAbstractFeature';
 
-/**
- * name of module (can be reused with other versions)
- */
-const WebXRHitTestModuleName = "xr-hit-test";
-
 // the plugin is registered at the end of the file
 
 /**
@@ -45,12 +40,12 @@ export interface IWebXRHitResult {
  * Hit test (or raycasting) is used to interact with the real world.
  * For further information read here - https://github.com/immersive-web/hit-test
  */
-export class WebXRHitTestLegacy extends WebXRAbstractFeature implements IWebXRFeature {
+export class WebXRHitTestLegacy extends WebXRAbstractFeature {
 
     /**
      * The module's name
      */
-    public static readonly Name = WebXRHitTestModuleName;
+    public static readonly Name = WebXRFeatureName.HIT_TEST;
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.

+ 3 - 5
src/Cameras/XR/features/WebXRPlaneDetector.ts

@@ -1,12 +1,10 @@
-import { WebXRFeaturesManager, IWebXRFeature } from '../webXRFeaturesManager';
+import { WebXRFeaturesManager, WebXRFeatureName } from '../webXRFeaturesManager';
 import { TransformNode } from '../../../Meshes/transformNode';
 import { WebXRSessionManager } from '../webXRSessionManager';
 import { Observable } from '../../../Misc/observable';
 import { Vector3, Matrix } from '../../../Maths/math.vector';
 import { WebXRAbstractFeature } from './WebXRAbstractFeature';
 
-const Name = "xr-plane-detector";
-
 /**
  * Options used in the plane detector module
  */
@@ -47,12 +45,12 @@ 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 implements IWebXRFeature {
+export class WebXRPlaneDetector extends WebXRAbstractFeature {
 
     /**
      * The module's name
      */
-    public static readonly Name = Name;
+    public static readonly Name = WebXRFeatureName.PLANE_DETECTION;
     /**
      * The (Babylon) version of this module.
      * This is an integer representing the implementation version.

+ 4 - 9
src/Cameras/XR/webXRCamera.ts

@@ -12,11 +12,6 @@ import { Viewport } from '../../Maths/math.viewport';
  */
 export class WebXRCamera extends FreeCamera {
 
-    /**
-     * Is the camera in debug mode. Used when using an emulator
-     */
-    public debugMode = false;
-
     private _firstFrame = false;
     private _referencedPosition: Vector3 = new Vector3();
     private _referenceQuaternion: Quaternion = Quaternion.Identity();
@@ -78,7 +73,10 @@ export class WebXRCamera extends FreeCamera {
      * @param otherCamera the non-vr camera to copy the transformation from
      * @param resetToBaseReferenceSpace should XR reset to the base reference space
      */
-    public setTransformationFromNonVRCamera(otherCamera: Camera, resetToBaseReferenceSpace: boolean = true) {
+    public setTransformationFromNonVRCamera(otherCamera: Camera = this.getScene().activeCamera!, resetToBaseReferenceSpace: boolean = true) {
+        if (!otherCamera || otherCamera === this) {
+            return;
+        }
         const mat = otherCamera.computeWorldMatrix();
         mat.decompose(undefined, this.rotationQuaternion, this.position);
         // set the ground level
@@ -250,9 +248,6 @@ export class WebXRCamera extends FreeCamera {
                 currentRig.viewport.x = viewport.x / width;
                 currentRig.viewport.y = viewport.y / height;
             }
-            if (this.debugMode) {
-                this._updateForDualEyeDebugging();
-            }
 
             // Set cameras to render to the session's render target
             currentRig.outputRenderTarget = this._xrSessionManager.getRenderTargetTextureForEye(view.eye);

+ 6 - 1
src/Cameras/XR/webXRExperienceHelper.ts

@@ -23,6 +23,9 @@ export class WebXRExperienceHelper implements IDisposable {
     public state: WebXRState = WebXRState.NOT_IN_XR;
 
     private _setState(val: WebXRState) {
+        if (this.state === val) {
+            return;
+        }
         this.state = val;
         this.onStateChangedObservable.notifyObservers(this.state);
     }
@@ -63,6 +66,7 @@ export class WebXRExperienceHelper implements IDisposable {
             helper._supported = true;
             return helper;
         }).catch((e) => {
+            helper._setState(WebXRState.NOT_IN_XR);
             helper.dispose();
             throw e;
         });
@@ -110,7 +114,7 @@ export class WebXRExperienceHelper implements IDisposable {
         return this.sessionManager.isSessionSupportedAsync(sessionMode).then(() => {
             return this.sessionManager.initializeSessionAsync(sessionMode, sessionCreationOptions);
         }).then(() => {
-            return this.sessionManager.setReferenceSpaceAsync(referenceSpaceType);
+            return this.sessionManager.setReferenceSpaceTypeAsync(referenceSpaceType);
         }).then(() => {
             return renderTarget.initializeXRLayerAsync(this.sessionManager.session);
         }).then(() => {
@@ -154,6 +158,7 @@ export class WebXRExperienceHelper implements IDisposable {
         }).catch((e: any) => {
             console.log(e);
             console.log(e.message);
+            this._setState(WebXRState.NOT_IN_XR);
             throw (e);
         });
     }

+ 53 - 7
src/Cameras/XR/webXRFeaturesManager.ts

@@ -10,12 +10,17 @@ export interface IWebXRFeature extends IDisposable {
      */
     attached: boolean;
     /**
+     * Should auto-attach be disabled?
+     */
+    disableAutoAttach: boolean;
+    /**
      * Attach the feature to the session
      * Will usually be called by the features manager
      *
+     * @param force should attachment be forced (even when already attached)
      * @returns true if successful.
      */
-    attach(): boolean;
+    attach(force?: boolean): boolean;
     /**
      * Detach the feature from the session
      * Will usually be called by the features manager
@@ -26,6 +31,36 @@ export interface IWebXRFeature extends IDisposable {
 }
 
 /**
+ * A list of the currently available features without referencing them
+ */
+export class WebXRFeatureName {
+    /**
+     * The name of the hit test feature
+     */
+    public static HIT_TEST = "xr-hit-test";
+    /**
+     * The name of the anchor system feature
+     */
+    public static ANCHOR_SYSTEM = "xr-anchor-system";
+    /**
+     * The name of the background remover feature
+     */
+    public static BACKGROUND_REMOVER = "xr-background-remover";
+    /**
+     * The name of the pointer selection feature
+     */
+    public static POINTER_SELECTION = "xr-controller-pointer-selection";
+    /**
+     * The name of the teleportation feature
+     */
+    public static TELEPORTATION = "xr-controller-teleportation";
+    /**
+     * The name of the plane detection feature
+     */
+    public static PLANE_DETECTION = "xr-plane-detection";
+}
+
+/**
  * Defining the constructor of a feature. Used to register the modules.
  */
 export type WebXRFeatureConstructor = (xrSessionManager: WebXRSessionManager, options?: any) => (() => IWebXRFeature);
@@ -139,7 +174,7 @@ export class WebXRFeaturesManager implements IDisposable {
         this._xrSessionManager.onXRSessionInit.add(() => {
             this.getEnabledFeatures().forEach((featureName) => {
                 const feature = this._features[featureName];
-                if (feature.enabled && !feature.featureImplementation.attached) {
+                if (feature.enabled && !feature.featureImplementation.attached && !feature.featureImplementation.disableAutoAttach) {
                     this.attachFeature(featureName);
                 }
             });
@@ -171,12 +206,18 @@ export class WebXRFeaturesManager implements IDisposable {
         const name = typeof featureName === 'string' ? featureName : featureName.Name;
         let versionToLoad = 0;
         if (typeof version === 'string') {
+            if (!version) {
+                throw new Error(`Error in provided version - ${name} (${version})`);
+            }
             if (version === 'stable') {
                 versionToLoad = WebXRFeaturesManager.GetStableVersionOfFeature(name);
             } else if (version === 'latest') {
                 versionToLoad = WebXRFeaturesManager.GetLatestVersionOfFeature(name);
+            } else {
+                // try loading the number the string represents
+                versionToLoad = +version;
             }
-            if (versionToLoad === -1) {
+            if (versionToLoad === -1 || isNaN(versionToLoad)) {
                 throw new Error(`feature not found - ${name} (${version})`);
             }
         } else {
@@ -201,10 +242,15 @@ export class WebXRFeaturesManager implements IDisposable {
             version: versionToLoad
         };
 
-        // if session started already, request and enable
-        if (this._xrSessionManager.session && !feature.featureImplementation.attached && attachIfPossible) {
-            // 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;

+ 7 - 6
src/Cameras/XR/webXRSessionManager.ts

@@ -139,17 +139,18 @@ export class WebXRSessionManager implements IDisposable {
 
             // handle when the session is ended (By calling session.end or device ends its own session eg. pressing home button on phone)
             this.session.addEventListener("end", () => {
+                const engine = this.scene.getEngine();
                 this._sessionEnded = true;
                 // Remove render target texture and notify frame obervers
                 this._rttProvider = null;
 
                 // Restore frame buffer to avoid clear on xr framebuffer after session end
-                this.scene.getEngine().restoreDefaultFramebuffer();
+                engine.restoreDefaultFramebuffer();
 
                 // Need to restart render loop as after the session is ended the last request for new frame will never call callback
-                this.scene.getEngine().customAnimationFrameRequester = null;
+                engine.customAnimationFrameRequester = null;
                 this.onXRSessionEnded.notifyObservers(null);
-                this.scene.getEngine()._renderLoop();
+                engine._renderLoop();
             }, { once: true });
             return this.session;
         });
@@ -157,11 +158,11 @@ export class WebXRSessionManager implements IDisposable {
 
     /**
      * Sets the reference space on the xr session
-     * @param referenceSpace space to set
+     * @param referenceSpaceType space to set
      * @returns a promise that will resolve once the reference space has been set
      */
-    public setReferenceSpaceAsync(referenceSpace: XRReferenceSpaceType = "local-floor"): Promise<XRReferenceSpace> {
-        return this.session.requestReferenceSpace(referenceSpace).then((referenceSpace: XRReferenceSpace) => {
+    public setReferenceSpaceTypeAsync(referenceSpaceType: XRReferenceSpaceType = "local-floor"): Promise<XRReferenceSpace> {
+        return this.session.requestReferenceSpace(referenceSpaceType).then((referenceSpace: XRReferenceSpace) => {
             return referenceSpace;
         }, (rejectionReason) => {
             Logger.Error("XR.requestReferenceSpace failed for the following reason: ");