webXROculusTouchMotionController.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. import {
  2. WebXRAbstractMotionController,
  3. IMinimalMotionControllerObject,
  4. MotionControllerHandness,
  5. IMotionControllerLayoutMap
  6. } from "./webXRAbstractController";
  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. // https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/microsoft/microsoft-mixed-reality.json
  13. const OculusTouchLayouts: IMotionControllerLayoutMap = {
  14. "left": {
  15. "selectComponentId": "xr-standard-trigger",
  16. "components": {
  17. "xr-standard-trigger": {
  18. "type": "trigger",
  19. "gamepadIndices": {
  20. "button": 0
  21. },
  22. "rootNodeName": "xr_standard_trigger",
  23. "visualResponses": {
  24. }
  25. },
  26. "xr-standard-squeeze": {
  27. "type": "squeeze",
  28. "gamepadIndices": {
  29. "button": 1
  30. },
  31. "rootNodeName": "xr_standard_squeeze",
  32. "visualResponses": {
  33. }
  34. },
  35. "xr-standard-thumbstick": {
  36. "type": "thumbstick",
  37. "gamepadIndices": {
  38. "button": 3,
  39. "xAxis": 2,
  40. "yAxis": 3
  41. },
  42. "rootNodeName": "xr_standard_thumbstick",
  43. "visualResponses": {
  44. }
  45. },
  46. "x-button": {
  47. "type": "button",
  48. "gamepadIndices": {
  49. "button": 4
  50. },
  51. "rootNodeName": "x_button",
  52. "visualResponses": {
  53. }
  54. },
  55. "y-button": {
  56. "type": "button",
  57. "gamepadIndices": {
  58. "button": 5
  59. },
  60. "rootNodeName": "y_button",
  61. "visualResponses": {
  62. }
  63. },
  64. "thumbrest": {
  65. "type": "button",
  66. "gamepadIndices": {
  67. "button": 6
  68. },
  69. "rootNodeName": "thumbrest",
  70. "visualResponses": {
  71. }
  72. }
  73. },
  74. "gamepadMapping": "xr-standard",
  75. "rootNodeName": "oculus-touch-v2-left",
  76. "assetPath": "left.glb"
  77. },
  78. "right": {
  79. "selectComponentId": "xr-standard-trigger",
  80. "components": {
  81. "xr-standard-trigger": {
  82. "type": "trigger",
  83. "gamepadIndices": {
  84. "button": 0
  85. },
  86. "rootNodeName": "xr_standard_trigger",
  87. "visualResponses": {
  88. }
  89. },
  90. "xr-standard-squeeze": {
  91. "type": "squeeze",
  92. "gamepadIndices": {
  93. "button": 1
  94. },
  95. "rootNodeName": "xr_standard_squeeze",
  96. "visualResponses": {
  97. }
  98. },
  99. "xr-standard-thumbstick": {
  100. "type": "thumbstick",
  101. "gamepadIndices": {
  102. "button": 3,
  103. "xAxis": 2,
  104. "yAxis": 3
  105. },
  106. "rootNodeName": "xr_standard_thumbstick",
  107. "visualResponses": {
  108. }
  109. },
  110. "a-button": {
  111. "type": "button",
  112. "gamepadIndices": {
  113. "button": 4
  114. },
  115. "rootNodeName": "a_button",
  116. "visualResponses": {
  117. }
  118. },
  119. "b-button": {
  120. "type": "button",
  121. "gamepadIndices": {
  122. "button": 5
  123. },
  124. "rootNodeName": "b_button",
  125. "visualResponses": {
  126. }
  127. },
  128. "thumbrest": {
  129. "type": "button",
  130. "gamepadIndices": {
  131. "button": 6
  132. },
  133. "rootNodeName": "thumbrest",
  134. "visualResponses": {
  135. }
  136. }
  137. },
  138. "gamepadMapping": "xr-standard",
  139. "rootNodeName": "oculus-touch-v2-right",
  140. "assetPath": "right.glb"
  141. }
  142. };
  143. const OculusTouchLegacyLayouts: IMotionControllerLayoutMap = {
  144. "left": {
  145. "selectComponentId": "xr-standard-trigger",
  146. "components": {
  147. "xr-standard-trigger": {
  148. "type": "trigger",
  149. "gamepadIndices": {
  150. "button": 1
  151. },
  152. "rootNodeName": "xr_standard_trigger",
  153. "visualResponses": {
  154. }
  155. },
  156. "xr-standard-squeeze": {
  157. "type": "squeeze",
  158. "gamepadIndices": {
  159. "button": 2
  160. },
  161. "rootNodeName": "xr_standard_squeeze",
  162. "visualResponses": {
  163. }
  164. },
  165. "xr-standard-thumbstick": {
  166. "type": "thumbstick",
  167. "gamepadIndices": {
  168. "button": 0,
  169. "xAxis": 0,
  170. "yAxis": 1
  171. },
  172. "rootNodeName": "xr_standard_thumbstick",
  173. "visualResponses": {
  174. }
  175. },
  176. "x-button": {
  177. "type": "button",
  178. "gamepadIndices": {
  179. "button": 3
  180. },
  181. "rootNodeName": "x_button",
  182. "visualResponses": {
  183. }
  184. },
  185. "y-button": {
  186. "type": "button",
  187. "gamepadIndices": {
  188. "button": 4
  189. },
  190. "rootNodeName": "y_button",
  191. "visualResponses": {
  192. }
  193. },
  194. "thumbrest": {
  195. "type": "button",
  196. "gamepadIndices": {
  197. "button": 5
  198. },
  199. "rootNodeName": "thumbrest",
  200. "visualResponses": {
  201. }
  202. }
  203. },
  204. "gamepadMapping": "xr-standard",
  205. "rootNodeName": "oculus-touch-v2-left",
  206. "assetPath": "left.glb"
  207. },
  208. "right": {
  209. "selectComponentId": "xr-standard-trigger",
  210. "components": {
  211. "xr-standard-trigger": {
  212. "type": "trigger",
  213. "gamepadIndices": {
  214. "button": 1
  215. },
  216. "rootNodeName": "xr_standard_trigger",
  217. "visualResponses": {
  218. }
  219. },
  220. "xr-standard-squeeze": {
  221. "type": "squeeze",
  222. "gamepadIndices": {
  223. "button": 2
  224. },
  225. "rootNodeName": "xr_standard_squeeze",
  226. "visualResponses": {
  227. }
  228. },
  229. "xr-standard-thumbstick": {
  230. "type": "thumbstick",
  231. "gamepadIndices": {
  232. "button": 0,
  233. "xAxis": 0,
  234. "yAxis": 1
  235. },
  236. "rootNodeName": "xr_standard_thumbstick",
  237. "visualResponses": {
  238. }
  239. },
  240. "a-button": {
  241. "type": "button",
  242. "gamepadIndices": {
  243. "button": 3
  244. },
  245. "rootNodeName": "a_button",
  246. "visualResponses": {
  247. }
  248. },
  249. "b-button": {
  250. "type": "button",
  251. "gamepadIndices": {
  252. "button": 4
  253. },
  254. "rootNodeName": "b_button",
  255. "visualResponses": {
  256. }
  257. },
  258. "thumbrest": {
  259. "type": "button",
  260. "gamepadIndices": {
  261. "button": 5
  262. },
  263. "rootNodeName": "thumbrest",
  264. "visualResponses": {
  265. }
  266. }
  267. },
  268. "gamepadMapping": "xr-standard",
  269. "rootNodeName": "oculus-touch-v2-right",
  270. "assetPath": "right.glb"
  271. }
  272. };
  273. /**
  274. * The motion controller class for oculus touch (quest, rift).
  275. * This class supports legacy mapping as well the standard xr mapping
  276. */
  277. export class WebXROculusTouchMotionController extends WebXRAbstractMotionController {
  278. /**
  279. * The base url used to load the left and right controller models
  280. */
  281. public static MODEL_BASE_URL: string = 'https://controllers.babylonjs.com/oculus/';
  282. /**
  283. * The name of the left controller model file
  284. */
  285. public static MODEL_LEFT_FILENAME: string = 'left.babylon';
  286. /**
  287. * The name of the right controller model file
  288. */
  289. public static MODEL_RIGHT_FILENAME: string = 'right.babylon';
  290. /**
  291. * Base Url for the Quest controller model.
  292. */
  293. public static QUEST_MODEL_BASE_URL: string = 'https://controllers.babylonjs.com/oculusQuest/';
  294. public profileId = "oculus-touch";
  295. private _modelRootNode: AbstractMesh;
  296. constructor(scene: Scene,
  297. gamepadObject: IMinimalMotionControllerObject,
  298. handness: MotionControllerHandness,
  299. legacyMapping: boolean = false,
  300. private _forceLegacyControllers: boolean = false) {
  301. super(scene, legacyMapping ? OculusTouchLegacyLayouts[handness] : OculusTouchLayouts[handness], gamepadObject, handness);
  302. }
  303. protected _processLoadedModel(_meshes: AbstractMesh[]): void {
  304. /*const isQuest = this._isQuest();
  305. const triggerDirection = this.handness === 'right' ? -1 : 1;
  306. this.layout.gamepad!.buttons.forEach((buttonName) => {
  307. const comp = buttonName && this.getComponent(buttonName);
  308. if (comp) {
  309. comp.onButtonStateChanged.add((component) => {
  310. if (!this.rootMesh) { return; }
  311. switch (buttonName) {
  312. case "xr-standard-trigger": // index trigger
  313. if (!isQuest) {
  314. (<AbstractMesh>(this._modelRootNode.getChildren()[3])).rotation.x = -component.value * 0.20;
  315. (<AbstractMesh>(this._modelRootNode.getChildren()[3])).position.y = -component.value * 0.005;
  316. (<AbstractMesh>(this._modelRootNode.getChildren()[3])).position.z = -component.value * 0.005;
  317. }
  318. return;
  319. case "xr-standard-squeeze": // secondary trigger
  320. if (!isQuest) {
  321. (<AbstractMesh>(this._modelRootNode.getChildren()[4])).position.x = triggerDirection * component.value * 0.0035;
  322. }
  323. return;
  324. case "xr-standard-thumbstick": // thumbstick
  325. return;
  326. case "a-button":
  327. case "x-button":
  328. if (!isQuest) {
  329. if (component.pressed) {
  330. (<AbstractMesh>(this._modelRootNode.getChildren()[1])).position.y = -0.001;
  331. }
  332. else {
  333. (<AbstractMesh>(this._modelRootNode.getChildren()[1])).position.y = 0;
  334. }
  335. }
  336. return;
  337. case "b-button":
  338. case "y-button":
  339. if (!isQuest) {
  340. if (component.pressed) {
  341. (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = -0.001;
  342. }
  343. else {
  344. (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = 0;
  345. }
  346. }
  347. return;
  348. }
  349. }, undefined, true);
  350. }
  351. });*/
  352. }
  353. protected _getFilenameAndPath(): { filename: string; path: string; } {
  354. let filename = "";
  355. if (this.handness === 'left') {
  356. filename = WebXROculusTouchMotionController.MODEL_LEFT_FILENAME;
  357. }
  358. else { // Right is the default if no hand is specified
  359. filename = WebXROculusTouchMotionController.MODEL_RIGHT_FILENAME;
  360. }
  361. let path = this._isQuest() ? WebXROculusTouchMotionController.QUEST_MODEL_BASE_URL : WebXROculusTouchMotionController.MODEL_BASE_URL;
  362. return {
  363. filename,
  364. path
  365. };
  366. }
  367. /**
  368. * Is this the new type of oculus touch. At the moment both have the same profile and it is impossible to differentiate
  369. * between the touch and touch 2.
  370. */
  371. private _isQuest() {
  372. // this is SADLY the only way to currently check. Until proper profiles will be available.
  373. return !!navigator.userAgent.match(/Quest/gi) && !this._forceLegacyControllers;
  374. }
  375. protected _updateModel(): void {
  376. // no-op. model is updated using observables.
  377. }
  378. protected _getModelLoadingConstraints(): boolean {
  379. return true;
  380. }
  381. protected _setRootMesh(meshes: AbstractMesh[]): void {
  382. this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
  383. this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
  384. meshes.forEach((mesh) => { mesh.isPickable = false; });
  385. if (this._isQuest()) {
  386. this._modelRootNode = meshes[0];
  387. } else {
  388. this._modelRootNode = meshes[1];
  389. this.rootMesh.position.y = 0.034;
  390. this.rootMesh.position.z = 0.052;
  391. }
  392. this._modelRootNode.parent = this.rootMesh;
  393. }
  394. }
  395. // register the profile
  396. WebXRMotionControllerManager.RegisterController("oculus-touch", (xrInput: XRInputSource, scene: Scene) => {
  397. return new WebXROculusTouchMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness);
  398. });
  399. WebXRMotionControllerManager.RegisterController("oculus-touch-legacy", (xrInput: XRInputSource, scene: Scene) => {
  400. return new WebXROculusTouchMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness, true);
  401. });