windowsMotionController.ts 25 KB

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