webXRFeaturesManager.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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. * Should auto-attach be disabled?
  13. */
  14. disableAutoAttach: boolean;
  15. /**
  16. * Attach the feature to the session
  17. * Will usually be called by the features manager
  18. *
  19. * @param force should attachment be forced (even when already attached)
  20. * @returns true if successful.
  21. */
  22. attach(force?: boolean): boolean;
  23. /**
  24. * Detach the feature from the session
  25. * Will usually be called by the features manager
  26. *
  27. * @returns true if successful.
  28. */
  29. detach(): boolean;
  30. }
  31. /**
  32. * A list of the currently available features without referencing them
  33. */
  34. export class WebXRFeatureName {
  35. /**
  36. * The name of the hit test feature
  37. */
  38. public static HIT_TEST = "xr-hit-test";
  39. /**
  40. * The name of the anchor system feature
  41. */
  42. public static ANCHOR_SYSTEM = "xr-anchor-system";
  43. /**
  44. * The name of the background remover feature
  45. */
  46. public static BACKGROUND_REMOVER = "xr-background-remover";
  47. /**
  48. * The name of the pointer selection feature
  49. */
  50. public static POINTER_SELECTION = "xr-controller-pointer-selection";
  51. /**
  52. * The name of the teleportation feature
  53. */
  54. public static TELEPORTATION = "xr-controller-teleportation";
  55. /**
  56. * The name of the plane detection feature
  57. */
  58. public static PLANE_DETECTION = "xr-plane-detection";
  59. }
  60. /**
  61. * Defining the constructor of a feature. Used to register the modules.
  62. */
  63. export type WebXRFeatureConstructor = (xrSessionManager: WebXRSessionManager, options?: any) => (() => IWebXRFeature);
  64. /**
  65. * The WebXR features manager is responsible of enabling or disabling features required for the current XR session.
  66. * It is mainly used in AR sessions.
  67. *
  68. * A feature can have a version that is defined by Babylon (and does not correspond with the webxr version).
  69. */
  70. export class WebXRFeaturesManager implements IDisposable {
  71. private static readonly _AvailableFeatures: {
  72. [name: string]: {
  73. stable: number;
  74. latest: number;
  75. [version: number]: WebXRFeatureConstructor;
  76. }
  77. } = {};
  78. /**
  79. * Used to register a module. After calling this function a developer can use this feature in the scene.
  80. * Mainly used internally.
  81. *
  82. * @param featureName the name of the feature to register
  83. * @param constructorFunction the function used to construct the module
  84. * @param version the (babylon) version of the module
  85. * @param stable is that a stable version of this module
  86. */
  87. public static AddWebXRFeature(featureName: string, constructorFunction: WebXRFeatureConstructor, version: number = 1, stable: boolean = false) {
  88. this._AvailableFeatures[featureName] = this._AvailableFeatures[featureName] || { latest: version };
  89. if (version > this._AvailableFeatures[featureName].latest) {
  90. this._AvailableFeatures[featureName].latest = version;
  91. }
  92. if (stable) {
  93. this._AvailableFeatures[featureName].stable = version;
  94. }
  95. this._AvailableFeatures[featureName][version] = constructorFunction;
  96. }
  97. /**
  98. * Returns a constructor of a specific feature.
  99. *
  100. * @param featureName the name of the feature to construct
  101. * @param version the version of the feature to load
  102. * @param xrSessionManager the xrSessionManager. Used to construct the module
  103. * @param options optional options provided to the module.
  104. * @returns a function that, when called, will return a new instance of this feature
  105. */
  106. public static ConstructFeature(featureName: string, version: number = 1, xrSessionManager: WebXRSessionManager, options?: any): (() => IWebXRFeature) {
  107. const constructorFunction = this._AvailableFeatures[featureName][version];
  108. if (!constructorFunction) {
  109. // throw an error? return nothing?
  110. throw new Error('feature not found');
  111. }
  112. return constructorFunction(xrSessionManager, options);
  113. }
  114. /**
  115. * Return the latest unstable version of this feature
  116. * @param featureName the name of the feature to search
  117. * @returns the version number. if not found will return -1
  118. */
  119. public static GetLatestVersionOfFeature(featureName: string): number {
  120. return (this._AvailableFeatures[featureName] && this._AvailableFeatures[featureName].latest) || -1;
  121. }
  122. /**
  123. * Return the latest stable version of this feature
  124. * @param featureName the name of the feature to search
  125. * @returns the version number. if not found will return -1
  126. */
  127. public static GetStableVersionOfFeature(featureName: string): number {
  128. return (this._AvailableFeatures[featureName] && this._AvailableFeatures[featureName].stable) || -1;
  129. }
  130. /**
  131. * Can be used to return the list of features currently registered
  132. *
  133. * @returns an Array of available features
  134. */
  135. public static GetAvailableFeatures() {
  136. return Object.keys(this._AvailableFeatures);
  137. }
  138. /**
  139. * Gets the versions available for a specific feature
  140. * @param featureName the name of the feature
  141. * @returns an array with the available versions
  142. */
  143. public static GetAvailableVersions(featureName: string) {
  144. return Object.keys(this._AvailableFeatures[featureName]);
  145. }
  146. private _features: {
  147. [name: string]: {
  148. featureImplementation: IWebXRFeature,
  149. version: number,
  150. enabled: boolean
  151. }
  152. } = {};
  153. /**
  154. * constructs a new features manages.
  155. *
  156. * @param _xrSessionManager an instance of WebXRSessionManager
  157. */
  158. constructor(private _xrSessionManager: WebXRSessionManager) {
  159. // when session starts / initialized - attach
  160. this._xrSessionManager.onXRSessionInit.add(() => {
  161. this.getEnabledFeatures().forEach((featureName) => {
  162. const feature = this._features[featureName];
  163. if (feature.enabled && !feature.featureImplementation.attached && !feature.featureImplementation.disableAutoAttach) {
  164. this.attachFeature(featureName);
  165. }
  166. });
  167. });
  168. // when session ends - detach
  169. this._xrSessionManager.onXRSessionEnded.add(() => {
  170. this.getEnabledFeatures().forEach((featureName) => {
  171. const feature = this._features[featureName];
  172. if (feature.enabled && feature.featureImplementation.attached) {
  173. // detach, but don't disable!
  174. this.detachFeature(featureName);
  175. }
  176. });
  177. });
  178. }
  179. /**
  180. * 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.
  181. * If used twice, the old version will be disposed and a new one will be constructed. This way you can re-enable with different configuration.
  182. *
  183. * @param featureName the name of the feature to load or the class of the feature
  184. * @param version optional version to load. if not provided the latest version will be enabled
  185. * @param moduleOptions options provided to the module. Ses the module documentation / constructor
  186. * @param attachIfPossible if set to true (default) the feature will be automatically attached, if it is currently possible
  187. * @returns a new constructed feature or throws an error if feature not found.
  188. */
  189. public enableFeature(featureName: string | { Name: string }, version: number | string = 'latest', moduleOptions: any = {}, attachIfPossible: boolean = true): IWebXRFeature {
  190. const name = typeof featureName === 'string' ? featureName : featureName.Name;
  191. let versionToLoad = 0;
  192. if (typeof version === 'string') {
  193. if (!version) {
  194. throw new Error(`Error in provided version - ${name} (${version})`);
  195. }
  196. if (version === 'stable') {
  197. versionToLoad = WebXRFeaturesManager.GetStableVersionOfFeature(name);
  198. } else if (version === 'latest') {
  199. versionToLoad = WebXRFeaturesManager.GetLatestVersionOfFeature(name);
  200. } else {
  201. // try loading the number the string represents
  202. versionToLoad = +version;
  203. }
  204. if (versionToLoad === -1 || isNaN(versionToLoad)) {
  205. throw new Error(`feature not found - ${name} (${version})`);
  206. }
  207. } else {
  208. versionToLoad = version;
  209. }
  210. // check if already initialized
  211. const feature = this._features[name];
  212. const constructFunction = WebXRFeaturesManager.ConstructFeature(name, versionToLoad, this._xrSessionManager, moduleOptions);
  213. if (!constructFunction) {
  214. // report error?
  215. throw new Error(`feature not found - ${name}`);
  216. }
  217. /* If the feature is already enabled, detach and dispose it, and create a new one */
  218. if (feature) {
  219. this.disableFeature(name);
  220. }
  221. this._features[name] = {
  222. featureImplementation: constructFunction(),
  223. enabled: true,
  224. version: versionToLoad
  225. };
  226. if (attachIfPossible) {
  227. // if session started already, request and enable
  228. if (this._xrSessionManager.session && !feature.featureImplementation.attached) {
  229. // enable feature
  230. this.attachFeature(name);
  231. }
  232. } else {
  233. // disable auto-attach when session starts
  234. this._features[name].featureImplementation.disableAutoAttach = true;
  235. }
  236. return this._features[name].featureImplementation;
  237. }
  238. /**
  239. * Used to disable an already-enabled feature
  240. * The feature will be disposed and will be recreated once enabled.
  241. * @param featureName the feature to disable
  242. * @returns true if disable was successful
  243. */
  244. public disableFeature(featureName: string | { Name: string }): boolean {
  245. const name = typeof featureName === 'string' ? featureName : featureName.Name;
  246. const feature = this._features[name];
  247. if (feature && feature.enabled) {
  248. feature.enabled = false;
  249. this.detachFeature(name);
  250. feature.featureImplementation.dispose();
  251. return true;
  252. }
  253. return false;
  254. }
  255. /**
  256. * Attach a feature to the current session. Mainly used when session started to start the feature effect.
  257. * Can be used during a session to start a feature
  258. * @param featureName the name of feature to attach
  259. */
  260. public attachFeature(featureName: string) {
  261. const feature = this._features[featureName];
  262. if (feature && feature.enabled && !feature.featureImplementation.attached) {
  263. feature.featureImplementation.attach();
  264. }
  265. }
  266. /**
  267. * Can be used inside a session or when the session ends to detach a specific feature
  268. * @param featureName the name of the feature to detach
  269. */
  270. public detachFeature(featureName: string) {
  271. const feature = this._features[featureName];
  272. if (feature && feature.featureImplementation.attached) {
  273. feature.featureImplementation.detach();
  274. }
  275. }
  276. /**
  277. * Get the list of enabled features
  278. * @returns an array of enabled features
  279. */
  280. public getEnabledFeatures() {
  281. return Object.keys(this._features);
  282. }
  283. /**
  284. * get the implementation of an enabled feature.
  285. * @param featureName the name of the feature to load
  286. * @returns the feature class, if found
  287. */
  288. public getEnabledFeature(featureName: string): IWebXRFeature {
  289. return this._features[featureName] && this._features[featureName].featureImplementation;
  290. }
  291. /**
  292. * dispose this features manager
  293. */
  294. dispose(): void {
  295. this.getEnabledFeatures().forEach((feature) => {
  296. this.disableFeature(feature);
  297. this._features[feature].featureImplementation.dispose();
  298. });
  299. }
  300. }