webXRInput.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { Nullable } from "../../types";
  2. import { Observer, Observable } from "../../Misc/observable";
  3. import { IDisposable, Scene } from "../../scene";
  4. import { AbstractMesh } from "../../Meshes/abstractMesh";
  5. import { WebXRExperienceHelper } from "./webXRExperienceHelper";
  6. import { Matrix, Quaternion } from '../../Maths/math';
  7. /**
  8. * Represents an XR input
  9. */
  10. export class WebXRController {
  11. /**
  12. * 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
  13. */
  14. public grip: AbstractMesh;
  15. /**
  16. * Pointer which can be used to select objects or attach a visible laser to
  17. */
  18. public pointer: AbstractMesh;
  19. private _tmpMatrix = new Matrix();
  20. /**
  21. * Creates the controller
  22. * @see https://doc.babylonjs.com/how_to/webxr
  23. * @param scene the scene which the controller should be associated to
  24. */
  25. constructor(scene: Scene, public inputSource:XRInputSource, parentContainer:Nullable<AbstractMesh> = null) {
  26. this.pointer = new AbstractMesh("controllerPointer", scene);
  27. this.grip = new AbstractMesh("controllerGrip", scene);
  28. if(parentContainer){
  29. parentContainer.addChild(this.pointer)
  30. parentContainer.addChild(this.grip)
  31. }
  32. }
  33. updateFromXRFrame(xrFrame:XRFrame, referenceSpaceType:XRReferenceSpaceType){
  34. var pose = xrFrame.getPose(this.inputSource.targetRaySpace, referenceSpaceType)
  35. if(pose){
  36. Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMatrix);
  37. if (!this.pointer.getScene().useRightHandedSystem) {
  38. this._tmpMatrix.toggleModelMatrixHandInPlace();
  39. }
  40. if (!this.pointer.rotationQuaternion) {
  41. this.pointer.rotationQuaternion = new Quaternion();
  42. }
  43. this._tmpMatrix.decompose(this.pointer.scaling, this.pointer.rotationQuaternion!, this.pointer.position);
  44. }
  45. var pose = xrFrame.getPose(this.inputSource.gripSpace, referenceSpaceType)
  46. if(pose){
  47. Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMatrix);
  48. if (!this.pointer.getScene().useRightHandedSystem) {
  49. this._tmpMatrix.toggleModelMatrixHandInPlace();
  50. }
  51. if (!this.grip.rotationQuaternion) {
  52. this.grip.rotationQuaternion = new Quaternion();
  53. }
  54. this._tmpMatrix.decompose(this.grip.scaling, this.grip.rotationQuaternion!, this.grip.position);
  55. }
  56. }
  57. /**
  58. * Disposes of the object
  59. */
  60. dispose() {
  61. if (this.grip) {
  62. this.grip.dispose();
  63. }
  64. this.pointer.dispose();
  65. }
  66. }
  67. /**
  68. * XR input used to track XR inputs such as controllers/rays
  69. */
  70. export class WebXRInput implements IDisposable {
  71. /**
  72. * XR controllers being tracked
  73. */
  74. public controllers: Array<WebXRController> = [];
  75. private _frameObserver: Nullable<Observer<any>>;
  76. public onControllerAddedObservable = new Observable<WebXRController>();
  77. public onControllerRemovedObservable = new Observable<WebXRController>();
  78. /**
  79. * Initializes the WebXRInput
  80. * @param helper experience helper which the input should be created for
  81. */
  82. public constructor(private helper: WebXRExperienceHelper) {
  83. this._frameObserver = helper.sessionManager.onXRFrameObservable.add(() => {
  84. if (!helper.sessionManager.currentFrame) {
  85. return;
  86. }
  87. // Start listing to input add/remove event
  88. if(this.controllers.length == 0 && helper.sessionManager.session.inputSources){
  89. this._addAndRemoveControllers(helper.sessionManager.session.inputSources, []);
  90. helper.sessionManager.session.addEventListener("inputsourceschange", this._onInputSourcesChange)
  91. }
  92. // Update controller pose info
  93. this.controllers.forEach((controller)=>{
  94. controller.updateFromXRFrame(helper.sessionManager.currentFrame!, helper.sessionManager.referenceSpaceType)
  95. })
  96. })
  97. }
  98. private _onInputSourcesChange = (event:XRInputSourceChangeEvent )=>{
  99. this._addAndRemoveControllers(event.added, event.removed)
  100. }
  101. private _addAndRemoveControllers(addInputs:Array<XRInputSource>, removeInputs:Array<XRInputSource>){
  102. // Add controllers if they don't already exist
  103. var sources = this.controllers.map((c)=>{return c.inputSource});
  104. addInputs.forEach((input)=>{
  105. if(sources.indexOf(input) === -1){
  106. var controller = new WebXRController(this.helper.camera._scene, input, this.helper.container);
  107. this.controllers.push(controller)
  108. this.onControllerAddedObservable.notifyObservers(controller)
  109. }
  110. })
  111. // Remove and dispose of controllers to be disposed
  112. var keepControllers: Array<WebXRController> = []
  113. var removedControllers: Array<WebXRController> = []
  114. this.controllers.forEach((c)=>{
  115. if(removeInputs.indexOf(c.inputSource) === -1){
  116. keepControllers.push(c)
  117. }else{
  118. removedControllers.push(c);
  119. }
  120. })
  121. this.controllers = keepControllers;
  122. removedControllers.forEach((c)=>{
  123. this.onControllerRemovedObservable.notifyObservers(c);
  124. c.dispose()
  125. })
  126. }
  127. /**
  128. * Disposes of the object
  129. */
  130. public dispose() {
  131. this.controllers.forEach((c) => {
  132. c.dispose();
  133. });
  134. this.helper.sessionManager.onXRFrameObservable.remove(this._frameObserver);
  135. }
  136. }