webXRExperienceHelper.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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 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. 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. return this.sessionManager
  109. .initializeSessionAsync(sessionMode, sessionCreationOptions)
  110. .then(() => {
  111. return this.sessionManager.setReferenceSpaceTypeAsync(referenceSpaceType);
  112. })
  113. .then(() => {
  114. return renderTarget.initializeXRLayerAsync(this.sessionManager.session);
  115. })
  116. .then(() => {
  117. return this.sessionManager.updateRenderStateAsync({
  118. depthFar: this.camera.maxZ,
  119. depthNear: this.camera.minZ,
  120. baseLayer: renderTarget.xrLayer!,
  121. });
  122. })
  123. .then(() => {
  124. // run the render loop
  125. this.sessionManager.runXRRenderLoop();
  126. // Cache pre xr scene settings
  127. this._originalSceneAutoClear = this.scene.autoClear;
  128. this._nonVRCamera = this.scene.activeCamera;
  129. this.scene.activeCamera = this.camera;
  130. // do not compensate when AR session is used
  131. if (sessionMode !== "immersive-ar") {
  132. this._nonXRToXRCamera();
  133. } else {
  134. // Kept here, TODO - check if needed
  135. this.scene.autoClear = false;
  136. this.camera.compensateOnFirstFrame = false;
  137. }
  138. this.sessionManager.onXRSessionEnded.addOnce(() => {
  139. // Reset camera rigs output render target to ensure sessions render target is not drawn after it ends
  140. this.camera.rigCameras.forEach((c) => {
  141. c.outputRenderTarget = null;
  142. });
  143. // Restore scene settings
  144. this.scene.autoClear = this._originalSceneAutoClear;
  145. this.scene.activeCamera = this._nonVRCamera;
  146. if (sessionMode !== "immersive-ar" && this.camera.compensateOnFirstFrame) {
  147. if ((<any>this._nonVRCamera).setPosition) {
  148. (<any>this._nonVRCamera).setPosition(this.camera.position);
  149. } else {
  150. this._nonVRCamera!.position.copyFrom(this.camera.position);
  151. }
  152. }
  153. this._setState(WebXRState.NOT_IN_XR);
  154. });
  155. // Wait until the first frame arrives before setting state to in xr
  156. this.sessionManager.onXRFrameObservable.addOnce(() => {
  157. this._setState(WebXRState.IN_XR);
  158. });
  159. return this.sessionManager;
  160. })
  161. .catch((e: any) => {
  162. console.log(e);
  163. console.log(e.message);
  164. this._setState(WebXRState.NOT_IN_XR);
  165. throw e;
  166. });
  167. }
  168. /**
  169. * Exits XR mode and returns the scene to its original state
  170. * @returns promise that resolves after xr mode has exited
  171. */
  172. public exitXRAsync() {
  173. // only exit if state is IN_XR
  174. if (this.state !== WebXRState.IN_XR) {
  175. return Promise.resolve();
  176. }
  177. this._setState(WebXRState.EXITING_XR);
  178. return this.sessionManager.exitXRAsync();
  179. }
  180. private _nonXRToXRCamera() {
  181. this.camera.setTransformationFromNonVRCamera(this._nonVRCamera!);
  182. this.onInitialXRPoseSetObservable.notifyObservers(this.camera);
  183. }
  184. private _setState(val: WebXRState) {
  185. if (this.state === val) {
  186. return;
  187. }
  188. this.state = val;
  189. this.onStateChangedObservable.notifyObservers(this.state);
  190. }
  191. }