webXRExperienceHelper.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { Nullable } from "../../types";
  2. import { Observable } from "../../Misc/observable";
  3. import { IDisposable, Scene } from "../../scene";
  4. import { Quaternion, Vector3 } from "../../Maths/math.vector";
  5. import { AbstractMesh } from "../../Meshes/abstractMesh";
  6. import { Camera } from "../../Cameras/camera";
  7. import { WebXRSessionManager } from "./webXRSessionManager";
  8. import { WebXRCamera } from "./webXRCamera";
  9. import { WebXRState, WebXRRenderTarget } from './webXRTypes';
  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. * 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
  17. */
  18. public container: AbstractMesh;
  19. /**
  20. * Camera used to render xr content
  21. */
  22. public camera: WebXRCamera;
  23. /**
  24. * The current state of the XR experience (eg. transitioning, in XR or not in XR)
  25. */
  26. public state: WebXRState = WebXRState.NOT_IN_XR;
  27. private _setState(val: WebXRState) {
  28. this.state = val;
  29. this.onStateChangedObservable.notifyObservers(this.state);
  30. }
  31. private static _TmpVector = new Vector3();
  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. private _nonVRCamera: Nullable<Camera> = null;
  39. private _originalSceneAutoClear = true;
  40. private _supported = false;
  41. /**
  42. * Creates the experience helper
  43. * @param scene the scene to attach the experience helper to
  44. * @returns a promise for the experience helper
  45. */
  46. public static CreateAsync(scene: Scene): Promise<WebXRExperienceHelper> {
  47. var helper = new WebXRExperienceHelper(scene);
  48. return helper.sessionManager.initializeAsync().then(() => {
  49. helper._supported = true;
  50. return helper;
  51. }).catch(() => {
  52. return helper;
  53. });
  54. }
  55. /**
  56. * Creates a WebXRExperienceHelper
  57. * @param scene The scene the helper should be created in
  58. */
  59. private constructor(private scene: Scene) {
  60. this.camera = new WebXRCamera("", scene);
  61. this.sessionManager = new WebXRSessionManager(scene);
  62. this.container = new AbstractMesh("WebXR Container", scene);
  63. this.camera.parent = this.container;
  64. scene.onDisposeObservable.add(() => {
  65. this.exitXRAsync();
  66. });
  67. }
  68. /**
  69. * Exits XR mode and returns the scene to its original state
  70. * @returns promise that resolves after xr mode has exited
  71. */
  72. public exitXRAsync() {
  73. this._setState(WebXRState.EXITING_XR);
  74. return this.sessionManager.exitXRAsync();
  75. }
  76. /**
  77. * Enters XR mode (This must be done within a user interaction in most browsers eg. button click)
  78. * @param sessionCreationOptions options for the XR session
  79. * @param referenceSpaceType frame of reference of the XR session
  80. * @param renderTarget the output canvas that will be used to enter XR mode
  81. * @returns promise that resolves after xr mode has entered
  82. */
  83. public enterXRAsync(sessionCreationOptions: XRSessionMode, referenceSpaceType: XRReferenceSpaceType, renderTarget: WebXRRenderTarget) {
  84. if (!this._supported) {
  85. throw "XR session not supported by this browser";
  86. }
  87. this._setState(WebXRState.ENTERING_XR);
  88. return this.sessionManager.initializeSessionAsync(sessionCreationOptions).then(() => {
  89. return this.sessionManager.setReferenceSpaceAsync(referenceSpaceType);
  90. }).then(() => {
  91. return renderTarget.initializeXRLayerAsync(this.sessionManager.session);
  92. }).then(() => {
  93. return this.sessionManager.updateRenderStateAsync({ depthFar: this.camera.maxZ, depthNear: this.camera.minZ, baseLayer: renderTarget.xrLayer! });
  94. }).then(() => {
  95. return this.sessionManager.startRenderingToXRAsync();
  96. }).then(() => {
  97. // Cache pre xr scene settings
  98. this._originalSceneAutoClear = this.scene.autoClear;
  99. this._nonVRCamera = this.scene.activeCamera;
  100. // Overwrite current scene settings
  101. this.scene.autoClear = false;
  102. this.scene.activeCamera = this.camera;
  103. this.sessionManager.onXRFrameObservable.add(() => {
  104. this.camera.updateFromXRSessionManager(this.sessionManager);
  105. });
  106. this.sessionManager.onXRSessionEnded.addOnce(() => {
  107. // Reset camera rigs output render target to ensure sessions render target is not drawn after it ends
  108. this.camera.rigCameras.forEach((c) => {
  109. c.outputRenderTarget = null;
  110. });
  111. // Restore scene settings
  112. this.scene.autoClear = this._originalSceneAutoClear;
  113. this.scene.activeCamera = this._nonVRCamera;
  114. this._setState(WebXRState.NOT_IN_XR);
  115. });
  116. // Wait until the first frame arrives before setting state to in xr
  117. this.sessionManager.onXRFrameObservable.addOnce(() => {
  118. this._setState(WebXRState.IN_XR);
  119. });
  120. }).catch((e: any) => {
  121. console.log(e);
  122. console.log(e.message);
  123. });
  124. }
  125. /**
  126. * Updates the global position of the camera by moving the camera's container
  127. * This should be used instead of modifying the camera's position as it will be overwritten by an xrSessions's update frame
  128. * @param position The desired global position of the camera
  129. */
  130. public setPositionOfCameraUsingContainer(position: Vector3) {
  131. this.camera.globalPosition.subtractToRef(position, WebXRExperienceHelper._TmpVector);
  132. this.container.position.subtractInPlace(WebXRExperienceHelper._TmpVector);
  133. }
  134. /**
  135. * Rotates the xr camera by rotating the camera's container around the camera's position
  136. * This should be used instead of modifying the camera's rotation as it will be overwritten by an xrSessions's update frame
  137. * @param rotation the desired quaternion rotation to apply to the camera
  138. */
  139. public rotateCameraByQuaternionUsingContainer(rotation: Quaternion) {
  140. if (!this.container.rotationQuaternion) {
  141. this.container.rotationQuaternion = Quaternion.FromEulerVector(this.container.rotation);
  142. }
  143. this.container.rotationQuaternion.multiplyInPlace(rotation);
  144. this.container.position.rotateByQuaternionAroundPointToRef(rotation, this.camera.globalPosition, this.container.position);
  145. }
  146. /**
  147. * Disposes of the experience helper
  148. */
  149. public dispose() {
  150. this.camera.dispose();
  151. this.container.dispose();
  152. this.onStateChangedObservable.clear();
  153. this.sessionManager.dispose();
  154. }
  155. }