webXRProfiledMotionController.ts 6.7 KB

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