瀏覽代碼

Port ArcRotateCameraPointersInput to use BaseCameraPointersInput.

duncan law 6 年之前
父節點
當前提交
410a9afa4d

+ 1 - 0
dist/preview release/what's new.md

@@ -93,6 +93,7 @@
 - Added MouseWheel bindings for FollowCamera. ([mrdunk](https://github.com))
 - Tweak MouseWheel bindings for FollowCamera orientations. ([mrdunk](https://github.com))
 - Added maximum and minimum limits for FollowCamera parameters. ([mrdunk](https://github.com))
+- Convert ArcRotateCamera to use new BaseCameraPointersInput. ([mrdunk](https://github.com))
 - Added per solid particle culling possibility : `solidParticle.isInFrustum()`  ([jerome](https://github.com/jbousquie))
 - Added transparency support to `GlowLayer` ([Sebavan](https://github.com/Sebavan))
 - Added option `forceDisposeChildren` to multiMaterial.dispose ([danjpar](https://github.com/danjpar))

+ 68 - 4
src/Cameras/Inputs/BaseCameraPointersInput.ts

@@ -23,6 +23,20 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
     protected abstract _className: string;
 
     /**
+     * Whether keyboard modifier keys are pressed at time of last mouse event.
+     */
+    protected _altKey: boolean;
+    protected _ctrlKey: boolean;
+    protected _metaKey: boolean;
+    protected _shiftKey: boolean;
+
+    /**
+     * Which mouse buttons were pressed at time of last mouse event.
+     * https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
+     */
+    protected _buttonsPressed: number;
+
+    /**
      * Defines the buttons associated with the input to handle camera move.
      */
     @serialize()
@@ -40,9 +54,15 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
         var previousPinchSquaredDistance = 0;
         var previousMultiTouchPanPosition: Nullable<PointerTouch> = null;
 
+        this._altKey = false;
+        this._ctrlKey = false;
+        this._metaKey = false;
+        this._shiftKey = false;
+        this._buttonsPressed = 0;
+
         this._pointerInput = (p, s) => {
             var evt = <PointerEvent>p.event;
-            let isTouch = (<any>p.event).pointerType === "touch";
+            let isTouch = evt.pointerType === "touch";
 
             if (engine.isInVRExclusivePointerMode) {
                 return;
@@ -55,6 +75,12 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
 
             let srcElement = <HTMLElement>(evt.srcElement || evt.target);
 
+            this._altKey = evt.altKey;
+            this._ctrlKey = evt.ctrlKey;
+            this._metaKey = evt.metaKey;
+            this._shiftKey = evt.shiftKey;
+            this._buttonsPressed = evt.buttons;
+
             if (engine.isPointerLock) {
                 var offsetX = evt.movementX ||
                               evt.mozMovementX ||
@@ -68,6 +94,8 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
                               0;
 
                 this.onTouch(null, offsetX, offsetY);
+                pointA = null;
+                pointB = null;
             } else if (p.type === PointerEventTypes.POINTERDOWN && srcElement) {
                 try {
                     srcElement.setPointerCapture(evt.pointerId);
@@ -87,12 +115,14 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
                               type: evt.pointerType };
                 }
 
+                this.onButtonDown(evt, pointB ? 2 : 1);
+
                 if (!noPreventDefault) {
                     evt.preventDefault();
                     element.focus();
                 }
             } else if (p.type === PointerEventTypes.POINTERDOUBLETAP) {
-                this.onDoubleTouch(evt.pointerType);
+                this.onDoubleTap(evt.pointerType);
             } else if (p.type === PointerEventTypes.POINTERUP && srcElement) {
                 try {
                     srcElement.releasePointerCapture(evt.pointerId);
@@ -139,6 +169,8 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
                   previousMultiTouchPanPosition = null;
                 }
 
+                this.onButtonUp(evt);
+
                 if (!noPreventDefault) {
                     evt.preventDefault();
                 }
@@ -190,6 +222,9 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
 
         this._onLostFocus = () => {
             pointA = pointB = null;
+            previousPinchSquaredDistance = 0;
+            previousMultiTouchPanPosition = null;
+            this.onLostFocus();
         };
 
         element.addEventListener("contextmenu",
@@ -221,6 +256,12 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
 
             this._onLostFocus = null;
         }
+
+        this._altKey = false;
+        this._ctrlKey = false;
+        this._metaKey = false;
+        this._shiftKey = false;
+        this._buttonsPressed = 0;
     }
 
     /**
@@ -243,14 +284,14 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
      * Called on pointer POINTERDOUBLETAP event.
      * Override this method to provide functionality on POINTERDOUBLETAP event.
      */
-    protected onDoubleTouch(type: string) {
+    protected onDoubleTap(type: string) {
     }
 
     /**
      * Called on pointer POINTERMOVE event if only a single touch is active.
      * Override this method to provide functionality.
      */
-    protected onTouch(pointA: Nullable<PointerTouch>,
+    protected onTouch(point: Nullable<PointerTouch>,
                       offsetX: number,
                       offsetY: number): void {
     }
@@ -285,6 +326,29 @@ export abstract class BaseCameraPointersInput implements ICameraInput<Camera> {
         evt.preventDefault();
     }
 
+    /**
+     * Called each time a new POINTERDOWN event occurs. Ie, for each button
+     * press.
+     * Override this method to provide functionality.
+     */
+    protected onButtonDown(evt: PointerEvent, buttonCount: number): void {
+    }
+
+    /**
+     * Called each time a new POINTERUP event occurs. Ie, for each button
+     * release.
+     * Override this method to provide functionality.
+     */
+    protected onButtonUp(evt: PointerEvent): void {
+    }
+
+    /**
+     * Called when window becomes inactive.
+     * Override this method to provide functionality.
+     */
+    protected onLostFocus(): void {
+    }
+
     private _pointerInput: (p: PointerInfo, s: EventState) => void;
     private _observer: Nullable<Observer<PointerInfo>>;
     private _onLostFocus: Nullable<(e: FocusEvent) => any>;

+ 125 - 329
src/Cameras/Inputs/arcRotateCameraPointersInput.ts

@@ -1,35 +1,41 @@
 import { Nullable } from "../../types";
 import { serialize } from "../../Misc/decorators";
-import { EventState, Observer } from "../../Misc/observable";
-import { Tools } from "../../Misc/tools";
 import { ArcRotateCamera } from "../../Cameras/arcRotateCamera";
-import { ICameraInput, CameraInputTypes } from "../../Cameras/cameraInputsManager";
-import { PointerInfo, PointerEventTypes } from "../../Events/pointerEvents";
+import { CameraInputTypes } from "../../Cameras/cameraInputsManager";
+import { BaseCameraPointersInput } from "../../Cameras/Inputs/BaseCameraPointersInput";
+import { PointerTouch } from "../../Events/pointerEvents";
 
 /**
  * Manage the pointers inputs to control an arc rotate camera.
  * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
  */
-export class ArcRotateCameraPointersInput implements ICameraInput<ArcRotateCamera> {
+export class ArcRotateCameraPointersInput extends BaseCameraPointersInput {
     /**
      * Defines the camera the input is attached to.
      */
     public camera: ArcRotateCamera;
 
     /**
+     * The class name of the current input.
+     */
+    protected _className = "ArcRotateCameraPointersInput";
+
+    /**
      * Defines the buttons associated with the input to handle camera move.
      */
     @serialize()
     public buttons = [0, 1, 2];
 
     /**
-     * Defines the pointer angular sensibility  along the X axis or how fast is the camera rotating.
+     * Defines the pointer angular sensibility  along the X axis or how fast is
+     * the camera rotating.
      */
     @serialize()
     public angularSensibilityX = 1000.0;
 
     /**
-     * Defines the pointer angular sensibility along the Y axis or how fast is the camera rotating.
+     * Defines the pointer angular sensibility along the Y axis or how fast is
+     * the camera rotating.
      */
     @serialize()
     public angularSensibilityY = 1000.0;
@@ -41,8 +47,10 @@ export class ArcRotateCameraPointersInput implements ICameraInput<ArcRotateCamer
     public pinchPrecision = 12.0;
 
     /**
-     * pinchDeltaPercentage will be used instead of pinchPrecision if different from 0.
-     * It defines the percentage of current camera.radius to use as delta when pinch zoom is used.
+     * pinchDeltaPercentage will be used instead of pinchPrecision if different
+     * from 0.
+     * It defines the percentage of current camera.radius to use as delta when
+     * pinch zoom is used.
      */
     @serialize()
     public pinchDeltaPercentage = 0;
@@ -60,7 +68,8 @@ export class ArcRotateCameraPointersInput implements ICameraInput<ArcRotateCamer
     public multiTouchPanning: boolean = true;
 
     /**
-     * Defines whether panning is enabled for both pan (2 fingers swipe) and zoom (pinch) through multitouch.
+     * Defines whether panning is enabled for both pan (2 fingers swipe) and
+     * zoom (pinch) through multitouch.
      */
     @serialize()
     public multiTouchPanAndZoom: boolean = true;
@@ -71,351 +80,138 @@ export class ArcRotateCameraPointersInput implements ICameraInput<ArcRotateCamer
     public pinchInwards = true;
 
     private _isPanClick: boolean = false;
-    private _pointerInput: (p: PointerInfo, s: EventState) => void;
-    private _observer: Nullable<Observer<PointerInfo>>;
-    private _onMouseMove: Nullable<(e: MouseEvent) => any>;
-    private _onGestureStart: Nullable<(e: PointerEvent) => void>;
-    private _onGesture: Nullable<(e: MSGestureEvent) => void>;
-    private _MSGestureHandler: Nullable<MSGesture>;
-    private _onLostFocus: Nullable<(e: FocusEvent) => any>;
-    private _onContextMenu: Nullable<(e: Event) => void>;
+    private _twoFingerActivityCount: number = 0;
+    private _isPinching: boolean = false;
 
     /**
-     * Attach the input controls to a specific dom element to get the input from.
-     * @param element Defines the element the controls should be listened from
-     * @param noPreventDefault Defines whether event caught by the controls should call preventdefault() (https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault)
+     * Called on pointer POINTERMOVE event if only a single touch is active.
      */
-    public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
-        var engine = this.camera.getEngine();
-        var cacheSoloPointer: Nullable<{ x: number, y: number, pointerId: number, type: any }>; // cache pointer object for better perf on camera rotation
-        var pointA: Nullable<{ x: number, y: number, pointerId: number, type: any }> = null;
-        var pointB: Nullable<{ x: number, y: number, pointerId: number, type: any }> = null;
-        var previousPinchSquaredDistance = 0;
-        var initialDistance = 0;
-        var twoFingerActivityCount = 0;
-        var previousMultiTouchPanPosition: { x: number, y: number, isPaning: boolean, isPinching: boolean } = { x: 0, y: 0, isPaning: false, isPinching: false };
-
-        this._pointerInput = (p, s) => {
-            var evt = <PointerEvent>p.event;
-            let isTouch = (<any>p.event).pointerType === "touch";
-
-            if (engine.isInVRExclusivePointerMode) {
-                return;
-            }
-
-            if (p.type !== PointerEventTypes.POINTERMOVE && this.buttons.indexOf(evt.button) === -1) {
-                return;
-            }
-
-            let srcElement = <HTMLElement>(evt.srcElement || evt.target);
-
-            if (p.type === PointerEventTypes.POINTERDOWN && srcElement) {
-                try {
-                    srcElement.setPointerCapture(evt.pointerId);
-                } catch (e) {
-                    //Nothing to do with the error. Execution will continue.
-                }
-
-                // Manage panning with pan button click
-                this._isPanClick = evt.button === this.camera._panningMouseButton;
-
-                // manage pointers
-                cacheSoloPointer = { x: evt.clientX, y: evt.clientY, pointerId: evt.pointerId, type: evt.pointerType };
-                if (pointA === null) {
-                    pointA = cacheSoloPointer;
-                }
-                else if (pointB === null) {
-                    pointB = cacheSoloPointer;
-                }
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                    element.focus();
-                }
-            }
-            else if (p.type === PointerEventTypes.POINTERDOUBLETAP) {
-                if (this.camera.useInputToRestoreState) {
-                    this.camera.restoreState();
-                }
-            }
-            else if (p.type === PointerEventTypes.POINTERUP && srcElement) {
-                try {
-                    srcElement.releasePointerCapture(evt.pointerId);
-                } catch (e) {
-                    //Nothing to do with the error.
-                }
-
-                cacheSoloPointer = null;
-                previousPinchSquaredDistance = 0;
-                previousMultiTouchPanPosition.isPaning = false;
-                previousMultiTouchPanPosition.isPinching = false;
-                twoFingerActivityCount = 0;
-                initialDistance = 0;
-
-                if (!isTouch) {
-                    pointB = null; // Mouse and pen are mono pointer
-                }
-
-                //would be better to use pointers.remove(evt.pointerId) for multitouch gestures,
-                //but emptying completly pointers collection is required to fix a bug on iPhone :
-                //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers
-                //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
-                if (engine._badOS) {
-                    pointA = pointB = null;
-                }
-                else {
-                    //only remove the impacted pointer in case of multitouch allowing on most
-                    //platforms switching from rotate to zoom and pan seamlessly.
-                    if (pointB && pointA && pointA.pointerId == evt.pointerId) {
-                        pointA = pointB;
-                        pointB = null;
-                        cacheSoloPointer = { x: pointA.x, y: pointA.y, pointerId: pointA.pointerId, type: evt.pointerType };
-                    }
-                    else if (pointA && pointB && pointB.pointerId == evt.pointerId) {
-                        pointB = null;
-                        cacheSoloPointer = { x: pointA.x, y: pointA.y, pointerId: pointA.pointerId, type: evt.pointerType };
-                    }
-                    else {
-                        pointA = pointB = null;
-                    }
-                }
-
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-            } else if (p.type === PointerEventTypes.POINTERMOVE) {
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-
-                // One button down
-                if (pointA && pointB === null && cacheSoloPointer) {
-                    if (this.panningSensibility !== 0 &&
-                        ((evt.ctrlKey && this.camera._useCtrlForPanning) || this._isPanClick)) {
-                        this.camera.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / this.panningSensibility;
-                        this.camera.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / this.panningSensibility;
-                    } else {
-                        var offsetX = evt.clientX - cacheSoloPointer.x;
-                        var offsetY = evt.clientY - cacheSoloPointer.y;
-                        this.camera.inertialAlphaOffset -= offsetX / this.angularSensibilityX;
-                        this.camera.inertialBetaOffset -= offsetY / this.angularSensibilityY;
-                    }
-
-                    cacheSoloPointer.x = evt.clientX;
-                    cacheSoloPointer.y = evt.clientY;
-                }
-
-                // Two buttons down: pinch/pan
-                else if (pointA && pointB) {
-                    //if (noPreventDefault) { evt.preventDefault(); } //if pinch gesture, could be useful to force preventDefault to avoid html page scroll/zoom in some mobile browsers
-                    var ed = (pointA.pointerId === evt.pointerId) ? pointA : pointB;
-                    ed.x = evt.clientX;
-                    ed.y = evt.clientY;
-                    var direction = this.pinchInwards ? 1 : -1;
-                    var distX = pointA.x - pointB.x;
-                    var distY = pointA.y - pointB.y;
-                    var pinchSquaredDistance = (distX * distX) + (distY * distY);
-                    var pinchDistance = Math.sqrt(pinchSquaredDistance);
-
-                    if (previousPinchSquaredDistance === 0) {
-                        initialDistance = pinchDistance;
-                        previousPinchSquaredDistance = pinchSquaredDistance;
-                        previousMultiTouchPanPosition.x = (pointA.x + pointB.x) / 2;
-                        previousMultiTouchPanPosition.y = (pointA.y + pointB.y) / 2;
-                        return;
-                    }
-
-                    if (this.multiTouchPanAndZoom) {
-                        if (this.pinchDeltaPercentage) {
-                            this.camera.inertialRadiusOffset += ((pinchSquaredDistance - previousPinchSquaredDistance) * 0.001) * this.camera.radius * this.pinchDeltaPercentage;
-                        } else {
-                            this.camera.inertialRadiusOffset += (pinchSquaredDistance - previousPinchSquaredDistance) /
-                                (this.pinchPrecision *
-                                    ((this.angularSensibilityX + this.angularSensibilityY) / 2) *
-                                    direction);
-                        }
-
-                        if (this.panningSensibility !== 0) {
-                            var pointersCenterX = (pointA.x + pointB.x) / 2;
-                            var pointersCenterY = (pointA.y + pointB.y) / 2;
-                            var pointersCenterDistX = pointersCenterX - previousMultiTouchPanPosition.x;
-                            var pointersCenterDistY = pointersCenterY - previousMultiTouchPanPosition.y;
-
-                            previousMultiTouchPanPosition.x = pointersCenterX;
-                            previousMultiTouchPanPosition.y = pointersCenterY;
-
-                            this.camera.inertialPanningX += -(pointersCenterDistX) / (this.panningSensibility);
-                            this.camera.inertialPanningY += (pointersCenterDistY) / (this.panningSensibility);
-                        }
-                    }
-                    else {
-                        twoFingerActivityCount++;
-
-                        if (previousMultiTouchPanPosition.isPinching || (twoFingerActivityCount < 20 && Math.abs(pinchDistance - initialDistance) > this.camera.pinchToPanMaxDistance)) {
-                            if (this.pinchDeltaPercentage) {
-                                this.camera.inertialRadiusOffset += ((pinchSquaredDistance - previousPinchSquaredDistance) * 0.001) * this.camera.radius * this.pinchDeltaPercentage;
-                            } else {
-                                this.camera.inertialRadiusOffset += (pinchSquaredDistance - previousPinchSquaredDistance) /
-                                    (this.pinchPrecision *
-                                        ((this.angularSensibilityX + this.angularSensibilityY) / 2) *
-                                        direction);
-                            }
-                            previousMultiTouchPanPosition.isPaning = false;
-                            previousMultiTouchPanPosition.isPinching = true;
-                        }
-                        else {
-                            if (cacheSoloPointer && cacheSoloPointer.pointerId === ed.pointerId && this.panningSensibility !== 0 && this.multiTouchPanning) {
-                                if (!previousMultiTouchPanPosition.isPaning) {
-                                    previousMultiTouchPanPosition.isPaning = true;
-                                    previousMultiTouchPanPosition.isPinching = false;
-                                    previousMultiTouchPanPosition.x = ed.x;
-                                    previousMultiTouchPanPosition.y = ed.y;
-                                    return;
-                                }
-
-                                this.camera.inertialPanningX += -(ed.x - previousMultiTouchPanPosition.x) / (this.panningSensibility);
-                                this.camera.inertialPanningY += (ed.y - previousMultiTouchPanPosition.y) / (this.panningSensibility);
-                            }
-                        }
-
-                        if (cacheSoloPointer && cacheSoloPointer.pointerId === evt.pointerId) {
-                            previousMultiTouchPanPosition.x = ed.x;
-                            previousMultiTouchPanPosition.y = ed.y;
-                        }
-                    }
-
-                    previousPinchSquaredDistance = pinchSquaredDistance;
-                }
-            }
-        };
-
-        this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput, PointerEventTypes.POINTERDOWN | PointerEventTypes.POINTERUP | PointerEventTypes.POINTERMOVE | PointerEventTypes.POINTERDOUBLETAP);
-
-        this._onContextMenu = (evt) => {
-            evt.preventDefault();
-        };
-
-        if (!this.camera._useCtrlForPanning) {
-            element.addEventListener("contextmenu", this._onContextMenu, false);
-        }
-
-        this._onLostFocus = () => {
-            //this._keys = [];
-            pointA = pointB = null;
-            previousPinchSquaredDistance = 0;
-            previousMultiTouchPanPosition.isPaning = false;
-            previousMultiTouchPanPosition.isPinching = false;
-            twoFingerActivityCount = 0;
-            cacheSoloPointer = null;
-            initialDistance = 0;
-        };
-
-        this._onMouseMove = (evt) => {
-            if (!engine.isPointerLock) {
-                return;
-            }
-
-            var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
-            var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
-
+    protected onTouch(point: Nullable<PointerTouch>,
+                      offsetX: number,
+                      offsetY: number): void {
+        if (this.panningSensibility !== 0 &&
+          ((this._ctrlKey && this.camera._useCtrlForPanning) || this._isPanClick)) {
+            this.camera.inertialPanningX += -offsetX / this.panningSensibility;
+            this.camera.inertialPanningY += offsetY / this.panningSensibility;
+        } else {
             this.camera.inertialAlphaOffset -= offsetX / this.angularSensibilityX;
             this.camera.inertialBetaOffset -= offsetY / this.angularSensibilityY;
-
-            if (!noPreventDefault) {
-                evt.preventDefault();
-            }
-        };
-
-        this._onGestureStart = (e) => {
-            if (window.MSGesture === undefined) {
-                return;
-            }
-
-            if (!this._MSGestureHandler) {
-                this._MSGestureHandler = new MSGesture();
-                this._MSGestureHandler.target = element;
-            }
-
-            this._MSGestureHandler.addPointer(e.pointerId);
-        };
-
-        this._onGesture = (e) => {
-            this.camera.radius *= e.scale;
-
-            if (e.preventDefault) {
-                if (!noPreventDefault) {
-                    e.stopPropagation();
-                    e.preventDefault();
-                }
-            }
-        };
-
-        element.addEventListener("mousemove", this._onMouseMove, false);
-        element.addEventListener("MSPointerDown", <EventListener>this._onGestureStart, false);
-        element.addEventListener("MSGestureChange", <EventListener>this._onGesture, false);
-
-        Tools.RegisterTopRootEvents([
-            { name: "blur", handler: this._onLostFocus }
-        ]);
+        }
     }
 
     /**
-     * Detach the current controls from the specified dom element.
-     * @param element Defines the element to stop listening the inputs from
+     * Called on pointer POINTERDOUBLETAP event.
      */
-    public detachControl(element: Nullable<HTMLElement>): void {
-        if (this._onLostFocus) {
-            Tools.UnregisterTopRootEvents([
-                { name: "blur", handler: this._onLostFocus }
-            ]);
+    protected onDoubleTap(type: string) {
+        if (this.camera.useInputToRestoreState) {
+            this.camera.restoreState();
         }
+    }
 
-        if (element && this._observer) {
-            this.camera.getScene().onPointerObservable.remove(this._observer);
-            this._observer = null;
-
-            if (this._onContextMenu) {
-                element.removeEventListener("contextmenu", this._onContextMenu);
-            }
+    /**
+     * Called on pointer POINTERMOVE event if multiple touches are active.
+     */
+    protected onMultiTouch(pointA: Nullable<PointerTouch>,
+                           pointB: Nullable<PointerTouch>,
+                           previousPinchSquaredDistance: number,
+                           pinchSquaredDistance: number,
+                           previousMultiTouchPanPosition: Nullable<PointerTouch>,
+                           multiTouchPanPosition: Nullable<PointerTouch>): void
+    {
+        if (previousPinchSquaredDistance === 0 && previousMultiTouchPanPosition === null) {
+            // First time this method is called for new pinch.
+            // Next time this is called there will be a
+            // previousPinchSquaredDistance and pinchSquaredDistance to compare.
+            return;
+        }
+        if (pinchSquaredDistance === 0 && multiTouchPanPosition === null) {
+            // Last time this method is called at the end of a pinch.
+            return;
+        }
 
-            if (this._onMouseMove) {
-                element.removeEventListener("mousemove", this._onMouseMove);
+        var direction = this.pinchInwards ? 1 : -1;
+
+        if (this.multiTouchPanAndZoom) {
+            if (this.pinchDeltaPercentage) {
+                this.camera.inertialRadiusOffset +=
+                    (pinchSquaredDistance - previousPinchSquaredDistance) * 0.001 *
+                    this.camera.radius * this.pinchDeltaPercentage;
+            } else {
+                this.camera.inertialRadiusOffset +=
+                    (pinchSquaredDistance - previousPinchSquaredDistance) /
+                    (this.pinchPrecision * direction *
+                    (this.angularSensibilityX + this.angularSensibilityY) / 2);
             }
 
-            if (this._onGestureStart) {
-                element.removeEventListener("MSPointerDown", <EventListener>this._onGestureStart);
+            if (this.panningSensibility !== 0 &&
+              previousMultiTouchPanPosition && multiTouchPanPosition) {
+                var moveDeltaX = multiTouchPanPosition.x - previousMultiTouchPanPosition.x;
+                var moveDeltaY = multiTouchPanPosition.y - previousMultiTouchPanPosition.y;
+                this.camera.inertialPanningX += -moveDeltaX / this.panningSensibility;
+                this.camera.inertialPanningY += moveDeltaY / this.panningSensibility;
             }
+        } else {
+            this._twoFingerActivityCount++;
+            var previousPinchDistance = Math.sqrt(previousPinchSquaredDistance);
+            var pinchDistance = Math.sqrt(pinchSquaredDistance);
+            if (this._isPinching ||
+              (this._twoFingerActivityCount < 20 &&
+               Math.abs(pinchDistance - previousPinchDistance) >
+               this.camera.pinchToPanMaxDistance)) {
+                // Since pinch has not been active long, assume we intend to zoom.
+                if (this.pinchDeltaPercentage) {
+                    this.camera.inertialRadiusOffset +=
+                      (pinchSquaredDistance - previousPinchSquaredDistance) * 0.001 *
+                      this.camera.radius * this.pinchDeltaPercentage;
+                } else {
+                    this.camera.inertialRadiusOffset +=
+                        (pinchSquaredDistance - previousPinchSquaredDistance) /
+                        (this.pinchPrecision * direction *
+                        (this.angularSensibilityX + this.angularSensibilityY) / 2);
+                }
 
-            if (this._onGesture) {
-                element.removeEventListener("MSGestureChange", <EventListener>this._onGesture);
+                // Since we are pinching, remain pinching on next iteration.
+                this._isPinching = true;
+            } else {
+                // Pause between pinch starting and moving implies not a zoom event.
+                // Pan instead.
+                if (this.panningSensibility !== 0 && this.multiTouchPanning &&
+                  multiTouchPanPosition && previousMultiTouchPanPosition) {
+                    var moveDeltaX = multiTouchPanPosition.x - previousMultiTouchPanPosition.x;
+                    var moveDeltaY = multiTouchPanPosition.y - previousMultiTouchPanPosition.y;
+                    this.camera.inertialPanningX += -moveDeltaX / this.panningSensibility;
+                    this.camera.inertialPanningY += moveDeltaY / this.panningSensibility;
+                }
             }
-
-            this._isPanClick = false;
-            this.pinchInwards = true;
-
-            this._onMouseMove = null;
-            this._onGestureStart = null;
-            this._onGesture = null;
-            this._MSGestureHandler = null;
-            this._onLostFocus = null;
-            this._onContextMenu = null;
         }
     }
 
     /**
-     * Gets the class name of the current intput.
-     * @returns the class name
+     * Called each time a new POINTERDOWN event occurs. Ie, for each button
+     * press.
+     */
+    protected onButtonDown(evt: PointerEvent, buttonCount: number): void {
+        this._isPanClick = evt.button === this.camera._panningMouseButton;
+    }
+
+    /**
+     * Called each time a new POINTERUP event occurs. Ie, for each button
+     * release.
      */
-    public getClassName(): string {
-        return "ArcRotateCameraPointersInput";
+    protected onButtonUp(evt: PointerEvent): void {
+        this._twoFingerActivityCount = 0;
+        this._isPinching = false;
     }
 
     /**
-     * Get the friendly name associated with the input class.
-     * @returns the input friendly name
+     * Called when window becomes inactive.
      */
-    public getSimpleName(): string {
-        return "pointers";
+    protected onLostFocus(): void {
+        this._isPanClick = false;
+        this._twoFingerActivityCount = 0;
+        this._isPinching = false;
     }
 }
+(<any>CameraInputTypes)["ArcRotateCameraPointersInput"] =
+  ArcRotateCameraPointersInput;
 
-(<any>CameraInputTypes)["ArcRotateCameraPointersInput"] = ArcRotateCameraPointersInput;