webXRFeaturesManager.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import { WebXRSessionManager } from './webXRSessionManager';
  2. import { IDisposable } from '../../scene';
  3. /**
  4. * Defining the interface required for a (webxr) feature
  5. */
  6. export interface IWebXRFeature extends IDisposable {
  7. /**
  8. * Is this feature attached
  9. */
  10. attached: boolean;
  11. /**
  12. * Attach the feature to the session
  13. * Will usually be called by the features manager
  14. *
  15. * @returns true if successful.
  16. */
  17. attach(): boolean;
  18. /**
  19. * Detach the feature from the session
  20. * Will usually be called by the features manager
  21. *
  22. * @returns true if successful.
  23. */
  24. detach(): boolean;
  25. }
  26. /**
  27. * Defining the constructor of a feature. Used to register the modules.
  28. */
  29. export type WebXRFeatureConstructor = (xrSessionManager: WebXRSessionManager, options?: any) => (() => IWebXRFeature);
  30. /**
  31. * The WebXR features manager is responsible of enabling or disabling features required for the current XR session.
  32. * It is mainly used in AR sessions.
  33. *
  34. * A feature can have a version that is defined by Babylon (and does not correspond with the webxr version).
  35. */
  36. export class WebXRFeaturesManager implements IDisposable {
  37. private static readonly _AvailableFeatures: {
  38. [name: string]: {
  39. stable: number;
  40. latest: number;
  41. [version: number]: WebXRFeatureConstructor;
  42. }
  43. } = {};
  44. /**
  45. * Used to register a module. After calling this function a developer can use this feature in the scene.
  46. * Mainly used internally.
  47. *
  48. * @param featureName the name of the feature to register
  49. * @param constructorFunction the function used to construct the module
  50. * @param version the (babylon) version of the module
  51. * @param stable is that a stable version of this module
  52. */
  53. public static AddWebXRFeature(featureName: string, constructorFunction: WebXRFeatureConstructor, version: number = 1, stable: boolean = false) {
  54. this._AvailableFeatures[featureName] = this._AvailableFeatures[featureName] || { latest: version };
  55. if (version > this._AvailableFeatures[featureName].latest) {
  56. this._AvailableFeatures[featureName].latest = version;
  57. }
  58. if (stable) {
  59. this._AvailableFeatures[featureName].stable = version;
  60. }
  61. this._AvailableFeatures[featureName][version] = constructorFunction;
  62. }
  63. /**
  64. * Returns a constructor of a specific feature.
  65. *
  66. * @param featureName the name of the feature to construct
  67. * @param version the version of the feature to load
  68. * @param xrSessionManager the xrSessionManager. Used to construct the module
  69. * @param options optional options provided to the module.
  70. * @returns a function that, when called, will return a new instance of this feature
  71. */
  72. public static ConstructFeature(featureName: string, version: number = 1, xrSessionManager: WebXRSessionManager, options?: any): (() => IWebXRFeature) {
  73. const constructorFunction = this._AvailableFeatures[featureName][version];
  74. if (!constructorFunction) {
  75. // throw an error? return nothing?
  76. throw new Error('feature not found');
  77. }
  78. return constructorFunction(xrSessionManager, options);
  79. }
  80. /**
  81. * Return the latest unstable version of this feature
  82. * @param featureName the name of the feature to search
  83. * @returns the version number. if not found will return -1
  84. */
  85. public static GetLatestVersionOfFeature(featureName: string): number {
  86. return (this._AvailableFeatures[featureName] && this._AvailableFeatures[featureName].latest) || -1;
  87. }
  88. /**
  89. * Return the latest stable version of this feature
  90. * @param featureName the name of the feature to search
  91. * @returns the version number. if not found will return -1
  92. */
  93. public static GetStableVersionOfFeature(featureName: string): number {
  94. return (this._AvailableFeatures[featureName] && this._AvailableFeatures[featureName].stable) || -1;
  95. }
  96. /**
  97. * Can be used to return the list of features currently registered
  98. *
  99. * @returns an Array of available features
  100. */
  101. public static GetAvailableFeatures() {
  102. return Object.keys(this._AvailableFeatures);
  103. }
  104. /**
  105. * Gets the versions available for a specific feature
  106. * @param featureName the name of the feature
  107. * @returns an array with the available versions
  108. */
  109. public static GetAvailableVersions(featureName: string) {
  110. return Object.keys(this._AvailableFeatures[featureName]);
  111. }
  112. private _features: {
  113. [name: string]: {
  114. featureImplementation: IWebXRFeature,
  115. version: number,
  116. enabled: boolean
  117. }
  118. } = {};
  119. /**
  120. * constructs a new features manages.
  121. *
  122. * @param _xrSessionManager an instance of WebXRSessionManager
  123. */
  124. constructor(private _xrSessionManager: WebXRSessionManager) {
  125. // when session starts / initialized - attach
  126. this._xrSessionManager.onXRSessionInit.add(() => {
  127. this.getEnabledFeatures().forEach((featureName) => {
  128. const feature = this._features[featureName];
  129. if (feature.enabled && !feature.featureImplementation.attached) {
  130. this.attachFeature(featureName);
  131. }
  132. });
  133. });
  134. // when session ends - detach
  135. this._xrSessionManager.onXRSessionEnded.add(() => {
  136. this.getEnabledFeatures().forEach((featureName) => {
  137. const feature = this._features[featureName];
  138. if (feature.enabled && feature.featureImplementation.attached) {
  139. // detach, but don't disable!
  140. this.detachFeature(featureName);
  141. }
  142. });
  143. });
  144. }
  145. /**
  146. * Enable a feature using its name and a version. This will enable it in the scene, and will be responsible to attach it when the session starts.
  147. *
  148. * @param featureName the name of the feature to load or the class of the feature
  149. * @param version optional version to load. if not provided the latest version will be enabled
  150. * @param moduleOptions options provided to the module. Ses the module documentation / constructor
  151. * @param attachIfPossible if set to true (default) the feature will be automatically attached, if it is currently possible
  152. * @returns a new constructed feature or throws an error if feature not found.
  153. */
  154. public enableFeature(featureName: string | { Name: string }, version: number | string = 'latest', moduleOptions: any = {}, attachIfPossible: boolean = true): IWebXRFeature {
  155. const name = typeof featureName === 'string' ? featureName : featureName.Name;
  156. let versionToLoad = 0;
  157. if (typeof version === 'string') {
  158. if (version === 'stable') {
  159. versionToLoad = WebXRFeaturesManager.GetStableVersionOfFeature(name);
  160. } else if (version === 'latest') {
  161. versionToLoad = WebXRFeaturesManager.GetLatestVersionOfFeature(name);
  162. }
  163. if (versionToLoad === -1) {
  164. throw new Error(`feature not found - ${name} (${version})`);
  165. }
  166. } else {
  167. versionToLoad = version;
  168. }
  169. // check if already initialized
  170. const feature = this._features[name];
  171. if (!feature || !feature.featureImplementation || feature.version !== versionToLoad) {
  172. const constructFunction = WebXRFeaturesManager.ConstructFeature(name, versionToLoad, this._xrSessionManager, moduleOptions);
  173. if (!constructFunction) {
  174. // report error?
  175. throw new Error(`feature not found - ${name}`);
  176. }
  177. if (feature) {
  178. this.disableFeature(name);
  179. }
  180. this._features[name] = {
  181. featureImplementation: constructFunction(),
  182. enabled: true,
  183. version: versionToLoad
  184. };
  185. } else {
  186. // make sure it is enabled now:
  187. feature.enabled = true;
  188. }
  189. // if session started already, request and enable
  190. if (this._xrSessionManager.session && !feature.featureImplementation.attached && attachIfPossible) {
  191. // enable feature
  192. this.attachFeature(name);
  193. }
  194. return this._features[name].featureImplementation;
  195. }
  196. /**
  197. * Used to disable an already-enabled feature
  198. * @param featureName the feature to disable
  199. * @returns true if disable was successful
  200. */
  201. public disableFeature(featureName: string | { Name: string }): boolean {
  202. const name = typeof featureName === 'string' ? featureName : featureName.Name;
  203. const feature = this._features[name];
  204. if (feature && feature.enabled) {
  205. feature.enabled = false;
  206. this.detachFeature(name);
  207. return true;
  208. }
  209. return false;
  210. }
  211. /**
  212. * Attach a feature to the current session. Mainly used when session started to start the feature effect.
  213. * Can be used during a session to start a feature
  214. * @param featureName the name of feature to attach
  215. */
  216. public attachFeature(featureName: string) {
  217. const feature = this._features[featureName];
  218. if (feature && feature.enabled && !feature.featureImplementation.attached) {
  219. feature.featureImplementation.attach();
  220. }
  221. }
  222. /**
  223. * Can be used inside a session or when the session ends to detach a specific feature
  224. * @param featureName the name of the feature to detach
  225. */
  226. public detachFeature(featureName: string) {
  227. const feature = this._features[featureName];
  228. if (feature && feature.featureImplementation.attached) {
  229. feature.featureImplementation.detach();
  230. }
  231. }
  232. /**
  233. * Get the list of enabled features
  234. * @returns an array of enabled features
  235. */
  236. public getEnabledFeatures() {
  237. return Object.keys(this._features);
  238. }
  239. /**
  240. * get the implementation of an enabled feature.
  241. * @param featureName the name of the feature to load
  242. * @returns the feature class, if found
  243. */
  244. public getEnabledFeature(featureName: string): IWebXRFeature {
  245. return this._features[featureName] && this._features[featureName].featureImplementation;
  246. }
  247. /**
  248. * dispose this features manager
  249. */
  250. dispose(): void {
  251. this.getEnabledFeatures().forEach((feature) => this._features[feature].featureImplementation.dispose());
  252. }
  253. }