babylon.windowsMotionController.ts 17 KB

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