webXRInputSource.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import { Observable } from "../Misc/observable.js";
  2. import { AbstractMesh } from "../Meshes/abstractMesh.js";
  3. import { Quaternion, Vector3 } from "../Maths/math.vector.js";
  4. import { WebXRMotionControllerManager } from "./motionController/webXRMotionControllerManager.js";
  5. import { Tools } from "../Misc/tools.js";
  6. let idCount = 0;
  7. /**
  8. * Represents an XR controller
  9. */
  10. export class WebXRInputSource {
  11. /**
  12. * Creates the input source object
  13. * @see https://doc.babylonjs.com/features/featuresDeepDive/webXR/webXRInputControllerSupport
  14. * @param _scene the scene which the controller should be associated to
  15. * @param inputSource the underlying input source for the controller
  16. * @param _options options for this controller creation
  17. */
  18. constructor(_scene,
  19. /** The underlying input source for the controller */
  20. inputSource, _options = {}) {
  21. this._scene = _scene;
  22. this.inputSource = inputSource;
  23. this._options = _options;
  24. this._tmpVector = new Vector3();
  25. this._disposed = false;
  26. /**
  27. * Event that fires when the controller is removed/disposed.
  28. * The object provided as event data is this controller, after associated assets were disposed.
  29. * uniqueId is still available.
  30. */
  31. this.onDisposeObservable = new Observable();
  32. /**
  33. * Will be triggered when the mesh associated with the motion controller is done loading.
  34. * It is also possible that this will never trigger (!) if no mesh was loaded, or if the developer decides to load a different mesh
  35. * A shortened version of controller -> motion controller -> on mesh loaded.
  36. */
  37. this.onMeshLoadedObservable = new Observable();
  38. /**
  39. * Observers registered here will trigger when a motion controller profile was assigned to this xr controller
  40. */
  41. this.onMotionControllerInitObservable = new Observable();
  42. this._uniqueId = `controller-${idCount++}-${inputSource.targetRayMode}-${inputSource.handedness}`;
  43. this.pointer = new AbstractMesh(`${this._uniqueId}-pointer`, _scene);
  44. this.pointer.rotationQuaternion = new Quaternion();
  45. if (this.inputSource.gripSpace) {
  46. this.grip = new AbstractMesh(`${this._uniqueId}-grip`, this._scene);
  47. this.grip.rotationQuaternion = new Quaternion();
  48. }
  49. this._tmpVector.set(0, 0, this._scene.useRightHandedSystem ? -1.0 : 1.0);
  50. // for now only load motion controllers if gamepad object available
  51. if (this.inputSource.gamepad && this.inputSource.targetRayMode === "tracked-pointer") {
  52. WebXRMotionControllerManager.GetMotionControllerWithXRInput(inputSource, _scene, this._options.forceControllerProfile).then((motionController) => {
  53. this.motionController = motionController;
  54. this.onMotionControllerInitObservable.notifyObservers(motionController);
  55. // should the model be loaded?
  56. if (!this._options.doNotLoadControllerMesh && !this.motionController._doNotLoadControllerMesh) {
  57. this.motionController.loadModel().then((success) => {
  58. if (success && this.motionController && this.motionController.rootMesh) {
  59. if (this._options.renderingGroupId) {
  60. // anything other than 0?
  61. this.motionController.rootMesh.renderingGroupId = this._options.renderingGroupId;
  62. this.motionController.rootMesh.getChildMeshes(false).forEach((mesh) => (mesh.renderingGroupId = this._options.renderingGroupId));
  63. }
  64. this.onMeshLoadedObservable.notifyObservers(this.motionController.rootMesh);
  65. this.motionController.rootMesh.parent = this.grip || this.pointer;
  66. this.motionController.disableAnimation = !!this._options.disableMotionControllerAnimation;
  67. }
  68. // make sure to dispose is the controller is already disposed
  69. if (this._disposed) {
  70. this.motionController?.dispose();
  71. }
  72. });
  73. }
  74. }, () => {
  75. Tools.Warn(`Could not find a matching motion controller for the registered input source`);
  76. });
  77. }
  78. }
  79. /**
  80. * Get this controllers unique id
  81. */
  82. get uniqueId() {
  83. return this._uniqueId;
  84. }
  85. /**
  86. * Disposes of the object
  87. */
  88. dispose() {
  89. if (this.grip) {
  90. this.grip.dispose(true);
  91. }
  92. if (this.motionController) {
  93. this.motionController.dispose();
  94. }
  95. this.pointer.dispose(true);
  96. this.onMotionControllerInitObservable.clear();
  97. this.onMeshLoadedObservable.clear();
  98. this.onDisposeObservable.notifyObservers(this);
  99. this.onDisposeObservable.clear();
  100. this._disposed = true;
  101. }
  102. /**
  103. * Gets a world space ray coming from the pointer or grip
  104. * @param result the resulting ray
  105. * @param gripIfAvailable use the grip mesh instead of the pointer, if available
  106. */
  107. getWorldPointerRayToRef(result, gripIfAvailable = false) {
  108. const object = gripIfAvailable && this.grip ? this.grip : this.pointer;
  109. Vector3.TransformNormalToRef(this._tmpVector, object.getWorldMatrix(), result.direction);
  110. result.direction.normalize();
  111. result.origin.copyFrom(object.absolutePosition);
  112. result.length = 1000;
  113. }
  114. /**
  115. * Updates the controller pose based on the given XRFrame
  116. * @param xrFrame xr frame to update the pose with
  117. * @param referenceSpace reference space to use
  118. * @param xrCamera the xr camera, used for parenting
  119. * @param xrSessionManager the session manager used to get the world reference system
  120. */
  121. updateFromXRFrame(xrFrame, referenceSpace, xrCamera, xrSessionManager) {
  122. const pose = xrFrame.getPose(this.inputSource.targetRaySpace, referenceSpace);
  123. this._lastXRPose = pose;
  124. // Update the pointer mesh
  125. if (pose) {
  126. const pos = pose.transform.position;
  127. this.pointer.position.set(pos.x, pos.y, pos.z).scaleInPlace(xrSessionManager.worldScalingFactor);
  128. const orientation = pose.transform.orientation;
  129. this.pointer.rotationQuaternion.set(orientation.x, orientation.y, orientation.z, orientation.w);
  130. if (!this._scene.useRightHandedSystem) {
  131. this.pointer.position.z *= -1;
  132. this.pointer.rotationQuaternion.z *= -1;
  133. this.pointer.rotationQuaternion.w *= -1;
  134. }
  135. this.pointer.parent = xrCamera.parent;
  136. this.pointer.scaling.setAll(xrSessionManager.worldScalingFactor);
  137. }
  138. // Update the grip mesh if it exists
  139. if (this.inputSource.gripSpace && this.grip) {
  140. const pose = xrFrame.getPose(this.inputSource.gripSpace, referenceSpace);
  141. if (pose) {
  142. const pos = pose.transform.position;
  143. const orientation = pose.transform.orientation;
  144. this.grip.position.set(pos.x, pos.y, pos.z).scaleInPlace(xrSessionManager.worldScalingFactor);
  145. this.grip.rotationQuaternion.set(orientation.x, orientation.y, orientation.z, orientation.w);
  146. if (!this._scene.useRightHandedSystem) {
  147. this.grip.position.z *= -1;
  148. this.grip.rotationQuaternion.z *= -1;
  149. this.grip.rotationQuaternion.w *= -1;
  150. }
  151. }
  152. this.grip.parent = xrCamera.parent;
  153. this.grip.scaling.setAll(xrSessionManager.worldScalingFactor);
  154. }
  155. if (this.motionController) {
  156. // either update buttons only or also position, if in gamepad mode
  157. this.motionController.updateFromXRFrame(xrFrame);
  158. }
  159. }
  160. }
  161. //# sourceMappingURL=webXRInputSource.js.map