WebXRHitTestLegacy.ts 8.4 KB

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