babylon.extendedGamepad.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. module BABYLON {
  2. export enum PoseEnabledControllerType {
  3. VIVE,
  4. OCULUS,
  5. GENERIC
  6. }
  7. export interface MutableGamepadButton {
  8. value: number;
  9. touched: boolean;
  10. pressed: boolean;
  11. }
  12. export class PoseEnabledControllerHelper {
  13. public static InitiateController(vrGamepad: any) {
  14. // for now, only Oculus and Vive are supported
  15. if (vrGamepad.id.indexOf('Oculus Touch') !== -1) {
  16. return new OculusTouchController(vrGamepad);
  17. } else {
  18. return new ViveController(vrGamepad);
  19. }
  20. }
  21. }
  22. export class PoseEnabledController extends Gamepad implements PoseControlled {
  23. devicePosition: Vector3;
  24. deviceRotationQuaternion: Quaternion;
  25. deviceScaleFactor: number = 1;
  26. public position: Vector3;
  27. public rotationQuaternion: Quaternion;
  28. public controllerType: PoseEnabledControllerType;
  29. private _calculatedPosition: Vector3;
  30. private _calculatedRotation: Quaternion;
  31. public rawPose: DevicePose; //GamepadPose;
  32. public _mesh: AbstractMesh; // a node that will be attached to this Gamepad
  33. private _poseControlledCamera: TargetCamera;
  34. private _leftHandSystemQuaternion: Quaternion = new Quaternion();
  35. constructor(public vrGamepad) {
  36. super(vrGamepad.id, vrGamepad.index, vrGamepad);
  37. this.type = Gamepad.POSE_ENABLED;
  38. this.controllerType = PoseEnabledControllerType.GENERIC;
  39. this.position = Vector3.Zero();
  40. this.rotationQuaternion = new Quaternion();
  41. this.devicePosition = Vector3.Zero();
  42. this.deviceRotationQuaternion = new Quaternion();
  43. this._calculatedPosition = Vector3.Zero();
  44. this._calculatedRotation = new Quaternion();
  45. Quaternion.RotationYawPitchRollToRef(Math.PI, 0, 0, this._leftHandSystemQuaternion);
  46. }
  47. public update() {
  48. super.update();
  49. // update this device's offset position from the attached camera, if provided
  50. //if (this._poseControlledCamera && this._poseControlledCamera.deviceScaleFactor) {
  51. //this.position.copyFrom(this._poseControlledCamera.position);
  52. //this.rotationQuaternion.copyFrom(this._poseControlledCamera.rotationQuaternion);
  53. //this.deviceScaleFactor = this._poseControlledCamera.deviceScaleFactor;
  54. //}
  55. var pose: GamepadPose = this.vrGamepad.pose;
  56. this.updateFromDevice(pose);
  57. if (this._mesh) {
  58. this._mesh.position.copyFrom(this._calculatedPosition);
  59. this._mesh.rotationQuaternion.copyFrom(this._calculatedRotation);
  60. }
  61. }
  62. updateFromDevice(poseData: DevicePose) {
  63. if (poseData) {
  64. this.rawPose = poseData;
  65. if (poseData.position) {
  66. this.devicePosition.copyFromFloats(poseData.position[0], poseData.position[1], -poseData.position[2]);
  67. if (this._mesh && this._mesh.getScene().useRightHandedSystem) {
  68. this.devicePosition.z *= -1;
  69. }
  70. this.devicePosition.scaleToRef(this.deviceScaleFactor, this._calculatedPosition);
  71. this._calculatedPosition.addInPlace(this.position);
  72. // scale the position using the scale factor, add the device's position
  73. /*if (this._poseControlledCamera) {
  74. // this allows total positioning freedom - the device, the camera and the mesh can be individually controlled.
  75. this._calculatedPosition.addInPlace(this._poseControlledCamera.position);
  76. }*/
  77. }
  78. if (poseData.orientation) {
  79. this.deviceRotationQuaternion.copyFromFloats(this.rawPose.orientation[0], this.rawPose.orientation[1], -this.rawPose.orientation[2], -this.rawPose.orientation[3]);
  80. if (this._mesh) {
  81. if (this._mesh.getScene().useRightHandedSystem) {
  82. this.deviceRotationQuaternion.z *= -1;
  83. this.deviceRotationQuaternion.w *= -1;
  84. } else {
  85. this.deviceRotationQuaternion.multiplyToRef(this._leftHandSystemQuaternion, this.deviceRotationQuaternion);
  86. }
  87. }
  88. // if the camera is set, rotate to the camera's rotation
  89. this.deviceRotationQuaternion.multiplyToRef(this.rotationQuaternion, this._calculatedRotation);
  90. /*if (this._poseControlledCamera) {
  91. Matrix.ScalingToRef(1, 1, 1, Tmp.Matrix[1]);
  92. this._calculatedRotation.toRotationMatrix(Tmp.Matrix[0]);
  93. Matrix.TranslationToRef(this._calculatedPosition.x, this._calculatedPosition.y, this._calculatedPosition.z, Tmp.Matrix[2]);
  94. //Matrix.Identity().multiplyToRef(Tmp.Matrix[1], Tmp.Matrix[4]);
  95. Tmp.Matrix[1].multiplyToRef(Tmp.Matrix[0], Tmp.Matrix[5]);
  96. this._poseControlledCamera.getWorldMatrix().getTranslationToRef(Tmp.Vector3[0])
  97. Matrix.ComposeToRef(new Vector3(this.deviceScaleFactor, this.deviceScaleFactor, this.deviceScaleFactor), this._poseControlledCamera.rotationQuaternion, Tmp.Vector3[0], Tmp.Matrix[4]);
  98. Tmp.Matrix[5].multiplyToRef(Tmp.Matrix[2], Tmp.Matrix[1]);
  99. Tmp.Matrix[1].multiplyToRef(Tmp.Matrix[4], Tmp.Matrix[2]);
  100. Tmp.Matrix[2].decompose(Tmp.Vector3[0], this._calculatedRotation, this._calculatedPosition);
  101. }*/
  102. }
  103. }
  104. }
  105. public attachToMesh(mesh: AbstractMesh) {
  106. if (this._mesh) {
  107. this._mesh.parent = undefined;
  108. }
  109. this._mesh = mesh;
  110. if (this._poseControlledCamera) {
  111. this._mesh.parent = this._poseControlledCamera;
  112. }
  113. if (!this._mesh.rotationQuaternion) {
  114. this._mesh.rotationQuaternion = new Quaternion();
  115. }
  116. }
  117. public attachToPoseControlledCamera(camera: TargetCamera) {
  118. this._poseControlledCamera = camera;
  119. if (this._mesh) {
  120. this._mesh.parent = this._poseControlledCamera;
  121. }
  122. }
  123. public detachMesh() {
  124. this._mesh = undefined;
  125. }
  126. }
  127. export interface GamepadButtonChanges {
  128. changed: boolean;
  129. pressChanged: boolean;
  130. touchChanged: boolean;
  131. valueChanged: boolean;
  132. }
  133. export abstract class WebVRController extends PoseEnabledController {
  134. //public onTriggerStateChangedObservable = new Observable<{ state: ExtendedGamepadButton, changes: GamepadButtonChanges }>();
  135. public onTriggerStateChangedObservable = new Observable<ExtendedGamepadButton>();
  136. public onMainButtonStateChangedObservable = new Observable<ExtendedGamepadButton>();
  137. public onSecondaryButtonStateChangedObservable = new Observable<ExtendedGamepadButton>();
  138. public onPadStateChangedObservable = new Observable<ExtendedGamepadButton>();
  139. public onPadValuesChangedObservable = new Observable<StickValues>();
  140. protected _buttons: Array<MutableGamepadButton>;
  141. private _onButtonStateChange: (controlledIndex: number, buttonIndex: number, state: ExtendedGamepadButton) => void;
  142. public onButtonStateChange(callback: (controlledIndex: number, buttonIndex: number, state: ExtendedGamepadButton) => void) {
  143. this._onButtonStateChange = callback;
  144. }
  145. public pad: StickValues = { x: 0, y: 0 };
  146. public hand: string; // 'left' or 'right', see https://w3c.github.io/gamepad/extensions.html#gamepadhand-enum
  147. constructor(vrGamepad) {
  148. super(vrGamepad);
  149. this._buttons = new Array<ExtendedGamepadButton>(vrGamepad.buttons.length);
  150. this.hand = vrGamepad.hand;
  151. }
  152. public update() {
  153. super.update();
  154. for (var index = 0; index < this._buttons.length; index++) {
  155. this._setButtonValue(this.vrGamepad.buttons[index], this._buttons[index], index);
  156. };
  157. if (this.leftStick.x !== this.pad.x || this.leftStick.y !== this.pad.y) {
  158. this.pad.x = this.leftStick.x;
  159. this.pad.y = this.leftStick.y;
  160. this.onPadValuesChangedObservable.notifyObservers(this.pad);
  161. }
  162. }
  163. protected abstract handleButtonChange(buttonIdx: number, value: ExtendedGamepadButton, changes: GamepadButtonChanges);
  164. public abstract initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void)
  165. private _setButtonValue(newState: ExtendedGamepadButton, currentState: ExtendedGamepadButton, buttonIndex: number) {
  166. if (!currentState) {
  167. this._buttons[buttonIndex] = {
  168. pressed: newState.pressed,
  169. touched: newState.touched,
  170. value: newState.value
  171. }
  172. return;
  173. }
  174. this._checkChanges(newState, currentState);
  175. if (this._changes.changed) {
  176. this._onButtonStateChange && this._onButtonStateChange(this.index, buttonIndex, newState);
  177. this.handleButtonChange(buttonIndex, newState, this._changes);
  178. }
  179. this._buttons[buttonIndex].pressed = newState.pressed;
  180. this._buttons[buttonIndex].touched = newState.touched;
  181. // oculus triggers are never 0, thou not touched.
  182. this._buttons[buttonIndex].value = newState.value < 0.00000001 ? 0 : newState.value;
  183. }
  184. // avoid GC, store state in a tmp object
  185. private _changes: GamepadButtonChanges = {
  186. pressChanged: false,
  187. touchChanged: false,
  188. valueChanged: false,
  189. changed: false
  190. };
  191. private _checkChanges(newState: ExtendedGamepadButton, currentState: ExtendedGamepadButton) {
  192. this._changes.pressChanged = newState.pressed !== currentState.pressed;
  193. this._changes.touchChanged = newState.touched !== currentState.touched;
  194. this._changes.valueChanged = newState.value !== currentState.value;
  195. this._changes.changed = this._changes.pressChanged || this._changes.touchChanged || this._changes.valueChanged;
  196. return this._changes;
  197. }
  198. }
  199. export class OculusTouchController extends WebVRController {
  200. private _defaultModel: BABYLON.AbstractMesh;
  201. public onSecondaryTriggerStateChangedObservable = new Observable<ExtendedGamepadButton>();
  202. public onThumbRestChangedObservable = new Observable<ExtendedGamepadButton>();
  203. constructor(vrGamepad) {
  204. super(vrGamepad);
  205. this.controllerType = PoseEnabledControllerType.OCULUS;
  206. }
  207. public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void) {
  208. let meshName = this.hand === 'right' ? 'RightTouch.babylon' : 'LeftTouch.babylon';
  209. SceneLoader.ImportMesh("", "http://yoda.blob.core.windows.net/models/", meshName, scene, (newMeshes) => {
  210. /*
  211. Parent Mesh name: oculus_touch_left
  212. - body
  213. - trigger
  214. - thumbstick
  215. - grip
  216. - button_y
  217. - button_x
  218. - button_enter
  219. */
  220. this._defaultModel = newMeshes[1];
  221. if (meshLoaded) {
  222. meshLoaded(this._defaultModel);
  223. }
  224. this.attachToMesh(this._defaultModel);
  225. });
  226. }
  227. // helper getters for left and right hand.
  228. public get onAButtonStateChangedObservable() {
  229. if (this.hand === 'right') {
  230. return this.onMainButtonStateChangedObservable;
  231. } else {
  232. throw new Error('No A button on left hand');
  233. }
  234. }
  235. public get onBButtonStateChangedObservable() {
  236. if (this.hand === 'right') {
  237. return this.onSecondaryButtonStateChangedObservable;
  238. } else {
  239. throw new Error('No B button on left hand');
  240. }
  241. }
  242. public get onXButtonStateChangedObservable() {
  243. if (this.hand === 'left') {
  244. return this.onMainButtonStateChangedObservable;
  245. } else {
  246. throw new Error('No X button on right hand');
  247. }
  248. }
  249. public get onYButtonStateChangedObservable() {
  250. if (this.hand === 'left') {
  251. return this.onSecondaryButtonStateChangedObservable;
  252. } else {
  253. throw new Error('No Y button on right hand');
  254. }
  255. }
  256. /*
  257. 0) thumb stick (touch, press, value = pressed (0,1)). value is in this.leftStick
  258. 1) index trigger (touch (?), press (only when value > 0.1), value 0 to 1)
  259. 2) secondary trigger (same)
  260. 3) A (right) X (left), touch, pressed = value
  261. 4) B / Y
  262. 5) thumb rest
  263. */
  264. protected handleButtonChange(buttonIdx: number, state: ExtendedGamepadButton, changes: GamepadButtonChanges) {
  265. let notifyObject = state; //{ state: state, changes: changes };
  266. switch (buttonIdx) {
  267. case 0:
  268. this.onPadStateChangedObservable.notifyObservers(notifyObject);
  269. return;
  270. case 1: // index trigger
  271. if (this._defaultModel) {
  272. (<AbstractMesh>(this._defaultModel.getChildren()[3])).rotation.x = -notifyObject.value * 0.20;
  273. (<AbstractMesh>(this._defaultModel.getChildren()[3])).position.y = -notifyObject.value * 0.005;
  274. (<AbstractMesh>(this._defaultModel.getChildren()[3])).position.z = -notifyObject.value * 0.005;
  275. }
  276. this.onTriggerStateChangedObservable.notifyObservers(notifyObject);
  277. return;
  278. case 2: // secondary trigger
  279. if (this._defaultModel) {
  280. (<AbstractMesh>(this._defaultModel.getChildren()[4])).position.x = notifyObject.value * 0.0035;
  281. }
  282. this.onSecondaryTriggerStateChangedObservable.notifyObservers(notifyObject);
  283. return;
  284. case 3:
  285. if (this._defaultModel) {
  286. if (notifyObject.pressed) {
  287. (<AbstractMesh>(this._defaultModel.getChildren()[1])).position.y = -0.001;
  288. }
  289. else {
  290. (<AbstractMesh>(this._defaultModel.getChildren()[1])).position.y = 0;
  291. }
  292. }
  293. this.onMainButtonStateChangedObservable.notifyObservers(notifyObject);
  294. return;
  295. case 4:
  296. if (this._defaultModel) {
  297. if (notifyObject.pressed) {
  298. (<AbstractMesh>(this._defaultModel.getChildren()[2])).position.y = -0.001;
  299. }
  300. else {
  301. (<AbstractMesh>(this._defaultModel.getChildren()[2])).position.y = 0;
  302. }
  303. }
  304. this.onSecondaryButtonStateChangedObservable.notifyObservers(notifyObject);
  305. return;
  306. case 5:
  307. this.onThumbRestChangedObservable.notifyObservers(notifyObject);
  308. return;
  309. }
  310. }
  311. }
  312. export class ViveController extends WebVRController {
  313. private _defaultModel: BABYLON.AbstractMesh;
  314. constructor(vrGamepad) {
  315. super(vrGamepad);
  316. this.controllerType = PoseEnabledControllerType.VIVE;
  317. }
  318. public initControllerMesh(scene: Scene, meshLoaded?: (mesh: AbstractMesh) => void) {
  319. SceneLoader.ImportMesh("", "http://yoda.blob.core.windows.net/models/", "ViveWand.babylon", scene, (newMeshes) => {
  320. /*
  321. Parent Mesh name: ViveWand
  322. - body
  323. - r_gripper
  324. - l_gripper
  325. - menu_button
  326. - system_button
  327. - trackpad
  328. - trigger
  329. - LED
  330. */
  331. this._defaultModel = newMeshes[1];
  332. if (meshLoaded) {
  333. meshLoaded(this._defaultModel);
  334. }
  335. this.attachToMesh(this._defaultModel);
  336. });
  337. }
  338. public get onLeftButtonStateChangedObservable() {
  339. return this.onMainButtonStateChangedObservable;
  340. }
  341. public get onRightButtonStateChangedObservable() {
  342. return this.onMainButtonStateChangedObservable;
  343. }
  344. public get onMenuButtonStateChangedObservable() {
  345. return this.onSecondaryButtonStateChangedObservable;
  346. }
  347. /**
  348. * Vive mapping:
  349. * 0: touchpad
  350. * 1: trigger
  351. * 2: left AND right buttons
  352. * 3: menu button
  353. */
  354. protected handleButtonChange(buttonIdx: number, state: ExtendedGamepadButton, changes: GamepadButtonChanges) {
  355. let notifyObject = state; //{ state: state, changes: changes };
  356. switch (buttonIdx) {
  357. case 0:
  358. this.onPadStateChangedObservable.notifyObservers(notifyObject);
  359. return;
  360. case 1: // index trigger
  361. if (this._defaultModel) {
  362. (<AbstractMesh>(this._defaultModel.getChildren()[6])).rotation.x = -notifyObject.value * 0.15;
  363. }
  364. this.onTriggerStateChangedObservable.notifyObservers(notifyObject);
  365. return;
  366. case 2: // left AND right button
  367. this.onMainButtonStateChangedObservable.notifyObservers(notifyObject);
  368. return;
  369. case 3:
  370. if (this._defaultModel) {
  371. if (notifyObject.pressed) {
  372. (<AbstractMesh>(this._defaultModel.getChildren()[2])).position.y = -0.001;
  373. }
  374. else {
  375. (<AbstractMesh>(this._defaultModel.getChildren()[2])).position.y = 0;
  376. }
  377. }
  378. this.onSecondaryButtonStateChangedObservable.notifyObservers(notifyObject);
  379. return;
  380. }
  381. }
  382. }
  383. }
  384. interface ExtendedGamepadButton extends GamepadButton {
  385. readonly pressed: boolean;
  386. readonly touched: boolean;
  387. readonly value: number;
  388. }