babylon.windowsMotionController.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. module BABYLON {
  2. class LoadedMeshInfo {
  3. public rootNode: AbstractMesh;
  4. public pointingPoseNode: AbstractMesh;
  5. public holdingPoseNode: AbstractMesh;
  6. public buttonMeshes: { [id: string] : IButtonMeshInfo; } = {};
  7. public axisMeshes: { [id: number] : IAxisMeshInfo; } = {};
  8. }
  9. interface IMeshInfo {
  10. index: number;
  11. value: AbstractMesh;
  12. }
  13. interface IButtonMeshInfo extends IMeshInfo {
  14. pressed: AbstractMesh;
  15. unpressed: AbstractMesh;
  16. }
  17. interface IAxisMeshInfo extends IMeshInfo {
  18. min: AbstractMesh;
  19. max: AbstractMesh;
  20. }
  21. export class WindowsMotionController extends WebVRController {
  22. private static readonly MODEL_BASE_URL:string = 'https://controllers.babylonjs.com/microsoft/';
  23. private static readonly MODEL_LEFT_FILENAME:string = 'left.glb';
  24. private static readonly MODEL_RIGHT_FILENAME:string = 'right.glb';
  25. private static readonly MODEL_ROOT_NODE_NAME:string = 'RootNode';
  26. private static readonly GLTF_ROOT_TRANSFORM_NAME:string = 'root';
  27. public static readonly GAMEPAD_ID_PREFIX:string = 'Spatial Controller (Spatial Interaction Source) ';
  28. private static readonly GAMEPAD_ID_PATTERN = /([0-9a-zA-Z]+-[0-9a-zA-Z]+)$/;
  29. private _loadedMeshInfo: LoadedMeshInfo;
  30. private readonly _mapping = {
  31. // Semantic button names
  32. buttons: ['thumbstick', 'trigger', 'grip', 'menu', 'trackpad'],
  33. // A mapping of the button name to glTF model node name
  34. // that should be transformed by button value.
  35. buttonMeshNames: {
  36. 'trigger': 'SELECT',
  37. 'menu': 'MENU',
  38. 'grip': 'GRASP',
  39. 'thumbstick': 'THUMBSTICK_PRESS',
  40. 'trackpad': 'TOUCHPAD_PRESS'
  41. },
  42. // This mapping is used to translate from the Motion Controller to Babylon semantics
  43. buttonObservableNames: {
  44. 'trigger': 'onTriggerStateChangedObservable',
  45. 'menu': 'onSecondaryButtonStateChangedObservable',
  46. 'grip': 'onMainButtonStateChangedObservable',
  47. 'thumbstick': 'onPadStateChangedObservable',
  48. 'trackpad': 'onTrackpadChangedObservable'
  49. },
  50. // A mapping of the axis name to glTF model node name
  51. // that should be transformed by axis value.
  52. // This array mirrors the browserGamepad.axes array, such that
  53. // the mesh corresponding to axis 0 is in this array index 0.
  54. axisMeshNames: [
  55. 'THUMBSTICK_X',
  56. 'THUMBSTICK_Y',
  57. 'TOUCHPAD_TOUCH_X',
  58. 'TOUCHPAD_TOUCH_Y'
  59. ]
  60. };
  61. public onTrackpadChangedObservable = new Observable<ExtendedGamepadButton>();
  62. constructor(vrGamepad) {
  63. super(vrGamepad);
  64. this.controllerType = PoseEnabledControllerType.WINDOWS;
  65. this._loadedMeshInfo = null;
  66. }
  67. public get onTriggerButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  68. return this.onTriggerStateChangedObservable;
  69. }
  70. public get onMenuButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  71. return this.onSecondaryButtonStateChangedObservable;
  72. }
  73. public get onGripButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  74. return this.onMainButtonStateChangedObservable;
  75. }
  76. public get onThumbstickButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  77. return this.onPadStateChangedObservable;
  78. }
  79. public get onTouchpadButtonStateChangedObservable(): Observable<ExtendedGamepadButton> {
  80. return this.onTrackpadChangedObservable;
  81. }
  82. /**
  83. * Called once per frame by the engine.
  84. */
  85. public update() {
  86. super.update();
  87. // Only need to animate axes if there is a loaded mesh
  88. if (this._loadedMeshInfo) {
  89. if (this.browserGamepad.axes) {
  90. for (let axis = 0; axis < this._mapping.axisMeshNames.length; axis++) {
  91. this.lerpAxisTransform(axis, this.browserGamepad.axes[axis]);
  92. }
  93. }
  94. }
  95. }
  96. /**
  97. * Called once for each button that changed state since the last frame
  98. * @param buttonIdx Which button index changed
  99. * @param state New state of the button
  100. * @param changes Which properties on the state changed since last frame
  101. */
  102. protected handleButtonChange(buttonIdx: number, state: ExtendedGamepadButton, changes: GamepadButtonChanges) {
  103. let buttonName = this._mapping.buttons[buttonIdx];
  104. if (!buttonName) {
  105. return;
  106. }
  107. // Only emit events for buttons that we know how to map from index to name
  108. let observable = this[this._mapping.buttonObservableNames[buttonName]];
  109. if (observable) {
  110. observable.notifyObservers(state);
  111. }
  112. this.lerpButtonTransform(buttonName, state.value);
  113. }
  114. protected lerpButtonTransform(buttonName: string, buttonValue: number) {
  115. // If there is no loaded mesh, there is nothing to transform.
  116. if (!this._loadedMeshInfo) {
  117. return;
  118. }
  119. var meshInfo = this._loadedMeshInfo.buttonMeshes[buttonName];
  120. BABYLON.Quaternion.SlerpToRef(
  121. meshInfo.unpressed.rotationQuaternion,
  122. meshInfo.pressed.rotationQuaternion,
  123. buttonValue,
  124. meshInfo.value.rotationQuaternion);
  125. BABYLON.Vector3.LerpToRef(
  126. meshInfo.unpressed.position,
  127. meshInfo.pressed.position,
  128. buttonValue,
  129. meshInfo.value.position);
  130. }
  131. protected lerpAxisTransform(axis:number, axisValue: number) {
  132. let meshInfo = this._loadedMeshInfo.axisMeshes[axis];
  133. if (!meshInfo) {
  134. return;
  135. }
  136. // Convert from gamepad value range (-1 to +1) to lerp range (0 to 1)
  137. let lerpValue = axisValue * 0.5 + 0.5;
  138. BABYLON.Quaternion.SlerpToRef(
  139. meshInfo.min.rotationQuaternion,
  140. meshInfo.max.rotationQuaternion,
  141. lerpValue,
  142. meshInfo.value.rotationQuaternion);
  143. BABYLON.Vector3.LerpToRef(
  144. meshInfo.min.position,
  145. meshInfo.max.position,
  146. lerpValue,
  147. meshInfo.value.position);
  148. }
  149. /**
  150. * Implements abstract method on WebVRController class, loading controller meshes and calling this.attachToMesh if successful.
  151. * @param scene scene in which to add meshes
  152. * @param meshLoaded optional callback function that will be called if the mesh loads successfully.
  153. */
  154. public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void, forceDefault = false) {
  155. let path: string;
  156. let filename: string;
  157. // Checking if GLB loader is present
  158. if (SceneLoader.GetPluginForExtension("glb")) {
  159. // Determine the device specific folder based on the ID suffix
  160. let device = 'default';
  161. if (this.id && !forceDefault) {
  162. let match = this.id.match(WindowsMotionController.GAMEPAD_ID_PATTERN);
  163. device = ((match && match[0]) || device);
  164. }
  165. // Hand
  166. if (this.hand === 'left') {
  167. filename = WindowsMotionController.MODEL_LEFT_FILENAME;
  168. }
  169. else { // Right is the default if no hand is specified
  170. filename = WindowsMotionController.MODEL_RIGHT_FILENAME;
  171. }
  172. path = WindowsMotionController.MODEL_BASE_URL + device + '/';
  173. } else {
  174. Tools.Warn("You need to reference GLTF loader to load Windows Motion Controllers model. Falling back to generic models");
  175. path = "http://yoda.blob.core.windows.net/models/";
  176. filename = "genericvrcontroller.babylon";
  177. }
  178. SceneLoader.ImportMesh("", path, filename, scene, (meshes: AbstractMesh[]) => {
  179. // glTF files successfully loaded from the remote server, now process them to ensure they are in the right format.
  180. this._loadedMeshInfo = this.processModel(scene, meshes);
  181. if (!this._loadedMeshInfo) {
  182. return;
  183. }
  184. this._defaultModel = this._loadedMeshInfo.rootNode;
  185. this.attachToMesh(this._defaultModel);
  186. if (meshLoaded) {
  187. meshLoaded(this._defaultModel);
  188. }
  189. }, null, (scene: Scene, message: string) => {
  190. Tools.Log(message);
  191. Tools.Warn('Failed to retrieve controller model from the remote server: ' + path + filename);
  192. if (!forceDefault) {
  193. this.initControllerMesh(scene, meshLoaded, true);
  194. }
  195. });
  196. }
  197. /**
  198. * Takes a list of meshes (as loaded from the glTF file) and finds the root node, as well as nodes that
  199. * can be transformed by button presses and axes values, based on this._mapping.
  200. *
  201. * @param scene scene in which the meshes exist
  202. * @param meshes list of meshes that make up the controller model to process
  203. * @return structured view of the given meshes, with mapping of buttons and axes to meshes that can be transformed.
  204. */
  205. private processModel(scene: Scene, meshes: AbstractMesh[]) : LoadedMeshInfo {
  206. let loadedMeshInfo = null;
  207. // Create a new mesh to contain the glTF hierarchy
  208. let parentMesh = new BABYLON.Mesh(this.id + " " + this.hand, scene);
  209. // Find the root node in the loaded glTF scene, and attach it as a child of 'parentMesh'
  210. let childMesh : AbstractMesh = null;
  211. for (let i = 0; i < meshes.length; i++) {
  212. let mesh = meshes[i];
  213. if (mesh.id === WindowsMotionController.MODEL_ROOT_NODE_NAME) {
  214. // There may be a parent mesh to perform the RH to LH matrix transform.
  215. // Exclude controller meshes from picking results
  216. mesh.isPickable = false;
  217. // Handle root node, attach to the new parentMesh
  218. if (mesh.parent && mesh.parent.name === WindowsMotionController.GLTF_ROOT_TRANSFORM_NAME)
  219. mesh = <AbstractMesh>mesh.parent;
  220. childMesh = mesh;
  221. break;
  222. }
  223. }
  224. if (childMesh) {
  225. childMesh.setParent(parentMesh);
  226. // Create our mesh info. Note that this method will always return non-null.
  227. loadedMeshInfo = this.createMeshInfo(parentMesh);
  228. } else {
  229. Tools.Warn('No node with name ' + WindowsMotionController.MODEL_ROOT_NODE_NAME +' in model file.');
  230. }
  231. return loadedMeshInfo;
  232. }
  233. private createMeshInfo(rootNode: AbstractMesh) : LoadedMeshInfo {
  234. let loadedMeshInfo = new LoadedMeshInfo();
  235. var i;
  236. loadedMeshInfo.rootNode = rootNode;
  237. // Reset the caches
  238. loadedMeshInfo.buttonMeshes = {};
  239. loadedMeshInfo.axisMeshes = {};
  240. // Button Meshes
  241. for (i = 0; i < this._mapping.buttons.length; i++) {
  242. var buttonMeshName = this._mapping.buttonMeshNames[this._mapping.buttons[i]];
  243. if (!buttonMeshName) {
  244. Tools.Log('Skipping unknown button at index: ' + i + ' with mapped name: ' + this._mapping.buttons[i]);
  245. continue;
  246. }
  247. var buttonMesh = getChildByName(rootNode, buttonMeshName);
  248. if (!buttonMesh) {
  249. Tools.Warn('Missing button mesh with name: ' + buttonMeshName);
  250. continue;
  251. }
  252. var buttonMeshInfo = {
  253. index: i,
  254. value: getImmediateChildByName(buttonMesh, 'VALUE'),
  255. pressed: getImmediateChildByName(buttonMesh, 'PRESSED'),
  256. unpressed: getImmediateChildByName(buttonMesh, 'UNPRESSED')
  257. };
  258. if (buttonMeshInfo.value && buttonMeshInfo.pressed && buttonMeshInfo.unpressed) {
  259. loadedMeshInfo.buttonMeshes[this._mapping.buttons[i]] = buttonMeshInfo;
  260. } else {
  261. // If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes.
  262. Tools.Warn('Missing button submesh under mesh with name: ' + buttonMeshName +
  263. '(VALUE: ' + !!buttonMeshInfo.value +
  264. ', PRESSED: ' + !!buttonMeshInfo.pressed +
  265. ', UNPRESSED:' + !!buttonMeshInfo.unpressed +
  266. ')');
  267. }
  268. }
  269. // Axis Meshes
  270. for (i = 0; i < this._mapping.axisMeshNames.length; i++) {
  271. var axisMeshName = this._mapping.axisMeshNames[i];
  272. if (!axisMeshName) {
  273. Tools.Log('Skipping unknown axis at index: ' + i);
  274. continue;
  275. }
  276. var axisMesh = getChildByName(rootNode, axisMeshName);
  277. if (!axisMesh) {
  278. Tools.Warn('Missing axis mesh with name: ' + axisMeshName);
  279. continue;
  280. }
  281. var axisMeshInfo = {
  282. index: i,
  283. value: getImmediateChildByName(axisMesh, 'VALUE'),
  284. min: getImmediateChildByName(axisMesh, 'MIN'),
  285. max: getImmediateChildByName(axisMesh, 'MAX')
  286. };
  287. if (axisMeshInfo.value && axisMeshInfo.min && axisMeshInfo.max) {
  288. loadedMeshInfo.axisMeshes[i] = axisMeshInfo;
  289. } else {
  290. // If we didn't find the mesh, it simply means thit axis won't have transforms applied as mapped axis values change.
  291. Tools.Warn('Missing axis submesh under mesh with name: ' + axisMeshName +
  292. '(VALUE: ' + !!axisMeshInfo.value +
  293. ', MIN: ' + !!axisMeshInfo.min +
  294. ', MAX:' + !!axisMeshInfo.max +
  295. ')');
  296. }
  297. }
  298. return loadedMeshInfo;
  299. // Look through all children recursively. This will return null if no mesh exists with the given name.
  300. function getChildByName(node, name) {
  301. return node.getChildMeshes(false, n => n.name === name)[0];
  302. }
  303. // Look through only immediate children. This will return null if no mesh exists with the given name.
  304. function getImmediateChildByName (node, name) : AbstractMesh {
  305. return node.getChildMeshes(true, n => n.name == name)[0];
  306. }
  307. }
  308. public dispose(): void {
  309. super.dispose();
  310. this.onTrackpadChangedObservable.clear();
  311. }
  312. }
  313. }