babylon.extendedGamepad.ts 20 KB

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