webXRMicrosoftMixedRealityController.ts 23 KB

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