WebXRHitTestLegacy.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import { IWebXRFeature, WebXRFeaturesManager } from '../webXRFeaturesManager';
  2. import { WebXRSessionManager } from '../webXRSessionManager';
  3. import { Observable, Observer } from '../../../Misc/observable';
  4. import { Vector3, Matrix } from '../../../Maths/math.vector';
  5. import { TransformNode } from '../../../Meshes/transformNode';
  6. import { Nullable } from '../../../types';
  7. /**
  8. * name of module (can be reused with other versions)
  9. */
  10. const WebXRHitTestModuleName = "xr-hit-test";
  11. // the plugin is registered at the end of the file
  12. /**
  13. * Options used for hit testing
  14. */
  15. export interface IWebXRHitTestOptions {
  16. /**
  17. * Only test when user interacted with the scene. Default - hit test every frame
  18. */
  19. testOnPointerDownOnly?: boolean;
  20. /**
  21. * The node to use to transform the local results to world coordinates
  22. */
  23. worldParentNode?: TransformNode;
  24. }
  25. /**
  26. * Interface defining the babylon result of raycasting/hit-test
  27. */
  28. export interface IWebXRHitResult {
  29. /**
  30. * The native hit test result
  31. */
  32. xrHitResult: XRHitResult;
  33. /**
  34. * Transformation matrix that can be applied to a node that will put it in the hit point location
  35. */
  36. transformationMatrix: Matrix;
  37. }
  38. /**
  39. * The currently-working hit-test module.
  40. * Hit test (or raycasting) is used to interact with the real world.
  41. * For further information read here - https://github.com/immersive-web/hit-test
  42. */
  43. export class WebXRHitTestLegacy implements IWebXRFeature {
  44. /**
  45. * The module's name
  46. */
  47. public static readonly Name = WebXRHitTestModuleName;
  48. /**
  49. * The (Babylon) version of this module.
  50. * This is an integer representing the implementation version.
  51. * This number does not correspond to the webxr specs version
  52. */
  53. public static readonly Version = 1;
  54. /**
  55. * Set to true when attached
  56. */
  57. public attached: boolean = false;
  58. /**
  59. * Execute a hit test on the current running session using a select event returned from a transient input (such as touch)
  60. * @param event the (select) event to use to select with
  61. * @param referenceSpace the reference space to use for this hit test
  62. * @returns a promise that resolves with an array of native XR hit result in xr coordinates system
  63. */
  64. public static XRHitTestWithSelectEvent(event: XRInputSourceEvent, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]> {
  65. let targetRayPose = event.frame.getPose(event.inputSource.targetRaySpace, referenceSpace);
  66. if (!targetRayPose) {
  67. return Promise.resolve([]);
  68. }
  69. let targetRay = new XRRay(targetRayPose.transform);
  70. return this.XRHitTestWithRay(event.frame.session, targetRay, referenceSpace);
  71. }
  72. /**
  73. * execute a hit test with an XR Ray
  74. *
  75. * @param xrSession a native xrSession that will execute this hit test
  76. * @param xrRay the ray (position and direction) to use for raycasting
  77. * @param referenceSpace native XR reference space to use for the hit-test
  78. * @param filter filter function that will filter the results
  79. * @returns a promise that resolves with an array of native XR hit result in xr coordinates system
  80. */
  81. public static XRHitTestWithRay(xrSession: XRSession, xrRay: XRRay, referenceSpace: XRReferenceSpace, filter?: (result: XRHitResult) => boolean): Promise<XRHitResult[]> {
  82. return xrSession.requestHitTest(xrRay, referenceSpace).then((results) => {
  83. const filterFunction = filter || ((result) => !!result.hitMatrix);
  84. return results.filter(filterFunction);
  85. });
  86. }
  87. /**
  88. * Triggered when new babylon (transformed) hit test results are available
  89. */
  90. public onHitTestResultObservable: Observable<IWebXRHitResult[]> = new Observable();
  91. private _onSelectEnabled = false;
  92. private _xrFrameObserver: Nullable<Observer<XRFrame>>;
  93. /**
  94. * Creates a new instance of the (legacy version) hit test feature
  95. * @param _xrSessionManager an instance of WebXRSessionManager
  96. * @param options options to use when constructing this feature
  97. */
  98. constructor(private _xrSessionManager: WebXRSessionManager,
  99. /**
  100. * options to use when constructing this feature
  101. */
  102. public readonly options: IWebXRHitTestOptions = {}) {
  103. }
  104. /**
  105. * Populated with the last native XR Hit Results
  106. */
  107. public lastNativeXRHitResults: XRHitResult[] = [];
  108. /**
  109. * attach this feature
  110. * Will usually be called by the features manager
  111. *
  112. * @returns true if successful.
  113. */
  114. attach(): boolean {
  115. if (this.options.testOnPointerDownOnly) {
  116. this._xrSessionManager.session.addEventListener('select', this._onSelect, false);
  117. } else {
  118. // we are in XR space!
  119. const origin = new Vector3(0, 0, 0);
  120. // in XR space z-forward is negative
  121. const direction = new Vector3(0, 0, -1);
  122. const mat = new Matrix();
  123. this._xrFrameObserver = this._xrSessionManager.onXRFrameObservable.add((frame) => {
  124. // make sure we do nothing if (async) not attached
  125. if (!this.attached) {
  126. return;
  127. }
  128. let pose = frame.getViewerPose(this._xrSessionManager.referenceSpace);
  129. if (!pose) {
  130. return;
  131. }
  132. Matrix.FromArrayToRef(pose.transform.matrix, 0, mat);
  133. Vector3.TransformCoordinatesFromFloatsToRef(0, 0, 0, mat, origin);
  134. Vector3.TransformCoordinatesFromFloatsToRef(0, 0, -1, mat, direction);
  135. direction.subtractInPlace(origin);
  136. direction.normalize();
  137. let ray = new XRRay((<DOMPointReadOnly>{ x: origin.x, y: origin.y, z: origin.z, w: 0 }),
  138. (<DOMPointReadOnly>{ x: direction.x, y: direction.y, z: direction.z, w: 0 }));
  139. WebXRHitTestLegacy.XRHitTestWithRay(this._xrSessionManager.session, ray, this._xrSessionManager.referenceSpace).then(this._onHitTestResults);
  140. });
  141. }
  142. this.attached = true;
  143. return true;
  144. }
  145. /**
  146. * detach this feature.
  147. * Will usually be called by the features manager
  148. *
  149. * @returns true if successful.
  150. */
  151. detach(): boolean {
  152. // disable select
  153. this._onSelectEnabled = false;
  154. this._xrSessionManager.session.removeEventListener('select', this._onSelect);
  155. if (this._xrFrameObserver) {
  156. this._xrSessionManager.onXRFrameObservable.remove(this._xrFrameObserver);
  157. this._xrFrameObserver = null;
  158. }
  159. this.attached = false;
  160. return true;
  161. }
  162. private _onHitTestResults = (xrResults: XRHitResult[]) => {
  163. const mats = xrResults.map((result) => {
  164. let mat = Matrix.FromArray(result.hitMatrix);
  165. if (!this._xrSessionManager.scene.useRightHandedSystem) {
  166. mat.toggleModelMatrixHandInPlace();
  167. }
  168. // if (this.options.coordinatesSpace === Space.WORLD) {
  169. if (this.options.worldParentNode) {
  170. mat.multiplyToRef(this.options.worldParentNode.getWorldMatrix(), mat);
  171. }
  172. return {
  173. xrHitResult: result,
  174. transformationMatrix: mat
  175. };
  176. });
  177. this.lastNativeXRHitResults = xrResults;
  178. this.onHitTestResultObservable.notifyObservers(mats);
  179. }
  180. // can be done using pointerdown event, and xrSessionManager.currentFrame
  181. private _onSelect = (event: XRInputSourceEvent) => {
  182. if (!this._onSelectEnabled) {
  183. return;
  184. }
  185. WebXRHitTestLegacy.XRHitTestWithSelectEvent(event, this._xrSessionManager.referenceSpace);
  186. }
  187. /**
  188. * Dispose this feature and all of the resources attached
  189. */
  190. dispose(): void {
  191. this.detach();
  192. this.onHitTestResultObservable.clear();
  193. }
  194. }
  195. //register the plugin versions
  196. WebXRFeaturesManager.AddWebXRFeature(WebXRHitTestLegacy.Name, (xrSessionManager, options) => {
  197. return () => new WebXRHitTestLegacy(xrSessionManager, options);
  198. }, WebXRHitTestLegacy.Version, true);