webXRMicrosoftMixedRealityController.ts 25 KB

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