windowsMotionController.ts 25 KB

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