WebXRHitTest.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { WebXRFeature, 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. const Name = "xr-hit-test";
  8. //register the plugin
  9. WebXRFeaturesManager.AddWebXRFeature(Name, (xrSessionManager, options) => {
  10. return () => new WebXRHitTest(xrSessionManager, options);
  11. });
  12. export interface WebXRHitTestOptions {
  13. testOnPointerDownOnly?: boolean;
  14. worldParentNode?: TransformNode;
  15. }
  16. export interface WebXRHitResult {
  17. xrHitResult: XRHitResult;
  18. transformationMatrix: Matrix;
  19. }
  20. export type WebXRHitResults = WebXRHitResult[];
  21. export class WebXRHitTest implements WebXRFeature {
  22. public static readonly Name = Name;
  23. public static XRHitTestWithSelectEvent(event: XRInputSourceEvent, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]> {
  24. let targetRayPose = event.frame.getPose(event.inputSource.targetRaySpace, referenceSpace);
  25. if (!targetRayPose) {
  26. return Promise.resolve([]);
  27. }
  28. let targetRay = new XRRay(targetRayPose.transform);
  29. return this.XRHitTestWithRay(event.frame.session, targetRay, referenceSpace);
  30. }
  31. public static XRHitTestWithRay(xrSession: XRSession, xrRay: XRRay, referenceSpace: XRReferenceSpace, filter?: (result: XRHitResult) => boolean): Promise<XRHitResult[]> {
  32. return xrSession.requestHitTest(xrRay, referenceSpace).then((results) => {
  33. const filterFunction = filter || ((result) => !!result.hitMatrix);
  34. return results.filter(filterFunction);
  35. });
  36. }
  37. public onHitTestResultObservable: Observable<WebXRHitResults> = new Observable();
  38. constructor(private xrSessionManager: WebXRSessionManager, public readonly options: WebXRHitTestOptions = {}) {
  39. }
  40. private _onSelectEnabled = false;
  41. private _xrFrameObserver: Nullable<Observer<XRFrame>>;
  42. private _attached: boolean = false;
  43. public lastNativeXRHitResults: XRHitResult[] = [];
  44. attach(): boolean {
  45. if (this.options.testOnPointerDownOnly) {
  46. this.xrSessionManager.session.addEventListener('select', this.onSelect, false);
  47. } else {
  48. // we are in XR space!
  49. const origin = new Vector3(0, 0, 0);
  50. // in XR space z-forward is negative
  51. const direction = new Vector3(0, 0, -1);
  52. const mat = new Matrix();
  53. this._xrFrameObserver = this.xrSessionManager.onXRFrameObservable.add((frame) => {
  54. // make sure we do nothing if (async) not attached
  55. if (!this._attached) {
  56. return;
  57. }
  58. let pose = frame.getViewerPose(this.xrSessionManager.referenceSpace);
  59. if (!pose) {
  60. return;
  61. }
  62. Matrix.FromArrayToRef(pose.transform.matrix, 0, mat);
  63. Vector3.TransformCoordinatesFromFloatsToRef(0, 0, 0, mat, origin);
  64. Vector3.TransformCoordinatesFromFloatsToRef(0, 0, -1, mat, direction);
  65. direction.subtractInPlace(origin);
  66. direction.normalize();
  67. let ray = new XRRay((<DOMPointReadOnly>{ x: origin.x, y: origin.y, z: origin.z, w: 0 }),
  68. (<DOMPointReadOnly>{ x: direction.x, y: direction.y, z: direction.z, w: 0 }));
  69. WebXRHitTest.XRHitTestWithRay(this.xrSessionManager.session, ray, this.xrSessionManager.referenceSpace).then(this.onHitTestResults);
  70. });
  71. }
  72. this._attached = true;
  73. return true;
  74. }
  75. detach(): boolean {
  76. // disable select
  77. this._onSelectEnabled = false;
  78. this.xrSessionManager.session.removeEventListener('select', this.onSelect);
  79. if (this._xrFrameObserver) {
  80. this.xrSessionManager.onXRFrameObservable.remove(this._xrFrameObserver);
  81. this._xrFrameObserver = null;
  82. }
  83. this._attached = false;
  84. return true;
  85. }
  86. private onHitTestResults = (xrResults: XRHitResult[]) => {
  87. const mats = xrResults.map((result) => {
  88. let mat = Matrix.FromArray(result.hitMatrix);
  89. if (!this.xrSessionManager.scene.useRightHandedSystem) {
  90. mat.toggleModelMatrixHandInPlace();
  91. }
  92. // if (this.options.coordinatesSpace === Space.WORLD) {
  93. if (this.options.worldParentNode) {
  94. mat.multiplyToRef(this.options.worldParentNode.getWorldMatrix(), mat);
  95. }
  96. return {
  97. xrHitResult: result,
  98. transformationMatrix: mat
  99. };
  100. });
  101. this.lastNativeXRHitResults = xrResults;
  102. this.onHitTestResultObservable.notifyObservers(mats);
  103. }
  104. // can be done using pointerdown event, and xrSessionManager.currentFrame
  105. private onSelect = (event: XRInputSourceEvent) => {
  106. if (!this._onSelectEnabled) {
  107. return;
  108. }
  109. WebXRHitTest.XRHitTestWithSelectEvent(event, this.xrSessionManager.referenceSpace);
  110. }
  111. dispose(): void {
  112. this.detach();
  113. this.onHitTestResultObservable.clear();
  114. }
  115. }