소스 검색

added xr experience helper

Trevor Baron 6 년 전
부모
커밋
a5abbb64dd

+ 2 - 1
Tools/Gulp/config.json

@@ -1299,7 +1299,8 @@
                 "../../src/Cameras/VR/babylon.vrDeviceOrientationGamepadCamera.js",
                 "../../src/Cameras/VR/babylon.vrExperienceHelper.js",
                 "../../src/Cameras/XR/babylon.webXRCamera.js",
-                "../../src/Cameras/XR/babylon.webXRSessionManager.js"
+                "../../src/Cameras/XR/babylon.webXRSessionManager.js",
+                "../../src/Cameras/XR/babylon.webXRExperienceHelper.js"
             ],
             "dependUpon": [
                 "core",

+ 4 - 2
src/Cameras/XR/babylon.webXRCamera.ts

@@ -51,15 +51,16 @@ module BABYLON {
         /**
          * Updates the cameras position from the current pose information of the  XR session
          * @param xrSessionManager the session containing pose information
+         * @returns true if the camera has been updated, false if the session did not contain pose or frame data
          */
         public updateFromXRSessionManager(xrSessionManager: WebXRSessionManager) {
             // Ensure all frame data is available
             if (!xrSessionManager._currentXRFrame || !xrSessionManager._currentXRFrame.getDevicePose) {
-                return;
+                return false;
             }
             var pose = xrSessionManager._currentXRFrame.getDevicePose(xrSessionManager._frameOfReference);
             if (!pose || !pose.poseModelMatrix) {
-                return;
+                return false;
             }
 
             // Update the parent cameras matrix
@@ -95,6 +96,7 @@ module BABYLON {
                 // Set cameras to render to the session's render target
                 this.rigCameras[i].outputRenderTarget = xrSessionManager._sessionRenderTargetTexture;
             });
+            return true;
         }
     }
 }

+ 114 - 0
src/Cameras/XR/babylon.webXRExperienceHelper.ts

@@ -0,0 +1,114 @@
+module BABYLON {
+    /**
+     * Helper class used to enable XR
+     * @see https://doc.babylonjs.com/how_to/webxr
+     */
+    export class WebXRExperienceHelper {
+        /**
+         * Container which stores the xr camera and controllers as children. This can be used to move the camera/user as the camera's position is updated by the xr device
+         */
+        public container:AbstractMesh;
+        /**
+         * Camera used to render xr content
+         */
+        public camera:WebXRCamera;
+
+        /** 
+         * If XR mode has completed being entered 
+         * After calling enterXR() this will be false until the returned promise is resolved 
+         */
+        public isInXRMode = false;
+        /** 
+         * If this is transitioning XR modes
+         * After calling enterXR() this will be true until the returned promise is resolved, then it will be false 
+         */
+        public isInStateTransition = false;
+        
+        private _sessionManager:WebXRSessionManager;
+
+        private _nonVRCamera:Nullable<Camera> = null;
+        private _originalSceneAutoClear = true;
+
+        private _outputCanvas:HTMLCanvasElement;
+        private _outputCanvasContext:WebGLRenderingContext;
+
+        /**
+         * Creates a WebXRExperienceHelper
+         * @param scene The scene the helper should be created in
+         */
+        constructor(private scene:BABYLON.Scene){
+            this.camera = new BABYLON.WebXRCamera("", scene);
+            this._sessionManager = new BABYLON.WebXRSessionManager(scene);
+            this.container = new AbstractMesh("", scene);
+            this._sessionManager.initialize()
+        }
+
+        /**
+         * Exits XR mode and returns the scene to its original state
+         */
+        public exitXR(){
+            this.isInStateTransition = true;
+            return this._sessionManager.exitXR()
+        }
+
+        /**
+         * Enters XR mode
+         * @param sessionCreationOptions options for the XR session 
+         * @param frameOfReference frame of reference of the XR session
+         */
+        public enterXR(sessionCreationOptions:XRSessionCreationOptions, frameOfReference:string){
+            this.isInStateTransition = true;
+
+            this._createCanvas();
+            if(!sessionCreationOptions.outputContext){
+                sessionCreationOptions.outputContext = this._outputCanvasContext;
+            }
+
+            return this._sessionManager.enterXR(sessionCreationOptions, frameOfReference).then(()=>{                
+                // Cache pre xr scene settings
+                this._originalSceneAutoClear = this.scene.autoClear;
+                this._nonVRCamera = this.scene.activeCamera;
+
+                // Overwrite current scene settings
+                this.scene.autoClear = false;
+                this.scene.activeCamera = this.camera
+
+                this._sessionManager.onXRFrameObservable.add(()=>{
+                    this.camera.updateFromXRSessionManager(this._sessionManager)
+                });
+
+                this._sessionManager.onXRSessionEnded.addOnce(()=>{
+                    // Reset camera rigs output render target to ensure sessions render target is not drawn after it ends
+                    this.camera.rigCameras.forEach((c)=>{
+                        c.outputRenderTarget = null;
+                    });
+
+                    // Restore scene settings
+                    this.scene.autoClear = this._originalSceneAutoClear;
+                    this.scene.activeCamera = this._nonVRCamera;
+                    this._sessionManager.onXRFrameObservable.clear();
+                    this._removeCanvas();
+
+                    this.isInStateTransition = false;
+                    this.isInXRMode = false;
+                })
+                this.isInStateTransition = false;
+                this.isInXRMode = true;
+            })
+        }
+
+        // create canvas used to mirror/vr xr content in fullscreen
+        private _createCanvas(){
+            this._removeCanvas();
+            this._outputCanvas = document.createElement('canvas');
+            this._outputCanvas.style.cssText = "position:absolute; bottom:0px;right:0px;z-index:10;width:100%;height:100%;background-color: #48989e;";
+            document.body.appendChild(this._outputCanvas);
+            this._outputCanvasContext = <any>this._outputCanvas.getContext('xrpresent')
+        }
+        private _removeCanvas(){
+            if(this._outputCanvas && document.body.contains(this._outputCanvas)){
+                document.body.removeChild(this._outputCanvas);
+            }
+        }
+    }
+}

+ 27 - 13
src/Cameras/XR/babylon.webXRSessionManager.ts

@@ -12,9 +12,11 @@ module BABYLON {
         /** @hidden */
         public _frameOfReference: XRFrameOfReference;
         /** @hidden */
-        public _sessionRenderTargetTexture: RenderTargetTexture;
+        public _sessionRenderTargetTexture: Nullable<RenderTargetTexture> = null;
         /** @hidden */
-        public _currentXRFrame: XRFrame;
+        public _currentXRFrame: Nullable<XRFrame>;
+        public onXRFrameObservable:Observable<any> = new BABYLON.Observable<any>();
+        public onXRSessionEnded:Observable<any> = new BABYLON.Observable<any>();
 
         /**
          * Constructs a WebXRSessionManager, this must be initialized within a user action before usage
@@ -48,10 +50,25 @@ module BABYLON {
          * @param frameOfReferenceType option to configure how the xr pose is expressed
          * @returns Promise which resolves after it enters XR
          */
-        public enterXR(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: XRFrameOfReferenceType): Promise<void> {
+        public enterXR(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: string): Promise<void> {
             // initialize session
             return this._xrDevice.requestSession(sessionCreationOptions).then((session: XRSession) => {
                 this._xrSession = session;
+
+                // handle when the session is ended (By calling session.end or device ends its own session eg. pressing home button on phone)
+                this._xrSession.addEventListener("end", ()=>{
+                    // Remove render target texture and notify frame obervers
+                    this._sessionRenderTargetTexture = null;
+
+                    // Restore frame buffer to avoid clear on xr framebuffer after session end
+                    this.scene.getEngine().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;
+                    this.onXRSessionEnded.notifyObservers(null);
+                    this.scene.getEngine()._renderLoop();
+                }, {once : true})
+
                 this._xrSession.baseLayer = new XRWebGLLayer(this._xrSession, this.scene.getEngine()._gl);
                 return this._xrSession.requestFrameOfReference(frameOfReferenceType);
             }).then((frameOfRef: any) => {
@@ -59,14 +76,19 @@ module BABYLON {
                 // Tell the engine's render loop to be driven by the xr session's refresh rate and provide xr pose information
                 this.scene.getEngine().customAnimationFrameRequester = {
                     requestAnimationFrame: this._xrSession.requestAnimationFrame.bind(this._xrSession),
-                    renderFunction: (timestamp: number, xrFrame: XRFrame) => {
+                    renderFunction: (timestamp: number, xrFrame: Nullable<XRFrame>) => {
                         // Store the XR frame in the manager to be consumed by the XR camera to update pose
                         this._currentXRFrame = xrFrame;
+                        this.onXRFrameObservable.notifyObservers(null);
                         this.scene.getEngine()._renderLoop();
                     }
                 };
                 // Create render target texture from xr's webgl render target
                 this._sessionRenderTargetTexture = WebXRSessionManager._CreateRenderTargetTextureFromSession(this._xrSession, this.scene);
+                
+                // Stop window's animation frame and trigger sessions animation frame
+                window.cancelAnimationFrame(this.scene.getEngine()._frameHandler);
+                this.scene.getEngine()._renderLoop();
             });
         }
 
@@ -75,15 +97,7 @@ module BABYLON {
          * @returns Promise which resolves after it exits XR
          */
         public exitXR() {
-            return new Promise((res) => {
-                this.scene.getEngine().customAnimationFrameRequester = null;
-                this._xrSession.end();
-                // Restore frame buffer to avoid clear on xr framebuffer after session end
-                this.scene.getEngine().restoreDefaultFramebuffer();
-                // Need to restart render loop as after calling session.end the last request for new frame will never call callback
-                this.scene.getEngine()._renderLoop();
-                res();
-            });
+            return this._xrSession.end();
         }
 
         /**

+ 1 - 1
src/Engine/babylon.engine.ts

@@ -867,7 +867,7 @@ module BABYLON {
         private _emptyCubeTexture: Nullable<InternalTexture>;
         private _emptyTexture3D: Nullable<InternalTexture>;
 
-        private _frameHandler: number;
+        public _frameHandler: number;
 
         private _nextFreeTextureSlots = new Array<number>();
         private _maxSimultaneousTextures = 0;

+ 5 - 8
src/babylon.mixins.ts

@@ -190,12 +190,14 @@ interface XRDevice {
 }
 interface XRSession {
     baseLayer: XRWebGLLayer;
-    requestFrameOfReference(type: XRFrameOfReferenceType): Promise<void>;
+    requestFrameOfReference(type: string): Promise<void>;
     requestHitTest(origin: Float32Array, direction: Float32Array, frameOfReference: any): any;
-    end(): void;
+    end(): Promise<void>;
     requestAnimationFrame: Function;
+    addEventListener: Function;
 }
 interface XRSessionCreationOptions {
+    outputContext:WebGLRenderingContext;
 }
 interface XRLayer {
     getViewport: Function;
@@ -218,9 +220,4 @@ interface XRWebGLLayer extends XRLayer {
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
     new(session: XRSession, context?: WebGLRenderingContext): XRWebGLLayer;
-};
-enum XRFrameOfReferenceType {
-    "head-model",
-    "eye-level",
-    "stage",
-}
+};