/** @internal NOTE: Internal APIs. Subject to change. Use of these APIs in production applications is not supported. */ /** */
import { BaseCameraPointersInput, Scalar, Vector2 } from '../../loader/babylonjs-import';
import { convertToCanvasCoordinatesToRef } from './input-utils';
import { boundingClientRectCache } from '../../BoundingClientRectCache';
var PinchOperation;
(function (PinchOperation) {
    PinchOperation[PinchOperation["None"] = 0] = "None";
    PinchOperation[PinchOperation["Zoom"] = 1] = "Zoom";
    PinchOperation[PinchOperation["Pan"] = 2] = "Pan";
})(PinchOperation || (PinchOperation = {}));
/**
 * Manages the touch inputs to control the movement of a PivotTargetCamera.
 * @see http://doc.babylonjs.com/how_to/customizing_camera_inputs
 */
export class TwinfinityCameraTouchInput extends BaseCameraPointersInput {
    _cameraOptions;
    static _tmp = {
        vector2A: Vector2.Zero(),
        vector2B: Vector2.Zero()
    };
    /** Factor to multiply normalized screen coordinates with in order to calcualte number of
     *  radians a screen coordinate (deltaX, deltaY) movement represents. Normalized screen
     *  coordinates are [-1, 1]
     */
    static _normalizedScreenCoordinatesToRadians = Math.PI * 2;
    _multiTouchPanCanvasCoordinate = { x: 0, y: 0 };
    _multiTouchZoomCanvasCoordinate = { x: 0, y: 0 };
    _pinchDelta = 0;
    _lastPointerEventType = '';
    _currentDirection = 0;
    _previousNormalizedPinchDistance = 0;
    _startNormalizedPinchDistance = 0;
    _pinchOperation = PinchOperation.None;
    _pinchOperations = [];
    _pointers = [];
    /**
     * Defines the camera the input is attached to.
     */
    camera;
    constructor(_cameraOptions) {
        super();
        this._cameraOptions = _cameraOptions;
    }
    getPointers(pointers) {
        pointers.length = 0;
        const i = this;
        if (i.pointA) {
            pointers.push(i.pointA);
        }
        if (i.pointB) {
            pointers.push(i.pointB);
        }
        return pointers;
    }
    /**
     * Get the friendly name associated with the input class.
     * @returns the input friendly name
     */
    getSimpleName() {
        return 'touch';
    }
    onButtonDown(evt) {
        this._lastPointerEventType = evt.pointerType;
        if (this._lastPointerEventType !== 'touch') {
            return;
        }
        // HACK! pointB is set when we have more than one touchpoint
        // we only want to set pivot point when there is one touchpoint.
        // unfortunately the only way to check this is by looking at BaseCameraPointersInput.pointB
        // which is private
        const baseCameraPointersInput = this;
        if (!this._cameraOptions.isRotationEnabled ||
            (baseCameraPointersInput.pointA && baseCameraPointersInput.pointB)) {
            return;
        }
        const scene = this.camera.getScene();
        convertToCanvasCoordinatesToRef(scene, evt, this._multiTouchPanCanvasCoordinate);
        this.camera.twinfinity.pivot.isVisible = true;
        this.camera.twinfinity.pivot.update({
            canvasCoordinate: this._multiTouchPanCanvasCoordinate
        });
    }
    onButtonUp(evt) {
        const pointers = this.getPointers(this._pointers);
        this.camera.twinfinity.pivot.isVisible = pointers.length > 0;
    }
    onTouch(point, offsetX, offsetY) {
        if (this._lastPointerEventType !== 'touch') {
            return;
        }
        if (!this._cameraOptions.isRotationEnabled) {
            return;
        }
        // TODO Implement rotation, offsetX and offsetY is delta but not normalized
        const scene = this.camera.getScene();
        const engine = this.camera.getEngine();
        const canvas = engine.getInputElement();
        const canvasRect = boundingClientRectCache.getOrAdd(canvas);
        let normalizedDeltax = offsetX / canvasRect.width;
        const normalizedDeltaY = offsetY / canvasRect.height;
        if (scene.useRightHandedSystem) {
            normalizedDeltax *= -1;
        }
        if (this.camera.parent && this.camera.parent._getWorldMatrixDeterminant() < 0) {
            normalizedDeltax *= -1;
        }
        this.camera.twinfinity.pivot.isVisible = true;
        this.camera.twinfinity.pivot.rotate(normalizedDeltaY * TwinfinityCameraTouchInput._normalizedScreenCoordinatesToRadians, normalizedDeltax * TwinfinityCameraTouchInput._normalizedScreenCoordinatesToRadians);
    }
    onDoubleTap(type) {
        // What did we double tap?? If something then teleport there!
    }
    onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition) {
        // onMultiTouch is called when
        // 1. Both pointA and pointB are valid. First time previousMultiTouchPanPosition will be null and
        //    previousPinchSquaredDistance will be 0. Subsequent times previousMultiTouchPanPosition will be non null
        //    and previousPinchSquaredDistance <> 0
        // 2. One finger is lifted in which case pinchSquaredDistance = 0 AND multiTouchPanPosition = null
        //alert('foo');
        if (this._lastPointerEventType !== 'touch' ||
            !(this._cameraOptions.isPanEnabled || this._cameraOptions.isZoomEnabled)) {
            return;
        }
        const pinchOperationEnded = pinchSquaredDistance === 0 && !multiTouchPanPosition;
        if (pinchOperationEnded) {
            // When a finger is lifted (POINTERUP) this happens. We end the current
            // pinch operation.
            this.resetPinchOperation();
            return;
        }
        if (!multiTouchPanPosition) {
            // Can never happen because condition above happens before. However keeps
            // the typescript compiler from flagging multiTouchPanPosition as possibly undefined
            // below.
            return;
        }
        this.camera.twinfinity.pivot.isVisible = true;
        convertToCanvasCoordinatesToRef(this.camera.getScene(), multiTouchPanPosition, this._multiTouchPanCanvasCoordinate);
        const pinchOperationStarted = previousPinchSquaredDistance === 0 && previousMultiTouchPanPosition === null;
        if (pinchOperationStarted) {
            // First time this method is called for ne
            // Next time this is called there will be a
            // previousPinchSquaredDistance and pinchSquaredDistance to compare.
            this.camera.twinfinity.pivot.update({
                canvasCoordinate: this._multiTouchPanCanvasCoordinate
            });
            // Pan coordinate will change but zoom coordinate on canvas will not change
            this._multiTouchZoomCanvasCoordinate.x = this._multiTouchPanCanvasCoordinate.x;
            this._multiTouchZoomCanvasCoordinate.y = this._multiTouchPanCanvasCoordinate.y;
            this.resetPinchOperation();
            // Record the initial pinchdistance (distance between fingers). If furhter pinchdistance's differ
            // from this value by little then we probably have a pan gesture (fingers are kept at approx) same
            // distance.
            this._startNormalizedPinchDistance = this.calculateNormalizedPinchDistance(pointA, pointB);
            this._previousNormalizedPinchDistance = this._startNormalizedPinchDistance;
            return;
        }
        // When we get here we know that pointA and pointB are valid. However we want
        // all points to be in normalized coordinates [0,1]. We also want the same for
        // pinchSquaredDistance and previousPinchSquaredDistance. Hence we have to calculate and keep
        // track of this ourselves.
        const normalizedPinchDistance = this.calculateNormalizedPinchDistance(pointA, pointB);
        const normalizedPinchDeltaDistance = normalizedPinchDistance - this._previousNormalizedPinchDistance;
        this._previousNormalizedPinchDistance = normalizedPinchDistance;
        if (this._pinchOperations.length < 5) {
            // Before we have 10 samples we do not really know if we have a pan or a zoom operation because
            // (unfortunately) a slow zoom operation will give a very small change between fingers between frames
            // we must therefore look at pinch distance for every frame and if it is steadily increasing/decreasing vs
            // the initial pinch distance then we have a zoom operation. Otherwise we have a pan operation.
            // this is NOT 100% fullproof but it is the best we have for now.
            const distanceMovedRelativeStart = Math.abs(normalizedPinchDistance - this._startNormalizedPinchDistance);
            this._pinchOperation = distanceMovedRelativeStart < 0.03 ? PinchOperation.Pan : PinchOperation.Zoom;
            this._pinchOperations.push(this._pinchOperation);
        }
        if (this._pinchOperation === PinchOperation.Zoom) {
            if (Scalar.WithinEpsilon(normalizedPinchDeltaDistance, 0, 0.0005)) {
                // Ignore cases where pinch distance has not changed at all during
                // zoom.
                return;
            }
            const newDirection = Math.sign(normalizedPinchDeltaDistance);
            if (newDirection !== this._currentDirection) {
                // Went from zooming in to out or vice versa.
                this._pinchDelta = Math.abs(normalizedPinchDeltaDistance) * 20;
                this._currentDirection = newDirection;
            }
            else {
                this._pinchDelta += Math.abs(normalizedPinchDeltaDistance) * 20;
            }
        }
        else if (this._pinchOperation === PinchOperation.Pan) {
            this.camera.twinfinity.pivot.pan(this._multiTouchPanCanvasCoordinate);
        }
    }
    checkInputs() {
        if (this._lastPointerEventType !== 'touch') {
            return;
        }
        // This function is executed on every frame. Use accumulated delta and count it down to zero using inertia
        if (Scalar.WithinEpsilon(this._pinchDelta, 0)) {
            return;
        }
        if (!this._cameraOptions.isZoomEnabled) {
            return;
        }
        const step = Scalar.Clamp(this._pinchDelta * 0.5, 0.01, 0.7);
        let deltaToUse = step;
        if (this._pinchDelta <= step) {
            deltaToUse = this._pinchDelta;
            this._pinchDelta = 0;
        }
        else {
            this._pinchDelta -= step;
        }
        this.camera.twinfinity.pivot.zoom(this._currentDirection * deltaToUse, this._multiTouchZoomCanvasCoordinate);
    }
    /**
     * Gets the class name of the current input.
     * @returns the class name
     */
    getClassName() {
        return 'PivotTargetCameraTouchInput';
    }
    onLostFocus() {
        this.resetPinchOperation();
        this._lastPointerEventType = '';
        this.camera.twinfinity.pivot.isVisible = false;
    }
    resetPinchOperation() {
        this._pinchDelta = 0;
        this._previousNormalizedPinchDistance = 0;
        this._startNormalizedPinchDistance = 0;
        this._currentDirection = 0;
        this._pinchOperations.length = 0;
        this._pinchOperation = PinchOperation.None;
    }
    calculateNormalizedPinchDistance(pointA, pointB) {
        const scene = this.camera.getScene();
        const canvasRect = boundingClientRectCache.getOrAdd(scene.getEngine().getInputElement());
        const k = 1 / Math.min(canvasRect.width, canvasRect.height);
        const normalizedPointA = convertToCanvasCoordinatesToRef(scene, pointA, TwinfinityCameraTouchInput._tmp.vector2A).scaleInPlace(k);
        const normalizedPointB = convertToCanvasCoordinatesToRef(scene, pointB, TwinfinityCameraTouchInput._tmp.vector2B).scaleInPlace(k);
        return Vector2.Distance(normalizedPointA, normalizedPointB);
    }
}
//# sourceMappingURL=TwinfinityCameraTouchInput.js.map