babylon.windowsMotionController.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. module BABYLON {
  2. /**
  3. * Defines the LoadedMeshInfo object that describes information about the loaded webVR controller mesh
  4. */
  5. class LoadedMeshInfo {
  6. /**
  7. * Root of the mesh
  8. */
  9. public rootNode: AbstractMesh;
  10. /**
  11. * Node of the mesh corrisponding to the direction the ray should be cast from the controller
  12. */
  13. public pointingPoseNode: AbstractMesh;
  14. /**
  15. * Map of the button meshes contained in the controller
  16. */
  17. public buttonMeshes: { [id: string]: IButtonMeshInfo; } = {};
  18. /**
  19. * Map of the axis meshes contained in the controller
  20. */
  21. public axisMeshes: { [id: number]: IAxisMeshInfo; } = {};
  22. }
  23. /**
  24. * Defines the IMeshInfo object that describes information a webvr controller mesh
  25. */
  26. interface IMeshInfo {
  27. /**
  28. * Index of the mesh inside the root mesh
  29. */
  30. index: number;
  31. /**
  32. * The mesh
  33. */
  34. value: AbstractMesh;
  35. }
  36. /**
  37. * Defines the IButtonMeshInfo object that describes a button mesh
  38. */
  39. interface IButtonMeshInfo extends IMeshInfo {
  40. /**
  41. * The mesh that should be displayed when pressed
  42. */
  43. pressed: AbstractMesh;
  44. /**
  45. * The mesh that should be displayed when not pressed
  46. */
  47. unpressed: AbstractMesh;
  48. }
  49. /**
  50. * Defines the IAxisMeshInfo object that describes an axis mesh
  51. */
  52. interface IAxisMeshInfo extends IMeshInfo {
  53. /**
  54. * The mesh that should be set when at its min
  55. */
  56. min: AbstractMesh;
  57. /**
  58. * The mesh that should be set when at its max
  59. */
  60. max: AbstractMesh;
  61. }
  62. /**
  63. * Defines the WindowsMotionController object that the state of the windows motion controller
  64. */
  65. export class WindowsMotionController extends WebVRController {
  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. /**
  79. * The controller name prefix for this controller type
  80. */
  81. public static readonly GAMEPAD_ID_PREFIX: string = 'Spatial Controller (Spatial Interaction Source) ';
  82. /**
  83. * The controller id pattern for this controller type
  84. */
  85. private static readonly GAMEPAD_ID_PATTERN = /([0-9a-zA-Z]+-[0-9a-zA-Z]+)$/;
  86. private _loadedMeshInfo: Nullable<LoadedMeshInfo>;
  87. private readonly _mapping = {
  88. // Semantic button names
  89. buttons: ['thumbstick', 'trigger', 'grip', 'menu', 'trackpad'],
  90. // A mapping of the button name to glTF model node name
  91. // that should be transformed by button value.
  92. buttonMeshNames: {
  93. 'trigger': 'SELECT',
  94. 'menu': 'MENU',
  95. 'grip': 'GRASP',
  96. 'thumbstick': 'THUMBSTICK_PRESS',
  97. 'trackpad': 'TOUCHPAD_PRESS'
  98. },
  99. // This mapping is used to translate from the Motion Controller to Babylon semantics
  100. buttonObservableNames: {
  101. 'trigger': 'onTriggerStateChangedObservable',
  102. 'menu': 'onSecondaryButtonStateChangedObservable',
  103. 'grip': 'onMainButtonStateChangedObservable',
  104. 'thumbstick': 'onPadStateChangedObservable',
  105. 'trackpad': 'onTrackpadChangedObservable'
  106. },
  107. // A mapping of the axis name to glTF model node name
  108. // that should be transformed by axis value.
  109. // This array mirrors the browserGamepad.axes array, such that
  110. // the mesh corresponding to axis 0 is in this array index 0.
  111. axisMeshNames: [
  112. 'THUMBSTICK_X',
  113. 'THUMBSTICK_Y',
  114. 'TOUCHPAD_TOUCH_X',
  115. 'TOUCHPAD_TOUCH_Y'
  116. ],
  117. pointingPoseMeshName: PoseEnabledController.POINTING_POSE
  118. };
  119. /**
  120. * Fired when the trackpad on this controller is clicked
  121. */
  122. public onTrackpadChangedObservable = new Observable<ExtendedGamepadButton>();
  123. /**
  124. * Fired when the trackpad on this controller is modified
  125. */
  126. public onTrackpadValuesChangedObservable = new Observable<StickValues>();
  127. /**
  128. * The current x and y values of this controller's trackpad
  129. */
  130. public trackpad: StickValues = { x: 0, y: 0 };
  131. /**
  132. * Creates a new WindowsMotionController from a gamepad
  133. * @param vrGamepad the gamepad that the controller should be created from
  134. */
  135. constructor(vrGamepad: any) {
  136. super(vrGamepad);
  137. this.controllerType = PoseEnabledControllerType.WINDOWS;
  138. this._loadedMeshInfo = null;
  139. }
  140. /**
  141. * Fired when the trigger on this controller is modified
  142. */
  143. public get onTriggerButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  144. return this.onTriggerStateChangedObservable;
  145. }
  146. /**
  147. * Fired when the menu button on this controller is modified
  148. */
  149. public get onMenuButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  150. return this.onSecondaryButtonStateChangedObservable;
  151. }
  152. /**
  153. * Fired when the grip button on this controller is modified
  154. */
  155. public get onGripButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  156. return this.onMainButtonStateChangedObservable;
  157. }
  158. /**
  159. * Fired when the thumbstick button on this controller is modified
  160. */
  161. public get onThumbstickButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  162. return this.onPadStateChangedObservable;
  163. }
  164. /**
  165. * Fired when the touchpad button on this controller is modified
  166. */
  167. public get onTouchpadButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  168. return this.onTrackpadChangedObservable;
  169. }
  170. /**
  171. * Fired when the touchpad values on this controller are modified
  172. */
  173. public get onTouchpadValuesChangedObservable(): Observable<StickValues> {
  174. return this.onTrackpadValuesChangedObservable;
  175. }
  176. private _updateTrackpad(){
  177. if (this.browserGamepad.axes && (this.browserGamepad.axes[2] != this.trackpad.x || this.browserGamepad.axes[3] != this.trackpad.y)) {
  178. this.trackpad.x = this.browserGamepad["axes"][2];
  179. this.trackpad.y = this.browserGamepad["axes"][3];
  180. this.onTrackpadValuesChangedObservable.notifyObservers(this.trackpad);
  181. }
  182. }
  183. /**
  184. * Called once per frame by the engine.
  185. */
  186. public update() {
  187. super.update();
  188. if (this.browserGamepad.axes) {
  189. this._updateTrackpad();
  190. // Only need to animate axes if there is a loaded mesh
  191. if (this._loadedMeshInfo) {
  192. for (let axis = 0; axis < this._mapping.axisMeshNames.length; axis++) {
  193. this._lerpAxisTransform(axis, this.browserGamepad.axes[axis]);
  194. }
  195. }
  196. }
  197. }
  198. /**
  199. * Called once for each button that changed state since the last frame
  200. * @param buttonIdx Which button index changed
  201. * @param state New state of the button
  202. * @param changes Which properties on the state changed since last frame
  203. */
  204. protected _handleButtonChange(buttonIdx: number, state: ExtendedGamepadButton, changes: GamepadButtonChanges) {
  205. let buttonName = this._mapping.buttons[buttonIdx];
  206. if (!buttonName) {
  207. return;
  208. }
  209. // Update the trackpad to ensure trackpad.x/y are accurate during button events between frames
  210. this._updateTrackpad();
  211. // Only emit events for buttons that we know how to map from index to name
  212. let observable = (<any>this)[(<any>(this._mapping.buttonObservableNames))[buttonName]];
  213. if (observable) {
  214. observable.notifyObservers(state);
  215. }
  216. this._lerpButtonTransform(buttonName, state.value);
  217. }
  218. /**
  219. * Moves the buttons on the controller mesh based on their current state
  220. * @param buttonName the name of the button to move
  221. * @param buttonValue the value of the button which determines the buttons new position
  222. */
  223. protected _lerpButtonTransform(buttonName: string, buttonValue: number) {
  224. // If there is no loaded mesh, there is nothing to transform.
  225. if (!this._loadedMeshInfo) {
  226. return;
  227. }
  228. var meshInfo = this._loadedMeshInfo.buttonMeshes[buttonName];
  229. if (!meshInfo.unpressed.rotationQuaternion || !meshInfo.pressed.rotationQuaternion || !meshInfo.value.rotationQuaternion) {
  230. return;
  231. }
  232. Quaternion.SlerpToRef(
  233. meshInfo.unpressed.rotationQuaternion,
  234. meshInfo.pressed.rotationQuaternion,
  235. buttonValue,
  236. meshInfo.value.rotationQuaternion);
  237. Vector3.LerpToRef(
  238. meshInfo.unpressed.position,
  239. meshInfo.pressed.position,
  240. buttonValue,
  241. meshInfo.value.position);
  242. }
  243. /**
  244. * Moves the axis on the controller mesh based on its current state
  245. * @param axis the index of the axis
  246. * @param axisValue the value of the axis which determines the meshes new position
  247. * @hidden
  248. */
  249. protected _lerpAxisTransform(axis: number, axisValue: number) {
  250. if (!this._loadedMeshInfo) {
  251. return;
  252. }
  253. let meshInfo = this._loadedMeshInfo.axisMeshes[axis];
  254. if (!meshInfo) {
  255. return;
  256. }
  257. if (!meshInfo.min.rotationQuaternion || !meshInfo.max.rotationQuaternion || !meshInfo.value.rotationQuaternion) {
  258. return;
  259. }
  260. // Convert from gamepad value range (-1 to +1) to lerp range (0 to 1)
  261. let lerpValue = axisValue * 0.5 + 0.5;
  262. Quaternion.SlerpToRef(
  263. meshInfo.min.rotationQuaternion,
  264. meshInfo.max.rotationQuaternion,
  265. lerpValue,
  266. meshInfo.value.rotationQuaternion);
  267. Vector3.LerpToRef(
  268. meshInfo.min.position,
  269. meshInfo.max.position,
  270. lerpValue,
  271. meshInfo.value.position);
  272. }
  273. /**
  274. * Implements abstract method on WebVRController class, loading controller meshes and calling this.attachToMesh if successful.
  275. * @param scene scene in which to add meshes
  276. * @param meshLoaded optional callback function that will be called if the mesh loads successfully.
  277. */
  278. public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void, forceDefault = false) {
  279. let path: string;
  280. let filename: string;
  281. // Checking if GLB loader is present
  282. if (SceneLoader.IsPluginForExtensionAvailable(".glb")) {
  283. // Determine the device specific folder based on the ID suffix
  284. let device = 'default';
  285. if (this.id && !forceDefault) {
  286. let match = this.id.match(WindowsMotionController.GAMEPAD_ID_PATTERN);
  287. device = ((match && match[0]) || device);
  288. }
  289. // Hand
  290. if (this.hand === 'left') {
  291. filename = WindowsMotionController.MODEL_LEFT_FILENAME;
  292. }
  293. else { // Right is the default if no hand is specified
  294. filename = WindowsMotionController.MODEL_RIGHT_FILENAME;
  295. }
  296. path = WindowsMotionController.MODEL_BASE_URL + device + '/';
  297. } else {
  298. Tools.Warn("You need to reference GLTF loader to load Windows Motion Controllers model. Falling back to generic models");
  299. path = GenericController.MODEL_BASE_URL;
  300. filename = GenericController.MODEL_FILENAME;
  301. }
  302. SceneLoader.ImportMesh("", path, filename, scene, (meshes: AbstractMesh[]) => {
  303. // glTF files successfully loaded from the remote server, now process them to ensure they are in the right format.
  304. this._loadedMeshInfo = this.processModel(scene, meshes);
  305. if (!this._loadedMeshInfo) {
  306. return;
  307. }
  308. this._defaultModel = this._loadedMeshInfo.rootNode;
  309. this.attachToMesh(this._defaultModel);
  310. if (meshLoaded) {
  311. meshLoaded(this._defaultModel);
  312. }
  313. }, null, (scene: Scene, message: string) => {
  314. Tools.Log(message);
  315. Tools.Warn('Failed to retrieve controller model from the remote server: ' + path + filename);
  316. if (!forceDefault) {
  317. this.initControllerMesh(scene, meshLoaded, true);
  318. }
  319. });
  320. }
  321. /**
  322. * Takes a list of meshes (as loaded from the glTF file) and finds the root node, as well as nodes that
  323. * can be transformed by button presses and axes values, based on this._mapping.
  324. *
  325. * @param scene scene in which the meshes exist
  326. * @param meshes list of meshes that make up the controller model to process
  327. * @return structured view of the given meshes, with mapping of buttons and axes to meshes that can be transformed.
  328. */
  329. private processModel(scene: Scene, meshes: AbstractMesh[]): Nullable<LoadedMeshInfo> {
  330. let loadedMeshInfo = null;
  331. // Create a new mesh to contain the glTF hierarchy
  332. let parentMesh = new Mesh(this.id + " " + this.hand, scene);
  333. // Find the root node in the loaded glTF scene, and attach it as a child of 'parentMesh'
  334. let childMesh: Nullable<AbstractMesh> = null;
  335. for (let i = 0; i < meshes.length; i++) {
  336. let mesh = meshes[i];
  337. if (!mesh.parent) {
  338. // Exclude controller meshes from picking results
  339. mesh.isPickable = false;
  340. // Handle root node, attach to the new parentMesh
  341. childMesh = mesh;
  342. break;
  343. }
  344. }
  345. if (childMesh) {
  346. childMesh.setParent(parentMesh);
  347. // Create our mesh info. Note that this method will always return non-null.
  348. loadedMeshInfo = this.createMeshInfo(parentMesh);
  349. } else {
  350. Tools.Warn('Could not find root node in model file.');
  351. }
  352. return loadedMeshInfo;
  353. }
  354. private createMeshInfo(rootNode: AbstractMesh): LoadedMeshInfo {
  355. let loadedMeshInfo = new LoadedMeshInfo();
  356. var i;
  357. loadedMeshInfo.rootNode = rootNode;
  358. // Reset the caches
  359. loadedMeshInfo.buttonMeshes = {};
  360. loadedMeshInfo.axisMeshes = {};
  361. // Button Meshes
  362. for (i = 0; i < this._mapping.buttons.length; i++) {
  363. var buttonMeshName = (<any>this._mapping.buttonMeshNames)[this._mapping.buttons[i]];
  364. if (!buttonMeshName) {
  365. Tools.Log('Skipping unknown button at index: ' + i + ' with mapped name: ' + this._mapping.buttons[i]);
  366. continue;
  367. }
  368. var buttonMesh = getChildByName(rootNode, buttonMeshName);
  369. if (!buttonMesh) {
  370. Tools.Warn('Missing button mesh with name: ' + buttonMeshName);
  371. continue;
  372. }
  373. var buttonMeshInfo = {
  374. index: i,
  375. value: getImmediateChildByName(buttonMesh, 'VALUE'),
  376. pressed: getImmediateChildByName(buttonMesh, 'PRESSED'),
  377. unpressed: getImmediateChildByName(buttonMesh, 'UNPRESSED')
  378. };
  379. if (buttonMeshInfo.value && buttonMeshInfo.pressed && buttonMeshInfo.unpressed) {
  380. loadedMeshInfo.buttonMeshes[this._mapping.buttons[i]] = buttonMeshInfo;
  381. } else {
  382. // If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes.
  383. Tools.Warn('Missing button submesh under mesh with name: ' + buttonMeshName +
  384. '(VALUE: ' + !!buttonMeshInfo.value +
  385. ', PRESSED: ' + !!buttonMeshInfo.pressed +
  386. ', UNPRESSED:' + !!buttonMeshInfo.unpressed +
  387. ')');
  388. }
  389. }
  390. // Axis Meshes
  391. for (i = 0; i < this._mapping.axisMeshNames.length; i++) {
  392. var axisMeshName = this._mapping.axisMeshNames[i];
  393. if (!axisMeshName) {
  394. Tools.Log('Skipping unknown axis at index: ' + i);
  395. continue;
  396. }
  397. var axisMesh = getChildByName(rootNode, axisMeshName);
  398. if (!axisMesh) {
  399. Tools.Warn('Missing axis mesh with name: ' + axisMeshName);
  400. continue;
  401. }
  402. var axisMeshInfo = {
  403. index: i,
  404. value: getImmediateChildByName(axisMesh, 'VALUE'),
  405. min: getImmediateChildByName(axisMesh, 'MIN'),
  406. max: getImmediateChildByName(axisMesh, 'MAX')
  407. };
  408. if (axisMeshInfo.value && axisMeshInfo.min && axisMeshInfo.max) {
  409. loadedMeshInfo.axisMeshes[i] = axisMeshInfo;
  410. } else {
  411. // If we didn't find the mesh, it simply means thit axis won't have transforms applied as mapped axis values change.
  412. Tools.Warn('Missing axis submesh under mesh with name: ' + axisMeshName +
  413. '(VALUE: ' + !!axisMeshInfo.value +
  414. ', MIN: ' + !!axisMeshInfo.min +
  415. ', MAX:' + !!axisMeshInfo.max +
  416. ')');
  417. }
  418. }
  419. // Pointing Ray
  420. loadedMeshInfo.pointingPoseNode = getChildByName(rootNode, this._mapping.pointingPoseMeshName);
  421. if (!loadedMeshInfo.pointingPoseNode) {
  422. Tools.Warn('Missing pointing pose mesh with name: ' + this._mapping.pointingPoseMeshName);
  423. }else{
  424. this._pointingPoseNode = loadedMeshInfo.pointingPoseNode;
  425. }
  426. return loadedMeshInfo;
  427. // Look through all children recursively. This will return null if no mesh exists with the given name.
  428. function getChildByName(node: Node, name: string) {
  429. return node.getChildMeshes(false, n => n.name === name)[0];
  430. }
  431. // Look through only immediate children. This will return null if no mesh exists with the given name.
  432. function getImmediateChildByName(node: Node, name: string): AbstractMesh {
  433. return node.getChildMeshes(true, n => n.name == name)[0];
  434. }
  435. }
  436. /**
  437. * Gets the ray of the controller in the direction the controller is pointing
  438. * @param length the length the resulting ray should be
  439. * @returns a ray in the direction the controller is pointing
  440. */
  441. public getForwardRay(length = 100): Ray {
  442. if (!(this._loadedMeshInfo && this._loadedMeshInfo.pointingPoseNode)) {
  443. return super.getForwardRay(length);
  444. }
  445. var m = this._loadedMeshInfo.pointingPoseNode.getWorldMatrix();
  446. var origin = m.getTranslation();
  447. var forward = new Vector3(0, 0, -1);
  448. var forwardWorld = Vector3.TransformNormal(forward, m);
  449. var direction = Vector3.Normalize(forwardWorld);
  450. return new Ray(origin, direction, length);
  451. }
  452. /**
  453. * Disposes of the controller
  454. */
  455. public dispose(): void {
  456. super.dispose();
  457. this.onTrackpadChangedObservable.clear();
  458. }
  459. }
  460. }