webXRARHitTest.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { WebXRFeature, WebXRFeaturesManager } from '../webXRFeaturesManager';
  2. import { WebXRSessionManager } from '../webXRSessionManager';
  3. import { Observable, Observer } from '../../../Misc/observable';
  4. import { Vector3, Matrix, Quaternion } from '../../../Maths/math';
  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 XRSession {
  13. }
  14. export interface WebXRHitTestOptions {
  15. testOnlyOnSelect?: boolean;
  16. nonXrSpaceNode?: TransformNode;
  17. }
  18. export class WebXRHitTest implements WebXRFeature {
  19. public static readonly Name = Name;
  20. public onHitTestResultObservable: Observable<Matrix[]> = new Observable();
  21. constructor(private xrSessionManager: WebXRSessionManager, private options: WebXRHitTestOptions = {}) {
  22. }
  23. readonly name: string = "ar-hit-test";
  24. private _onSelectEnabled = false;
  25. private _xrFrameObserver: Nullable<Observer<XRFrame>>;
  26. private _tmpMatrix = new Matrix();
  27. attachAsync(): Promise<boolean> {
  28. if (this.options.testOnlyOnSelect) {
  29. this.xrSessionManager.session.addEventListener('select', this.onSelect, false);
  30. } else {
  31. // we are in XR space!
  32. const origin = new Vector3(0, 0, 0);
  33. // in XR space z-forward is negative
  34. const direction = new Vector3(0, 0, -1);
  35. const mat = new Matrix();
  36. this._xrFrameObserver = this.xrSessionManager.onXRFrameObservable.add((frame) => {
  37. let pose = frame.getViewerPose(this.xrSessionManager.referenceSpace);
  38. if (!pose) {
  39. return;
  40. }
  41. Matrix.FromArrayToRef(pose.transform.matrix, 0, mat);
  42. Vector3.TransformCoordinatesFromFloatsToRef(0, 0, 0, mat, origin);
  43. Vector3.TransformCoordinatesFromFloatsToRef(0, 0, -1, mat, direction);
  44. direction.subtractInPlace(origin);
  45. direction.normalize();
  46. let ray = new XRRay((<DOMPointReadOnly>{ x: origin.x, y: origin.y, z: origin.z, w: 0 }),
  47. (<DOMPointReadOnly>{ x: direction.x, y: direction.y, z: direction.z, w: 0 }));
  48. this.requestHitTest(ray);
  49. });
  50. }
  51. return Promise.resolve(true);
  52. }
  53. detachAsync(): Promise<boolean> {
  54. // disable select
  55. this._onSelectEnabled = false;
  56. this.xrSessionManager.session.removeEventListener('select', this.onSelect);
  57. if (this._xrFrameObserver) {
  58. this.xrSessionManager.onXRFrameObservable.remove(this._xrFrameObserver);
  59. this._xrFrameObserver = null;
  60. }
  61. return Promise.resolve(true);
  62. }
  63. private requestHitTest(ray: XRRay) {
  64. this.xrSessionManager.session.requestHitTest(ray, this.xrSessionManager.referenceSpace).then((results) => {
  65. // convert to babylon world space and notify the matrices results
  66. const mats = results.map((result) => {
  67. let mat = Matrix.FromArray(result.hitMatrix);
  68. if (!this.xrSessionManager.scene.useRightHandedSystem) {
  69. mat.toggleModelMatrixHandInPlace();
  70. }
  71. if (this.options.nonXrSpaceNode) {
  72. const node = this.options.nonXrSpaceNode;
  73. Matrix.ComposeToRef(node.scaling, node.rotationQuaternion || new Quaternion(), node.position, this._tmpMatrix);
  74. mat.multiplyToRef(this._tmpMatrix, mat);
  75. }
  76. return mat;
  77. });
  78. this.onHitTestResultObservable.notifyObservers(mats);
  79. });
  80. }
  81. // can be done using pointerdown event, and onXRFrame...addOnce
  82. private onSelect = (event: XRInputSourceEvent) => {
  83. if (!this._onSelectEnabled) {
  84. return;
  85. }
  86. let targetRayPose = event.frame.getPose(event.inputSource.targetRaySpace, this.xrSessionManager.referenceSpace);
  87. if (!targetRayPose) {
  88. return;
  89. }
  90. let targetRay = new XRRay(targetRayPose.transform);
  91. this.requestHitTest(targetRay);
  92. }
  93. dispose(): void {
  94. this.detachAsync();
  95. this.onHitTestResultObservable.clear();
  96. }
  97. }