webXRProfiledMotionController.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import { AbstractMesh } from "../../Meshes/abstractMesh";
  2. import { WebXRAbstractMotionController, IMotionControllerProfile, IMotionControllerMeshMap } from "./webXRAbstractMotionController";
  3. import { Scene } from "../../scene";
  4. import { SceneLoader } from "../../Loading/sceneLoader";
  5. import { Mesh } from "../../Meshes/mesh";
  6. import { Axis, Space } from "../../Maths/math.axis";
  7. import { Color3 } from "../../Maths/math.color";
  8. import { WebXRControllerComponent } from "./webXRControllerComponent";
  9. import { SphereBuilder } from "../../Meshes/Builders/sphereBuilder";
  10. import { StandardMaterial } from "../../Materials/standardMaterial";
  11. import { Logger } from "../../Misc/logger";
  12. /**
  13. * A profiled motion controller has its profile loaded from an online repository.
  14. * The class is responsible of loading the model, mapping the keys and enabling model-animations
  15. */
  16. export class WebXRProfiledMotionController extends WebXRAbstractMotionController {
  17. private _buttonMeshMapping: {
  18. [buttonName: string]: {
  19. mainMesh: AbstractMesh;
  20. states: {
  21. [state: string]: IMotionControllerMeshMap;
  22. };
  23. };
  24. } = {};
  25. private _touchDots: { [visKey: string]: AbstractMesh } = {};
  26. /**
  27. * The profile ID of this controller. Will be populated when the controller initializes.
  28. */
  29. public profileId: string;
  30. constructor(scene: Scene, xrInput: XRInputSource, _profile: IMotionControllerProfile, private _repositoryUrl: string) {
  31. super(scene, _profile.layouts[xrInput.handedness || "none"], xrInput.gamepad as any, xrInput.handedness);
  32. this.profileId = _profile.profileId;
  33. }
  34. public dispose() {
  35. super.dispose();
  36. Object.keys(this._touchDots).forEach((visResKey) => {
  37. this._touchDots[visResKey].dispose();
  38. });
  39. }
  40. protected _getFilenameAndPath(): { filename: string; path: string } {
  41. return {
  42. filename: this.layout.assetPath,
  43. path: `${this._repositoryUrl}/profiles/${this.profileId}/`,
  44. };
  45. }
  46. protected _getModelLoadingConstraints(): boolean {
  47. const glbLoaded = SceneLoader.IsPluginForExtensionAvailable(".glb");
  48. if (!glbLoaded) {
  49. Logger.Warn("glTF / glb loaded was not registered, using generic controller instead");
  50. }
  51. return glbLoaded;
  52. }
  53. protected _processLoadedModel(_meshes: AbstractMesh[]): void {
  54. this.getComponentIds().forEach((type) => {
  55. const componentInLayout = this.layout.components[type];
  56. this._buttonMeshMapping[type] = {
  57. mainMesh: this._getChildByName(this.rootMesh!, componentInLayout.rootNodeName),
  58. states: {},
  59. };
  60. Object.keys(componentInLayout.visualResponses).forEach((visualResponseKey) => {
  61. const visResponse = componentInLayout.visualResponses[visualResponseKey];
  62. if (visResponse.valueNodeProperty === "transform") {
  63. this._buttonMeshMapping[type].states[visualResponseKey] = {
  64. valueMesh: this._getChildByName(this.rootMesh!, visResponse.valueNodeName!),
  65. minMesh: this._getChildByName(this.rootMesh!, visResponse.minNodeName!),
  66. maxMesh: this._getChildByName(this.rootMesh!, visResponse.maxNodeName!),
  67. };
  68. } else {
  69. // visibility, usually for touchpads
  70. const nameOfMesh = componentInLayout.type === WebXRControllerComponent.TOUCHPAD_TYPE && componentInLayout.touchPointNodeName ? componentInLayout.touchPointNodeName : visResponse.valueNodeName!;
  71. this._buttonMeshMapping[type].states[visualResponseKey] = {
  72. valueMesh: this._getChildByName(this.rootMesh!, nameOfMesh),
  73. };
  74. if (componentInLayout.type === WebXRControllerComponent.TOUCHPAD_TYPE && !this._touchDots[visualResponseKey]) {
  75. const dot = SphereBuilder.CreateSphere(
  76. visualResponseKey + "dot",
  77. {
  78. diameter: 0.0015,
  79. segments: 8,
  80. },
  81. this.scene
  82. );
  83. dot.material = new StandardMaterial(visualResponseKey + "mat", this.scene);
  84. (<StandardMaterial>dot.material).diffuseColor = Color3.Red();
  85. dot.parent = this._buttonMeshMapping[type].states[visualResponseKey].valueMesh;
  86. dot.isVisible = false;
  87. this._touchDots[visualResponseKey] = dot;
  88. }
  89. }
  90. });
  91. });
  92. }
  93. protected _setRootMesh(meshes: AbstractMesh[]): void {
  94. this.rootMesh = new Mesh(this.profileId + "-" + this.handedness, this.scene);
  95. this.rootMesh.isPickable = false;
  96. let rootMesh;
  97. // Find the root node in the loaded glTF scene, and attach it as a child of 'parentMesh'
  98. for (let i = 0; i < meshes.length; i++) {
  99. let mesh = meshes[i];
  100. mesh.isPickable = false;
  101. if (!mesh.parent) {
  102. // Handle root node, attach to the new parentMesh
  103. rootMesh = mesh;
  104. }
  105. }
  106. if (rootMesh) {
  107. rootMesh.setParent(this.rootMesh);
  108. }
  109. if (!this.scene.useRightHandedSystem) {
  110. this.rootMesh.rotate(Axis.Y, Math.PI, Space.WORLD);
  111. }
  112. }
  113. protected _updateModel(_xrFrame: XRFrame): void {
  114. if (this.disableAnimation) {
  115. return;
  116. }
  117. this.getComponentIds().forEach((id) => {
  118. const component = this.getComponent(id);
  119. if (!component.hasChanges) {
  120. return;
  121. }
  122. const meshes = this._buttonMeshMapping[id];
  123. const componentInLayout = this.layout.components[id];
  124. Object.keys(componentInLayout.visualResponses).forEach((visualResponseKey) => {
  125. const visResponse = componentInLayout.visualResponses[visualResponseKey];
  126. let value = component.value;
  127. if (visResponse.componentProperty === "xAxis") {
  128. value = component.axes.x;
  129. } else if (visResponse.componentProperty === "yAxis") {
  130. value = component.axes.y;
  131. }
  132. if (visResponse.valueNodeProperty === "transform") {
  133. this._lerpTransform(meshes.states[visualResponseKey], value, visResponse.componentProperty !== "button");
  134. } else {
  135. // visibility
  136. meshes.states[visualResponseKey].valueMesh.isVisible = component.touched || component.pressed;
  137. if (this._touchDots[visualResponseKey]) {
  138. this._touchDots[visualResponseKey].isVisible = component.touched || component.pressed;
  139. }
  140. }
  141. });
  142. });
  143. }
  144. }