webXRController.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { Observable } from "../Misc/observable";
  2. import { AbstractMesh } from "../Meshes/abstractMesh";
  3. import { Quaternion, Vector3 } from '../Maths/math.vector';
  4. import { Ray } from '../Culling/ray';
  5. import { Scene } from '../scene';
  6. import { WebXRAbstractMotionController } from './motionController/webXRAbstractController';
  7. import { WebXRMotionControllerManager } from './motionController/webXRMotionControllerManager';
  8. let idCount = 0;
  9. /**
  10. * Configuration options for the WebXR controller creation
  11. */
  12. export interface IWebXRControllerOptions {
  13. /**
  14. * Force a specific controller type for this controller.
  15. * This can be used when creating your own profile or when testing different controllers
  16. */
  17. forceControllerProfile?: string;
  18. /**
  19. * Do not load the controller mesh, in case a different mesh needs to be loaded.
  20. */
  21. doNotLoadControllerMesh?: boolean;
  22. /**
  23. * Should the controller mesh be animated when a user interacts with it
  24. * The pressed buttons / thumbstick and touchpad animations will be disabled
  25. */
  26. disableMotionControllerAnimation?: boolean;
  27. }
  28. /**
  29. * Represents an XR controller
  30. */
  31. export class WebXRController {
  32. /**
  33. * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
  34. */
  35. public grip?: AbstractMesh;
  36. /**
  37. * Pointer which can be used to select objects or attach a visible laser to
  38. */
  39. public pointer: AbstractMesh;
  40. /**
  41. * If available, this is the gamepad object related to this controller.
  42. * Using this object it is possible to get click events and trackpad changes of the
  43. * webxr controller that is currently being used.
  44. */
  45. public motionController?: WebXRAbstractMotionController;
  46. /**
  47. * Observers registered here will trigger when a motion controller profile was assigned to this xr controller
  48. */
  49. public onMotionControllerInitObservable = new Observable<WebXRAbstractMotionController>();
  50. /**
  51. * Will be triggered when the mesh associated with the motion controller is done loading.
  52. * It is also possible that this will never trigger (!) if no mesh was loaded, or if the developer decides to load a different mesh
  53. * A shortened version of controller -> motion controller -> on mesh loaded.
  54. */
  55. public onMeshLoadedObservable = new Observable<AbstractMesh>();
  56. /**
  57. * Event that fires when the controller is removed/disposed.
  58. * The object provided as event data is this controller, after associated assets were disposed.
  59. * uniqueId is still available.
  60. */
  61. public onDisposeObservable = new Observable<WebXRController>();
  62. private _tmpQuaternion = new Quaternion();
  63. private _tmpVector = new Vector3();
  64. private _uniqueId: string;
  65. /**
  66. * Creates the controller
  67. * @see https://doc.babylonjs.com/how_to/webxr
  68. * @param _scene the scene which the controller should be associated to
  69. * @param inputSource the underlying input source for the controller
  70. * @param _options options for this controller creation
  71. */
  72. constructor(
  73. private _scene: Scene,
  74. /** The underlying input source for the controller */
  75. public inputSource: XRInputSource,
  76. private _options: IWebXRControllerOptions = {}) {
  77. this._uniqueId = `controller-${idCount++}-${inputSource.targetRayMode}-${inputSource.handedness}`;
  78. this.pointer = new AbstractMesh(`${this._uniqueId}-pointer`, _scene);
  79. this.pointer.rotationQuaternion = new Quaternion();
  80. if (this.inputSource.gripSpace) {
  81. this.grip = new AbstractMesh(`${this._uniqueId}-grip`, this._scene);
  82. this.grip.rotationQuaternion = new Quaternion();
  83. }
  84. // for now only load motion controllers if gamepad object available
  85. if (this.inputSource.gamepad) {
  86. WebXRMotionControllerManager.GetMotionControllerWithXRInput(inputSource, _scene, this._options.forceControllerProfile).then((motionController) => {
  87. this.motionController = motionController;
  88. this.onMotionControllerInitObservable.notifyObservers(motionController);
  89. // should the model be loaded?
  90. if (!this._options.doNotLoadControllerMesh) {
  91. this.motionController.loadModel().then((success) => {
  92. if (success) {
  93. this.onMeshLoadedObservable.notifyObservers(this.motionController!.rootMesh!);
  94. this.motionController!.rootMesh!.parent = this.grip || this.pointer;
  95. this.motionController!.disableAnimation = !!this._options.disableMotionControllerAnimation;
  96. }
  97. });
  98. }
  99. });
  100. }
  101. }
  102. /**
  103. * Get this controllers unique id
  104. */
  105. public get uniqueId() {
  106. return this._uniqueId;
  107. }
  108. /**
  109. * Updates the controller pose based on the given XRFrame
  110. * @param xrFrame xr frame to update the pose with
  111. * @param referenceSpace reference space to use
  112. */
  113. public updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace) {
  114. let pose = xrFrame.getPose(this.inputSource.targetRaySpace, referenceSpace);
  115. // Update the pointer mesh
  116. if (pose) {
  117. this.pointer.position.copyFrom(<any>(pose.transform.position));
  118. this.pointer.rotationQuaternion!.copyFrom(<any>(pose.transform.orientation));
  119. if (!this._scene.useRightHandedSystem) {
  120. this.pointer.position.z *= -1;
  121. this.pointer.rotationQuaternion!.z *= -1;
  122. this.pointer.rotationQuaternion!.w *= -1;
  123. }
  124. }
  125. // Update the grip mesh if it exists
  126. if (this.inputSource.gripSpace && this.grip) {
  127. let pose = xrFrame.getPose(this.inputSource.gripSpace, referenceSpace);
  128. if (pose) {
  129. this.grip.position.copyFrom(<any>(pose.transform.position));
  130. this.grip.rotationQuaternion!.copyFrom(<any>(pose.transform.orientation));
  131. if (!this._scene.useRightHandedSystem) {
  132. this.grip.position.z *= -1;
  133. this.grip.rotationQuaternion!.z *= -1;
  134. this.grip.rotationQuaternion!.w *= -1;
  135. }
  136. }
  137. }
  138. if (this.motionController) {
  139. // either update buttons only or also position, if in gamepad mode
  140. this.motionController.updateFromXRFrame(xrFrame);
  141. }
  142. }
  143. /**
  144. * Gets a world space ray coming from the controller
  145. * @param result the resulting ray
  146. */
  147. public getWorldPointerRayToRef(result: Ray) {
  148. let worldMatrix = this.pointer.computeWorldMatrix();
  149. worldMatrix.decompose(undefined, this._tmpQuaternion, undefined);
  150. this._tmpVector.set(0, 0, 1);
  151. this._tmpVector.rotateByQuaternionToRef(this._tmpQuaternion, this._tmpVector);
  152. result.origin.copyFrom(this.pointer.absolutePosition);
  153. result.direction.copyFrom(this._tmpVector);
  154. result.length = 1000;
  155. }
  156. /**
  157. * Disposes of the object
  158. */
  159. dispose() {
  160. if (this.grip) {
  161. this.grip.dispose();
  162. }
  163. if (this.motionController) {
  164. this.motionController.dispose();
  165. }
  166. this.pointer.dispose();
  167. this.onMotionControllerInitObservable.clear();
  168. this.onMeshLoadedObservable.clear();
  169. this.onDisposeObservable.notifyObservers(this);
  170. this.onDisposeObservable.clear();
  171. }
  172. }