webXRControllerPointerSelection.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import { Nullable } from "../../types";
  2. import { Vector3, Axis } from '../../Maths/math';
  3. import { Mesh } from '../../Meshes/mesh';
  4. import { Ray } from '../../Culling/ray';
  5. import { StandardMaterial } from '../../Materials/standardMaterial';
  6. import { WebXRInput } from './webXRInput';
  7. import { Color3 } from '../../Maths/math.color';
  8. /**
  9. * Handles pointer input automatically for the pointer of XR controllers
  10. */
  11. export class WebXRControllerPointerSelection {
  12. private static _idCounter = 0;
  13. private _tmpRay = new Ray(new Vector3(), new Vector3());
  14. /**
  15. * Creates a WebXRControllerPointerSelection
  16. * @param input input manager to setup pointer selection
  17. */
  18. constructor(input: WebXRInput) {
  19. input.onControllerAddedObservable.add((controller) => {
  20. let scene = controller.pointer.getScene();
  21. let laserPointer: Mesh;
  22. let cursorMesh: Mesh;
  23. let triggerDown = false;
  24. let id: number;
  25. id = WebXRControllerPointerSelection._idCounter++;
  26. // Create a laser pointer for the XR controller
  27. laserPointer = Mesh.CreateCylinder("laserPointer", 1, 0.0002, 0.004, 20, 1, scene, false);
  28. laserPointer.parent = controller.pointer;
  29. let laserPointerMaterial = new StandardMaterial("laserPointerMat", scene);
  30. laserPointerMaterial.emissiveColor = new Color3(0.7, 0.7, 0.7);
  31. laserPointerMaterial.alpha = 0.6;
  32. laserPointer.material = laserPointerMaterial;
  33. laserPointer.rotation.x = Math.PI / 2;
  34. this._updatePointerDistance(laserPointer, 1);
  35. laserPointer.isPickable = false;
  36. // Create a gaze tracker for the XR controlelr
  37. cursorMesh = Mesh.CreateTorus("gazeTracker", 0.0035 * 3, 0.0025 * 3, 20, scene, false);
  38. cursorMesh.bakeCurrentTransformIntoVertices();
  39. cursorMesh.isPickable = false;
  40. cursorMesh.isVisible = false;
  41. let targetMat = new StandardMaterial("targetMat", scene);
  42. targetMat.specularColor = Color3.Black();
  43. targetMat.emissiveColor = new Color3(0.7, 0.7, 0.7);
  44. targetMat.backFaceCulling = false;
  45. cursorMesh.material = targetMat;
  46. let renderObserver = scene.onBeforeRenderObservable.add(() => {
  47. // Every frame check collisions/input
  48. controller.getWorldPointerRayToRef(this._tmpRay);
  49. let pick = scene.pickWithRay(this._tmpRay);
  50. if (pick) {
  51. if (controller.inputSource.gamepad && controller.inputSource.gamepad.buttons[0] && controller.inputSource.gamepad.buttons[0].value > 0.7) {
  52. if (!triggerDown) {
  53. scene.simulatePointerDown(pick, { pointerId: id });
  54. }
  55. triggerDown = true;
  56. } else {
  57. if (triggerDown) {
  58. scene.simulatePointerUp(pick, { pointerId: id });
  59. }
  60. triggerDown = false;
  61. }
  62. scene.simulatePointerMove(pick, { pointerId: id });
  63. }
  64. if (pick && pick.pickedPoint && pick.hit) {
  65. // Update laser state
  66. this._updatePointerDistance(laserPointer, pick.distance);
  67. // Update cursor state
  68. cursorMesh.position.copyFrom(pick.pickedPoint);
  69. cursorMesh.scaling.x = Math.sqrt(pick.distance);
  70. cursorMesh.scaling.y = Math.sqrt(pick.distance);
  71. cursorMesh.scaling.z = Math.sqrt(pick.distance);
  72. // To avoid z-fighting
  73. let pickNormal = this._convertNormalToDirectionOfRay(pick.getNormal(), this._tmpRay);
  74. let deltaFighting = 0.002;
  75. cursorMesh.position.copyFrom(pick.pickedPoint);
  76. if (pickNormal) {
  77. let axis1 = Vector3.Cross(Axis.Y, pickNormal);
  78. let axis2 = Vector3.Cross(pickNormal, axis1);
  79. Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, cursorMesh.rotation);
  80. cursorMesh.position.addInPlace(pickNormal.scale(deltaFighting));
  81. }
  82. cursorMesh.isVisible = true;
  83. } else {
  84. cursorMesh.isVisible = false;
  85. }
  86. });
  87. controller.onDisposeObservable.addOnce(() => {
  88. laserPointer.dispose();
  89. cursorMesh.dispose();
  90. scene.onBeforeRenderObservable.remove(renderObserver);
  91. });
  92. });
  93. }
  94. private _convertNormalToDirectionOfRay(normal: Nullable<Vector3>, ray: Ray) {
  95. if (normal) {
  96. let angle = Math.acos(Vector3.Dot(normal, ray.direction));
  97. if (angle < Math.PI / 2) {
  98. normal.scaleInPlace(-1);
  99. }
  100. }
  101. return normal;
  102. }
  103. private _updatePointerDistance(_laserPointer: Mesh, distance: number = 100) {
  104. _laserPointer.scaling.y = distance;
  105. _laserPointer.position.z = distance / 2;
  106. }
  107. }