WebXRAnchorSystem.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import { WebXRFeature, WebXRFeaturesManager } from '../webXRFeaturesManager';
  2. import { WebXRSessionManager } from '../webXRSessionManager';
  3. import { Observable, Observer } from '../../../Misc/observable';
  4. import { Matrix } from '../../../Maths/math.vector';
  5. import { TransformNode } from '../../../Meshes/transformNode';
  6. import { WebXRPlaneDetector } from './WebXRPlaneDetector';
  7. import { Nullable } from '../../../types';
  8. import { WebXRHitTestLegacy } from './WebXRHitTestLegacy';
  9. const Name = "xr-anchor-system";
  10. /**
  11. * Configuration options of the anchor system
  12. */
  13. export interface WebXRAnchorSystemOptions {
  14. /**
  15. * a node that will be used to convert local to world coordinates
  16. */
  17. worldParentNode?: TransformNode;
  18. /**
  19. * should the anchor system use plane detection.
  20. * If set to true, the plane-detection feature should be set using setPlaneDetector
  21. */
  22. usePlaneDetection?: boolean;
  23. /**
  24. * Should a new anchor be added every time a select event is triggered
  25. */
  26. addAnchorOnSelect?: boolean;
  27. }
  28. /**
  29. * A babylon container for an XR Anchor
  30. */
  31. export interface WebXRAnchor {
  32. /**
  33. * A babylon-assigned ID for this anchor
  34. */
  35. id: number;
  36. /**
  37. * The native anchor object
  38. */
  39. xrAnchor: XRAnchor;
  40. /**
  41. * Transformation matrix to apply to an object attached to this anchor
  42. */
  43. transformationMatrix: Matrix;
  44. }
  45. let anchorIdProvider = 0;
  46. /**
  47. * An implementation of the anchor system of WebXR.
  48. * Note that the current documented implementation is not available in any browser. Future implementations
  49. * will use the frame to create an anchor and not the session or a detected plane
  50. * For further information see https://github.com/immersive-web/anchors/
  51. */
  52. export class WebXRAnchorSystem implements WebXRFeature {
  53. /**
  54. * The module's name
  55. */
  56. public static readonly Name = Name;
  57. /**
  58. * The (Babylon) version of this module.
  59. * This is an integer representing the implementation version.
  60. * This number does not correspond to the webxr specs version
  61. */
  62. public static readonly Version = 1;
  63. /**
  64. * Observers registered here will be executed when a new anchor was added to the session
  65. */
  66. public onAnchorAddedObservable: Observable<WebXRAnchor> = new Observable();
  67. /**
  68. * Observers registered here will be executed when an existing anchor updates
  69. * This can execute N times every frame
  70. */
  71. public onAnchorUpdatedObservable: Observable<WebXRAnchor> = new Observable();
  72. /**
  73. * Observers registered here will be executed when an anchor was removed from the session
  74. */
  75. public onAnchorRemovedObservable: Observable<WebXRAnchor> = new Observable();
  76. private _planeDetector: WebXRPlaneDetector;
  77. private _hitTestModule: WebXRHitTestLegacy;
  78. private _enabled: boolean = false;
  79. private _attached: boolean = false;
  80. private _trackedAnchors: Array<WebXRAnchor> = [];
  81. private _lastFrameDetected: XRAnchorSet = new Set();
  82. private _observerTracked: Nullable<Observer<XRFrame>>;
  83. /**
  84. * constructs a new anchor system
  85. * @param xrSessionManager an instance of WebXRSessionManager
  86. * @param options configuration object for this feature
  87. */
  88. constructor(private xrSessionManager: WebXRSessionManager, private options: WebXRAnchorSystemOptions = {}) {
  89. }
  90. /**
  91. * set the plane detector to use in order to create anchors from frames
  92. * @param planeDetector the plane-detector module to use
  93. * @param enable enable plane-anchors. default is true
  94. */
  95. public setPlaneDetector(planeDetector: WebXRPlaneDetector, enable: boolean = true) {
  96. this._planeDetector = planeDetector;
  97. this.options.usePlaneDetection = enable;
  98. }
  99. /**
  100. * If set, it will improve performance by using the current hit-test results instead of executing a new hit-test
  101. * @param hitTestModule the hit-test module to use.
  102. */
  103. public setHitTestModule(hitTestModule: WebXRHitTestLegacy) {
  104. this._hitTestModule = hitTestModule;
  105. }
  106. /**
  107. * attach this feature
  108. * Will usually be called by the features manager
  109. *
  110. * @returns true if successful.
  111. */
  112. attach(): boolean {
  113. this._observerTracked = this.xrSessionManager.onXRFrameObservable.add(() => {
  114. const frame = this.xrSessionManager.currentFrame;
  115. if (!this._attached || !this._enabled || !frame) { return; }
  116. // const timestamp = this.xrSessionManager.currentTimestamp;
  117. const trackedAnchors = frame.trackedAnchors;
  118. if (trackedAnchors && trackedAnchors.size) {
  119. this._trackedAnchors.filter((anchor) => !trackedAnchors.has(anchor.xrAnchor)).map((anchor) => {
  120. const index = this._trackedAnchors.indexOf(anchor);
  121. this._trackedAnchors.splice(index, 1);
  122. this.onAnchorRemovedObservable.notifyObservers(anchor);
  123. });
  124. // now check for new ones
  125. trackedAnchors.forEach((xrAnchor) => {
  126. if (!this._lastFrameDetected.has(xrAnchor)) {
  127. const newAnchor: Partial<WebXRAnchor> = {
  128. id: anchorIdProvider++,
  129. xrAnchor: xrAnchor
  130. };
  131. const plane = this.updateAnchorWithXRFrame(xrAnchor, newAnchor, frame);
  132. this._trackedAnchors.push(plane);
  133. this.onAnchorAddedObservable.notifyObservers(plane);
  134. } else {
  135. // updated?
  136. if (xrAnchor.lastChangedTime === this.xrSessionManager.currentTimestamp) {
  137. let index = this.findIndexInAnchorArray(xrAnchor);
  138. const anchor = this._trackedAnchors[index];
  139. this.updateAnchorWithXRFrame(xrAnchor, anchor, frame);
  140. this.onAnchorUpdatedObservable.notifyObservers(anchor);
  141. }
  142. }
  143. });
  144. this._lastFrameDetected = trackedAnchors;
  145. }
  146. });
  147. if (this.options.addAnchorOnSelect) {
  148. this.xrSessionManager.session.addEventListener('select', this.onSelect, false);
  149. }
  150. this._attached = true;
  151. return true;
  152. }
  153. /**
  154. * detach this feature.
  155. * Will usually be called by the features manager
  156. *
  157. * @returns true if successful.
  158. */
  159. detach(): boolean {
  160. this._attached = false;
  161. this.xrSessionManager.session.removeEventListener('select', this.onSelect);
  162. if (this._observerTracked) {
  163. this.xrSessionManager.onXRFrameObservable.remove(this._observerTracked);
  164. }
  165. return true;
  166. }
  167. /**
  168. * Dispose this feature and all of the resources attached
  169. */
  170. dispose(): void {
  171. this.detach();
  172. this.onAnchorAddedObservable.clear();
  173. this.onAnchorRemovedObservable.clear();
  174. this.onAnchorUpdatedObservable.clear();
  175. }
  176. private onSelect = (event: XRInputSourceEvent) => {
  177. if (!this.options.addAnchorOnSelect) {
  178. return;
  179. }
  180. const onResults = (results: XRHitResult[]) => {
  181. if (results.length) {
  182. const hitResult = results[0];
  183. const transform = new XRRigidTransform(hitResult.hitMatrix);
  184. // find the plane on which to add.
  185. this.addAnchorAtRigidTransformation(transform);
  186. }
  187. };
  188. // avoid the hit-test, if the hit-test module is defined
  189. if (this._hitTestModule && !this._hitTestModule.options.testOnPointerDownOnly) {
  190. onResults(this._hitTestModule.lastNativeXRHitResults);
  191. }
  192. WebXRHitTestLegacy.XRHitTestWithSelectEvent(event, this.xrSessionManager.referenceSpace).then(onResults);
  193. // API will soon change, will need to use the plane
  194. this._planeDetector;
  195. }
  196. /**
  197. * Add anchor at a specific XR point.
  198. *
  199. * @param xrRigidTransformation xr-coordinates where a new anchor should be added
  200. * @param anchorCreator the object o use to create an anchor with. either a session or a plane
  201. * @returns a promise the fulfills when the anchor was created
  202. */
  203. public addAnchorAtRigidTransformation(xrRigidTransformation: XRRigidTransform, anchorCreator?: XRAnchorCreator): Promise<XRAnchor> {
  204. const creator = anchorCreator || this.xrSessionManager.session;
  205. return creator.createAnchor(xrRigidTransformation, this.xrSessionManager.referenceSpace);
  206. }
  207. private updateAnchorWithXRFrame(xrAnchor: XRAnchor, anchor: Partial<WebXRAnchor>, xrFrame: XRFrame): WebXRAnchor {
  208. // matrix
  209. const pose = xrFrame.getPose(xrAnchor.anchorSpace, this.xrSessionManager.referenceSpace);
  210. if (pose) {
  211. const mat = anchor.transformationMatrix || new Matrix();
  212. Matrix.FromArrayToRef(pose.transform.matrix, 0, mat);
  213. if (!this.xrSessionManager.scene.useRightHandedSystem) {
  214. mat.toggleModelMatrixHandInPlace();
  215. }
  216. anchor.transformationMatrix = mat;
  217. if (!this.options.worldParentNode) {
  218. // Logger.Warn("Please provide a world parent node to apply world transformation");
  219. } else {
  220. mat.multiplyToRef(this.options.worldParentNode.getWorldMatrix(), mat);
  221. }
  222. }
  223. return <WebXRAnchor>anchor;
  224. }
  225. /**
  226. * avoiding using Array.find for global support.
  227. * @param xrAnchor the plane to find in the array
  228. */
  229. private findIndexInAnchorArray(xrAnchor: XRAnchor) {
  230. for (let i = 0; i < this._trackedAnchors.length; ++i) {
  231. if (this._trackedAnchors[i].xrAnchor === xrAnchor) {
  232. return i;
  233. }
  234. }
  235. return -1;
  236. }
  237. }
  238. //register the plugin
  239. WebXRFeaturesManager.AddWebXRFeature(WebXRAnchorSystem.Name, (xrSessionManager, options) => {
  240. return () => new WebXRAnchorSystem(xrSessionManager, options);
  241. }, WebXRAnchorSystem.Version);