webXROculusTouchMotionController.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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": { "type": "trigger" },
  18. "xr-standard-squeeze": { "type": "squeeze" },
  19. "xr-standard-thumbstick": { "type": "thumbstick" },
  20. "a-button": { "type": "button" },
  21. "b-button": { "type": "button" },
  22. "thumbrest": { "type": "button" }
  23. },
  24. "gamepad": {
  25. "mapping": "xr-standard",
  26. "buttons": [
  27. "xr-standard-trigger",
  28. "xr-standard-squeeze",
  29. null,
  30. "xr-standard-thumbstick",
  31. "a-button",
  32. "b-button",
  33. "thumbrest"
  34. ],
  35. "axes": [
  36. null,
  37. null,
  38. { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
  39. { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
  40. ]
  41. }
  42. },
  43. "right": {
  44. "selectComponentId": "xr-standard-trigger",
  45. "components": {
  46. "xr-standard-trigger": { "type": "trigger" },
  47. "xr-standard-squeeze": { "type": "squeeze" },
  48. "xr-standard-thumbstick": { "type": "thumbstick" },
  49. "x-button": { "type": "button" },
  50. "y-button": { "type": "button" },
  51. "thumbrest": { "type": "button" }
  52. },
  53. "gamepad": {
  54. "mapping": "xr-standard",
  55. "buttons": [
  56. "xr-standard-trigger",
  57. "xr-standard-squeeze",
  58. null,
  59. "xr-standard-thumbstick",
  60. "x-button",
  61. "y-button",
  62. "thumbrest"
  63. ],
  64. "axes": [
  65. null,
  66. null,
  67. { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
  68. { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
  69. ]
  70. }
  71. }
  72. };
  73. const OculusTouchLegacyLayouts: IMotionControllerLayoutMap = {
  74. "left": {
  75. "selectComponentId": "xr-standard-trigger",
  76. "components": {
  77. "xr-standard-trigger": { "type": "trigger" },
  78. "xr-standard-squeeze": { "type": "squeeze" },
  79. "xr-standard-thumbstick": { "type": "thumbstick" },
  80. "a-button": { "type": "button" },
  81. "b-button": { "type": "button" },
  82. "thumbrest": { "type": "button" }
  83. },
  84. "gamepad": {
  85. "mapping": "",
  86. "buttons": [
  87. "xr-standard-thumbstick",
  88. "xr-standard-trigger",
  89. "xr-standard-squeeze",
  90. "a-button",
  91. "b-button",
  92. "thumbrest"
  93. ],
  94. "axes": [
  95. { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
  96. { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
  97. ]
  98. }
  99. },
  100. "right": {
  101. "selectComponentId": "xr-standard-trigger",
  102. "components": {
  103. "xr-standard-trigger": { "type": "trigger" },
  104. "xr-standard-squeeze": { "type": "squeeze" },
  105. "xr-standard-thumbstick": { "type": "thumbstick" },
  106. "x-button": { "type": "button" },
  107. "y-button": { "type": "button" },
  108. "thumbrest": { "type": "button" }
  109. },
  110. "gamepad": {
  111. "mapping": "",
  112. "buttons": [
  113. "xr-standard-thumbstick",
  114. "xr-standard-trigger",
  115. "xr-standard-squeeze",
  116. "x-button",
  117. "y-button",
  118. "thumbrest"
  119. ],
  120. "axes": [
  121. { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
  122. { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
  123. ]
  124. }
  125. }
  126. };
  127. /**
  128. * The motion controller class for oculus touch (quest, rift).
  129. * This class supports legacy mapping as well the standard xr mapping
  130. */
  131. export class WebXROculusTouchMotionController extends WebXRAbstractMotionController {
  132. /**
  133. * The base url used to load the left and right controller models
  134. */
  135. public static MODEL_BASE_URL: string = 'https://controllers.babylonjs.com/oculus/';
  136. /**
  137. * The name of the left controller model file
  138. */
  139. public static MODEL_LEFT_FILENAME: string = 'left.babylon';
  140. /**
  141. * The name of the right controller model file
  142. */
  143. public static MODEL_RIGHT_FILENAME: string = 'right.babylon';
  144. /**
  145. * Base Url for the Quest controller model.
  146. */
  147. public static QUEST_MODEL_BASE_URL: string = 'https://controllers.babylonjs.com/oculusQuest/';
  148. public profileId = "oculus-touch";
  149. private _modelRootNode: AbstractMesh;
  150. constructor(scene: Scene,
  151. gamepadObject: IMinimalMotionControllerObject,
  152. handness: MotionControllerHandness,
  153. legacyMapping: boolean = false,
  154. private _forceLegacyControllers: boolean = false) {
  155. super(scene, legacyMapping ? OculusTouchLegacyLayouts[handness] : OculusTouchLayouts[handness], gamepadObject, handness);
  156. }
  157. protected _processLoadedModel(_meshes: AbstractMesh[]): void {
  158. const isQuest = this._isQuest();
  159. const triggerDirection = this.handness === 'right' ? -1 : 1;
  160. this.layout.gamepad!.buttons.forEach((buttonName) => {
  161. const comp = buttonName && this.getComponent(buttonName);
  162. if (comp) {
  163. comp.onButtonStateChanged.add((component) => {
  164. if (!this.rootMesh) { return; }
  165. switch (buttonName) {
  166. case "xr-standard-trigger": // index trigger
  167. if (!isQuest) {
  168. (<AbstractMesh>(this._modelRootNode.getChildren()[3])).rotation.x = -component.value * 0.20;
  169. (<AbstractMesh>(this._modelRootNode.getChildren()[3])).position.y = -component.value * 0.005;
  170. (<AbstractMesh>(this._modelRootNode.getChildren()[3])).position.z = -component.value * 0.005;
  171. }
  172. return;
  173. case "xr-standard-squeeze": // secondary trigger
  174. if (!isQuest) {
  175. (<AbstractMesh>(this._modelRootNode.getChildren()[4])).position.x = triggerDirection * component.value * 0.0035;
  176. }
  177. return;
  178. case "xr-standard-thumbstick": // thumbstick
  179. return;
  180. case "a-button":
  181. case "x-button":
  182. if (!isQuest) {
  183. if (component.pressed) {
  184. (<AbstractMesh>(this._modelRootNode.getChildren()[1])).position.y = -0.001;
  185. }
  186. else {
  187. (<AbstractMesh>(this._modelRootNode.getChildren()[1])).position.y = 0;
  188. }
  189. }
  190. return;
  191. case "b-button":
  192. case "y-button":
  193. if (!isQuest) {
  194. if (component.pressed) {
  195. (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = -0.001;
  196. }
  197. else {
  198. (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = 0;
  199. }
  200. }
  201. return;
  202. }
  203. }, undefined, true);
  204. }
  205. });
  206. }
  207. protected _getFilenameAndPath(): { filename: string; path: string; } {
  208. let filename = "";
  209. if (this.handness === 'left') {
  210. filename = WebXROculusTouchMotionController.MODEL_LEFT_FILENAME;
  211. }
  212. else { // Right is the default if no hand is specified
  213. filename = WebXROculusTouchMotionController.MODEL_RIGHT_FILENAME;
  214. }
  215. let path = this._isQuest() ? WebXROculusTouchMotionController.QUEST_MODEL_BASE_URL : WebXROculusTouchMotionController.MODEL_BASE_URL;
  216. return {
  217. filename,
  218. path
  219. };
  220. }
  221. /**
  222. * Is this the new type of oculus touch. At the moment both have the same profile and it is impossible to differentiate
  223. * between the touch and touch 2.
  224. */
  225. private _isQuest() {
  226. // this is SADLY the only way to currently check. Until proper profiles will be available.
  227. return !!navigator.userAgent.match(/Quest/gi) && !this._forceLegacyControllers;
  228. }
  229. protected _updateModel(): void {
  230. // no-op. model is updated using observables.
  231. }
  232. protected _getModelLoadingConstraints(): boolean {
  233. return true;
  234. }
  235. protected _setRootMesh(meshes: AbstractMesh[]): void {
  236. this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
  237. meshes.forEach((mesh) => { mesh.isPickable = false; });
  238. if (this._isQuest()) {
  239. this._modelRootNode = meshes[0];
  240. this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(0, Math.PI, 0);
  241. this.rootMesh.position.y = 0.034;
  242. this.rootMesh.position.z = 0.052;
  243. } else {
  244. this._modelRootNode = meshes[1];
  245. this.rootMesh.rotationQuaternion = Quaternion.FromEulerAngles(Math.PI / -4, Math.PI, 0);
  246. }
  247. this._modelRootNode.parent = this.rootMesh;
  248. }
  249. }
  250. // register the profile
  251. WebXRMotionControllerManager.RegisterController("oculus-touch", (xrInput: XRInputSource, scene: Scene) => {
  252. return new WebXROculusTouchMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness);
  253. });
  254. WebXRMotionControllerManager.RegisterController("oculus-touch-legacy", (xrInput: XRInputSource, scene: Scene) => {
  255. return new WebXROculusTouchMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness, true);
  256. });