WebXRAnchorSystem.ts 9.8 KB

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