WebXRHitTest.ts 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import { WebXRFeaturesManager, WebXRFeatureName } from "../webXRFeaturesManager";
  2. import { WebXRSessionManager } from "../webXRSessionManager";
  3. import { Observable } from "../../Misc/observable";
  4. import { Vector3, Matrix, Quaternion } from "../../Maths/math.vector";
  5. import { WebXRAbstractFeature } from "./WebXRAbstractFeature";
  6. import { IWebXRLegacyHitTestOptions, IWebXRLegacyHitResult, IWebXRHitTestFeature } from "./WebXRHitTestLegacy";
  7. import { Tools } from "../../Misc/tools";
  8. import { Nullable } from "../../types";
  9. /**
  10. * Options used for hit testing (version 2)
  11. */
  12. export interface IWebXRHitTestOptions extends IWebXRLegacyHitTestOptions {
  13. /**
  14. * Do not create a permanent hit test. Will usually be used when only
  15. * transient inputs are needed.
  16. */
  17. disablePermanentHitTest?: boolean;
  18. /**
  19. * Enable transient (for example touch-based) hit test inspections
  20. */
  21. enableTransientHitTest?: boolean;
  22. /**
  23. * Offset ray for the permanent hit test
  24. */
  25. offsetRay?: Vector3;
  26. /**
  27. * Offset ray for the transient hit test
  28. */
  29. transientOffsetRay?: Vector3;
  30. /**
  31. * Instead of using viewer space for hit tests, use the reference space defined in the session manager
  32. */
  33. useReferenceSpace?: boolean;
  34. /**
  35. * Override the default entity type(s) of the hit-test result
  36. */
  37. entityTypes?: XRHitTestTrackableType[];
  38. }
  39. /**
  40. * Interface defining the babylon result of hit-test
  41. */
  42. export interface IWebXRHitResult extends IWebXRLegacyHitResult {
  43. /**
  44. * The input source that generated this hit test (if transient)
  45. */
  46. inputSource?: XRInputSource;
  47. /**
  48. * Is this a transient hit test
  49. */
  50. isTransient?: boolean;
  51. /**
  52. * Position of the hit test result
  53. */
  54. position: Vector3;
  55. /**
  56. * Rotation of the hit test result
  57. */
  58. rotationQuaternion: Quaternion;
  59. /**
  60. * The native hit test result
  61. */
  62. xrHitResult: XRHitTestResult;
  63. }
  64. /**
  65. * The currently-working hit-test module.
  66. * Hit test (or Ray-casting) is used to interact with the real world.
  67. * For further information read here - https://github.com/immersive-web/hit-test
  68. *
  69. * Tested on chrome (mobile) 80.
  70. */
  71. export class WebXRHitTest extends WebXRAbstractFeature implements IWebXRHitTestFeature<IWebXRHitResult> {
  72. private _tmpMat: Matrix = new Matrix();
  73. private _tmpPos: Vector3 = new Vector3();
  74. private _tmpQuat: Quaternion = new Quaternion();
  75. private _transientXrHitTestSource: Nullable<XRTransientInputHitTestSource>;
  76. // in XR space z-forward is negative
  77. private _xrHitTestSource: Nullable<XRHitTestSource>;
  78. private initHitTestSource = (referenceSpace: XRReferenceSpace) => {
  79. if (!referenceSpace) {
  80. return;
  81. }
  82. const offsetRay = new XRRay(this.options.offsetRay || {});
  83. const hitTestOptions: XRHitTestOptionsInit = {
  84. space: this.options.useReferenceSpace ? referenceSpace : this._xrSessionManager.viewerReferenceSpace,
  85. offsetRay: offsetRay,
  86. };
  87. if (this.options.entityTypes) {
  88. hitTestOptions.entityTypes = this.options.entityTypes;
  89. }
  90. if (!hitTestOptions.space) {
  91. Tools.Warn("waiting for viewer reference space to initialize");
  92. return;
  93. }
  94. this._xrSessionManager.session.requestHitTestSource(hitTestOptions).then((hitTestSource) => {
  95. if (this._xrHitTestSource) {
  96. this._xrHitTestSource.cancel();
  97. }
  98. this._xrHitTestSource = hitTestSource;
  99. });
  100. };
  101. /**
  102. * The module's name
  103. */
  104. public static readonly Name = WebXRFeatureName.HIT_TEST;
  105. /**
  106. * The (Babylon) version of this module.
  107. * This is an integer representing the implementation version.
  108. * This number does not correspond to the WebXR specs version
  109. */
  110. public static readonly Version = 2;
  111. /**
  112. * When set to true, each hit test will have its own position/rotation objects
  113. * When set to false, position and rotation objects will be reused for each hit test. It is expected that
  114. * the developers will clone them or copy them as they see fit.
  115. */
  116. public autoCloneTransformation: boolean = false;
  117. /**
  118. * Triggered when new babylon (transformed) hit test results are available
  119. */
  120. public onHitTestResultObservable: Observable<IWebXRHitResult[]> = new Observable();
  121. /**
  122. * Use this to temporarily pause hit test checks.
  123. */
  124. public paused: boolean = false;
  125. /**
  126. * Creates a new instance of the hit test feature
  127. * @param _xrSessionManager an instance of WebXRSessionManager
  128. * @param options options to use when constructing this feature
  129. */
  130. constructor(
  131. _xrSessionManager: WebXRSessionManager,
  132. /**
  133. * options to use when constructing this feature
  134. */
  135. public readonly options: IWebXRHitTestOptions = {}
  136. ) {
  137. super(_xrSessionManager);
  138. this.xrNativeFeatureName = "hit-test";
  139. Tools.Warn("Hit test is an experimental and unstable feature.");
  140. }
  141. /**
  142. * attach this feature
  143. * Will usually be called by the features manager
  144. *
  145. * @returns true if successful.
  146. */
  147. public attach(): boolean {
  148. if (!super.attach()) {
  149. return false;
  150. }
  151. // Feature enabled, but not available
  152. if (!this._xrSessionManager.session.requestHitTestSource) {
  153. return false;
  154. }
  155. if (!this.options.disablePermanentHitTest) {
  156. if (this._xrSessionManager.referenceSpace) {
  157. this.initHitTestSource(this._xrSessionManager.referenceSpace);
  158. }
  159. this._xrSessionManager.onXRReferenceSpaceChanged.add(this.initHitTestSource);
  160. }
  161. if (this.options.enableTransientHitTest) {
  162. const offsetRay = new XRRay(this.options.transientOffsetRay || {});
  163. this._xrSessionManager.session
  164. .requestHitTestSourceForTransientInput({
  165. profile: "generic-touchscreen",
  166. offsetRay,
  167. entityTypes: this.options.entityTypes
  168. })
  169. .then((hitSource) => {
  170. this._transientXrHitTestSource = hitSource;
  171. });
  172. }
  173. return true;
  174. }
  175. /**
  176. * detach this feature.
  177. * Will usually be called by the features manager
  178. *
  179. * @returns true if successful.
  180. */
  181. public detach(): boolean {
  182. if (!super.detach()) {
  183. return false;
  184. }
  185. if (this._xrHitTestSource) {
  186. this._xrHitTestSource.cancel();
  187. this._xrHitTestSource = null;
  188. }
  189. this._xrSessionManager.onXRReferenceSpaceChanged.removeCallback(this.initHitTestSource);
  190. if (this._transientXrHitTestSource) {
  191. this._transientXrHitTestSource.cancel();
  192. this._transientXrHitTestSource = null;
  193. }
  194. return true;
  195. }
  196. /**
  197. * Dispose this feature and all of the resources attached
  198. */
  199. public dispose(): void {
  200. super.dispose();
  201. this.onHitTestResultObservable.clear();
  202. }
  203. protected _onXRFrame(frame: XRFrame) {
  204. // make sure we do nothing if (async) not attached
  205. if (!this.attached || this.paused) {
  206. return;
  207. }
  208. if (this._xrHitTestSource) {
  209. const results = frame.getHitTestResults(this._xrHitTestSource);
  210. if (results.length) {
  211. this._processWebXRHitTestResult(results);
  212. }
  213. }
  214. if (this._transientXrHitTestSource) {
  215. let hitTestResultsPerInputSource = frame.getHitTestResultsForTransientInput(this._transientXrHitTestSource);
  216. hitTestResultsPerInputSource.forEach((resultsPerInputSource) => {
  217. if (resultsPerInputSource.results.length > 0) {
  218. this._processWebXRHitTestResult(resultsPerInputSource.results, resultsPerInputSource.inputSource);
  219. }
  220. });
  221. }
  222. }
  223. private _processWebXRHitTestResult(hitTestResults: XRHitTestResult[], inputSource?: XRInputSource) {
  224. const results: IWebXRHitResult[] = [];
  225. hitTestResults.forEach((hitTestResult) => {
  226. const pose = hitTestResult.getPose(this._xrSessionManager.referenceSpace);
  227. if (!pose) {
  228. return;
  229. }
  230. const pos = pose.transform.position;
  231. const quat = pose.transform.orientation;
  232. this._tmpPos.set(pos.x, pos.y, pos.z);
  233. this._tmpQuat.set(quat.x, quat.y, quat.z, quat.w);
  234. Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMat);
  235. if (!this._xrSessionManager.scene.useRightHandedSystem) {
  236. this._tmpPos.z *= -1;
  237. this._tmpQuat.z *= -1;
  238. this._tmpQuat.w *= -1;
  239. this._tmpMat.toggleModelMatrixHandInPlace();
  240. }
  241. const result: IWebXRHitResult = {
  242. position: this.autoCloneTransformation ? this._tmpPos.clone() : this._tmpPos,
  243. rotationQuaternion: this.autoCloneTransformation ? this._tmpQuat.clone() : this._tmpQuat,
  244. transformationMatrix: this.autoCloneTransformation ? this._tmpMat.clone() : this._tmpMat,
  245. inputSource: inputSource,
  246. isTransient: !!inputSource,
  247. xrHitResult: hitTestResult,
  248. };
  249. results.push(result);
  250. });
  251. if (results.length) {
  252. this.onHitTestResultObservable.notifyObservers(results);
  253. }
  254. }
  255. }
  256. //register the plugin versions
  257. WebXRFeaturesManager.AddWebXRFeature(
  258. WebXRHitTest.Name,
  259. (xrSessionManager, options) => {
  260. return () => new WebXRHitTest(xrSessionManager, options);
  261. },
  262. WebXRHitTest.Version,
  263. false
  264. );