webXRExperienceHelper.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import { Nullable } from "../types";
  2. import { Observable } from "../Misc/observable";
  3. import { IDisposable, Scene } from "../scene";
  4. import { Camera } from "../Cameras/camera";
  5. import { WebXRSessionManager } from "./webXRSessionManager";
  6. import { WebXRCamera } from "./webXRCamera";
  7. import { WebXRState, WebXRRenderTarget } from "./webXRTypes";
  8. import { WebXRFeaturesManager } from "./webXRFeaturesManager";
  9. import { Logger } from "../Misc/logger";
  10. /**
  11. * Base set of functionality needed to create an XR experience (WebXRSessionManager, Camera, StateManagement, etc.)
  12. * @see https://doc.babylonjs.com/how_to/webxr_experience_helpers
  13. */
  14. export class WebXRExperienceHelper implements IDisposable {
  15. private _nonVRCamera: Nullable<Camera> = null;
  16. private _originalSceneAutoClear = true;
  17. private _supported = false;
  18. /**
  19. * Camera used to render xr content
  20. */
  21. public camera: WebXRCamera;
  22. /** A features manager for this xr session */
  23. public featuresManager: WebXRFeaturesManager;
  24. /**
  25. * Observers registered here will be triggered after the camera's initial transformation is set
  26. * This can be used to set a different ground level or an extra rotation.
  27. *
  28. * Note that ground level is considered to be at 0. The height defined by the XR camera will be added
  29. * to the position set after this observable is done executing.
  30. */
  31. public onInitialXRPoseSetObservable = new Observable<WebXRCamera>();
  32. /**
  33. * Fires when the state of the experience helper has changed
  34. */
  35. public onStateChangedObservable = new Observable<WebXRState>();
  36. /** Session manager used to keep track of xr session */
  37. public sessionManager: WebXRSessionManager;
  38. /**
  39. * The current state of the XR experience (eg. transitioning, in XR or not in XR)
  40. */
  41. public state: WebXRState = WebXRState.NOT_IN_XR;
  42. /**
  43. * Creates a WebXRExperienceHelper
  44. * @param scene The scene the helper should be created in
  45. */
  46. private constructor(private scene: Scene) {
  47. this.sessionManager = new WebXRSessionManager(scene);
  48. this.camera = new WebXRCamera("", scene, this.sessionManager);
  49. this.featuresManager = new WebXRFeaturesManager(this.sessionManager);
  50. scene.onDisposeObservable.add(() => {
  51. this.exitXRAsync();
  52. });
  53. }
  54. /**
  55. * Creates the experience helper
  56. * @param scene the scene to attach the experience helper to
  57. * @returns a promise for the experience helper
  58. */
  59. public static CreateAsync(scene: Scene): Promise<WebXRExperienceHelper> {
  60. var helper = new WebXRExperienceHelper(scene);
  61. return helper.sessionManager
  62. .initializeAsync()
  63. .then(() => {
  64. helper._supported = true;
  65. return helper;
  66. })
  67. .catch((e) => {
  68. helper._setState(WebXRState.NOT_IN_XR);
  69. helper.dispose();
  70. throw e;
  71. });
  72. }
  73. /**
  74. * Disposes of the experience helper
  75. */
  76. public dispose() {
  77. this.camera.dispose();
  78. this.onStateChangedObservable.clear();
  79. this.onInitialXRPoseSetObservable.clear();
  80. this.sessionManager.dispose();
  81. if (this._nonVRCamera) {
  82. this.scene.activeCamera = this._nonVRCamera;
  83. }
  84. }
  85. /**
  86. * Enters XR mode (This must be done within a user interaction in most browsers eg. button click)
  87. * @param sessionMode options for the XR session
  88. * @param referenceSpaceType frame of reference of the XR session
  89. * @param renderTarget the output canvas that will be used to enter XR mode
  90. * @param sessionCreationOptions optional XRSessionInit object to init the session with
  91. * @returns promise that resolves after xr mode has entered
  92. */
  93. public async enterXRAsync(sessionMode: XRSessionMode, referenceSpaceType: XRReferenceSpaceType, renderTarget: WebXRRenderTarget = this.sessionManager.getWebXRRenderTarget(), sessionCreationOptions: XRSessionInit = {}): Promise<WebXRSessionManager> {
  94. if (!this._supported) {
  95. throw "WebXR not supported in this browser or environment";
  96. }
  97. this._setState(WebXRState.ENTERING_XR);
  98. if (referenceSpaceType !== "viewer" && referenceSpaceType !== "local") {
  99. sessionCreationOptions.optionalFeatures = sessionCreationOptions.optionalFeatures || [];
  100. sessionCreationOptions.optionalFeatures.push(referenceSpaceType);
  101. }
  102. sessionCreationOptions = await this.featuresManager._extendXRSessionInitObject(sessionCreationOptions);
  103. // we currently recommend "unbounded" space in AR (#7959)
  104. if (sessionMode === "immersive-ar" && referenceSpaceType !== "unbounded") {
  105. Logger.Warn("We recommend using 'unbounded' reference space type when using 'immersive-ar' session mode");
  106. }
  107. // make sure that the session mode is supported
  108. try {
  109. await this.sessionManager.initializeSessionAsync(sessionMode, sessionCreationOptions);
  110. await this.sessionManager.setReferenceSpaceTypeAsync(referenceSpaceType);
  111. await renderTarget.initializeXRLayerAsync(this.sessionManager.session);
  112. await this.sessionManager.updateRenderStateAsync({
  113. depthFar: this.camera.maxZ,
  114. depthNear: this.camera.minZ,
  115. baseLayer: renderTarget.xrLayer!,
  116. });
  117. // run the render loop
  118. this.sessionManager.runXRRenderLoop();
  119. // Cache pre xr scene settings
  120. this._originalSceneAutoClear = this.scene.autoClear;
  121. this._nonVRCamera = this.scene.activeCamera;
  122. this.scene.activeCamera = this.camera;
  123. // do not compensate when AR session is used
  124. if (sessionMode !== "immersive-ar") {
  125. this._nonXRToXRCamera();
  126. } else {
  127. // Kept here, TODO - check if needed
  128. this.scene.autoClear = false;
  129. this.camera.compensateOnFirstFrame = false;
  130. }
  131. this.sessionManager.onXRSessionEnded.addOnce(() => {
  132. // Reset camera rigs output render target to ensure sessions render target is not drawn after it ends
  133. this.camera.rigCameras.forEach((c) => {
  134. c.outputRenderTarget = null;
  135. });
  136. // Restore scene settings
  137. this.scene.autoClear = this._originalSceneAutoClear;
  138. this.scene.activeCamera = this._nonVRCamera;
  139. if (sessionMode !== "immersive-ar" && this.camera.compensateOnFirstFrame) {
  140. if ((<any>this._nonVRCamera).setPosition) {
  141. (<any>this._nonVRCamera).setPosition(this.camera.position);
  142. } else {
  143. this._nonVRCamera!.position.copyFrom(this.camera.position);
  144. }
  145. }
  146. this._setState(WebXRState.NOT_IN_XR);
  147. });
  148. // Wait until the first frame arrives before setting state to in xr
  149. this.sessionManager.onXRFrameObservable.addOnce(() => {
  150. this._setState(WebXRState.IN_XR);
  151. });
  152. return this.sessionManager;
  153. } catch (e) {
  154. console.log(e);
  155. console.log(e.message);
  156. this._setState(WebXRState.NOT_IN_XR);
  157. throw e;
  158. }
  159. }
  160. /**
  161. * Exits XR mode and returns the scene to its original state
  162. * @returns promise that resolves after xr mode has exited
  163. */
  164. public exitXRAsync() {
  165. // only exit if state is IN_XR
  166. if (this.state !== WebXRState.IN_XR) {
  167. return Promise.resolve();
  168. }
  169. this._setState(WebXRState.EXITING_XR);
  170. return this.sessionManager.exitXRAsync();
  171. }
  172. private _nonXRToXRCamera() {
  173. this.camera.setTransformationFromNonVRCamera(this._nonVRCamera!);
  174. this.onInitialXRPoseSetObservable.notifyObservers(this.camera);
  175. }
  176. private _setState(val: WebXRState) {
  177. if (this.state === val) {
  178. return;
  179. }
  180. this.state = val;
  181. this.onStateChangedObservable.notifyObservers(this.state);
  182. }
  183. }