flyCameraMouseInput.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import { Nullable } from "../../types";
  2. import { serialize } from "../../Misc/decorators";
  3. import { Observer } from "../../Misc/observable";
  4. import { ICameraInput, CameraInputTypes } from "../../Cameras/cameraInputsManager";
  5. import { FlyCamera } from "../../Cameras/flyCamera";
  6. import { PointerInfo, PointerEventTypes } from "../../Events/pointerEvents";
  7. import { Scene } from "../../scene";
  8. import { Quaternion } from "../../Maths/math.vector";
  9. import { Axis } from '../../Maths/math.axis';
  10. /**
  11. * Listen to mouse events to control the camera.
  12. * @see https://doc.babylonjs.com/how_to/customizing_camera_inputs
  13. */
  14. export class FlyCameraMouseInput implements ICameraInput<FlyCamera> {
  15. /**
  16. * Defines the camera the input is attached to.
  17. */
  18. public camera: FlyCamera;
  19. /**
  20. * Defines if touch is enabled. (Default is true.)
  21. */
  22. public touchEnabled: boolean;
  23. /**
  24. * Defines the buttons associated with the input to handle camera rotation.
  25. */
  26. @serialize()
  27. public buttons = [0, 1, 2];
  28. /**
  29. * Assign buttons for Yaw control.
  30. */
  31. public buttonsYaw: number[] = [-1, 0, 1];
  32. /**
  33. * Assign buttons for Pitch control.
  34. */
  35. public buttonsPitch: number[] = [-1, 0, 1];
  36. /**
  37. * Assign buttons for Roll control.
  38. */
  39. public buttonsRoll: number[] = [2];
  40. /**
  41. * Detect if any button is being pressed while mouse is moved.
  42. * -1 = Mouse locked.
  43. * 0 = Left button.
  44. * 1 = Middle Button.
  45. * 2 = Right Button.
  46. */
  47. public activeButton: number = -1;
  48. /**
  49. * Defines the pointer's angular sensibility, to control the camera rotation speed.
  50. * Higher values reduce its sensitivity.
  51. */
  52. @serialize()
  53. public angularSensibility = 1000.0;
  54. private _mousemoveCallback: (e: MouseEvent) => void;
  55. private _observer: Nullable<Observer<PointerInfo>>;
  56. private _rollObserver: Nullable<Observer<Scene>>;
  57. private previousPosition: Nullable<{ x: number, y: number }> = null;
  58. private noPreventDefault: boolean | undefined;
  59. private element: HTMLElement;
  60. /**
  61. * Listen to mouse events to control the camera.
  62. * @param touchEnabled Define if touch is enabled. (Default is true.)
  63. * @see https://doc.babylonjs.com/how_to/customizing_camera_inputs
  64. */
  65. constructor(touchEnabled = true) {
  66. }
  67. /**
  68. * Attach the mouse control to the HTML DOM element.
  69. * @param element Defines the element that listens to the input events.
  70. * @param noPreventDefault Defines whether events caught by the controls should call preventdefault().
  71. */
  72. public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
  73. this.element = element;
  74. this.noPreventDefault = noPreventDefault;
  75. this._observer = this.camera.getScene().onPointerObservable.add(
  76. (p: any, s: any) => {
  77. this._pointerInput(p, s);
  78. },
  79. PointerEventTypes.POINTERDOWN | PointerEventTypes.POINTERUP | PointerEventTypes.POINTERMOVE
  80. );
  81. // Correct Roll by rate, if enabled.
  82. this._rollObserver = this.camera.getScene().onBeforeRenderObservable.add(
  83. () => {
  84. if (this.camera.rollCorrect) {
  85. this.camera.restoreRoll(this.camera.rollCorrect);
  86. }
  87. }
  88. );
  89. // Helper function to keep 'this'.
  90. this._mousemoveCallback = (e: any) => {
  91. this._onMouseMove(e);
  92. };
  93. element.addEventListener("mousemove", this._mousemoveCallback, false);
  94. }
  95. /**
  96. * Detach the current controls from the specified dom element.
  97. * @param element Defines the element to stop listening the inputs from
  98. */
  99. public detachControl(element: Nullable<HTMLElement>): void {
  100. if (this._observer && element) {
  101. this.camera.getScene().onPointerObservable.remove(this._observer);
  102. this.camera.getScene().onBeforeRenderObservable.remove(this._rollObserver);
  103. if (this._mousemoveCallback) {
  104. element.removeEventListener("mousemove", this._mousemoveCallback);
  105. }
  106. this._observer = null;
  107. this._rollObserver = null;
  108. this.previousPosition = null;
  109. this.noPreventDefault = undefined;
  110. }
  111. }
  112. /**
  113. * Gets the class name of the current input.
  114. * @returns the class name.
  115. */
  116. public getClassName(): string {
  117. return "FlyCameraMouseInput";
  118. }
  119. /**
  120. * Get the friendly name associated with the input class.
  121. * @returns the input's friendly name.
  122. */
  123. public getSimpleName(): string {
  124. return "mouse";
  125. }
  126. // Track mouse movement, when the pointer is not locked.
  127. private _pointerInput(p: any, s: any): void {
  128. var e = <PointerEvent>p.event;
  129. let camera = this.camera;
  130. let engine = camera.getEngine();
  131. if (engine.isInVRExclusivePointerMode) {
  132. return;
  133. }
  134. if (!this.touchEnabled && e.pointerType === "touch") {
  135. return;
  136. }
  137. // Mouse is moved but an unknown mouse button is pressed.
  138. if (p.type !== PointerEventTypes.POINTERMOVE && this.buttons.indexOf(e.button) === -1) {
  139. return;
  140. }
  141. var srcElement = <HTMLElement>(e.srcElement || e.target);
  142. // Mouse down.
  143. if (p.type === PointerEventTypes.POINTERDOWN && srcElement) {
  144. try {
  145. srcElement.setPointerCapture(e.pointerId);
  146. } catch (e) {
  147. // Nothing to do with the error. Execution continues.
  148. }
  149. this.previousPosition = {
  150. x: e.clientX,
  151. y: e.clientY
  152. };
  153. this.activeButton = e.button;
  154. if (!this.noPreventDefault) {
  155. e.preventDefault();
  156. this.element.focus();
  157. }
  158. } else
  159. // Mouse up.
  160. if (p.type === PointerEventTypes.POINTERUP && srcElement) {
  161. try {
  162. srcElement.releasePointerCapture(e.pointerId);
  163. } catch (e) {
  164. // Nothing to do with the error. Execution continues.
  165. }
  166. this.activeButton = -1;
  167. this.previousPosition = null;
  168. if (!this.noPreventDefault) {
  169. e.preventDefault();
  170. }
  171. } else
  172. // Mouse move.
  173. if (p.type === PointerEventTypes.POINTERMOVE) {
  174. if (!this.previousPosition || engine.isPointerLock) {
  175. return;
  176. }
  177. var offsetX = e.clientX - this.previousPosition.x;
  178. var offsetY = e.clientY - this.previousPosition.y;
  179. this.rotateCamera(offsetX, offsetY);
  180. this.previousPosition = {
  181. x: e.clientX,
  182. y: e.clientY
  183. };
  184. if (!this.noPreventDefault) {
  185. e.preventDefault();
  186. }
  187. }
  188. }
  189. // Track mouse movement, when pointer is locked.
  190. private _onMouseMove(e: any): void {
  191. let camera = this.camera;
  192. let engine = camera.getEngine();
  193. if (!engine.isPointerLock || engine.isInVRExclusivePointerMode) {
  194. return;
  195. }
  196. var offsetX = e.movementX || e.mozMovementX || e.webkitMovementX || e.msMovementX || 0;
  197. var offsetY = e.movementY || e.mozMovementY || e.webkitMovementY || e.msMovementY || 0;
  198. this.rotateCamera(offsetX, offsetY);
  199. this.previousPosition = null;
  200. if (!this.noPreventDefault) {
  201. e.preventDefault();
  202. }
  203. }
  204. /**
  205. * Rotate camera by mouse offset.
  206. */
  207. private rotateCamera(offsetX: number, offsetY: number): void {
  208. let camera = this.camera;
  209. let scene = this.camera.getScene();
  210. if (scene.useRightHandedSystem) {
  211. offsetX *= -1;
  212. }
  213. if (camera.parent && camera.parent._getWorldMatrixDeterminant() < 0) {
  214. offsetX *= -1;
  215. }
  216. var x = offsetX / this.angularSensibility;
  217. var y = offsetY / this.angularSensibility;
  218. // Initialize to current rotation.
  219. var currentRotation = Quaternion.RotationYawPitchRoll(
  220. camera.rotation.y,
  221. camera.rotation.x,
  222. camera.rotation.z
  223. );
  224. var rotationChange: Quaternion;
  225. // Pitch.
  226. if (this.buttonsPitch.some((v) => { return v === this.activeButton; })) {
  227. // Apply change in Radians to vector Angle.
  228. rotationChange = Quaternion.RotationAxis(Axis.X, y);
  229. // Apply Pitch to quaternion.
  230. currentRotation.multiplyInPlace(rotationChange);
  231. }
  232. // Yaw.
  233. if (this.buttonsYaw.some((v) => { return v === this.activeButton; })) {
  234. // Apply change in Radians to vector Angle.
  235. rotationChange = Quaternion.RotationAxis(Axis.Y, x);
  236. // Apply Yaw to quaternion.
  237. currentRotation.multiplyInPlace(rotationChange);
  238. // Add Roll, if banked turning is enabled, within Roll limit.
  239. let limit = (camera.bankedTurnLimit) + camera._trackRoll; // Defaults to 90° plus manual roll.
  240. if (camera.bankedTurn && -limit < camera.rotation.z && camera.rotation.z < limit) {
  241. let bankingDelta = camera.bankedTurnMultiplier * -x;
  242. // Apply change in Radians to vector Angle.
  243. rotationChange = Quaternion.RotationAxis(Axis.Z, bankingDelta);
  244. // Apply Yaw to quaternion.
  245. currentRotation.multiplyInPlace(rotationChange);
  246. }
  247. }
  248. // Roll.
  249. if (this.buttonsRoll.some((v) => { return v === this.activeButton; })) {
  250. // Apply change in Radians to vector Angle.
  251. rotationChange = Quaternion.RotationAxis(Axis.Z, -x);
  252. // Track Rolling.
  253. camera._trackRoll -= x;
  254. // Apply Pitch to quaternion.
  255. currentRotation.multiplyInPlace(rotationChange);
  256. }
  257. // Apply rotationQuaternion to Euler camera.rotation.
  258. currentRotation.toEulerAnglesToRef(camera.rotation);
  259. }
  260. }
  261. (<any>CameraInputTypes)["FlyCameraMouseInput"] = FlyCameraMouseInput;