BaseCameraPointersInput.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import { __decorate } from "../../tslib.es6.js";
  2. import { serialize } from "../../Misc/decorators.js";
  3. import { Tools } from "../../Misc/tools.js";
  4. import { PointerEventTypes } from "../../Events/pointerEvents.js";
  5. /**
  6. * Base class for Camera Pointer Inputs.
  7. * See FollowCameraPointersInput in src/Cameras/Inputs/followCameraPointersInput.ts
  8. * for example usage.
  9. */
  10. export class BaseCameraPointersInput {
  11. constructor() {
  12. this._currentActiveButton = -1;
  13. /**
  14. * Defines the buttons associated with the input to handle camera move.
  15. */
  16. this.buttons = [0, 1, 2];
  17. }
  18. /**
  19. * Attach the input controls to a specific dom element to get the input from.
  20. * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
  21. */
  22. attachControl(noPreventDefault) {
  23. // eslint-disable-next-line prefer-rest-params
  24. noPreventDefault = Tools.BackCompatCameraNoPreventDefault(arguments);
  25. const engine = this.camera.getEngine();
  26. const element = engine.getInputElement();
  27. let previousPinchSquaredDistance = 0;
  28. let previousMultiTouchPanPosition = null;
  29. this._pointA = null;
  30. this._pointB = null;
  31. this._altKey = false;
  32. this._ctrlKey = false;
  33. this._metaKey = false;
  34. this._shiftKey = false;
  35. this._buttonsPressed = 0;
  36. this._pointerInput = (p) => {
  37. const evt = p.event;
  38. const isTouch = evt.pointerType === "touch";
  39. if (p.type !== PointerEventTypes.POINTERMOVE && this.buttons.indexOf(evt.button) === -1) {
  40. return;
  41. }
  42. const srcElement = evt.target;
  43. this._altKey = evt.altKey;
  44. this._ctrlKey = evt.ctrlKey;
  45. this._metaKey = evt.metaKey;
  46. this._shiftKey = evt.shiftKey;
  47. this._buttonsPressed = evt.buttons;
  48. if (engine.isPointerLock) {
  49. const offsetX = evt.movementX;
  50. const offsetY = evt.movementY;
  51. this.onTouch(null, offsetX, offsetY);
  52. this._pointA = null;
  53. this._pointB = null;
  54. }
  55. else if (p.type !== PointerEventTypes.POINTERDOWN && isTouch && this._pointA?.pointerId !== evt.pointerId && this._pointB?.pointerId !== evt.pointerId) {
  56. return; // If we get a non-down event for a touch that we're not tracking, ignore it
  57. }
  58. else if (p.type === PointerEventTypes.POINTERDOWN && (this._currentActiveButton === -1 || isTouch)) {
  59. try {
  60. srcElement?.setPointerCapture(evt.pointerId);
  61. }
  62. catch (e) {
  63. //Nothing to do with the error. Execution will continue.
  64. }
  65. if (this._pointA === null) {
  66. this._pointA = {
  67. x: evt.clientX,
  68. y: evt.clientY,
  69. pointerId: evt.pointerId,
  70. type: evt.pointerType,
  71. };
  72. }
  73. else if (this._pointB === null) {
  74. this._pointB = {
  75. x: evt.clientX,
  76. y: evt.clientY,
  77. pointerId: evt.pointerId,
  78. type: evt.pointerType,
  79. };
  80. }
  81. else {
  82. return; // We are already tracking two pointers so ignore this one
  83. }
  84. if (this._currentActiveButton === -1 && !isTouch) {
  85. this._currentActiveButton = evt.button;
  86. }
  87. this.onButtonDown(evt);
  88. if (!noPreventDefault) {
  89. evt.preventDefault();
  90. element && element.focus();
  91. }
  92. }
  93. else if (p.type === PointerEventTypes.POINTERDOUBLETAP) {
  94. this.onDoubleTap(evt.pointerType);
  95. }
  96. else if (p.type === PointerEventTypes.POINTERUP && (this._currentActiveButton === evt.button || isTouch)) {
  97. try {
  98. srcElement?.releasePointerCapture(evt.pointerId);
  99. }
  100. catch (e) {
  101. //Nothing to do with the error.
  102. }
  103. if (!isTouch) {
  104. this._pointB = null; // Mouse and pen are mono pointer
  105. }
  106. //would be better to use pointers.remove(evt.pointerId) for multitouch gestures,
  107. //but emptying completely pointers collection is required to fix a bug on iPhone :
  108. //when changing orientation while pinching camera,
  109. //one pointer stay pressed forever if we don't release all pointers
  110. //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
  111. if (engine._badOS) {
  112. this._pointA = this._pointB = null;
  113. }
  114. else {
  115. //only remove the impacted pointer in case of multitouch allowing on most
  116. //platforms switching from rotate to zoom and pan seamlessly.
  117. if (this._pointB && this._pointA && this._pointA.pointerId == evt.pointerId) {
  118. this._pointA = this._pointB;
  119. this._pointB = null;
  120. }
  121. else if (this._pointA && this._pointB && this._pointB.pointerId == evt.pointerId) {
  122. this._pointB = null;
  123. }
  124. else {
  125. this._pointA = this._pointB = null;
  126. }
  127. }
  128. if (previousPinchSquaredDistance !== 0 || previousMultiTouchPanPosition) {
  129. // Previous pinch data is populated but a button has been lifted
  130. // so pinch has ended.
  131. this.onMultiTouch(this._pointA, this._pointB, previousPinchSquaredDistance, 0, // pinchSquaredDistance
  132. previousMultiTouchPanPosition, null // multiTouchPanPosition
  133. );
  134. previousPinchSquaredDistance = 0;
  135. previousMultiTouchPanPosition = null;
  136. }
  137. this._currentActiveButton = -1;
  138. this.onButtonUp(evt);
  139. if (!noPreventDefault) {
  140. evt.preventDefault();
  141. }
  142. }
  143. else if (p.type === PointerEventTypes.POINTERMOVE) {
  144. if (!noPreventDefault) {
  145. evt.preventDefault();
  146. }
  147. // One button down
  148. if (this._pointA && this._pointB === null) {
  149. const offsetX = evt.clientX - this._pointA.x;
  150. const offsetY = evt.clientY - this._pointA.y;
  151. this.onTouch(this._pointA, offsetX, offsetY);
  152. this._pointA.x = evt.clientX;
  153. this._pointA.y = evt.clientY;
  154. }
  155. // Two buttons down: pinch
  156. else if (this._pointA && this._pointB) {
  157. const ed = this._pointA.pointerId === evt.pointerId ? this._pointA : this._pointB;
  158. ed.x = evt.clientX;
  159. ed.y = evt.clientY;
  160. const distX = this._pointA.x - this._pointB.x;
  161. const distY = this._pointA.y - this._pointB.y;
  162. const pinchSquaredDistance = distX * distX + distY * distY;
  163. const multiTouchPanPosition = {
  164. x: (this._pointA.x + this._pointB.x) / 2,
  165. y: (this._pointA.y + this._pointB.y) / 2,
  166. pointerId: evt.pointerId,
  167. type: p.type,
  168. };
  169. this.onMultiTouch(this._pointA, this._pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition);
  170. previousMultiTouchPanPosition = multiTouchPanPosition;
  171. previousPinchSquaredDistance = pinchSquaredDistance;
  172. }
  173. }
  174. };
  175. this._observer = this.camera
  176. .getScene()
  177. ._inputManager._addCameraPointerObserver(this._pointerInput, PointerEventTypes.POINTERDOWN | PointerEventTypes.POINTERUP | PointerEventTypes.POINTERMOVE | PointerEventTypes.POINTERDOUBLETAP);
  178. this._onLostFocus = () => {
  179. this._pointA = this._pointB = null;
  180. previousPinchSquaredDistance = 0;
  181. previousMultiTouchPanPosition = null;
  182. this.onLostFocus();
  183. };
  184. this._contextMenuBind = (evt) => this.onContextMenu(evt);
  185. element && element.addEventListener("contextmenu", this._contextMenuBind, false);
  186. const hostWindow = this.camera.getScene().getEngine().getHostWindow();
  187. if (hostWindow) {
  188. Tools.RegisterTopRootEvents(hostWindow, [{ name: "blur", handler: this._onLostFocus }]);
  189. }
  190. }
  191. /**
  192. * Detach the current controls from the specified dom element.
  193. */
  194. detachControl() {
  195. if (this._onLostFocus) {
  196. const hostWindow = this.camera.getScene().getEngine().getHostWindow();
  197. if (hostWindow) {
  198. Tools.UnregisterTopRootEvents(hostWindow, [{ name: "blur", handler: this._onLostFocus }]);
  199. }
  200. }
  201. if (this._observer) {
  202. this.camera.getScene()._inputManager._removeCameraPointerObserver(this._observer);
  203. this._observer = null;
  204. if (this._contextMenuBind) {
  205. const inputElement = this.camera.getScene().getEngine().getInputElement();
  206. inputElement && inputElement.removeEventListener("contextmenu", this._contextMenuBind);
  207. }
  208. this._onLostFocus = null;
  209. }
  210. this._altKey = false;
  211. this._ctrlKey = false;
  212. this._metaKey = false;
  213. this._shiftKey = false;
  214. this._buttonsPressed = 0;
  215. this._currentActiveButton = -1;
  216. }
  217. /**
  218. * Gets the class name of the current input.
  219. * @returns the class name
  220. */
  221. getClassName() {
  222. return "BaseCameraPointersInput";
  223. }
  224. /**
  225. * Get the friendly name associated with the input class.
  226. * @returns the input friendly name
  227. */
  228. getSimpleName() {
  229. return "pointers";
  230. }
  231. /**
  232. * Called on pointer POINTERDOUBLETAP event.
  233. * Override this method to provide functionality on POINTERDOUBLETAP event.
  234. * @param type type of event
  235. */
  236. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  237. onDoubleTap(type) { }
  238. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  239. /**
  240. * Called on pointer POINTERMOVE event if only a single touch is active.
  241. * Override this method to provide functionality.
  242. * @param point The current position of the pointer
  243. * @param offsetX The offsetX of the pointer when the event occurred
  244. * @param offsetY The offsetY of the pointer when the event occurred
  245. */
  246. onTouch(point, offsetX, offsetY) { }
  247. /**
  248. * Called on pointer POINTERMOVE event if multiple touches are active.
  249. * Override this method to provide functionality.
  250. * @param _pointA First point in the pair
  251. * @param _pointB Second point in the pair
  252. * @param previousPinchSquaredDistance Sqr Distance between the points the last time this event was fired (by this input)
  253. * @param pinchSquaredDistance Sqr Distance between the points this time
  254. * @param previousMultiTouchPanPosition Previous center point between the points
  255. * @param multiTouchPanPosition Current center point between the points
  256. */
  257. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  258. onMultiTouch(_pointA, _pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition) { }
  259. /**
  260. * Called on JS contextmenu event.
  261. * Override this method to provide functionality.
  262. * @param evt the event to be handled
  263. */
  264. onContextMenu(evt) {
  265. evt.preventDefault();
  266. }
  267. /**
  268. * Called each time a new POINTERDOWN event occurs. Ie, for each button
  269. * press.
  270. * Override this method to provide functionality.
  271. * @param _evt Defines the event to track
  272. */
  273. onButtonDown(_evt) { }
  274. /**
  275. * Called each time a new POINTERUP event occurs. Ie, for each button
  276. * release.
  277. * Override this method to provide functionality.
  278. * @param _evt Defines the event to track
  279. */
  280. onButtonUp(_evt) { }
  281. /**
  282. * Called when window becomes inactive.
  283. * Override this method to provide functionality.
  284. */
  285. onLostFocus() { }
  286. }
  287. __decorate([
  288. serialize()
  289. ], BaseCameraPointersInput.prototype, "buttons", void 0);
  290. //# sourceMappingURL=BaseCameraPointersInput.js.map