babylon.cameraInputsManager.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. module BABYLON {
  2. /**
  3. * This is a list of all the different input types that are available in the application.
  4. * Fo instance: ArcRotateCameraGamepadInput...
  5. */
  6. export var CameraInputTypes = {};
  7. /**
  8. * This is the contract to implement in order to create a new input class.
  9. * Inputs are dealing with listening to user actions and moving the camera accordingly.
  10. */
  11. export interface ICameraInput<TCamera extends Camera> {
  12. /**
  13. * Defines the camera the input is attached to.
  14. */
  15. camera: Nullable<TCamera>;
  16. /**
  17. * Gets the class name of the current intput.
  18. * @returns the class name
  19. */
  20. getClassName(): string;
  21. /**
  22. * Get the friendly name associated with the input class.
  23. * @returns the input friendly name
  24. */
  25. getSimpleName(): string;
  26. /**
  27. * Attach the input controls to a specific dom element to get the input from.
  28. * @param element Defines the element the controls should be listened from
  29. * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
  30. */
  31. attachControl(element: HTMLElement, noPreventDefault?: boolean): void;
  32. /**
  33. * Detach the current controls from the specified dom element.
  34. * @param element Defines the element to stop listening the inputs from
  35. */
  36. detachControl(element: Nullable<HTMLElement>): void;
  37. /**
  38. * Update the current camera state depending on the inputs that have been used this frame.
  39. * This is a dynamically created lambda to avoid the performance penalty of looping for inputs in the render loop.
  40. */
  41. checkInputs?: () => void;
  42. }
  43. /**
  44. * Represents a map of input types to input instance or input index to input instance.
  45. */
  46. export interface CameraInputsMap<TCamera extends Camera> {
  47. /**
  48. * Accessor to the input by input type.
  49. */
  50. [name: string]: ICameraInput<TCamera>;
  51. /**
  52. * Accessor to the input by input index.
  53. */
  54. [idx: number]: ICameraInput<TCamera>;
  55. }
  56. /**
  57. * This represents the input manager used within a camera.
  58. * It helps dealing with all the different kind of input attached to a camera.
  59. * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
  60. */
  61. export class CameraInputsManager<TCamera extends Camera> {
  62. /**
  63. * Defines the list of inputs attahed to the camera.
  64. */
  65. public attached: CameraInputsMap<TCamera>;
  66. /**
  67. * Defines the dom element the camera is collecting inputs from.
  68. * This is null if the controls have not been attached.
  69. */
  70. public attachedElement: Nullable<HTMLElement>;
  71. /**
  72. * Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
  73. */
  74. public noPreventDefault: boolean;
  75. /**
  76. * Defined the camera the input manager belongs to.
  77. */
  78. public camera: TCamera;
  79. /**
  80. * Update the current camera state depending on the inputs that have been used this frame.
  81. * This is a dynamically created lambda to avoid the performance penalty of looping for inputs in the render loop.
  82. */
  83. public checkInputs: () => void;
  84. /**
  85. * Instantiate a new Camera Input Manager.
  86. * @param camera Defines the camera the input manager blongs to
  87. */
  88. constructor(camera: TCamera) {
  89. this.attached = {};
  90. this.camera = camera;
  91. this.checkInputs = () => { };
  92. }
  93. /**
  94. * Add an input method to a camera
  95. * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
  96. * @param input camera input method
  97. */
  98. public add(input: ICameraInput<TCamera>): void {
  99. var type = input.getSimpleName();
  100. if (this.attached[type]) {
  101. Tools.Warn("camera input of type " + type + " already exists on camera");
  102. return;
  103. }
  104. this.attached[type] = input;
  105. input.camera = this.camera;
  106. //for checkInputs, we are dynamically creating a function
  107. //the goal is to avoid the performance penalty of looping for inputs in the render loop
  108. if (input.checkInputs) {
  109. this.checkInputs = this._addCheckInputs(input.checkInputs.bind(input));
  110. }
  111. if (this.attachedElement) {
  112. input.attachControl(this.attachedElement);
  113. }
  114. }
  115. /**
  116. * Remove a specific input method from a camera
  117. * example: camera.inputs.remove(camera.inputs.attached.mouse);
  118. * @param inputToRemove camera input method
  119. */
  120. public remove(inputToRemove: ICameraInput<TCamera>): void {
  121. for (var cam in this.attached) {
  122. var input = this.attached[cam];
  123. if (input === inputToRemove) {
  124. input.detachControl(this.attachedElement);
  125. input.camera = null;
  126. delete this.attached[cam];
  127. this.rebuildInputCheck();
  128. }
  129. }
  130. }
  131. /**
  132. * Remove a specific input type from a camera
  133. * example: camera.inputs.remove("ArcRotateCameraGamepadInput");
  134. * @param inputType the type of the input to remove
  135. */
  136. public removeByType(inputType: string): void {
  137. for (var cam in this.attached) {
  138. var input = this.attached[cam];
  139. if (input.getClassName() === inputType) {
  140. input.detachControl(this.attachedElement);
  141. input.camera = null;
  142. delete this.attached[cam];
  143. this.rebuildInputCheck();
  144. }
  145. }
  146. }
  147. private _addCheckInputs(fn: () => void) {
  148. var current = this.checkInputs;
  149. return () => {
  150. current();
  151. fn();
  152. }
  153. }
  154. /**
  155. * Attach the input controls to the currently attached dom element to listen the events from.
  156. * @param input Defines the input to attach
  157. */
  158. public attachInput(input: ICameraInput<TCamera>): void {
  159. if (this.attachedElement) {
  160. input.attachControl(this.attachedElement, this.noPreventDefault);
  161. }
  162. }
  163. /**
  164. * Attach the current manager inputs controls to a specific dom element to listen the events from.
  165. * @param element Defines the dom element to collect the events from
  166. * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
  167. */
  168. public attachElement(element: HTMLElement, noPreventDefault: boolean = false): void {
  169. if (this.attachedElement) {
  170. return;
  171. }
  172. noPreventDefault = Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
  173. this.attachedElement = element;
  174. this.noPreventDefault = noPreventDefault;
  175. for (var cam in this.attached) {
  176. this.attached[cam].attachControl(element, noPreventDefault);
  177. }
  178. }
  179. /**
  180. * Detach the current manager inputs controls from a specific dom element.
  181. * @param element Defines the dom element to collect the events from
  182. * @param disconnect Defines whether the input should be removed from the current list of attached inputs
  183. */
  184. public detachElement(element: HTMLElement, disconnect = false): void {
  185. if (this.attachedElement !== element) {
  186. return;
  187. }
  188. for (var cam in this.attached) {
  189. this.attached[cam].detachControl(element);
  190. if (disconnect) {
  191. this.attached[cam].camera = null;
  192. }
  193. }
  194. this.attachedElement = null;
  195. }
  196. /**
  197. * Rebuild the dynamic inputCheck function from the current list of
  198. * defined inputs in the manager.
  199. */
  200. public rebuildInputCheck(): void {
  201. this.checkInputs = () => { };
  202. for (var cam in this.attached) {
  203. var input = this.attached[cam];
  204. if (input.checkInputs) {
  205. this.checkInputs = this._addCheckInputs(input.checkInputs.bind(input));
  206. }
  207. }
  208. }
  209. /**
  210. * Remove all attached input methods from a camera
  211. */
  212. public clear(): void {
  213. if (this.attachedElement) {
  214. this.detachElement(this.attachedElement, true);
  215. }
  216. this.attached = {};
  217. this.attachedElement = null;
  218. this.checkInputs = () => { };
  219. }
  220. /**
  221. * Serialize the current input manager attached to a camera.
  222. * This ensures than once parsed,
  223. * the input associated to the camera will be identical to the current ones
  224. * @param serializedCamera Defines the camera serialization JSON the input serialization should write to
  225. */
  226. public serialize(serializedCamera: any): void {
  227. var inputs: { [key: string]: any } = {};
  228. for (var cam in this.attached) {
  229. var input = this.attached[cam];
  230. var res = SerializationHelper.Serialize(input);
  231. inputs[input.getClassName()] = res;
  232. }
  233. serializedCamera.inputsmgr = inputs;
  234. }
  235. /**
  236. * Parses an input manager serialized JSON to restore the previous list of inputs
  237. * and states associated to a camera.
  238. * @param parsedCamera Defines the JSON to parse
  239. */
  240. public parse(parsedCamera: any): void {
  241. var parsedInputs = parsedCamera.inputsmgr;
  242. if (parsedInputs) {
  243. this.clear();
  244. for (var n in parsedInputs) {
  245. var construct = (<any>CameraInputTypes)[n];
  246. if (construct) {
  247. var parsedinput = parsedInputs[n];
  248. var input = SerializationHelper.Parse(() => { return new construct() }, parsedinput, null);
  249. this.add(input as any);
  250. }
  251. }
  252. } else {
  253. //2016-03-08 this part is for managing backward compatibility
  254. for (var n in this.attached) {
  255. var construct = (<any>CameraInputTypes)[this.attached[n].getClassName()];
  256. if (construct) {
  257. var input = SerializationHelper.Parse(() => { return new construct() }, parsedCamera, null);
  258. this.remove(this.attached[n]);
  259. this.add(input as any);
  260. }
  261. }
  262. }
  263. }
  264. }
  265. }