cameraInputsManager.ts 12 KB

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