WebXRHitTestLegacy.ts 7.8 KB

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