webXRProfiledMotionController.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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)
  71. ? componentInLayout.touchPointNodeName : visResponse.valueNodeName!;
  72. this._buttonMeshMapping[type].states[visualResponseKey] = {
  73. valueMesh: this._getChildByName(this.rootMesh!, nameOfMesh)
  74. };
  75. if (componentInLayout.type === WebXRControllerComponent.TOUCHPAD_TYPE && !this._touchDots[visualResponseKey]) {
  76. const dot = SphereBuilder.CreateSphere(visualResponseKey + 'dot', {
  77. diameter: 0.0015,
  78. segments: 8
  79. }, this.scene);
  80. dot.material = new StandardMaterial(visualResponseKey + 'mat', this.scene);
  81. (<StandardMaterial>dot.material).diffuseColor = Color3.Red();
  82. dot.parent = this._buttonMeshMapping[type].states[visualResponseKey].valueMesh;
  83. dot.isVisible = false;
  84. this._touchDots[visualResponseKey] = dot;
  85. }
  86. }
  87. });
  88. });
  89. }
  90. protected _setRootMesh(meshes: AbstractMesh[]): void {
  91. this.rootMesh = new Mesh(this.profileId + "-" + this.handness, this.scene);
  92. this.rootMesh.isPickable = false;
  93. let rootMesh;
  94. // Find the root node in the loaded glTF scene, and attach it as a child of 'parentMesh'
  95. for (let i = 0; i < meshes.length; i++) {
  96. let mesh = meshes[i];
  97. mesh.isPickable = false;
  98. if (!mesh.parent) {
  99. // Handle root node, attach to the new parentMesh
  100. rootMesh = mesh;
  101. }
  102. }
  103. if (rootMesh) {
  104. rootMesh.setParent(this.rootMesh);
  105. }
  106. this.rootMesh.rotate(Axis.Y, Math.PI, Space.WORLD);
  107. }
  108. protected _updateModel(_xrFrame: XRFrame): void {
  109. if (this.disableAnimation) {
  110. return;
  111. }
  112. this.getComponentIds().forEach((id) => {
  113. const component = this.getComponent(id);
  114. if (!component.hasChanges) { return; }
  115. const meshes = this._buttonMeshMapping[id];
  116. const componentInLayout = this.layout.components[id];
  117. Object.keys(componentInLayout.visualResponses).forEach((visualResponseKey) => {
  118. const visResponse = componentInLayout.visualResponses[visualResponseKey];
  119. let value = component.value;
  120. if (visResponse.componentProperty === "xAxis") {
  121. value = component.axes.x;
  122. } else if (visResponse.componentProperty === "yAxis") {
  123. value = component.axes.y;
  124. }
  125. if (visResponse.valueNodeProperty === "transform") {
  126. this._lerpTransform(meshes.states[visualResponseKey], value, visResponse.componentProperty !== "button");
  127. } else {
  128. // visibility
  129. meshes.states[visualResponseKey].valueMesh.isVisible = component.touched || component.pressed;
  130. if (this._touchDots[visualResponseKey]) {
  131. this._touchDots[visualResponseKey].isVisible = component.touched || component.pressed;
  132. }
  133. }
  134. });
  135. });
  136. }
  137. }