webXRMicrosoftMixedRealityController.ts 25 KB

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