import * as THREE from 'three';
import { SphericalCoordsXYZ } from './SphericalCoordinates.ts';
var EPSILON = 0.001;
var STATE = {
    NONE: -1,
    ROTATE: 0,
    DOLLY: 1,
    PAN: 2,
    TOUCH_ROTATE: 3,
    TOUCH_DOLLY: 4,
    TOUCH_PAN: 5,
};
var OrbitCamera = /** @class */ (function () {
    function OrbitCamera(camera, domElement) {
        this.camera = camera;
        this.enabled = true;
        this.minDistance = 0;
        this.maxDistance = Infinity;
        this.minPolarAngle = 0; // radians
        this.maxPolarAngle = Math.PI; // radians
        this.minAzimuthAngle = -Infinity; // radians
        this.maxAzimuthAngle = Infinity; // radians
        this.dampingFactor = 0.05;
        this.draggingDampingFactor = 0.25;
        this.dollySpeed = 1.0;
        this.truckSpeed = 2.0;
        this.domElement = domElement;
        // the location of focus, where the object orbits around
        this.target = new THREE.Vector3();
        this._targetEnd = new THREE.Vector3();
        // rotation
        this._spherical = new SphericalCoordsXYZ();
        this._spherical.setFromVector3(this.camera.position);
        this._sphericalEnd = new SphericalCoordsXYZ().copy(this._spherical);
        // reset
        this._target0 = this.target.clone();
        this._position0 = this.camera.position.clone();
        this._needsUpdate = true;
        this.update(0);
        if (!this.domElement) {
            this.dispose = function () { };
        }
        else {
            var scope_1 = this;
            var dragStart_1 = new THREE.Vector2();
            var dollyStart_1 = new THREE.Vector2();
            var state_1 = STATE.NONE;
            var elementRect_1;
            var savedDampingFactor_1;
            var onMouseDown_1 = function (event) {
                if (!scope_1.enabled)
                    return;
                event.preventDefault();
                var prevState = state_1;
                switch (event.button) {
                    case THREE.MOUSE.LEFT:
                        state_1 = STATE.ROTATE;
                        break;
                    case THREE.MOUSE.MIDDLE:
                        state_1 = STATE.DOLLY;
                        break;
                    case THREE.MOUSE.RIGHT:
                        state_1 = STATE.PAN;
                        break;
                }
                if (prevState === STATE.NONE) {
                    startDragging_1(event);
                }
            };
            var onTouchStart_1 = function (event) {
                if (!scope_1.enabled)
                    return;
                event.preventDefault();
                var prevState = state_1;
                switch (event.touches.length) {
                    case 1: // one-fingered touch: rotate
                        state_1 = STATE.TOUCH_ROTATE;
                        break;
                    case 2: // two-fingered touch: dolly
                        state_1 = STATE.TOUCH_DOLLY;
                        break;
                    case 3: // three-fingered touch: pan
                        state_1 = STATE.TOUCH_PAN;
                        break;
                }
                if (prevState === STATE.NONE) {
                    startDragging_1(event);
                }
            };
            var onMouseWheel_1 = function (event) {
                if (!scope_1.enabled)
                    return;
                event.preventDefault();
                if (event.deltaY < 0) {
                    dollyIn_1();
                }
                else if (event.deltaY > 0) {
                    dollyOut_1();
                }
            };
            var onContextMenu_1 = function (event) {
                if (!scope_1.enabled)
                    return;
                event.preventDefault();
            };
            var startDragging_1 = function (event) {
                if (!scope_1.enabled)
                    return;
                event.preventDefault();
                var _event = event.touches ? event.touches[0] : event;
                var x = _event.clientX;
                var y = _event.clientY;
                elementRect_1 = scope_1.domElement.getBoundingClientRect();
                dragStart_1.set(x, y);
                if (state_1 === STATE.TOUCH_DOLLY) {
                    var dx = x - event.touches[1].pageX;
                    var dy = y - event.touches[1].pageY;
                    var distance = Math.sqrt(dx * dx + dy * dy);
                    dollyStart_1.set(0, distance);
                }
                savedDampingFactor_1 = scope_1.dampingFactor;
                scope_1.dampingFactor = scope_1.draggingDampingFactor;
                document.addEventListener('mousemove', dragging_1, { passive: false });
                document.addEventListener('touchmove', dragging_1, { passive: false });
                document.addEventListener('mouseup', endDragging_1);
                document.addEventListener('touchend', endDragging_1);
            };
            var dragging_1 = function (event) {
                if (!scope_1.enabled)
                    return;
                event.preventDefault();
                var _event = event.touches ? event.touches[0] : event;
                var x = _event.clientX;
                var y = _event.clientY;
                var deltaX = dragStart_1.x - x;
                var deltaY = dragStart_1.y - y;
                dragStart_1.set(x, y);
                switch (state_1) {
                    case STATE.ROTATE:
                    case STATE.TOUCH_ROTATE:
                        var rotTheta = (2 * Math.PI * deltaX) / elementRect_1.width;
                        var rotPhi = (2 * Math.PI * deltaY) / elementRect_1.height;
                        scope_1.rotate(rotTheta, rotPhi, true);
                        break;
                    case STATE.DOLLY:
                        // not implemented
                        break;
                    case STATE.TOUCH_DOLLY:
                        var dx = x - event.touches[1].pageX;
                        var dy = y - event.touches[1].pageY;
                        var distance = Math.sqrt(dx * dx + dy * dy);
                        var dollyDelta = dollyStart_1.y - distance;
                        if (dollyDelta > 0) {
                            dollyOut_1();
                        }
                        else if (dollyDelta < 0) {
                            dollyIn_1();
                        }
                        dollyStart_1.set(0, distance);
                        break;
                    case STATE.PAN:
                    case STATE.TOUCH_PAN:
                        var offset = new THREE.Vector3()
                            .copy(scope_1.camera.position)
                            .sub(scope_1.target);
                        var targetDistance = void 0;
                        if (scope_1.camera instanceof THREE.PerspectiveCamera) {
                            // half of the fov is center to top of screen
                            targetDistance =
                                offset.length() *
                                    Math.tan(((scope_1.camera.fov / 2) * Math.PI) / 180);
                        }
                        else {
                            targetDistance = offset.length() / 2;
                        }
                        var panX = (scope_1.truckSpeed * deltaX * targetDistance) / elementRect_1.height;
                        var panY = (scope_1.truckSpeed * deltaY * targetDistance) / elementRect_1.height;
                        scope_1.pan(panX, panY, true);
                        break;
                }
            };
            var endDragging_1 = function () {
                if (!scope_1.enabled)
                    return;
                scope_1.dampingFactor = savedDampingFactor_1;
                state_1 = STATE.NONE;
                document.removeEventListener('mousemove', dragging_1);
                document.removeEventListener('touchmove', dragging_1);
                document.removeEventListener('mouseup', endDragging_1);
                document.removeEventListener('touchend', endDragging_1);
            };
            var dollyIn_1 = function () {
                var zoomScale = Math.pow(0.7, scope_1.dollySpeed);
                scope_1.dolly(scope_1._sphericalEnd.radius * zoomScale - scope_1._sphericalEnd.radius);
            };
            var dollyOut_1 = function () {
                var zoomScale = Math.pow(0.7, scope_1.dollySpeed);
                scope_1.dolly(scope_1._sphericalEnd.radius / zoomScale - scope_1._sphericalEnd.radius);
            };
            this.domElement.addEventListener('mousedown', onMouseDown_1);
            this.domElement.addEventListener('touchstart', onTouchStart_1);
            this.domElement.addEventListener('wheel', onMouseWheel_1);
            this.domElement.addEventListener('contextmenu', onContextMenu_1);
            this.dispose = function () {
                scope_1.domElement.removeEventListener('mousedown', onMouseDown_1);
                scope_1.domElement.removeEventListener('touchstart', onTouchStart_1);
                scope_1.domElement.removeEventListener('wheel', onMouseWheel_1);
                scope_1.domElement.removeEventListener('contextmenu', onContextMenu_1);
                document.removeEventListener('mousemove', dragging_1);
                document.removeEventListener('touchmove', dragging_1);
                document.removeEventListener('mouseup', endDragging_1);
                document.removeEventListener('touchend', endDragging_1);
            };
        }
    }
    // rotX in radian
    // rotY in radian
    OrbitCamera.prototype.rotate = function (rotTheta, rotPhi, enableTransition) {
        this.rotateTo(this._sphericalEnd.theta + rotTheta, this._sphericalEnd.phi + rotPhi, enableTransition);
    };
    // rotX in radian
    // rotY in radian
    OrbitCamera.prototype.rotateTo = function (rotTheta, rotPhi, enableTransition) {
        var theta = Math.max(this.minAzimuthAngle, Math.min(this.maxAzimuthAngle, rotTheta));
        var phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, rotPhi));
        this._sphericalEnd.theta = theta;
        this._sphericalEnd.phi = phi;
        this._sphericalEnd.makeSafe();
        if (!enableTransition) {
            this._spherical.theta = this._sphericalEnd.theta;
            this._spherical.phi = this._sphericalEnd.phi;
        }
        this._needsUpdate = true;
    };
    OrbitCamera.prototype.zoomIn = function () {
        var zoomScale = Math.pow(0.95, this.dollySpeed);
        this.dolly(this._sphericalEnd.radius * zoomScale - this._sphericalEnd.radius);
    };
    OrbitCamera.prototype.zoomOut = function () {
        var zoomScale = Math.pow(0.95, this.dollySpeed);
        this.dolly(this._sphericalEnd.radius / zoomScale - this._sphericalEnd.radius);
    };
    OrbitCamera.prototype.dolly = function (distance, enableTransition) {
        if (enableTransition === void 0) { enableTransition = false; }
        this.dollyTo(this._sphericalEnd.radius + distance, enableTransition);
    };
    OrbitCamera.prototype.dollyTo = function (distance, enableTransition) {
        this._sphericalEnd.radius = THREE.Math.clamp(distance, this.minDistance, this.maxDistance);
        if (!enableTransition) {
            this._spherical.radius = this._sphericalEnd.radius;
        }
        this._needsUpdate = true;
    };
    OrbitCamera.prototype.pan = function (x, y, enableTransition) {
        this.truck(x, y, enableTransition);
    };
    OrbitCamera.prototype.truck = function (x, y, enableTransition) {
        this.camera.updateMatrix();
        var _xColumn = new THREE.Vector3().setFromMatrixColumn(this.camera.matrix, 0);
        var _yColumn = new THREE.Vector3().setFromMatrixColumn(this.camera.matrix, 1);
        _xColumn.multiplyScalar(x);
        _yColumn.multiplyScalar(-y);
        var offset = new THREE.Vector3()
            .copy(_xColumn)
            .add(_yColumn);
        this._targetEnd.add(offset);
        if (!enableTransition) {
            this.target.copy(this._targetEnd);
        }
        this._needsUpdate = true;
    };
    OrbitCamera.prototype.moveTo = function (x, y, z, enableTransition) {
        this._targetEnd.set(x, y, z);
        if (!enableTransition) {
            this.target.copy(this._targetEnd);
        }
        this._needsUpdate = true;
    };
    OrbitCamera.prototype.saveState = function () {
        this._target0.copy(this.target);
        this._position0.copy(this.camera.position);
    };
    OrbitCamera.prototype.reset = function (enableTransition) {
        this._targetEnd.copy(this._target0);
        this._sphericalEnd.setFromVector3(this._position0);
        this._sphericalEnd.theta = this._sphericalEnd.theta % (2 * Math.PI);
        this._spherical.theta = this._spherical.theta % (2 * Math.PI);
        if (!enableTransition) {
            this.target.copy(this._targetEnd);
            this._spherical.copy(this._sphericalEnd);
        }
        this._needsUpdate = true;
    };
    OrbitCamera.prototype.update = function (delta) {
        var dampingFactor = (this.dampingFactor * delta) / 0.016;
        var deltaTheta = this._sphericalEnd.theta - this._spherical.theta;
        var deltaPhi = this._sphericalEnd.phi - this._spherical.phi;
        var deltaRadius = this._sphericalEnd.radius - this._spherical.radius;
        var deltaTarget = new THREE.Vector3().subVectors(this._targetEnd, this.target);
        if (Math.abs(deltaTheta) > EPSILON ||
            Math.abs(deltaPhi) > EPSILON ||
            Math.abs(deltaRadius) > EPSILON ||
            Math.abs(deltaTarget.x) > EPSILON ||
            Math.abs(deltaTarget.y) > EPSILON ||
            Math.abs(deltaTarget.z) > EPSILON) {
            this._spherical.set(this._spherical.radius + deltaRadius * dampingFactor, this._spherical.phi + deltaPhi * dampingFactor, this._spherical.theta + deltaTheta * dampingFactor);
            this.target.add(deltaTarget.multiplyScalar(dampingFactor));
            this._needsUpdate = true;
        }
        else {
            this._spherical.copy(this._sphericalEnd);
            this.target.copy(this._targetEnd);
        }
        this._spherical.makeSafe();
        this.camera.position
            .set(this._spherical.cartesian.x, this._spherical.cartesian.y, this._spherical.cartesian.z)
            .add(this.target);
        this.camera.lookAt(this.target);
        var needsUpdate = this._needsUpdate;
        this._needsUpdate = false;
        return needsUpdate;
    };
    OrbitCamera.prototype.toJSON = function () {
        return {
            enabled: this.enabled,
            minDistance: this.minDistance,
            maxDistance: infinityToMaxNumber(this.maxDistance),
            minPolarAngle: this.minPolarAngle,
            maxPolarAngle: infinityToMaxNumber(this.maxPolarAngle),
            minAzimuthAngle: infinityToMaxNumber(this.minAzimuthAngle),
            maxAzimuthAngle: infinityToMaxNumber(this.maxAzimuthAngle),
            dampingFactor: this.dampingFactor,
            draggingDampingFactor: this.draggingDampingFactor,
            dollySpeed: this.dollySpeed,
            truckSpeed: this.truckSpeed,
            target: this._targetEnd.toArray(),
            position: this.camera.position.toArray(),
            target0: this._target0.toArray(),
            position0: this._position0.toArray(),
        };
    };
    OrbitCamera.prototype.fromJSON = function (json, enableTransition) {
        var obj = JSON.parse(json);
        var position = new THREE.Vector3().fromArray(obj.position);
        this.enabled = obj.enabled;
        this.minDistance = obj.minDistance;
        this.maxDistance = maxNumberToInfinity(obj.maxDistance);
        this.minPolarAngle = obj.minPolarAngle;
        this.maxPolarAngle = maxNumberToInfinity(obj.maxPolarAngle);
        this.minAzimuthAngle = maxNumberToInfinity(obj.minAzimuthAngle);
        this.maxAzimuthAngle = maxNumberToInfinity(obj.maxAzimuthAngle);
        this.dampingFactor = obj.dampingFactor;
        this.draggingDampingFactor = obj.draggingDampingFactor;
        this.dollySpeed = obj.dollySpeed;
        this.truckSpeed = obj.truckSpeed;
        this._target0.fromArray(obj.target0);
        this._position0.fromArray(obj.position0);
        this._targetEnd.fromArray(obj.target);
        this._sphericalEnd.setFromVector3(position.sub(this._target0));
        if (!enableTransition) {
            this.target.copy(this._targetEnd);
            this._spherical.copy(this._sphericalEnd);
        }
        this._needsUpdate = true;
    };
    return OrbitCamera;
}());
export default OrbitCamera;
var infinityToMaxNumber = function (value) {
    if (isFinite(value))
        return value;
    if (value < 0)
        return -Number.MAX_VALUE;
    return Number.MAX_VALUE;
};
var maxNumberToInfinity = function (value) {
    if (Math.abs(value) < Number.MAX_VALUE)
        return value;
    return value * Infinity;
};
//# sourceMappingURL=OrbitCamera.js.map