webXRMicrosoftMixedRealityController.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. import { WebXRAbstractMotionController } from "./webXRAbstractMotionController.js";
  2. import { WebXRMotionControllerManager } from "./webXRMotionControllerManager.js";
  3. import { Mesh } from "../../Meshes/mesh.js";
  4. import { Quaternion } from "../../Maths/math.vector.js";
  5. import { SceneLoader } from "../../Loading/sceneLoader.js";
  6. import { Logger } from "../../Misc/logger.js";
  7. /**
  8. * The motion controller class for all microsoft mixed reality controllers
  9. */
  10. export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
  11. constructor(scene, gamepadObject, handedness) {
  12. super(scene, MixedRealityProfile["left-right"], gamepadObject, handedness);
  13. // use this in the future - https://github.com/immersive-web/webxr-input-profiles/tree/master/packages/assets/profiles/microsoft
  14. this._mapping = {
  15. defaultButton: {
  16. valueNodeName: "VALUE",
  17. unpressedNodeName: "UNPRESSED",
  18. pressedNodeName: "PRESSED",
  19. },
  20. defaultAxis: {
  21. valueNodeName: "VALUE",
  22. minNodeName: "MIN",
  23. maxNodeName: "MAX",
  24. },
  25. buttons: {
  26. "xr-standard-trigger": {
  27. rootNodeName: "SELECT",
  28. componentProperty: "button",
  29. states: ["default", "touched", "pressed"],
  30. },
  31. "xr-standard-squeeze": {
  32. rootNodeName: "GRASP",
  33. componentProperty: "state",
  34. states: ["pressed"],
  35. },
  36. "xr-standard-touchpad": {
  37. rootNodeName: "TOUCHPAD_PRESS",
  38. labelAnchorNodeName: "squeeze-label",
  39. touchPointNodeName: "TOUCH", // TODO - use this for visual feedback
  40. },
  41. "xr-standard-thumbstick": {
  42. rootNodeName: "THUMBSTICK_PRESS",
  43. componentProperty: "state",
  44. states: ["pressed"],
  45. },
  46. },
  47. axes: {
  48. "xr-standard-touchpad": {
  49. "x-axis": {
  50. rootNodeName: "TOUCHPAD_TOUCH_X",
  51. },
  52. "y-axis": {
  53. rootNodeName: "TOUCHPAD_TOUCH_Y",
  54. },
  55. },
  56. "xr-standard-thumbstick": {
  57. "x-axis": {
  58. rootNodeName: "THUMBSTICK_X",
  59. },
  60. "y-axis": {
  61. rootNodeName: "THUMBSTICK_Y",
  62. },
  63. },
  64. },
  65. };
  66. this.profileId = "microsoft-mixed-reality";
  67. }
  68. _getFilenameAndPath() {
  69. let filename = "";
  70. if (this.handedness === "left") {
  71. filename = WebXRMicrosoftMixedRealityController.MODEL_LEFT_FILENAME;
  72. }
  73. else {
  74. // Right is the default if no hand is specified
  75. filename = WebXRMicrosoftMixedRealityController.MODEL_RIGHT_FILENAME;
  76. }
  77. const device = "default";
  78. const path = WebXRMicrosoftMixedRealityController.MODEL_BASE_URL + device + "/";
  79. return {
  80. filename,
  81. path,
  82. };
  83. }
  84. _getModelLoadingConstraints() {
  85. const glbLoaded = SceneLoader.IsPluginForExtensionAvailable(".glb");
  86. if (!glbLoaded) {
  87. Logger.Warn("glTF / glb loaded was not registered, using generic controller instead");
  88. }
  89. return glbLoaded;
  90. }
  91. _processLoadedModel(_meshes) {
  92. if (!this.rootMesh) {
  93. return;
  94. }
  95. // Button Meshes
  96. this.getComponentIds().forEach((id, i) => {
  97. if (this.disableAnimation) {
  98. return;
  99. }
  100. if (id && this.rootMesh) {
  101. const buttonMap = this._mapping.buttons[id];
  102. const buttonMeshName = buttonMap.rootNodeName;
  103. if (!buttonMeshName) {
  104. Logger.Log("Skipping unknown button at index: " + i + " with mapped name: " + id);
  105. return;
  106. }
  107. const buttonMesh = this._getChildByName(this.rootMesh, buttonMeshName);
  108. if (!buttonMesh) {
  109. Logger.Warn("Missing button mesh with name: " + buttonMeshName);
  110. return;
  111. }
  112. buttonMap.valueMesh = this._getImmediateChildByName(buttonMesh, this._mapping.defaultButton.valueNodeName);
  113. buttonMap.pressedMesh = this._getImmediateChildByName(buttonMesh, this._mapping.defaultButton.pressedNodeName);
  114. buttonMap.unpressedMesh = this._getImmediateChildByName(buttonMesh, this._mapping.defaultButton.unpressedNodeName);
  115. if (buttonMap.valueMesh && buttonMap.pressedMesh && buttonMap.unpressedMesh) {
  116. const comp = this.getComponent(id);
  117. if (comp) {
  118. comp.onButtonStateChangedObservable.add((component) => {
  119. this._lerpTransform(buttonMap, component.value);
  120. }, undefined, true);
  121. }
  122. }
  123. else {
  124. // If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes.
  125. Logger.Warn("Missing button submesh under mesh with name: " + buttonMeshName);
  126. }
  127. }
  128. });
  129. // Axis Meshes
  130. this.getComponentIds().forEach((id) => {
  131. const comp = this.getComponent(id);
  132. if (!comp.isAxes()) {
  133. return;
  134. }
  135. ["x-axis", "y-axis"].forEach((axis) => {
  136. if (!this.rootMesh) {
  137. return;
  138. }
  139. const axisMap = this._mapping.axes[id][axis];
  140. const axisMesh = this._getChildByName(this.rootMesh, axisMap.rootNodeName);
  141. if (!axisMesh) {
  142. Logger.Warn("Missing axis mesh with name: " + axisMap.rootNodeName);
  143. return;
  144. }
  145. axisMap.valueMesh = this._getImmediateChildByName(axisMesh, this._mapping.defaultAxis.valueNodeName);
  146. axisMap.minMesh = this._getImmediateChildByName(axisMesh, this._mapping.defaultAxis.minNodeName);
  147. axisMap.maxMesh = this._getImmediateChildByName(axisMesh, this._mapping.defaultAxis.maxNodeName);
  148. if (axisMap.valueMesh && axisMap.minMesh && axisMap.maxMesh) {
  149. if (comp) {
  150. comp.onAxisValueChangedObservable.add((axisValues) => {
  151. const value = axis === "x-axis" ? axisValues.x : axisValues.y;
  152. this._lerpTransform(axisMap, value, true);
  153. }, undefined, true);
  154. }
  155. }
  156. else {
  157. // If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes.
  158. Logger.Warn("Missing axis submesh under mesh with name: " + axisMap.rootNodeName);
  159. }
  160. });
  161. });
  162. }
  163. _setRootMesh(meshes) {
  164. this.rootMesh = new Mesh(this.profileId + " " + this.handedness, this.scene);
  165. this.rootMesh.isPickable = false;
  166. let rootMesh;
  167. // Find the root node in the loaded glTF scene, and attach it as a child of 'parentMesh'
  168. for (let i = 0; i < meshes.length; i++) {
  169. const mesh = meshes[i];
  170. mesh.isPickable = false;
  171. if (!mesh.parent) {
  172. // Handle root node, attach to the new parentMesh
  173. rootMesh = mesh;
  174. }
  175. }
  176. if (rootMesh) {
  177. rootMesh.setParent(this.rootMesh);
  178. }
  179. if (!this.scene.useRightHandedSystem) {
  180. this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
  181. }
  182. }
  183. _updateModel() {
  184. // no-op. model is updated using observables.
  185. }
  186. }
  187. /**
  188. * The base url used to load the left and right controller models
  189. */
  190. WebXRMicrosoftMixedRealityController.MODEL_BASE_URL = "https://controllers.babylonjs.com/microsoft/";
  191. /**
  192. * The name of the left controller model file
  193. */
  194. WebXRMicrosoftMixedRealityController.MODEL_LEFT_FILENAME = "left.glb";
  195. /**
  196. * The name of the right controller model file
  197. */
  198. WebXRMicrosoftMixedRealityController.MODEL_RIGHT_FILENAME = "right.glb";
  199. // register the profile
  200. WebXRMotionControllerManager.RegisterController("windows-mixed-reality", (xrInput, scene) => {
  201. return new WebXRMicrosoftMixedRealityController(scene, xrInput.gamepad, xrInput.handedness);
  202. });
  203. // https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/microsoft/microsoft-mixed-reality.json
  204. const MixedRealityProfile = {
  205. left: {
  206. selectComponentId: "xr-standard-trigger",
  207. components: {
  208. "xr-standard-trigger": {
  209. type: "trigger",
  210. gamepadIndices: {
  211. button: 0,
  212. },
  213. rootNodeName: "xr_standard_trigger",
  214. visualResponses: {
  215. xr_standard_trigger_pressed: {
  216. componentProperty: "button",
  217. states: ["default", "touched", "pressed"],
  218. valueNodeProperty: "transform",
  219. valueNodeName: "xr_standard_trigger_pressed_value",
  220. minNodeName: "xr_standard_trigger_pressed_min",
  221. maxNodeName: "xr_standard_trigger_pressed_max",
  222. },
  223. },
  224. },
  225. "xr-standard-squeeze": {
  226. type: "squeeze",
  227. gamepadIndices: {
  228. button: 1,
  229. },
  230. rootNodeName: "xr_standard_squeeze",
  231. visualResponses: {
  232. xr_standard_squeeze_pressed: {
  233. componentProperty: "button",
  234. states: ["default", "touched", "pressed"],
  235. valueNodeProperty: "transform",
  236. valueNodeName: "xr_standard_squeeze_pressed_value",
  237. minNodeName: "xr_standard_squeeze_pressed_min",
  238. maxNodeName: "xr_standard_squeeze_pressed_max",
  239. },
  240. },
  241. },
  242. "xr-standard-touchpad": {
  243. type: "touchpad",
  244. gamepadIndices: {
  245. button: 2,
  246. xAxis: 0,
  247. yAxis: 1,
  248. },
  249. rootNodeName: "xr_standard_touchpad",
  250. visualResponses: {
  251. xr_standard_touchpad_pressed: {
  252. componentProperty: "button",
  253. states: ["default", "touched", "pressed"],
  254. valueNodeProperty: "transform",
  255. valueNodeName: "xr_standard_touchpad_pressed_value",
  256. minNodeName: "xr_standard_touchpad_pressed_min",
  257. maxNodeName: "xr_standard_touchpad_pressed_max",
  258. },
  259. xr_standard_touchpad_xaxis_pressed: {
  260. componentProperty: "xAxis",
  261. states: ["default", "touched", "pressed"],
  262. valueNodeProperty: "transform",
  263. valueNodeName: "xr_standard_touchpad_xaxis_pressed_value",
  264. minNodeName: "xr_standard_touchpad_xaxis_pressed_min",
  265. maxNodeName: "xr_standard_touchpad_xaxis_pressed_max",
  266. },
  267. xr_standard_touchpad_yaxis_pressed: {
  268. componentProperty: "yAxis",
  269. states: ["default", "touched", "pressed"],
  270. valueNodeProperty: "transform",
  271. valueNodeName: "xr_standard_touchpad_yaxis_pressed_value",
  272. minNodeName: "xr_standard_touchpad_yaxis_pressed_min",
  273. maxNodeName: "xr_standard_touchpad_yaxis_pressed_max",
  274. },
  275. xr_standard_touchpad_xaxis_touched: {
  276. componentProperty: "xAxis",
  277. states: ["default", "touched", "pressed"],
  278. valueNodeProperty: "transform",
  279. valueNodeName: "xr_standard_touchpad_xaxis_touched_value",
  280. minNodeName: "xr_standard_touchpad_xaxis_touched_min",
  281. maxNodeName: "xr_standard_touchpad_xaxis_touched_max",
  282. },
  283. xr_standard_touchpad_yaxis_touched: {
  284. componentProperty: "yAxis",
  285. states: ["default", "touched", "pressed"],
  286. valueNodeProperty: "transform",
  287. valueNodeName: "xr_standard_touchpad_yaxis_touched_value",
  288. minNodeName: "xr_standard_touchpad_yaxis_touched_min",
  289. maxNodeName: "xr_standard_touchpad_yaxis_touched_max",
  290. },
  291. xr_standard_touchpad_axes_touched: {
  292. componentProperty: "state",
  293. states: ["touched", "pressed"],
  294. valueNodeProperty: "visibility",
  295. valueNodeName: "xr_standard_touchpad_axes_touched_value",
  296. },
  297. },
  298. touchPointNodeName: "xr_standard_touchpad_axes_touched_value",
  299. },
  300. "xr-standard-thumbstick": {
  301. type: "thumbstick",
  302. gamepadIndices: {
  303. button: 3,
  304. xAxis: 2,
  305. yAxis: 3,
  306. },
  307. rootNodeName: "xr_standard_thumbstick",
  308. visualResponses: {
  309. xr_standard_thumbstick_pressed: {
  310. componentProperty: "button",
  311. states: ["default", "touched", "pressed"],
  312. valueNodeProperty: "transform",
  313. valueNodeName: "xr_standard_thumbstick_pressed_value",
  314. minNodeName: "xr_standard_thumbstick_pressed_min",
  315. maxNodeName: "xr_standard_thumbstick_pressed_max",
  316. },
  317. xr_standard_thumbstick_xaxis_pressed: {
  318. componentProperty: "xAxis",
  319. states: ["default", "touched", "pressed"],
  320. valueNodeProperty: "transform",
  321. valueNodeName: "xr_standard_thumbstick_xaxis_pressed_value",
  322. minNodeName: "xr_standard_thumbstick_xaxis_pressed_min",
  323. maxNodeName: "xr_standard_thumbstick_xaxis_pressed_max",
  324. },
  325. xr_standard_thumbstick_yaxis_pressed: {
  326. componentProperty: "yAxis",
  327. states: ["default", "touched", "pressed"],
  328. valueNodeProperty: "transform",
  329. valueNodeName: "xr_standard_thumbstick_yaxis_pressed_value",
  330. minNodeName: "xr_standard_thumbstick_yaxis_pressed_min",
  331. maxNodeName: "xr_standard_thumbstick_yaxis_pressed_max",
  332. },
  333. },
  334. },
  335. },
  336. gamepadMapping: "xr-standard",
  337. rootNodeName: "microsoft-mixed-reality-left",
  338. assetPath: "left.glb",
  339. },
  340. right: {
  341. selectComponentId: "xr-standard-trigger",
  342. components: {
  343. "xr-standard-trigger": {
  344. type: "trigger",
  345. gamepadIndices: {
  346. button: 0,
  347. },
  348. rootNodeName: "xr_standard_trigger",
  349. visualResponses: {
  350. xr_standard_trigger_pressed: {
  351. componentProperty: "button",
  352. states: ["default", "touched", "pressed"],
  353. valueNodeProperty: "transform",
  354. valueNodeName: "xr_standard_trigger_pressed_value",
  355. minNodeName: "xr_standard_trigger_pressed_min",
  356. maxNodeName: "xr_standard_trigger_pressed_max",
  357. },
  358. },
  359. },
  360. "xr-standard-squeeze": {
  361. type: "squeeze",
  362. gamepadIndices: {
  363. button: 1,
  364. },
  365. rootNodeName: "xr_standard_squeeze",
  366. visualResponses: {
  367. xr_standard_squeeze_pressed: {
  368. componentProperty: "button",
  369. states: ["default", "touched", "pressed"],
  370. valueNodeProperty: "transform",
  371. valueNodeName: "xr_standard_squeeze_pressed_value",
  372. minNodeName: "xr_standard_squeeze_pressed_min",
  373. maxNodeName: "xr_standard_squeeze_pressed_max",
  374. },
  375. },
  376. },
  377. "xr-standard-touchpad": {
  378. type: "touchpad",
  379. gamepadIndices: {
  380. button: 2,
  381. xAxis: 0,
  382. yAxis: 1,
  383. },
  384. rootNodeName: "xr_standard_touchpad",
  385. visualResponses: {
  386. xr_standard_touchpad_pressed: {
  387. componentProperty: "button",
  388. states: ["default", "touched", "pressed"],
  389. valueNodeProperty: "transform",
  390. valueNodeName: "xr_standard_touchpad_pressed_value",
  391. minNodeName: "xr_standard_touchpad_pressed_min",
  392. maxNodeName: "xr_standard_touchpad_pressed_max",
  393. },
  394. xr_standard_touchpad_xaxis_pressed: {
  395. componentProperty: "xAxis",
  396. states: ["default", "touched", "pressed"],
  397. valueNodeProperty: "transform",
  398. valueNodeName: "xr_standard_touchpad_xaxis_pressed_value",
  399. minNodeName: "xr_standard_touchpad_xaxis_pressed_min",
  400. maxNodeName: "xr_standard_touchpad_xaxis_pressed_max",
  401. },
  402. xr_standard_touchpad_yaxis_pressed: {
  403. componentProperty: "yAxis",
  404. states: ["default", "touched", "pressed"],
  405. valueNodeProperty: "transform",
  406. valueNodeName: "xr_standard_touchpad_yaxis_pressed_value",
  407. minNodeName: "xr_standard_touchpad_yaxis_pressed_min",
  408. maxNodeName: "xr_standard_touchpad_yaxis_pressed_max",
  409. },
  410. xr_standard_touchpad_xaxis_touched: {
  411. componentProperty: "xAxis",
  412. states: ["default", "touched", "pressed"],
  413. valueNodeProperty: "transform",
  414. valueNodeName: "xr_standard_touchpad_xaxis_touched_value",
  415. minNodeName: "xr_standard_touchpad_xaxis_touched_min",
  416. maxNodeName: "xr_standard_touchpad_xaxis_touched_max",
  417. },
  418. xr_standard_touchpad_yaxis_touched: {
  419. componentProperty: "yAxis",
  420. states: ["default", "touched", "pressed"],
  421. valueNodeProperty: "transform",
  422. valueNodeName: "xr_standard_touchpad_yaxis_touched_value",
  423. minNodeName: "xr_standard_touchpad_yaxis_touched_min",
  424. maxNodeName: "xr_standard_touchpad_yaxis_touched_max",
  425. },
  426. xr_standard_touchpad_axes_touched: {
  427. componentProperty: "state",
  428. states: ["touched", "pressed"],
  429. valueNodeProperty: "visibility",
  430. valueNodeName: "xr_standard_touchpad_axes_touched_value",
  431. },
  432. },
  433. touchPointNodeName: "xr_standard_touchpad_axes_touched_value",
  434. },
  435. "xr-standard-thumbstick": {
  436. type: "thumbstick",
  437. gamepadIndices: {
  438. button: 3,
  439. xAxis: 2,
  440. yAxis: 3,
  441. },
  442. rootNodeName: "xr_standard_thumbstick",
  443. visualResponses: {
  444. xr_standard_thumbstick_pressed: {
  445. componentProperty: "button",
  446. states: ["default", "touched", "pressed"],
  447. valueNodeProperty: "transform",
  448. valueNodeName: "xr_standard_thumbstick_pressed_value",
  449. minNodeName: "xr_standard_thumbstick_pressed_min",
  450. maxNodeName: "xr_standard_thumbstick_pressed_max",
  451. },
  452. xr_standard_thumbstick_xaxis_pressed: {
  453. componentProperty: "xAxis",
  454. states: ["default", "touched", "pressed"],
  455. valueNodeProperty: "transform",
  456. valueNodeName: "xr_standard_thumbstick_xaxis_pressed_value",
  457. minNodeName: "xr_standard_thumbstick_xaxis_pressed_min",
  458. maxNodeName: "xr_standard_thumbstick_xaxis_pressed_max",
  459. },
  460. xr_standard_thumbstick_yaxis_pressed: {
  461. componentProperty: "yAxis",
  462. states: ["default", "touched", "pressed"],
  463. valueNodeProperty: "transform",
  464. valueNodeName: "xr_standard_thumbstick_yaxis_pressed_value",
  465. minNodeName: "xr_standard_thumbstick_yaxis_pressed_min",
  466. maxNodeName: "xr_standard_thumbstick_yaxis_pressed_max",
  467. },
  468. },
  469. },
  470. },
  471. gamepadMapping: "xr-standard",
  472. rootNodeName: "microsoft-mixed-reality-right",
  473. assetPath: "right.glb",
  474. },
  475. };
  476. //# sourceMappingURL=webXRMicrosoftMixedRealityController.js.map