WebXRHitTest.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { WebXRFeaturesManager, WebXRFeatureName } from "../webXRFeaturesManager.js";
  2. import { Observable } from "../../Misc/observable.js";
  3. import { Vector3, Matrix, Quaternion } from "../../Maths/math.vector.js";
  4. import { WebXRAbstractFeature } from "./WebXRAbstractFeature.js";
  5. import { Tools } from "../../Misc/tools.js";
  6. /**
  7. * The currently-working hit-test module.
  8. * Hit test (or Ray-casting) is used to interact with the real world.
  9. * For further information read here - https://github.com/immersive-web/hit-test
  10. *
  11. * Tested on chrome (mobile) 80.
  12. */
  13. export class WebXRHitTest extends WebXRAbstractFeature {
  14. /**
  15. * Creates a new instance of the hit test feature
  16. * @param _xrSessionManager an instance of WebXRSessionManager
  17. * @param options options to use when constructing this feature
  18. */
  19. constructor(_xrSessionManager,
  20. /**
  21. * options to use when constructing this feature
  22. */
  23. options = {}) {
  24. super(_xrSessionManager);
  25. this.options = options;
  26. this._tmpMat = new Matrix();
  27. this._tmpPos = new Vector3();
  28. this._tmpQuat = new Quaternion();
  29. this._initHitTestSource = (referenceSpace) => {
  30. if (!referenceSpace) {
  31. return;
  32. }
  33. const offsetRay = new XRRay(this.options.offsetRay || {});
  34. const hitTestOptions = {
  35. space: this.options.useReferenceSpace ? referenceSpace : this._xrSessionManager.viewerReferenceSpace,
  36. offsetRay: offsetRay,
  37. };
  38. if (this.options.entityTypes) {
  39. hitTestOptions.entityTypes = this.options.entityTypes;
  40. }
  41. if (!hitTestOptions.space) {
  42. Tools.Warn("waiting for viewer reference space to initialize");
  43. return;
  44. }
  45. this._xrSessionManager.session.requestHitTestSource(hitTestOptions).then((hitTestSource) => {
  46. if (this._xrHitTestSource) {
  47. this._xrHitTestSource.cancel();
  48. }
  49. this._xrHitTestSource = hitTestSource;
  50. });
  51. };
  52. /**
  53. * When set to true, each hit test will have its own position/rotation objects
  54. * When set to false, position and rotation objects will be reused for each hit test. It is expected that
  55. * the developers will clone them or copy them as they see fit.
  56. */
  57. this.autoCloneTransformation = false;
  58. /**
  59. * Triggered when new babylon (transformed) hit test results are available
  60. * Note - this will be called when results come back from the device. It can be an empty array!!
  61. */
  62. this.onHitTestResultObservable = new Observable();
  63. /**
  64. * Use this to temporarily pause hit test checks.
  65. */
  66. this.paused = false;
  67. this.xrNativeFeatureName = "hit-test";
  68. Tools.Warn("Hit test is an experimental and unstable feature.");
  69. }
  70. /**
  71. * attach this feature
  72. * Will usually be called by the features manager
  73. *
  74. * @returns true if successful.
  75. */
  76. attach() {
  77. if (!super.attach()) {
  78. return false;
  79. }
  80. // Feature enabled, but not available
  81. if (!this._xrSessionManager.session.requestHitTestSource) {
  82. return false;
  83. }
  84. if (!this.options.disablePermanentHitTest) {
  85. if (this._xrSessionManager.referenceSpace) {
  86. this._initHitTestSource(this._xrSessionManager.referenceSpace);
  87. }
  88. this._xrSessionManager.onXRReferenceSpaceChanged.add(this._initHitTestSource);
  89. }
  90. if (this.options.enableTransientHitTest) {
  91. const offsetRay = new XRRay(this.options.transientOffsetRay || {});
  92. this._xrSessionManager.session.requestHitTestSourceForTransientInput({
  93. profile: this.options.transientHitTestProfile || "generic-touchscreen",
  94. offsetRay,
  95. entityTypes: this.options.entityTypes,
  96. }).then((hitSource) => {
  97. this._transientXrHitTestSource = hitSource;
  98. });
  99. }
  100. return true;
  101. }
  102. /**
  103. * detach this feature.
  104. * Will usually be called by the features manager
  105. *
  106. * @returns true if successful.
  107. */
  108. detach() {
  109. if (!super.detach()) {
  110. return false;
  111. }
  112. if (this._xrHitTestSource) {
  113. this._xrHitTestSource.cancel();
  114. this._xrHitTestSource = null;
  115. }
  116. this._xrSessionManager.onXRReferenceSpaceChanged.removeCallback(this._initHitTestSource);
  117. if (this._transientXrHitTestSource) {
  118. this._transientXrHitTestSource.cancel();
  119. this._transientXrHitTestSource = null;
  120. }
  121. return true;
  122. }
  123. /**
  124. * Dispose this feature and all of the resources attached
  125. */
  126. dispose() {
  127. super.dispose();
  128. this.onHitTestResultObservable.clear();
  129. }
  130. _onXRFrame(frame) {
  131. // make sure we do nothing if (async) not attached
  132. if (!this.attached || this.paused) {
  133. return;
  134. }
  135. if (this._xrHitTestSource) {
  136. const results = frame.getHitTestResults(this._xrHitTestSource);
  137. this._processWebXRHitTestResult(results);
  138. }
  139. if (this._transientXrHitTestSource) {
  140. const hitTestResultsPerInputSource = frame.getHitTestResultsForTransientInput(this._transientXrHitTestSource);
  141. hitTestResultsPerInputSource.forEach((resultsPerInputSource) => {
  142. this._processWebXRHitTestResult(resultsPerInputSource.results, resultsPerInputSource.inputSource);
  143. });
  144. }
  145. }
  146. _processWebXRHitTestResult(hitTestResults, inputSource) {
  147. const results = [];
  148. hitTestResults.forEach((hitTestResult) => {
  149. const pose = hitTestResult.getPose(this._xrSessionManager.referenceSpace);
  150. if (!pose) {
  151. return;
  152. }
  153. const pos = pose.transform.position;
  154. const quat = pose.transform.orientation;
  155. this._tmpPos.set(pos.x, pos.y, pos.z).scaleInPlace(this._xrSessionManager.worldScalingFactor);
  156. this._tmpQuat.set(quat.x, quat.y, quat.z, quat.w);
  157. Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMat);
  158. if (!this._xrSessionManager.scene.useRightHandedSystem) {
  159. this._tmpPos.z *= -1;
  160. this._tmpQuat.z *= -1;
  161. this._tmpQuat.w *= -1;
  162. this._tmpMat.toggleModelMatrixHandInPlace();
  163. }
  164. const result = {
  165. position: this.autoCloneTransformation ? this._tmpPos.clone() : this._tmpPos,
  166. rotationQuaternion: this.autoCloneTransformation ? this._tmpQuat.clone() : this._tmpQuat,
  167. transformationMatrix: this.autoCloneTransformation ? this._tmpMat.clone() : this._tmpMat,
  168. inputSource: inputSource,
  169. isTransient: !!inputSource,
  170. xrHitResult: hitTestResult,
  171. };
  172. results.push(result);
  173. });
  174. this.onHitTestResultObservable.notifyObservers(results);
  175. }
  176. }
  177. /**
  178. * The module's name
  179. */
  180. WebXRHitTest.Name = WebXRFeatureName.HIT_TEST;
  181. /**
  182. * The (Babylon) version of this module.
  183. * This is an integer representing the implementation version.
  184. * This number does not correspond to the WebXR specs version
  185. */
  186. WebXRHitTest.Version = 2;
  187. //register the plugin versions
  188. WebXRFeaturesManager.AddWebXRFeature(WebXRHitTest.Name, (xrSessionManager, options) => {
  189. return () => new WebXRHitTest(xrSessionManager, options);
  190. }, WebXRHitTest.Version, false);
  191. //# sourceMappingURL=WebXRHitTest.js.map