|
@@ -2,12 +2,10 @@ import { Logger } from "../../Misc/logger";
|
|
|
import { Observable } from "../../Misc/observable";
|
|
|
import { Nullable } from "../../types";
|
|
|
import { IDisposable, Scene } from "../../scene";
|
|
|
-import { Vector3, Matrix } from "../../Maths/math";
|
|
|
import { InternalTexture } from "../../Materials/Textures/internalTexture";
|
|
|
import { RenderTargetTexture } from "../../Materials/Textures/renderTargetTexture";
|
|
|
-import { Ray } from "../../Culling/ray";
|
|
|
/**
|
|
|
- * Manages an XRSession
|
|
|
+ * Manages an XRSession to work with Babylon's engine
|
|
|
* @see https://doc.babylonjs.com/how_to/webxr
|
|
|
*/
|
|
|
export class WebXRSessionManager implements IDisposable {
|
|
@@ -20,17 +18,25 @@ export class WebXRSessionManager implements IDisposable {
|
|
|
*/
|
|
|
public onXRSessionEnded: Observable<any> = new Observable<any>();
|
|
|
|
|
|
- /** @hidden */
|
|
|
- public _xrSession: XRSession;
|
|
|
- /** @hidden */
|
|
|
- public _frameOfReference: XRFrameOfReference;
|
|
|
+ /**
|
|
|
+ * Underlying xr session
|
|
|
+ */
|
|
|
+ public session: XRSession;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Type of reference space used when creating the session
|
|
|
+ */
|
|
|
+ public referenceSpace: XRReferenceSpace;
|
|
|
+
|
|
|
/** @hidden */
|
|
|
public _sessionRenderTargetTexture: Nullable<RenderTargetTexture> = null;
|
|
|
- /** @hidden */
|
|
|
- public _currentXRFrame: Nullable<XRFrame>;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Current XR frame
|
|
|
+ */
|
|
|
+ public currentFrame: Nullable<XRFrame>;
|
|
|
private _xrNavigator: any;
|
|
|
- private _xrDevice: XRDevice;
|
|
|
- private _tmpMatrix = new Matrix();
|
|
|
+ private baseLayer: Nullable<XRWebGLLayer> = null;
|
|
|
|
|
|
/**
|
|
|
* Constructs a WebXRSessionManager, this must be initialized within a user action before usage
|
|
@@ -52,26 +58,20 @@ export class WebXRSessionManager implements IDisposable {
|
|
|
if (!this._xrNavigator.xr) {
|
|
|
return Promise.reject("webXR not supported by this browser");
|
|
|
}
|
|
|
- // Request the webXR device
|
|
|
- return this._xrNavigator.xr.requestDevice().then((device: XRDevice) => {
|
|
|
- this._xrDevice = device;
|
|
|
- return (<any>this.scene.getEngine()._gl).setCompatibleXRDevice(this._xrDevice);
|
|
|
- });
|
|
|
+ return Promise.resolve();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Enters XR with the desired XR session options, this must be done with a user action (eg. button click event)
|
|
|
- * @param sessionCreationOptions xr options to create the session with
|
|
|
- * @param frameOfReferenceType option to configure how the xr pose is expressed
|
|
|
- * @returns Promise which resolves after it enters XR
|
|
|
+ * Initializes an xr session
|
|
|
+ * @param xrSessionMode mode to initialize
|
|
|
+ * @returns a promise which will resolve once the session has been initialized
|
|
|
*/
|
|
|
- public enterXRAsync(sessionCreationOptions: XRSessionCreationOptions, frameOfReferenceType: string): Promise<void> {
|
|
|
- // initialize session
|
|
|
- return this._xrDevice.requestSession(sessionCreationOptions).then((session: XRSession) => {
|
|
|
- this._xrSession = session;
|
|
|
+ public initializeSessionAsync(xrSessionMode: XRSessionMode) {
|
|
|
+ return this._xrNavigator.xr.requestSession(xrSessionMode).then((session: XRSession) => {
|
|
|
+ this.session = 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", () => {
|
|
|
+ this.session.addEventListener("end", () => {
|
|
|
// Remove render target texture and notify frame obervers
|
|
|
this._sessionRenderTargetTexture = null;
|
|
|
|
|
@@ -83,83 +83,80 @@ export class WebXRSessionManager implements IDisposable {
|
|
|
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) => {
|
|
|
- this._frameOfReference = frameOfRef;
|
|
|
- // 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: 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();
|
|
|
+ /**
|
|
|
+ * Sets the reference space on the xr session
|
|
|
+ * @param referenceSpace space to set
|
|
|
+ * @returns a promise that will resolve once the reference space has been set
|
|
|
+ */
|
|
|
+ public setReferenceSpaceAsync(referenceSpace: XRReferenceSpaceType) {
|
|
|
+ return this.session.requestReferenceSpace(referenceSpace).then((referenceSpace: XRReferenceSpace) => {
|
|
|
+ this.referenceSpace = referenceSpace;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Stops the xrSession and restores the renderloop
|
|
|
- * @returns Promise which resolves after it exits XR
|
|
|
+ * Updates the render state of the session
|
|
|
+ * @param state state to set
|
|
|
+ * @returns a promise that resolves once the render state has been updated
|
|
|
*/
|
|
|
- public exitXRAsync() {
|
|
|
- return this._xrSession.end();
|
|
|
+ public updateRenderStateAsync(state: any) {
|
|
|
+ if (state.baseLayer) {
|
|
|
+ this.baseLayer = state.baseLayer;
|
|
|
+ }
|
|
|
+ return this.session.updateRenderState(state);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Fires a ray and returns the closest hit in the xr sessions enviornment, useful to place objects in AR
|
|
|
- * @param ray ray to cast into the environment
|
|
|
- * @returns Promise which resolves with a collision point in the environment if it exists
|
|
|
+ * Starts rendering to the xr layer
|
|
|
+ * @returns a promise that will resolve once rendering has started
|
|
|
*/
|
|
|
- public environmentPointHitTestAsync(ray: Ray): Promise<Nullable<Vector3>> {
|
|
|
- return new Promise((res) => {
|
|
|
- // Compute left handed inputs to request hit test
|
|
|
- var origin = new Float32Array([ray.origin.x, ray.origin.y, ray.origin.z]);
|
|
|
- var direction = new Float32Array([ray.direction.x, ray.direction.y, ray.direction.z]);
|
|
|
- if (!this.scene.useRightHandedSystem) {
|
|
|
- origin[2] *= -1;
|
|
|
- direction[2] *= -1;
|
|
|
+ public startRenderingToXRAsync() {
|
|
|
+ // 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.session.requestAnimationFrame.bind(this.session),
|
|
|
+ renderFunction: (timestamp: number, xrFrame: Nullable<XRFrame>) => {
|
|
|
+ // Store the XR frame in the manager to be consumed by the XR camera to update pose
|
|
|
+ this.currentFrame = xrFrame;
|
|
|
+ this.onXRFrameObservable.notifyObservers(null);
|
|
|
+ this.scene.getEngine()._renderLoop();
|
|
|
}
|
|
|
+ };
|
|
|
+ // Create render target texture from xr's webgl render target
|
|
|
+ this._sessionRenderTargetTexture = WebXRSessionManager._CreateRenderTargetTextureFromSession(this.session, this.scene, this.baseLayer!);
|
|
|
+
|
|
|
+ // Stop window's animation frame and trigger sessions animation frame
|
|
|
+ window.cancelAnimationFrame(this.scene.getEngine()._frameHandler);
|
|
|
+ this.scene.getEngine()._renderLoop();
|
|
|
+ return Promise.resolve();
|
|
|
+ }
|
|
|
|
|
|
- // Fire hittest
|
|
|
- this._xrSession.requestHitTest(origin, direction, this._frameOfReference)
|
|
|
- .then((hits: any) => {
|
|
|
- if (hits.length > 0) {
|
|
|
- Matrix.FromFloat32ArrayToRefScaled(hits[0].hitMatrix, 0, 1.0, this._tmpMatrix);
|
|
|
- var hitPoint = this._tmpMatrix.getTranslation();
|
|
|
- if (!this.scene.useRightHandedSystem) {
|
|
|
- hitPoint.z *= -1;
|
|
|
- }
|
|
|
- res(hitPoint);
|
|
|
- } else {
|
|
|
- res(null);
|
|
|
- }
|
|
|
- }).catch(() => {
|
|
|
- res(null);
|
|
|
- });
|
|
|
- });
|
|
|
+ /**
|
|
|
+ * Stops the xrSession and restores the renderloop
|
|
|
+ * @returns Promise which resolves after it exits XR
|
|
|
+ */
|
|
|
+ public exitXRAsync() {
|
|
|
+ return this.session.end();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Checks if a session would be supported for the creation options specified
|
|
|
- * @param options creation options to check if they are supported
|
|
|
+ * @param sessionMode session mode to check if supported eg. immersive-vr
|
|
|
* @returns true if supported
|
|
|
*/
|
|
|
- public supportsSessionAsync(options: XRSessionCreationOptions) {
|
|
|
- return this._xrDevice.supportsSession(options).then(() => {
|
|
|
- return true;
|
|
|
- }).catch(() => {
|
|
|
- return false;
|
|
|
- });
|
|
|
+ public supportsSessionAsync(sessionMode: XRSessionMode) {
|
|
|
+ if (!(navigator as any).xr || !(navigator as any).xr.supportsSession) {
|
|
|
+ return Promise.resolve(false);
|
|
|
+ }else {
|
|
|
+ return (navigator as any).xr.supportsSession(sessionMode).then(() => {
|
|
|
+ return Promise.resolve(true);
|
|
|
+ }).catch((e: any) => {
|
|
|
+ Logger.Warn(e);
|
|
|
+ return Promise.resolve(false);
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -168,12 +165,15 @@ export class WebXRSessionManager implements IDisposable {
|
|
|
* @param session session to create render target for
|
|
|
* @param scene scene the new render target should be created for
|
|
|
*/
|
|
|
- public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: Scene) {
|
|
|
+ public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: Scene, baseLayer: XRWebGLLayer) {
|
|
|
+ if (!baseLayer) {
|
|
|
+ throw "no layer";
|
|
|
+ }
|
|
|
// Create internal texture
|
|
|
var internalTexture = new InternalTexture(scene.getEngine(), InternalTexture.DATASOURCE_UNKNOWN, true);
|
|
|
- internalTexture.width = session.baseLayer.framebufferWidth;
|
|
|
- internalTexture.height = session.baseLayer.framebufferHeight;
|
|
|
- internalTexture._framebuffer = session.baseLayer.framebuffer;
|
|
|
+ internalTexture.width = baseLayer.framebufferWidth;
|
|
|
+ internalTexture.height = baseLayer.framebufferHeight;
|
|
|
+ internalTexture._framebuffer = baseLayer.framebuffer;
|
|
|
|
|
|
// Create render target texture from the internal texture
|
|
|
var renderTargetTexture = new RenderTargetTexture("XR renderTargetTexture", { width: internalTexture.width, height: internalTexture.height }, scene, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true);
|
|
@@ -189,4 +189,4 @@ export class WebXRSessionManager implements IDisposable {
|
|
|
this.onXRFrameObservable.clear();
|
|
|
this.onXRSessionEnded.clear();
|
|
|
}
|
|
|
-}
|
|
|
+}
|