WebXRHitTestLegacy.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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. * Execute a hit test on the current running session using a select event returned from a transient input (such as touch)
  56. * @param event the (select) event to use to select with
  57. * @param referenceSpace the reference space to use for this hit test
  58. * @returns a promise that resolves with an array of native XR hit result in xr coordinates system
  59. */
  60. public static XRHitTestWithSelectEvent(event: XRInputSourceEvent, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]> {
  61. let targetRayPose = event.frame.getPose(event.inputSource.targetRaySpace, referenceSpace);
  62. if (!targetRayPose) {
  63. return Promise.resolve([]);
  64. }
  65. let targetRay = new XRRay(targetRayPose.transform);
  66. return this.XRHitTestWithRay(event.frame.session, targetRay, referenceSpace);
  67. }
  68. /**
  69. * execute a hit test with an XR Ray
  70. *
  71. * @param xrSession a native xrSession that will execute this hit test
  72. * @param xrRay the ray (position and direction) to use for raycasting
  73. * @param referenceSpace native XR reference space to use for the hit-test
  74. * @param filter filter function that will filter the results
  75. * @returns a promise that resolves with an array of native XR hit result in xr coordinates system
  76. */
  77. public static XRHitTestWithRay(xrSession: XRSession, xrRay: XRRay, referenceSpace: XRReferenceSpace, filter?: (result: XRHitResult) => boolean): Promise<XRHitResult[]> {
  78. return xrSession.requestHitTest(xrRay, referenceSpace).then((results) => {
  79. const filterFunction = filter || ((result) => !!result.hitMatrix);
  80. return results.filter(filterFunction);
  81. });
  82. }
  83. /**
  84. * Triggered when new babylon (transformed) hit test results are available
  85. */
  86. public onHitTestResultObservable: Observable<IWebXRHitResult[]> = new Observable();
  87. /**
  88. * Creates a new instance of the (legacy version) hit test feature
  89. * @param _xrSessionManager an instance of WebXRSessionManager
  90. * @param options options to use when constructing this feature
  91. */
  92. constructor(private _xrSessionManager: WebXRSessionManager,
  93. /**
  94. * options to use when constructing this feature
  95. */
  96. public readonly options: IWebXRHitTestOptions = {}) { }
  97. private _onSelectEnabled = false;
  98. private _xrFrameObserver: Nullable<Observer<XRFrame>>;
  99. private _attached: boolean = false;
  100. /**
  101. * Populated with the last native XR Hit Results
  102. */
  103. public lastNativeXRHitResults: XRHitResult[] = [];
  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 (this.options.testOnPointerDownOnly) {
  112. this._xrSessionManager.session.addEventListener('select', this._onSelect, false);
  113. } else {
  114. // we are in XR space!
  115. const origin = new Vector3(0, 0, 0);
  116. // in XR space z-forward is negative
  117. const direction = new Vector3(0, 0, -1);
  118. const mat = new Matrix();
  119. this._xrFrameObserver = this._xrSessionManager.onXRFrameObservable.add((frame) => {
  120. // make sure we do nothing if (async) not attached
  121. if (!this._attached) {
  122. return;
  123. }
  124. let pose = frame.getViewerPose(this._xrSessionManager.referenceSpace);
  125. if (!pose) {
  126. return;
  127. }
  128. Matrix.FromArrayToRef(pose.transform.matrix, 0, mat);
  129. Vector3.TransformCoordinatesFromFloatsToRef(0, 0, 0, mat, origin);
  130. Vector3.TransformCoordinatesFromFloatsToRef(0, 0, -1, mat, direction);
  131. direction.subtractInPlace(origin);
  132. direction.normalize();
  133. let ray = new XRRay((<DOMPointReadOnly>{ x: origin.x, y: origin.y, z: origin.z, w: 0 }),
  134. (<DOMPointReadOnly>{ x: direction.x, y: direction.y, z: direction.z, w: 0 }));
  135. WebXRHitTestLegacy.XRHitTestWithRay(this._xrSessionManager.session, ray, this._xrSessionManager.referenceSpace).then(this._onHitTestResults);
  136. });
  137. }
  138. this._attached = true;
  139. return true;
  140. }
  141. /**
  142. * detach this feature.
  143. * Will usually be called by the features manager
  144. *
  145. * @returns true if successful.
  146. */
  147. detach(): boolean {
  148. // disable select
  149. this._onSelectEnabled = false;
  150. this._xrSessionManager.session.removeEventListener('select', this._onSelect);
  151. if (this._xrFrameObserver) {
  152. this._xrSessionManager.onXRFrameObservable.remove(this._xrFrameObserver);
  153. this._xrFrameObserver = null;
  154. }
  155. this._attached = false;
  156. return true;
  157. }
  158. private _onHitTestResults = (xrResults: XRHitResult[]) => {
  159. const mats = xrResults.map((result) => {
  160. let mat = Matrix.FromArray(result.hitMatrix);
  161. if (!this._xrSessionManager.scene.useRightHandedSystem) {
  162. mat.toggleModelMatrixHandInPlace();
  163. }
  164. // if (this.options.coordinatesSpace === Space.WORLD) {
  165. if (this.options.worldParentNode) {
  166. mat.multiplyToRef(this.options.worldParentNode.getWorldMatrix(), mat);
  167. }
  168. return {
  169. xrHitResult: result,
  170. transformationMatrix: mat
  171. };
  172. });
  173. this.lastNativeXRHitResults = xrResults;
  174. this.onHitTestResultObservable.notifyObservers(mats);
  175. }
  176. // can be done using pointerdown event, and xrSessionManager.currentFrame
  177. private _onSelect = (event: XRInputSourceEvent) => {
  178. if (!this._onSelectEnabled) {
  179. return;
  180. }
  181. WebXRHitTestLegacy.XRHitTestWithSelectEvent(event, this._xrSessionManager.referenceSpace);
  182. }
  183. /**
  184. * Dispose this feature and all of the resources attached
  185. */
  186. dispose(): void {
  187. this.detach();
  188. this.onHitTestResultObservable.clear();
  189. }
  190. }
  191. //register the plugin versions
  192. WebXRFeaturesManager.AddWebXRFeature(WebXRHitTestLegacy.Name, (xrSessionManager, options) => {
  193. return () => new WebXRHitTestLegacy(xrSessionManager, options);
  194. }, WebXRHitTestLegacy.Version, true);