WebXRImageTracking.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import { WebXRFeaturesManager, WebXRFeatureName } from "../webXRFeaturesManager";
  2. import { WebXRSessionManager } from "../webXRSessionManager";
  3. import { Observable } from "../../Misc/observable";
  4. import { WebXRAbstractFeature } from "./WebXRAbstractFeature";
  5. import { Matrix } from "../../Maths/math.vector";
  6. import { Tools } from "../../Misc/tools";
  7. import { Nullable } from "../../types";
  8. declare const XRImageTrackingResult: XRImageTrackingResult;
  9. /**
  10. * Options interface for the background remover plugin
  11. */
  12. export interface IWebXRImageTrackingOptions {
  13. /**
  14. * A required array with images to track
  15. */
  16. images: {
  17. /**
  18. * The source of the image. can be a URL or an image bitmap
  19. */
  20. src: string | ImageBitmap;
  21. /**
  22. * The estimated width in the real world (in meters)
  23. */
  24. estimatedRealWorldWidth: number; // In meters!
  25. }[];
  26. }
  27. /**
  28. * An object representing an image tracked by the system
  29. */
  30. export interface IWebXRTrackedImage {
  31. /**
  32. * The ID of this image (which is the same as the position in the array that was used to initialize the feature)
  33. */
  34. id: number;
  35. /**
  36. * Is the transformation provided emulated. If it is, the system "guesses" its real position. Otherwise it can be considered as exact position.
  37. */
  38. emulated?: boolean;
  39. /**
  40. * Just in case it is needed - the image bitmap that is being tracked
  41. */
  42. originalBitmap: ImageBitmap;
  43. /**
  44. * The native XR result image tracking result, untouched
  45. */
  46. xrTrackingResult?: XRImageTrackingResult;
  47. /**
  48. * Width in real world (meters)
  49. */
  50. realWorldWidth?: number;
  51. /**
  52. * A transformation matrix of this current image in the current reference space.
  53. */
  54. transformationMatrix: Matrix;
  55. /**
  56. * The width/height ratio of this image. can be used to calculate the size of the detected object/image
  57. */
  58. ratio?: number;
  59. }
  60. /**
  61. * Image tracking for immersive AR sessions.
  62. * Providing a list of images and their estimated widths will enable tracking those images in the real world.
  63. */
  64. export class WebXRImageTracking extends WebXRAbstractFeature {
  65. /**
  66. * The module's name
  67. */
  68. public static readonly Name = WebXRFeatureName.IMAGE_TRACKING;
  69. /**
  70. * The (Babylon) version of this module.
  71. * This is an integer representing the implementation version.
  72. * This number does not correspond to the WebXR specs version
  73. */
  74. public static readonly Version = 1;
  75. /**
  76. * This will be triggered if the underlying system deems an image untrackable.
  77. * The index is the index of the image from the array used to initialize the feature.
  78. */
  79. public onUntrackableImageFoundObservable: Observable<number> = new Observable();
  80. /**
  81. * An image was deemed trackable, and the system will start tracking it.
  82. */
  83. public onTrackableImageFoundObservable: Observable<IWebXRTrackedImage> = new Observable();
  84. /**
  85. * The image was found and its state was updated.
  86. */
  87. public onTrackedImageUpdatedObservable: Observable<IWebXRTrackedImage> = new Observable();
  88. private _trackedImages: IWebXRTrackedImage[] = [];
  89. private _originalTrackingRequest: XRTrackedImageInit[];
  90. /**
  91. * constructs the image tracking feature
  92. * @param _xrSessionManager the session manager for this module
  93. * @param options read-only options to be used in this module
  94. */
  95. constructor(
  96. _xrSessionManager: WebXRSessionManager,
  97. /**
  98. * read-only options to be used in this module
  99. */
  100. public readonly options: IWebXRImageTrackingOptions
  101. ) {
  102. super(_xrSessionManager);
  103. this.xrNativeFeatureName = "image-tracking";
  104. if (this.options.images.length === 0) {
  105. // no images provided?... return.
  106. return;
  107. }
  108. if (this._xrSessionManager.session) {
  109. this._init();
  110. } else {
  111. this._xrSessionManager.onXRSessionInit.addOnce(() => {
  112. this._init();
  113. });
  114. }
  115. }
  116. /**
  117. * attach this feature
  118. * Will usually be called by the features manager
  119. *
  120. * @returns true if successful.
  121. */
  122. public attach(): boolean {
  123. return super.attach();
  124. }
  125. /**
  126. * detach this feature.
  127. * Will usually be called by the features manager
  128. *
  129. * @returns true if successful.
  130. */
  131. public detach(): boolean {
  132. return super.detach();
  133. }
  134. /**
  135. * Check if the needed objects are defined.
  136. * This does not mean that the feature is enabled, but that the objects needed are well defined.
  137. */
  138. public isCompatible(): boolean {
  139. return typeof XRImageTrackingResult !== "undefined";
  140. }
  141. /**
  142. * Get a tracked image by its ID.
  143. *
  144. * @param id the id of the image to load (position in the init array)
  145. * @returns a trackable image, if exists in this location
  146. */
  147. public getTrackedImageById(id: number): Nullable<IWebXRTrackedImage> {
  148. return this._trackedImages[id] || null;
  149. }
  150. /**
  151. * Dispose this feature and all of the resources attached
  152. */
  153. public dispose(): void {
  154. super.dispose();
  155. this._trackedImages.forEach((trackedImage) => {
  156. trackedImage.originalBitmap.close();
  157. });
  158. this._trackedImages.length = 0;
  159. this.onTrackableImageFoundObservable.clear();
  160. this.onUntrackableImageFoundObservable.clear();
  161. this.onTrackedImageUpdatedObservable.clear();
  162. }
  163. /**
  164. * Extends the session init object if needed
  165. * @returns augmentation object fo the xr session init object.
  166. */
  167. public async getXRSessionInitExtension(): Promise<Partial<XRSessionInit>> {
  168. if (!this.options.images || !this.options.images.length) {
  169. return {};
  170. }
  171. const promises = this.options.images.map((image) => {
  172. if (typeof image.src === "string") {
  173. const p = new Promise<ImageBitmap>((resolve, reject) => {
  174. if (typeof image.src === "string") {
  175. const img = new Image();
  176. img.src = image.src;
  177. img.onload = () => {
  178. img.decode().then(() => {
  179. createImageBitmap(img).then((imageBitmap) => {
  180. resolve(imageBitmap);
  181. });
  182. });
  183. };
  184. img.onerror = () => {
  185. Tools.Error(`Error loading image ${image.src}`);
  186. reject(`Error loading image ${image.src}`);
  187. };
  188. }
  189. });
  190. return p;
  191. } else {
  192. return Promise.resolve(image.src); // resolve is probably unneeded
  193. }
  194. });
  195. const images = await Promise.all(promises);
  196. this._originalTrackingRequest = images.map((image, idx) => {
  197. return {
  198. image,
  199. widthInMeters: this.options.images[idx].estimatedRealWorldWidth,
  200. };
  201. });
  202. return {
  203. trackedImages: this._originalTrackingRequest,
  204. };
  205. }
  206. protected _onXRFrame(_xrFrame: XRFrame) {
  207. if (!_xrFrame.getImageTrackingResults) {
  208. return;
  209. }
  210. const imageTrackedResults = _xrFrame.getImageTrackingResults();
  211. for (const result of imageTrackedResults) {
  212. let changed = false;
  213. const imageIndex = result.index;
  214. const imageObject = this._trackedImages[imageIndex];
  215. if (!imageObject) {
  216. // something went wrong!
  217. continue;
  218. }
  219. imageObject.xrTrackingResult = result;
  220. if (imageObject.realWorldWidth !== result.measuredWidthInMeters) {
  221. imageObject.realWorldWidth = result.measuredWidthInMeters;
  222. changed = true;
  223. }
  224. // Get the pose of the image relative to a reference space.
  225. const pose = _xrFrame.getPose(result.imageSpace, this._xrSessionManager.referenceSpace);
  226. if (pose) {
  227. const mat = imageObject.transformationMatrix;
  228. Matrix.FromArrayToRef(pose.transform.matrix, 0, mat);
  229. if (!this._xrSessionManager.scene.useRightHandedSystem) {
  230. mat.toggleModelMatrixHandInPlace();
  231. }
  232. changed = true;
  233. }
  234. const state = result.trackingState;
  235. const emulated = state === "emulated";
  236. if (imageObject.emulated !== emulated) {
  237. imageObject.emulated = emulated;
  238. changed = true;
  239. }
  240. if (changed) {
  241. this.onTrackedImageUpdatedObservable.notifyObservers(imageObject);
  242. }
  243. }
  244. }
  245. private async _init() {
  246. if (!this._xrSessionManager.session.getTrackedImageScores) {
  247. return;
  248. }
  249. //
  250. const imageScores = await this._xrSessionManager.session.getTrackedImageScores();
  251. // check the scores for all
  252. for (let idx = 0; idx < imageScores.length; ++idx) {
  253. if (imageScores[idx] == "untrackable") {
  254. this.onUntrackableImageFoundObservable.notifyObservers(idx);
  255. } else {
  256. const originalBitmap = this._originalTrackingRequest[idx].image;
  257. const imageObject: IWebXRTrackedImage = {
  258. id: idx,
  259. originalBitmap,
  260. transformationMatrix: new Matrix(),
  261. ratio: originalBitmap.width / originalBitmap.height,
  262. };
  263. this._trackedImages[idx] = imageObject;
  264. this.onTrackableImageFoundObservable.notifyObservers(imageObject);
  265. }
  266. }
  267. }
  268. }
  269. //register the plugin
  270. WebXRFeaturesManager.AddWebXRFeature(
  271. WebXRImageTracking.Name,
  272. (xrSessionManager, options) => {
  273. return () => new WebXRImageTracking(xrSessionManager, options);
  274. },
  275. WebXRImageTracking.Version,
  276. false
  277. );