babylon.arcRotateCameraPointersInput.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. module BABYLON {
  2. var eventPrefix = Tools.GetPointerPrefix();
  3. export class ArcRotateCameraPointersInput implements ICameraInput<ArcRotateCamera> {
  4. camera: ArcRotateCamera;
  5. @serialize()
  6. public buttons = [0, 1, 2];
  7. @serialize()
  8. public angularSensibilityX = 1000.0;
  9. @serialize()
  10. public angularSensibilityY = 1000.0;
  11. @serialize()
  12. public pinchPrecision = 6.0;
  13. @serialize()
  14. public panningSensibility: number = 50.0;
  15. @serialize()
  16. public multiTouchPanning: boolean = true;
  17. private _isPanClick: boolean = false;
  18. public pinchInwards = true;
  19. private _pointerInput: (p: PointerInfo, s: EventState) => void;
  20. private _observer: Observer<PointerInfo>;
  21. private _onMouseMove: (e: MouseEvent) => any;
  22. private _onGestureStart: (e: PointerEvent) => void;
  23. private _onGesture: (e: MSGestureEvent) => void;
  24. private _MSGestureHandler: MSGesture;
  25. private _onLostFocus: (e: FocusEvent) => any;
  26. private _onContextMenu: (e: PointerEvent) => void;
  27. public attachControl(element: HTMLElement, noPreventDefault?: boolean) {
  28. var engine = this.camera.getEngine();
  29. var cacheSoloPointer: { x: number, y: number, pointerId: number, type: any }; // cache pointer object for better perf on camera rotation
  30. var pointA: { x: number, y: number, pointerId: number, type: any }, pointB: { x: number, y: number, pointerId: number, type: any };
  31. var previousPinchSquaredDistance = 0;
  32. var previousPinchDistance = 0;
  33. var previousMultiTouchPanPosition: { x: number, y: number, isPaning: boolean } = { x: 0, y:0, isPaning: false };
  34. this._pointerInput = (p, s) => {
  35. var evt = <PointerEvent>p.event;
  36. if (engine.isInVRExclusivePointerMode) {
  37. return;
  38. }
  39. if (p.type !== PointerEventTypes.POINTERMOVE && this.buttons.indexOf(evt.button) === -1) {
  40. return;
  41. }
  42. if (p.type === PointerEventTypes.POINTERDOWN) {
  43. try {
  44. evt.srcElement.setPointerCapture(evt.pointerId);
  45. } catch (e) {
  46. //Nothing to do with the error. Execution will continue.
  47. }
  48. // Manage panning with pan button click
  49. this._isPanClick = evt.button === this.camera._panningMouseButton;
  50. // manage pointers
  51. cacheSoloPointer = { x: evt.clientX, y: evt.clientY, pointerId: evt.pointerId, type: evt.pointerType };
  52. if (pointA === undefined) {
  53. pointA = cacheSoloPointer;
  54. }
  55. else if (pointB === undefined) {
  56. pointB = cacheSoloPointer;
  57. }
  58. if (!noPreventDefault) {
  59. evt.preventDefault();
  60. element.focus();
  61. }
  62. }
  63. else if (p.type === PointerEventTypes.POINTERDOUBLETAP) {
  64. this.camera.restoreState();
  65. }
  66. else if (p.type === PointerEventTypes.POINTERUP) {
  67. try {
  68. evt.srcElement.releasePointerCapture(evt.pointerId);
  69. } catch (e) {
  70. //Nothing to do with the error.
  71. }
  72. cacheSoloPointer = null;
  73. previousPinchSquaredDistance = 0;
  74. previousPinchDistance = 0;
  75. previousMultiTouchPanPosition.isPaning = false;
  76. //would be better to use pointers.remove(evt.pointerId) for multitouch gestures,
  77. //but emptying completly pointers collection is required to fix a bug on iPhone :
  78. //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers
  79. //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
  80. pointA = pointB = undefined;
  81. if (!noPreventDefault) {
  82. evt.preventDefault();
  83. }
  84. } else if (p.type === PointerEventTypes.POINTERMOVE) {
  85. if (!noPreventDefault) {
  86. evt.preventDefault();
  87. }
  88. // One button down
  89. if (pointA && pointB === undefined) {
  90. if (this.panningSensibility !== 0 &&
  91. ((evt.ctrlKey && this.camera._useCtrlForPanning) || this._isPanClick)) {
  92. this.camera.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / this.panningSensibility;
  93. this.camera.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / this.panningSensibility;
  94. } else {
  95. var offsetX = evt.clientX - cacheSoloPointer.x;
  96. var offsetY = evt.clientY - cacheSoloPointer.y;
  97. this.camera.inertialAlphaOffset -= offsetX / this.angularSensibilityX;
  98. this.camera.inertialBetaOffset -= offsetY / this.angularSensibilityY;
  99. }
  100. cacheSoloPointer.x = evt.clientX;
  101. cacheSoloPointer.y = evt.clientY;
  102. }
  103. // Two buttons down: pinch/pan
  104. else if (pointA && pointB) {
  105. //if (noPreventDefault) { evt.preventDefault(); } //if pinch gesture, could be useful to force preventDefault to avoid html page scroll/zoom in some mobile browsers
  106. var ed = (pointA.pointerId === evt.pointerId) ? pointA : pointB;
  107. ed.x = evt.clientX;
  108. ed.y = evt.clientY;
  109. var direction = this.pinchInwards ? 1 : -1;
  110. var distX = pointA.x - pointB.x;
  111. var distY = pointA.y - pointB.y;
  112. var pinchSquaredDistance = (distX * distX) + (distY * distY);
  113. var pinchDistance = Math.sqrt(pinchSquaredDistance);
  114. if (previousPinchSquaredDistance === 0) {
  115. previousPinchSquaredDistance = pinchSquaredDistance;
  116. previousPinchDistance = pinchDistance;
  117. return;
  118. }
  119. if (pinchDistance > this.camera.panMaxFingersDistance || Math.abs(pinchDistance - previousPinchDistance) > this.camera.pinchToPanMaxDistance) {
  120. this.camera
  121. .inertialRadiusOffset += (pinchSquaredDistance - previousPinchSquaredDistance) /
  122. (this.pinchPrecision *
  123. ((this.angularSensibilityX + this.angularSensibilityY) / 2) *
  124. direction);
  125. previousMultiTouchPanPosition.isPaning = false;
  126. }
  127. else {
  128. if (cacheSoloPointer.pointerId === ed.pointerId && this.panningSensibility !== 0 && this.multiTouchPanning) {
  129. if (!previousMultiTouchPanPosition.isPaning) {
  130. previousMultiTouchPanPosition.isPaning = true;
  131. previousMultiTouchPanPosition.x = ed.x;
  132. previousMultiTouchPanPosition.y = ed.y;
  133. return;
  134. }
  135. this.camera.inertialPanningX += -(ed.x - previousMultiTouchPanPosition.x) / this.panningSensibility;
  136. this.camera.inertialPanningY += (ed.y - previousMultiTouchPanPosition.y) / this.panningSensibility;
  137. }
  138. }
  139. if (cacheSoloPointer.pointerId === evt.pointerId) {
  140. previousMultiTouchPanPosition.x = ed.x;
  141. previousMultiTouchPanPosition.y = ed.y;
  142. }
  143. previousPinchSquaredDistance = pinchSquaredDistance;
  144. previousPinchDistance = pinchDistance;
  145. }
  146. }
  147. }
  148. this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput, PointerEventTypes.POINTERDOWN | PointerEventTypes.POINTERUP | PointerEventTypes.POINTERMOVE | PointerEventTypes._POINTERDOUBLETAP);
  149. this._onContextMenu = evt => {
  150. evt.preventDefault();
  151. };
  152. if (!this.camera._useCtrlForPanning) {
  153. element.addEventListener("contextmenu", this._onContextMenu, false);
  154. }
  155. this._onLostFocus = () => {
  156. //this._keys = [];
  157. pointA = pointB = undefined;
  158. previousPinchSquaredDistance = 0;
  159. previousPinchDistance = 0;
  160. previousMultiTouchPanPosition.isPaning = false;
  161. cacheSoloPointer = null;
  162. };
  163. this._onMouseMove = evt => {
  164. if (!engine.isPointerLock) {
  165. return;
  166. }
  167. var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
  168. var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
  169. this.camera.inertialAlphaOffset -= offsetX / this.angularSensibilityX;
  170. this.camera.inertialBetaOffset -= offsetY / this.angularSensibilityY;
  171. if (!noPreventDefault) {
  172. evt.preventDefault();
  173. }
  174. };
  175. this._onGestureStart = e => {
  176. if (window.MSGesture === undefined) {
  177. return;
  178. }
  179. if (!this._MSGestureHandler) {
  180. this._MSGestureHandler = new MSGesture();
  181. this._MSGestureHandler.target = element;
  182. }
  183. this._MSGestureHandler.addPointer(e.pointerId);
  184. };
  185. this._onGesture = e => {
  186. this.camera.radius *= e.scale;
  187. if (e.preventDefault) {
  188. if (!noPreventDefault) {
  189. e.stopPropagation();
  190. e.preventDefault();
  191. }
  192. }
  193. };
  194. element.addEventListener("mousemove", this._onMouseMove, false);
  195. element.addEventListener("MSPointerDown", this._onGestureStart, false);
  196. element.addEventListener("MSGestureChange", this._onGesture, false);
  197. Tools.RegisterTopRootEvents([
  198. { name: "blur", handler: this._onLostFocus }
  199. ]);
  200. }
  201. public detachControl(element: HTMLElement) {
  202. Tools.UnregisterTopRootEvents([
  203. { name: "blur", handler: this._onLostFocus }
  204. ]);
  205. if (element && this._observer) {
  206. this.camera.getScene().onPointerObservable.remove(this._observer);
  207. this._observer = null;
  208. element.removeEventListener("contextmenu", this._onContextMenu);
  209. element.removeEventListener("mousemove", this._onMouseMove);
  210. element.removeEventListener("MSPointerDown", this._onGestureStart);
  211. element.removeEventListener("MSGestureChange", this._onGesture);
  212. this._isPanClick = false;
  213. this.pinchInwards = true;
  214. this._onMouseMove = null;
  215. this._onGestureStart = null;
  216. this._onGesture = null;
  217. this._MSGestureHandler = null;
  218. this._onLostFocus = null;
  219. this._onContextMenu = null;
  220. }
  221. }
  222. getClassName(): string {
  223. return "ArcRotateCameraPointersInput";
  224. }
  225. getSimpleName() {
  226. return "pointers";
  227. }
  228. }
  229. CameraInputTypes["ArcRotateCameraPointersInput"] = ArcRotateCameraPointersInput;
  230. }