webXRExperienceHelper.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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 experince (WebXRSessionManager, Camera, StateManagement, etc.)
  12. * @see https://doc.babylonjs.com/how_to/webxr
  13. */
  14. export class WebXRExperienceHelper implements IDisposable {
  15. /**
  16. * Camera used to render xr content
  17. */
  18. public camera: WebXRCamera;
  19. /**
  20. * The current state of the XR experience (eg. transitioning, in XR or not in XR)
  21. */
  22. public state: WebXRState = WebXRState.NOT_IN_XR;
  23. private _setState(val: WebXRState) {
  24. if (this.state === val) {
  25. return;
  26. }
  27. this.state = val;
  28. this.onStateChangedObservable.notifyObservers(this.state);
  29. }
  30. /**
  31. * Fires when the state of the experience helper has changed
  32. */
  33. public onStateChangedObservable = new Observable<WebXRState>();
  34. /**
  35. * Observers registered here will be triggered after the camera's initial transformation is set
  36. * This can be used to set a different ground level or an extra rotation.
  37. *
  38. * Note that ground level is considered to be at 0. The height defined by the XR camera will be added
  39. * to the position set after this observable is done executing.
  40. */
  41. public onInitialXRPoseSetObservable = new Observable<WebXRCamera>();
  42. /** Session manager used to keep track of xr session */
  43. public sessionManager: WebXRSessionManager;
  44. /** A features manager for this xr session */
  45. public featuresManager: WebXRFeaturesManager;
  46. private _nonVRCamera: Nullable<Camera> = null;
  47. private _originalSceneAutoClear = true;
  48. private _supported = false;
  49. /**
  50. * Creates the experience helper
  51. * @param scene the scene to attach the experience helper to
  52. * @returns a promise for the experience helper
  53. */
  54. public static CreateAsync(scene: Scene): Promise<WebXRExperienceHelper> {
  55. var helper = new WebXRExperienceHelper(scene);
  56. return helper.sessionManager.initializeAsync().then(() => {
  57. helper._supported = true;
  58. return helper;
  59. }).catch((e) => {
  60. helper._setState(WebXRState.NOT_IN_XR);
  61. helper.dispose();
  62. throw e;
  63. });
  64. }
  65. /**
  66. * Creates a WebXRExperienceHelper
  67. * @param scene The scene the helper should be created in
  68. */
  69. private constructor(private scene: Scene) {
  70. this.sessionManager = new WebXRSessionManager(scene);
  71. this.camera = new WebXRCamera("", scene, this.sessionManager);
  72. this.featuresManager = new WebXRFeaturesManager(this.sessionManager);
  73. scene.onDisposeObservable.add(() => {
  74. this.exitXRAsync();
  75. });
  76. }
  77. /**
  78. * Exits XR mode and returns the scene to its original state
  79. * @returns promise that resolves after xr mode has exited
  80. */
  81. public exitXRAsync() {
  82. this._setState(WebXRState.EXITING_XR);
  83. return this.sessionManager.exitXRAsync();
  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. * @returns promise that resolves after xr mode has entered
  91. */
  92. public enterXRAsync(sessionMode: XRSessionMode, referenceSpaceType: XRReferenceSpaceType, renderTarget: WebXRRenderTarget = this.sessionManager.getWebXRRenderTarget()): Promise<WebXRSessionManager> {
  93. if (!this._supported) {
  94. throw "WebXR not supported";
  95. }
  96. this._setState(WebXRState.ENTERING_XR);
  97. let sessionCreationOptions: XRSessionInit = {
  98. optionalFeatures: (referenceSpaceType !== "viewer" && referenceSpaceType !== "local") ? [referenceSpaceType] : []
  99. };
  100. // we currently recommend "local" space in AR
  101. if (sessionMode === "immersive-ar" && referenceSpaceType !== "local") {
  102. Logger.Warn("We recommend using 'local' reference space type when using 'immersive-ar' session mode");
  103. }
  104. // make sure that the session mode is supported
  105. return this.sessionManager.isSessionSupportedAsync(sessionMode).then((supported) => {
  106. if (!supported) {
  107. throw new Error(`Session mode "${sessionMode}" not supported in browser`);
  108. }
  109. return this.sessionManager.initializeSessionAsync(sessionMode, sessionCreationOptions);
  110. }).then(() => {
  111. return this.sessionManager.setReferenceSpaceTypeAsync(referenceSpaceType);
  112. }).then(() => {
  113. return renderTarget.initializeXRLayerAsync(this.sessionManager.session);
  114. }).then(() => {
  115. return this.sessionManager.updateRenderStateAsync({ depthFar: this.camera.maxZ, depthNear: this.camera.minZ, baseLayer: renderTarget.xrLayer! });
  116. }).then(() => {
  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. // Overwrite current scene settings
  123. this.scene.autoClear = false;
  124. this.scene.activeCamera = this.camera;
  125. // do not compensate when AR session is used
  126. if (sessionMode !== 'immersive-ar') {
  127. this._nonXRToXRCamera();
  128. } else {
  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: any) => {
  154. console.log(e);
  155. console.log(e.message);
  156. this._setState(WebXRState.NOT_IN_XR);
  157. throw (e);
  158. });
  159. }
  160. /**
  161. * Disposes of the experience helper
  162. */
  163. public dispose() {
  164. this.camera.dispose();
  165. this.onStateChangedObservable.clear();
  166. this.onInitialXRPoseSetObservable.clear();
  167. this.sessionManager.dispose();
  168. if (this._nonVRCamera) {
  169. this.scene.activeCamera = this._nonVRCamera;
  170. }
  171. }
  172. private _nonXRToXRCamera() {
  173. this.camera.setTransformationFromNonVRCamera(this._nonVRCamera!);
  174. this.onInitialXRPoseSetObservable.notifyObservers(this.camera);
  175. }
  176. }