freeCameraMouseInput.ts 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { Observer, EventState, Observable } from "../../Misc/observable";
  2. import { serialize } from "../../Misc/decorators";
  3. import { Nullable } from "../../types";
  4. import { ICameraInput, CameraInputTypes } from "../../Cameras/cameraInputsManager";
  5. import { FreeCamera } from "../../Cameras/freeCamera";
  6. import { PointerInfo, PointerEventTypes } from "../../Events/pointerEvents";
  7. /**
  8. * Manage the mouse inputs to control the movement of a free camera.
  9. * @see https://doc.babylonjs.com/how_to/customizing_camera_inputs
  10. */
  11. export class FreeCameraMouseInput implements ICameraInput<FreeCamera> {
  12. /**
  13. * Defines the camera the input is attached to.
  14. */
  15. public camera: FreeCamera;
  16. /**
  17. * Defines the buttons associated with the input to handle camera move.
  18. */
  19. @serialize()
  20. public buttons = [0, 1, 2];
  21. /**
  22. * Defines the pointer angular sensibility along the X and Y axis or how fast is the camera rotating.
  23. */
  24. @serialize()
  25. public angularSensibility = 2000.0;
  26. private _pointerInput: (p: PointerInfo, s: EventState) => void;
  27. private _onMouseMove: Nullable<(e: MouseEvent) => any>;
  28. private _observer: Nullable<Observer<PointerInfo>>;
  29. private previousPosition: Nullable<{ x: number, y: number }> = null;
  30. /**
  31. * Observable for when a pointer move event occurs containing the move offset
  32. */
  33. public onPointerMovedObservable = new Observable<{ offsetX: number, offsetY: number }>();
  34. /**
  35. * @hidden
  36. * If the camera should be rotated automatically based on pointer movement
  37. */
  38. public _allowCameraRotation = true;
  39. /**
  40. * Manage the mouse inputs to control the movement of a free camera.
  41. * @see https://doc.babylonjs.com/how_to/customizing_camera_inputs
  42. * @param touchEnabled Defines if touch is enabled or not
  43. */
  44. constructor(
  45. /**
  46. * Define if touch is enabled in the mouse input
  47. */
  48. public touchEnabled = true) {
  49. }
  50. /**
  51. * Attach the input controls to a specific dom element to get the input from.
  52. * @param element Defines the element the controls should be listened from
  53. * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
  54. */
  55. public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
  56. var engine = this.camera.getEngine();
  57. if (!this._pointerInput) {
  58. this._pointerInput = (p) => {
  59. var evt = <PointerEvent>p.event;
  60. if (engine.isInVRExclusivePointerMode) {
  61. return;
  62. }
  63. if (!this.touchEnabled && evt.pointerType === "touch") {
  64. return;
  65. }
  66. if (p.type !== PointerEventTypes.POINTERMOVE && this.buttons.indexOf(evt.button) === -1) {
  67. return;
  68. }
  69. let srcElement = <HTMLElement>(evt.srcElement || evt.target);
  70. if (p.type === PointerEventTypes.POINTERDOWN && srcElement) {
  71. try {
  72. srcElement.setPointerCapture(evt.pointerId);
  73. } catch (e) {
  74. //Nothing to do with the error. Execution will continue.
  75. }
  76. this.previousPosition = {
  77. x: evt.clientX,
  78. y: evt.clientY
  79. };
  80. if (!noPreventDefault) {
  81. evt.preventDefault();
  82. element.focus();
  83. }
  84. }
  85. else if (p.type === PointerEventTypes.POINTERUP && srcElement) {
  86. try {
  87. srcElement.releasePointerCapture(evt.pointerId);
  88. } catch (e) {
  89. //Nothing to do with the error.
  90. }
  91. this.previousPosition = null;
  92. if (!noPreventDefault) {
  93. evt.preventDefault();
  94. }
  95. }
  96. else if (p.type === PointerEventTypes.POINTERMOVE) {
  97. if (!this.previousPosition || engine.isPointerLock) {
  98. return;
  99. }
  100. var offsetX = evt.clientX - this.previousPosition.x;
  101. var offsetY = evt.clientY - this.previousPosition.y;
  102. if (this.camera.getScene().useRightHandedSystem) { offsetX *= -1; }
  103. if (this.camera.parent && this.camera.parent._getWorldMatrixDeterminant() < 0) { offsetX *= -1; }
  104. if (this._allowCameraRotation) {
  105. this.camera.cameraRotation.y += offsetX / this.angularSensibility;
  106. this.camera.cameraRotation.x += offsetY / this.angularSensibility;
  107. }
  108. this.onPointerMovedObservable.notifyObservers({offsetX: offsetX, offsetY: offsetY});
  109. this.previousPosition = {
  110. x: evt.clientX,
  111. y: evt.clientY
  112. };
  113. if (!noPreventDefault) {
  114. evt.preventDefault();
  115. }
  116. }
  117. };
  118. }
  119. this._onMouseMove = (evt) => {
  120. if (!engine.isPointerLock) {
  121. return;
  122. }
  123. if (engine.isInVRExclusivePointerMode) {
  124. return;
  125. }
  126. var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
  127. if (this.camera.getScene().useRightHandedSystem) { offsetX *= -1; }
  128. if (this.camera.parent && this.camera.parent._getWorldMatrixDeterminant() < 0) { offsetX *= -1; }
  129. this.camera.cameraRotation.y += offsetX / this.angularSensibility;
  130. var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
  131. this.camera.cameraRotation.x += offsetY / this.angularSensibility;
  132. this.previousPosition = null;
  133. if (!noPreventDefault) {
  134. evt.preventDefault();
  135. }
  136. };
  137. this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput, PointerEventTypes.POINTERDOWN | PointerEventTypes.POINTERUP | PointerEventTypes.POINTERMOVE);
  138. if (document.onpointermove === undefined){
  139. element.addEventListener("mousemove", this._onMouseMove, false);
  140. }
  141. else {
  142. element.addEventListener("pointermove", this._onMouseMove, false);
  143. }
  144. element.addEventListener("contextmenu",
  145. <EventListener>this.onContextMenu.bind(this), false);
  146. }
  147. /**
  148. * Called on JS contextmenu event.
  149. * Override this method to provide functionality.
  150. */
  151. protected onContextMenu(evt: PointerEvent): void {
  152. evt.preventDefault();
  153. }
  154. /**
  155. * Detach the current controls from the specified dom element.
  156. * @param element Defines the element to stop listening the inputs from
  157. */
  158. public detachControl(element: Nullable<HTMLElement>): void {
  159. if (this._observer && element) {
  160. this.camera.getScene().onPointerObservable.remove(this._observer);
  161. if (this._onMouseMove) {
  162. if (document.onpointermove === undefined){
  163. element.removeEventListener("mousemove", this._onMouseMove);
  164. }
  165. else {
  166. element.removeEventListener("pointermove", this._onMouseMove);
  167. }
  168. }
  169. if (this.onContextMenu) {
  170. element.removeEventListener("contextmenu", <EventListener>this.onContextMenu);
  171. }
  172. if (this.onPointerMovedObservable) {
  173. this.onPointerMovedObservable.clear();
  174. }
  175. this._observer = null;
  176. this._onMouseMove = null;
  177. this.previousPosition = null;
  178. }
  179. }
  180. /**
  181. * Gets the class name of the current intput.
  182. * @returns the class name
  183. */
  184. public getClassName(): string {
  185. return "FreeCameraMouseInput";
  186. }
  187. /**
  188. * Get the friendly name associated with the input class.
  189. * @returns the input friendly name
  190. */
  191. public getSimpleName(): string {
  192. return "mouse";
  193. }
  194. }
  195. (<any>CameraInputTypes)["FreeCameraMouseInput"] = FreeCameraMouseInput;