WebXRImageTracking.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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. */
  146. public getTrackedImageById(id: number): Nullable<IWebXRTrackedImage> {
  147. return this._trackedImages[id] || null;
  148. }
  149. /**
  150. * Dispose this feature and all of the resources attached
  151. */
  152. public dispose(): void {
  153. super.dispose();
  154. this._trackedImages.forEach((trackedImage) => {
  155. trackedImage.originalBitmap.close();
  156. });
  157. this._trackedImages.length = 0;
  158. this.onTrackableImageFoundObservable.clear();
  159. this.onUntrackableImageFoundObservable.clear();
  160. this.onTrackedImageUpdatedObservable.clear();
  161. }
  162. /**
  163. * Extends the session init object if needed
  164. */
  165. public async getXRSessionInitExtension(): Promise<Partial<XRSessionInit>> {
  166. if (!this.options.images || !this.options.images.length) {
  167. return {};
  168. }
  169. const promises = this.options.images.map((image) => {
  170. if (typeof image.src === "string") {
  171. const p = new Promise<ImageBitmap>((resolve, reject) => {
  172. if (typeof image.src === "string") {
  173. const img = new Image();
  174. img.src = image.src;
  175. img.onload = () => {
  176. img.decode().then(() => {
  177. createImageBitmap(img).then((imageBitmap) => {
  178. resolve(imageBitmap);
  179. });
  180. });
  181. };
  182. img.onerror = () => {
  183. Tools.Error(`Error loading image ${image.src}`);
  184. reject(`Error loading image ${image.src}`);
  185. };
  186. }
  187. });
  188. return p;
  189. } else {
  190. return Promise.resolve(image.src); // resolve is probably unneeded
  191. }
  192. });
  193. const images = await Promise.all(promises);
  194. this._originalTrackingRequest = images.map((image, idx) => {
  195. return {
  196. image,
  197. widthInMeters: this.options.images[idx].estimatedRealWorldWidth,
  198. };
  199. });
  200. return {
  201. trackedImages: this._originalTrackingRequest,
  202. };
  203. }
  204. protected _onXRFrame(_xrFrame: XRFrame) {
  205. if (!_xrFrame.getImageTrackingResults) {
  206. return;
  207. }
  208. const imageTrackedResults = _xrFrame.getImageTrackingResults();
  209. for (const result of imageTrackedResults) {
  210. let changed = false;
  211. const imageIndex = result.index;
  212. const imageObject = this._trackedImages[imageIndex];
  213. if (!imageObject) {
  214. // something went wrong!
  215. continue;
  216. }
  217. imageObject.xrTrackingResult = result;
  218. if (imageObject.realWorldWidth !== result.measuredWidthInMeters) {
  219. imageObject.realWorldWidth = result.measuredWidthInMeters;
  220. changed = true;
  221. }
  222. // Get the pose of the image relative to a reference space.
  223. const pose = _xrFrame.getPose(result.imageSpace, this._xrSessionManager.referenceSpace);
  224. if (pose) {
  225. const mat = imageObject.transformationMatrix;
  226. Matrix.FromArrayToRef(pose.transform.matrix, 0, mat);
  227. if (!this._xrSessionManager.scene.useRightHandedSystem) {
  228. mat.toggleModelMatrixHandInPlace();
  229. }
  230. changed = true;
  231. }
  232. const state = result.trackingState;
  233. const emulated = state === "emulated";
  234. if (imageObject.emulated !== emulated) {
  235. imageObject.emulated = emulated;
  236. changed = true;
  237. }
  238. if (changed) {
  239. this.onTrackedImageUpdatedObservable.notifyObservers(imageObject);
  240. }
  241. }
  242. }
  243. private async _init() {
  244. if (!this._xrSessionManager.session.getTrackedImageScores) {
  245. return;
  246. }
  247. //
  248. const imageScores = await this._xrSessionManager.session.getTrackedImageScores();
  249. // check the scores for all
  250. for (let idx = 0; idx < imageScores.length; ++idx) {
  251. if (imageScores[idx] == "untrackable") {
  252. this.onUntrackableImageFoundObservable.notifyObservers(idx);
  253. } else {
  254. const originalBitmap = this._originalTrackingRequest[idx].image;
  255. const imageObject: IWebXRTrackedImage = {
  256. id: idx,
  257. originalBitmap,
  258. transformationMatrix: new Matrix(),
  259. ratio: originalBitmap.width / originalBitmap.height,
  260. };
  261. this._trackedImages[idx] = imageObject;
  262. this.onTrackableImageFoundObservable.notifyObservers(imageObject);
  263. }
  264. }
  265. }
  266. }
  267. //register the plugin
  268. WebXRFeaturesManager.AddWebXRFeature(
  269. WebXRImageTracking.Name,
  270. (xrSessionManager, options) => {
  271. return () => new WebXRImageTracking(xrSessionManager, options);
  272. },
  273. WebXRImageTracking.Version,
  274. false
  275. );