windowsMotionController.ts 22 KB

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