import { Plane } from "../../Maths/math.plane.js";
import { TmpVectors, Vector3 } from "../../Maths/math.vector.js";
import { OrbitCameraPointersInput } from "./orbitCameraPointersInput.js";
/**
 * @experimental
 * Geospatial camera inputs can simulate dragging the globe around or tilting the camera around some point on the globe
 * The input will update the camera's localTranslation or localRotation values, and the camera is responsible for using these updates to calculate viewMatrix appropriately
 *
 * As of right now, the camera correction logic (to keep the camera geospatially oriented around the globe) is happening within the camera class when calculating viewmatrix
 * As this is experimental, it is possible we move that correction step to live within the input class (to enable non-corrected translations in the future), say if we want to allow the camera to move outside of the globe's orbit
 *
 * Left mouse button: drag globe
 * Middle mouse button: tilt globe around cursor location
 * Right mouse button: tilt globe around center of screen
 *
 */
export class GeospatialCameraPointersInput extends OrbitCameraPointersInput {
    constructor() {
        super(...arguments);
        /**
         * Mouse sensitivity for rotation (lower = more sensitive)
         */
        this.angularSensibility = 200.0;
        this._dragPlane = new Plane(0, 0, 0, 0);
        this._dragPlaneNormal = Vector3.Zero();
        this._dragPlaneOriginPoint = Vector3.Zero();
        this._dragPlaneHitPoint = Vector3.Zero();
        this._dragPlaneOffsetVector = Vector3.Zero();
    }
    getClassName() {
        return "GeospatialCameraPointersInput";
    }
    onButtonDown(evt) {
        const scene = this.camera.getScene();
        let pickResult;
        switch (evt.button) {
            case 0: // Left button - drag/pan globe under cursor
                pickResult = scene.pick(scene.pointerX, scene.pointerY, this.camera.pickPredicate);
                if (pickResult.pickedPoint && pickResult.ray) {
                    // Store radius from earth center to pickedPoint, used when calculating drag plane
                    this._hitPointRadius = pickResult.pickedPoint.length();
                    // The dragPlaneOffsetVector will later be recalculated when drag occurs, and the delta between the offset vectors will be applied to localTranslation
                    this._recalculateDragPlaneOffsetVectorToRef(this._hitPointRadius, pickResult.ray, this._dragPlaneOffsetVector);
                }
                else {
                    this._hitPointRadius = undefined; // can't drag without a hit on the globe
                }
                break;
            case 1: // Middle button - tilt camera around cursor
                pickResult = scene.pick(scene.pointerX, scene.pointerY, this.camera.pickPredicate);
                pickResult.pickedPoint && (this.camera._alternateRotationPt = pickResult.pickedPoint);
                break;
            case 2: // Right button - tilt camera around center of screen, already the default
                this.camera._alternateRotationPt = this.camera.center;
                break;
            default:
                return;
        }
    }
    onTouch(point, offsetX, offsetY) {
        switch (point?.button) {
            case 0: // Left button - drag/pan globe under cursor
                this._hitPointRadius !== undefined && this._handleDrag(this._hitPointRadius);
                break;
            case 1: // Middle button - tilt camera around cursor
            case 2: // Right button - tilt camera
                this._handleTilt(offsetX, offsetY);
                break;
        }
    }
    /**
     * Move camera from multi touch panning positions.
     * In geospatialcamera, multi touch panning tilts the globe (whereas single touch will pan/drag it)
     * @param previousMultiTouchPanPosition
     * @param multiTouchPanPosition
     */
    _computeMultiTouchPanning(previousMultiTouchPanPosition, multiTouchPanPosition) {
        if (previousMultiTouchPanPosition && multiTouchPanPosition) {
            const moveDeltaX = multiTouchPanPosition.x - previousMultiTouchPanPosition.x;
            const moveDeltaY = multiTouchPanPosition.y - previousMultiTouchPanPosition.y;
            this._handleTilt(moveDeltaX, moveDeltaY);
        }
    }
    onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition) {
        this._shouldStartPinchZoom = this._twoFingerActivityCount < 20 && Math.abs(Math.sqrt(pinchSquaredDistance) - Math.sqrt(previousPinchSquaredDistance)) > 20; // move to limits once limits exist
        super.onMultiTouch(pointA, pointB, previousPinchSquaredDistance, pinchSquaredDistance, previousMultiTouchPanPosition, multiTouchPanPosition);
    }
    onButtonUp(_evt) {
        this._hitPointRadius = undefined;
        this.camera._alternateRotationPt = null;
        super.onButtonUp(_evt);
    }
    /**
     * The DragPlaneOffsetVector represents the vector between the dragPlane hit point and the dragPlane origin point.
     * As the drag movement occurs, we will continuously recalculate this vector. The delta between the offsetVectors is the delta we will apply to the camera's localtranslation
     * @param hitPointRadius The distance between the world origin (center of globe) and the initial drag hit point
     * @param ray The ray from the camera to the new cursor location
     * @param ref The offset vector between the drag plane's hitPoint and originPoint
     */
    _recalculateDragPlaneOffsetVectorToRef(hitPointRadius, ray, ref) {
        // Use the camera's geocentric normal to find the dragPlaneOriginPoint which lives at hitPointRadius along the camera's geocentric normal
        this.camera.position.normalizeToRef(this._dragPlaneNormal);
        this._dragPlaneNormal.scaleToRef(hitPointRadius, this._dragPlaneOriginPoint);
        // Now create a plane at that point, perpendicular to the camera's geocentric normal
        Plane.FromPositionAndNormalToRef(this._dragPlaneOriginPoint, this._dragPlaneNormal, this._dragPlane);
        // Lastly, find the _dragPlaneHitPoint where the ray intersects the _dragPlane
        IntersectRayWithPlaneToRef(ray, this._dragPlane, this._dragPlaneHitPoint);
        // Store the new offset between the drag plane's hitPoint and originPoint
        this._dragPlaneHitPoint.subtractToRef(this._dragPlaneOriginPoint, ref);
    }
    _handleDrag(hitPointRadius) {
        const scene = this.camera.getScene();
        const pickResult = scene.pick(scene.pointerX, scene.pointerY);
        if (pickResult.ray) {
            const newDragPlaneOffsetVector = TmpVectors.Vector3[5];
            this._recalculateDragPlaneOffsetVectorToRef(hitPointRadius, pickResult.ray, newDragPlaneOffsetVector);
            const delta = TmpVectors.Vector3[6];
            newDragPlaneOffsetVector.subtractToRef(this._dragPlaneOffsetVector, delta);
            this._dragPlaneOffsetVector.copyFrom(newDragPlaneOffsetVector);
            this.camera._perFrameGeocentricTranslation.subtractInPlace(delta); // ???
        }
    }
    _handleTilt(deltaX, deltaY) {
        this.camera._perFrameGeocentricRotation.y += -deltaX / this.angularSensibility; // yaw - looking side to side
        this.camera._perFrameGeocentricRotation.x += -deltaY / this.angularSensibility; // pitch - look up towards sky / down towards ground
    }
}
function IntersectRayWithPlaneToRef(ray, plane, ref) {
    // Distance along the ray to the plane; null if no hit
    const dist = ray.intersectsPlane(plane);
    if (dist !== null && dist >= 0) {
        ray.origin.addToRef(ray.direction.scaleToRef(dist, TmpVectors.Vector3[0]), ref);
        return true;
    }
    return false;
}
//# sourceMappingURL=geospatialCameraPointersInput.js.map