followCameraKeyboardMoveInput.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import { ICameraInput, CameraInputTypes } from "../../Cameras/cameraInputsManager";
  2. import { FollowCamera } from "../../Cameras/followCamera";
  3. import { serialize } from "../../Misc/decorators";
  4. import { Nullable } from "../../types";
  5. import { Observer } from "../../Misc/observable";
  6. import { Engine } from "../../Engines/engine";
  7. import { KeyboardInfo, KeyboardEventTypes } from "../../Events/keyboardEvents";
  8. import { Scene } from "../../scene";
  9. /**
  10. * Track which combination of modifier keys are pressed.
  11. */
  12. export enum ModifierKey {
  13. None,
  14. Alt,
  15. Ctrl,
  16. Shift,
  17. AltCtrl,
  18. AltCtrlShift,
  19. AltShift,
  20. CtrlShift
  21. }
  22. /**
  23. * Manage the keyboard inputs to control the movement of a follow camera.
  24. * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
  25. */
  26. export class FollowCameraKeyboardMoveInput implements ICameraInput<FollowCamera> {
  27. /**
  28. * Possible combinations of modifierKeys.
  29. * Used to assign values to keysHeightOffsetModifier, keysRotateOffsetModifier
  30. * and keysRadiusModifier.
  31. */
  32. public readonly modifierKeyChoices = ModifierKey;
  33. /**
  34. * Defines the camera the input is attached to.
  35. */
  36. public camera: FollowCamera;
  37. /**
  38. * Defines the list of key codes associated with the up action (increase heightOffset)
  39. */
  40. @serialize()
  41. public keysHeightOffsetIncr = [38];
  42. /**
  43. * Defines the list of key codes associated with the down action (decrease heightOffset)
  44. */
  45. @serialize()
  46. public keysHeightOffsetDecr = [40];
  47. /**
  48. * Defines whether any modifier key is required to move up/down (alter heightOffset)
  49. */
  50. @serialize()
  51. public keysHeightOffsetModifier: ModifierKey = this.modifierKeyChoices.None;
  52. /**
  53. * Defines the list of key codes associated with the left action (increase rotationOffset)
  54. */
  55. @serialize()
  56. public keysRotateOffsetIncr = [37];
  57. /**
  58. * Defines the list of key codes associated with the right action (decrease rotationOffset)
  59. */
  60. @serialize()
  61. public keysRotateOffsetDecr = [39];
  62. /**
  63. * Defines whether any modifier key is required to move up/down (alter heightOffset)
  64. */
  65. @serialize()
  66. public keysRotateOffsetModifier: ModifierKey = this.modifierKeyChoices.None;
  67. /**
  68. * Defines the list of key codes associated with the zoom-in action (decrease radius)
  69. */
  70. @serialize()
  71. public keysRadiusIncr = [40];
  72. /**
  73. * Defines the list of key codes associated with the zoom-out action (increase radius)
  74. */
  75. @serialize()
  76. public keysRadiusDecr = [38];
  77. /**
  78. * Defines whether any modifier key is required to zoom in/out (alter radius value)
  79. */
  80. @serialize()
  81. public keysRadiusModifier: ModifierKey = this.modifierKeyChoices.Alt;
  82. /**
  83. * Defines the rate of change of heightOffset.
  84. */
  85. @serialize()
  86. public heightSensibility: number = 1;
  87. /**
  88. * Defines the rate of change of rotationOffset.
  89. */
  90. @serialize()
  91. public rotationSensibility: number = 1;
  92. /**
  93. * Defines the rate of change of radius.
  94. */
  95. @serialize()
  96. public radiusSensibility: number = 1;
  97. /**
  98. * Defines the minimum heightOffset value.
  99. */
  100. @serialize()
  101. public minHeightOffset: number = -1000;
  102. /**
  103. * Defines the maximum heightOffset value.
  104. */
  105. @serialize()
  106. public maxHeightOffset: number = 1000;
  107. /**
  108. * Defines the minimum radius value.
  109. */
  110. @serialize()
  111. public minRadius: number = 0;
  112. /**
  113. * Defines the maximum radius value.
  114. */
  115. @serialize()
  116. public maxRadius: number = 1000;
  117. /**
  118. * Defines the minimum rotationOffset value.
  119. */
  120. @serialize()
  121. public minRotationOffset: number = -360;
  122. /**
  123. * Defines the maximum rotationOffset value.
  124. */
  125. @serialize()
  126. public maxRotationOffset: number = 360;
  127. private _keys = new Array<number>();
  128. private _ctrlPressed: boolean;
  129. private _altPressed: boolean;
  130. private _shiftPressed: boolean;
  131. private _onCanvasBlurObserver: Nullable<Observer<Engine>>;
  132. private _onKeyboardObserver: Nullable<Observer<KeyboardInfo>>;
  133. private _engine: Engine;
  134. private _scene: Scene;
  135. /**
  136. * Attach the input controls to a specific dom element to get the input from.
  137. * @param element Defines the element the controls should be listened from
  138. * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
  139. */
  140. public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
  141. if (this._onCanvasBlurObserver) {
  142. return;
  143. }
  144. this._scene = this.camera.getScene();
  145. this._engine = this._scene.getEngine();
  146. this._onCanvasBlurObserver = this._engine.onCanvasBlurObservable.add(() => {
  147. this._keys = [];
  148. });
  149. this._onKeyboardObserver = this._scene.onKeyboardObservable.add((info) => {
  150. let evt = info.event;
  151. if (!evt.metaKey) {
  152. if (info.type === KeyboardEventTypes.KEYDOWN) {
  153. this._ctrlPressed = evt.ctrlKey;
  154. this._altPressed = evt.altKey;
  155. this._shiftPressed = evt.shiftKey;
  156. if (this.keysHeightOffsetIncr.indexOf(evt.keyCode) !== -1 ||
  157. this.keysHeightOffsetDecr.indexOf(evt.keyCode) !== -1 ||
  158. this.keysRotateOffsetIncr.indexOf(evt.keyCode) !== -1 ||
  159. this.keysRotateOffsetDecr.indexOf(evt.keyCode) !== -1 ||
  160. this.keysRadiusIncr.indexOf(evt.keyCode) !== -1 ||
  161. this.keysRadiusDecr.indexOf(evt.keyCode) !== -1) {
  162. var index = this._keys.indexOf(evt.keyCode);
  163. if (index === -1) {
  164. this._keys.push(evt.keyCode);
  165. }
  166. if (evt.preventDefault) {
  167. if (!noPreventDefault) {
  168. evt.preventDefault();
  169. }
  170. }
  171. }
  172. }
  173. else {
  174. if (this.keysHeightOffsetIncr.indexOf(evt.keyCode) !== -1 ||
  175. this.keysHeightOffsetDecr.indexOf(evt.keyCode) !== -1 ||
  176. this.keysRotateOffsetIncr.indexOf(evt.keyCode) !== -1 ||
  177. this.keysRotateOffsetDecr.indexOf(evt.keyCode) !== -1 ||
  178. this.keysRadiusIncr.indexOf(evt.keyCode) !== -1 ||
  179. this.keysRadiusDecr.indexOf(evt.keyCode) !== -1) {
  180. var index = this._keys.indexOf(evt.keyCode);
  181. if (index >= 0) {
  182. this._keys.splice(index, 1);
  183. }
  184. if (evt.preventDefault) {
  185. if (!noPreventDefault) {
  186. evt.preventDefault();
  187. }
  188. }
  189. }
  190. }
  191. }
  192. });
  193. }
  194. /**
  195. * Detach the current controls from the specified dom element.
  196. * @param element Defines the element to stop listening the inputs from
  197. */
  198. public detachControl(element: Nullable<HTMLElement>) {
  199. if (this._scene) {
  200. if (this._onKeyboardObserver) {
  201. this._scene.onKeyboardObservable.remove(this._onKeyboardObserver);
  202. }
  203. if (this._onCanvasBlurObserver) {
  204. this._engine.onCanvasBlurObservable.remove(this._onCanvasBlurObserver);
  205. }
  206. this._onKeyboardObserver = null;
  207. this._onCanvasBlurObserver = null;
  208. }
  209. this._keys = [];
  210. }
  211. /**
  212. * Update the current camera state depending on the inputs that have been used this frame.
  213. * This is a dynamically created lambda to avoid the performance penalty of looping for inputs in the render loop.
  214. */
  215. public checkInputs(): void {
  216. if (this._onKeyboardObserver) {
  217. for (var index = 0; index < this._keys.length; index++) {
  218. console.assert(this.maxHeightOffset > this.minHeightOffset,
  219. "FollowCameraKeyboardMoveInput.maxHeightOffset must be greater " +
  220. "than FollowCameraKeyboardMoveInput.minHeightOffset.");
  221. console.assert(this.maxRotationOffset > this.minRotationOffset,
  222. "FollowCameraKeyboardMoveInput.maxRotationOffset must be greater " +
  223. "than FollowCameraKeyboardMoveInput.minRotationOffset.");
  224. console.assert(this.maxRadius > this.minRadius,
  225. "FollowCameraKeyboardMoveInput.maxRadius must be greater " +
  226. "than FollowCameraKeyboardMoveInput.minRadius.");
  227. var keyCode = this._keys[index];
  228. var modifierHeightOffset = this._checkModifierKey(this.keysHeightOffsetModifier);
  229. var modifierRotationOffset = this._checkModifierKey(this.keysRotateOffsetModifier);
  230. var modifierRaduis = this._checkModifierKey(this.keysRadiusModifier);
  231. if (this.keysHeightOffsetIncr.indexOf(keyCode) !== -1 && modifierHeightOffset) {
  232. this.camera.heightOffset += this.heightSensibility;
  233. this.camera.heightOffset =
  234. Math.min(this.maxHeightOffset, this.camera.heightOffset);
  235. } else if (this.keysHeightOffsetDecr.indexOf(keyCode) !== -1 && modifierHeightOffset) {
  236. this.camera.heightOffset -= this.heightSensibility;
  237. this.camera.heightOffset =
  238. Math.max(this.minHeightOffset, this.camera.heightOffset);
  239. } else if (this.keysRotateOffsetIncr.indexOf(keyCode) !== -1 && modifierRotationOffset) {
  240. this.camera.rotationOffset += this.rotationSensibility;
  241. this.camera.rotationOffset %= 360;
  242. this.camera.rotationOffset =
  243. Math.min(this.maxRotationOffset, this.camera.rotationOffset);
  244. } else if (this.keysRotateOffsetDecr.indexOf(keyCode) !== -1 && modifierRotationOffset) {
  245. this.camera.rotationOffset -= this.rotationSensibility;
  246. this.camera.rotationOffset %= 360;
  247. this.camera.rotationOffset =
  248. Math.max(this.minRotationOffset, this.camera.rotationOffset);
  249. } else if (this.keysRadiusIncr.indexOf(keyCode) !== -1 && modifierRaduis) {
  250. this.camera.radius += this.radiusSensibility;
  251. this.camera.radius =
  252. Math.min(this.maxRadius, this.camera.radius);
  253. } else if (this.keysRadiusDecr.indexOf(keyCode) !== -1 && modifierRaduis) {
  254. this.camera.radius -= this.radiusSensibility;
  255. this.camera.radius =
  256. Math.max(this.minRadius, this.camera.radius);
  257. }
  258. }
  259. }
  260. }
  261. /**
  262. * Gets the class name of the current intput.
  263. * @returns the class name
  264. */
  265. public getClassName(): string {
  266. return "FollowCameraKeyboardMoveInput";
  267. }
  268. /**
  269. * Get the friendly name associated with the input class.
  270. * @returns the input friendly name
  271. */
  272. public getSimpleName(): string {
  273. return "keyboard";
  274. }
  275. /**
  276. * Compare provided value to actual stare of Alt, Ctrl and Shift keys.
  277. */
  278. private _checkModifierKey(expected: ModifierKey) : boolean {
  279. let returnVal = false;
  280. switch(expected) {
  281. case ModifierKey.None:
  282. returnVal = !this._altPressed && !this._ctrlPressed && !this._shiftPressed;
  283. break;
  284. case ModifierKey.Alt:
  285. returnVal = this._altPressed && !this._ctrlPressed && !this._shiftPressed;
  286. break;
  287. case ModifierKey.Ctrl:
  288. returnVal = !this._altPressed && this._ctrlPressed && !this._shiftPressed;
  289. break;
  290. case ModifierKey.Shift:
  291. returnVal = !this._altPressed && !this._ctrlPressed && this._shiftPressed;
  292. break;
  293. case ModifierKey.AltCtrl:
  294. returnVal = this._altPressed && this._ctrlPressed && !this._shiftPressed;
  295. break;
  296. case ModifierKey.AltShift:
  297. returnVal = this._altPressed && !this._ctrlPressed && this._shiftPressed;
  298. break;
  299. case ModifierKey.CtrlShift:
  300. returnVal = !this._altPressed && this._ctrlPressed && this._shiftPressed;
  301. break;
  302. case ModifierKey.AltCtrlShift:
  303. returnVal = this._altPressed && this._ctrlPressed && this._shiftPressed;
  304. break;
  305. }
  306. return returnVal;
  307. }
  308. }
  309. (<any>CameraInputTypes)["FollowCameraKeyboardMoveInput"] = FollowCameraKeyboardMoveInput;