webXRInput.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { Nullable } from "../types";
  2. import { Observer, Observable } from "../Misc/observable";
  3. import { IDisposable } from "../scene";
  4. import { WebXRInputSource, IWebXRControllerOptions } from "./webXRInputSource";
  5. import { WebXRSessionManager } from "./webXRSessionManager";
  6. import { WebXRCamera } from "./webXRCamera";
  7. import { WebXRMotionControllerManager } from "./motionController/webXRMotionControllerManager";
  8. /**
  9. * The schema for initialization options of the XR Input class
  10. */
  11. export interface IWebXRInputOptions {
  12. /**
  13. * If set to true no model will be automatically loaded
  14. */
  15. doNotLoadControllerMeshes?: boolean;
  16. /**
  17. * If set, this profile will be used for all controllers loaded (for example "microsoft-mixed-reality")
  18. * If not found, the xr input profile data will be used.
  19. * Profiles are defined here - https://github.com/immersive-web/webxr-input-profiles/
  20. */
  21. forceInputProfile?: string;
  22. /**
  23. * Do not send a request to the controller repository to load the profile.
  24. *
  25. * Instead, use the controllers available in babylon itself.
  26. */
  27. disableOnlineControllerRepository?: boolean;
  28. /**
  29. * A custom URL for the controllers repository
  30. */
  31. customControllersRepositoryURL?: string;
  32. /**
  33. * Should the controller model's components not move according to the user input
  34. */
  35. disableControllerAnimation?: boolean;
  36. /**
  37. * Optional options to pass to the controller. Will be overridden by the Input options where applicable
  38. */
  39. controllerOptions?: IWebXRControllerOptions;
  40. }
  41. /**
  42. * XR input used to track XR inputs such as controllers/rays
  43. */
  44. export class WebXRInput implements IDisposable {
  45. /**
  46. * XR controllers being tracked
  47. */
  48. public controllers: Array<WebXRInputSource> = [];
  49. private _frameObserver: Nullable<Observer<any>>;
  50. private _sessionEndedObserver: Nullable<Observer<any>>;
  51. private _sessionInitObserver: Nullable<Observer<any>>;
  52. /**
  53. * Event when a controller has been connected/added
  54. */
  55. public onControllerAddedObservable = new Observable<WebXRInputSource>();
  56. /**
  57. * Event when a controller has been removed/disconnected
  58. */
  59. public onControllerRemovedObservable = new Observable<WebXRInputSource>();
  60. /**
  61. * Initializes the WebXRInput
  62. * @param xrSessionManager the xr session manager for this session
  63. * @param xrCamera the WebXR camera for this session. Mainly used for teleportation
  64. * @param options = initialization options for this xr input
  65. */
  66. public constructor(
  67. /**
  68. * the xr session manager for this session
  69. */
  70. public xrSessionManager: WebXRSessionManager,
  71. /**
  72. * the WebXR camera for this session. Mainly used for teleportation
  73. */
  74. public xrCamera: WebXRCamera,
  75. private readonly options: IWebXRInputOptions = {}
  76. ) {
  77. // Remove controllers when exiting XR
  78. this._sessionEndedObserver = this.xrSessionManager.onXRSessionEnded.add(() => {
  79. this._addAndRemoveControllers(
  80. [],
  81. this.controllers.map((c) => {
  82. return c.inputSource;
  83. })
  84. );
  85. });
  86. this._sessionInitObserver = this.xrSessionManager.onXRSessionInit.add((session) => {
  87. session.addEventListener("inputsourceschange", this._onInputSourcesChange);
  88. });
  89. this._frameObserver = this.xrSessionManager.onXRFrameObservable.add((frame) => {
  90. // Update controller pose info
  91. this.controllers.forEach((controller) => {
  92. controller.updateFromXRFrame(frame, this.xrSessionManager.referenceSpace);
  93. });
  94. });
  95. if (this.options.customControllersRepositoryURL) {
  96. WebXRMotionControllerManager.BaseRepositoryUrl = this.options.customControllersRepositoryURL;
  97. }
  98. WebXRMotionControllerManager.UseOnlineRepository = !this.options.disableOnlineControllerRepository;
  99. if (WebXRMotionControllerManager.UseOnlineRepository) {
  100. // pre-load the profiles list to load the controllers quicker afterwards
  101. try {
  102. WebXRMotionControllerManager.UpdateProfilesList().catch(() => {
  103. WebXRMotionControllerManager.UseOnlineRepository = false;
  104. });
  105. } catch (e) {
  106. WebXRMotionControllerManager.UseOnlineRepository = false;
  107. }
  108. }
  109. }
  110. private _onInputSourcesChange = (event: XRInputSourceChangeEvent) => {
  111. this._addAndRemoveControllers(event.added, event.removed);
  112. };
  113. private _addAndRemoveControllers(addInputs: Array<XRInputSource>, removeInputs: Array<XRInputSource>) {
  114. // Add controllers if they don't already exist
  115. let sources = this.controllers.map((c) => {
  116. return c.inputSource;
  117. });
  118. for (let input of addInputs) {
  119. if (sources.indexOf(input) === -1) {
  120. let controller = new WebXRInputSource(this.xrSessionManager.scene, input, {
  121. ...(this.options.controllerOptions || {}),
  122. forceControllerProfile: this.options.forceInputProfile,
  123. doNotLoadControllerMesh: this.options.doNotLoadControllerMeshes,
  124. disableMotionControllerAnimation: this.options.disableControllerAnimation,
  125. });
  126. this.controllers.push(controller);
  127. this.onControllerAddedObservable.notifyObservers(controller);
  128. }
  129. }
  130. // Remove and dispose of controllers to be disposed
  131. let keepControllers: Array<WebXRInputSource> = [];
  132. let removedControllers: Array<WebXRInputSource> = [];
  133. this.controllers.forEach((c) => {
  134. if (removeInputs.indexOf(c.inputSource) === -1) {
  135. keepControllers.push(c);
  136. } else {
  137. removedControllers.push(c);
  138. }
  139. });
  140. this.controllers = keepControllers;
  141. removedControllers.forEach((c) => {
  142. this.onControllerRemovedObservable.notifyObservers(c);
  143. c.dispose();
  144. });
  145. }
  146. /**
  147. * Disposes of the object
  148. */
  149. public dispose() {
  150. this.controllers.forEach((c) => {
  151. c.dispose();
  152. });
  153. this.xrSessionManager.onXRFrameObservable.remove(this._frameObserver);
  154. this.xrSessionManager.onXRSessionInit.remove(this._sessionInitObserver);
  155. this.xrSessionManager.onXRSessionEnded.remove(this._sessionEndedObserver);
  156. this.onControllerAddedObservable.clear();
  157. this.onControllerRemovedObservable.clear();
  158. }
  159. }