import * as MathUtils from "./MathUtils";
import { createQuaternionFromEuler } from "./Quaternion";
import { createMatrix4, makeRotationFromQuaternion } from "./Matrix4";
var tmpMatrix4 = createMatrix4();
/** Class with public static methods to make arithmetic operations on vectors easier. */
var Vector = /** @class */ (function () {
    function Vector() {
    }
    /**
     * Creates a new vec2 with absolute values of components
     * @param input
     */
    Vector.AbsoluteComponents2 = function (input) {
        return { x: Math.abs(input.x), y: Math.abs(input.y) };
    };
    /**
     * Creates a new vec3 with absolute values of components
     * @param input
     */
    Vector.AbsoluteComponents3 = function (input) {
        return {
            x: Math.abs(input.x),
            y: Math.abs(input.y),
            z: Math.abs(input.z),
        };
    };
    /**
     * Factory acting as constructor for vec2
     * @param x
     * @param y
     */
    Vector.CreateVec2 = function (x, y) {
        if (x === void 0) { x = 0; }
        if (y === void 0) { y = 0; }
        return { x: x, y: y };
    };
    /**
     * Factory acting as constructor for vec3
     * @param x
     * @param y
     * @param z
     */
    Vector.CreateVec3 = function (x, y, z) {
        if (x === void 0) { x = 0; }
        if (y === void 0) { y = 0; }
        if (z === void 0) { z = 0; }
        return { x: x, y: y, z: z };
    };
    /**
     * Clone function for vec2
     * @param input
     */
    Vector.CloneVec2 = function (input) {
        return { x: input.x, y: input.y };
    };
    /**
     * Clone function for vec3
     * @param input
     */
    Vector.CloneVec3 = function (input) {
        return { x: input.x, y: input.y, z: input.z };
    };
    /**
     * Copy vec2 vector
     * @param input
     */
    Vector.CopyVec2 = function (from, to) {
        to.x = from.x;
        to.y = from.y;
    };
    /**
     * Copy vec3 vector
     * @param input
     */
    Vector.CopyVec3 = function (from, to) {
        to.x = from.x;
        to.y = from.y;
        to.z = from.z;
    };
    /**
     * Returns normalized vec2
     * @param vector
     */
    Vector.Normalize2 = function (vector, length) {
        if (length === void 0) { length = 1.0; }
        var magnitude = Vector.Magnitude2(vector);
        if (magnitude === 0) {
            return undefined;
        }
        return Vector.ScalarMultiply2(length / magnitude, vector);
    };
    /**
     * Returns normalized vec3
     * @param vector
     */
    Vector.Normalize3 = function (vector, length) {
        if (length === void 0) { length = 1.0; }
        var magnitude = Vector.Magnitude3(vector);
        if (magnitude === 0) {
            return undefined;
        }
        return Vector.ScalarMultiply3(length / magnitude, vector);
    };
    /**
     * Normalizes vec2 vector
     * @param vector
     */
    Vector.NormalizeInPlace2 = function (vector, length) {
        if (length === void 0) { length = 1.0; }
        var magnitude = Vector.Magnitude2(vector);
        if (magnitude === 0) {
            return undefined;
        }
        return Vector.ScalarMultiplyInPlace2(length / magnitude, vector);
    };
    /**
     * Normalizes vec3 vector
     * @param vector
     */
    Vector.NormalizeInPlace3 = function (vector, length) {
        if (length === void 0) { length = 1.0; }
        var magnitude = Vector.Magnitude3(vector);
        if (magnitude === 0) {
            return undefined;
        }
        return Vector.ScalarMultiplyInPlace3(length / magnitude, vector);
    };
    /**
     * Subtracts second from first vec2
     * @param first
     * @param second
     */
    Vector.Sub2 = function (first, second) {
        return { x: first.x - second.x, y: first.y - second.y };
    };
    /**
     * Subtracts second from first vec3
     * @param first
     * @param second
     */
    Vector.Sub3 = function (first, second) {
        return {
            x: first.x - second.x,
            y: first.y - second.y,
            z: first.z - second.z,
        };
    };
    /**
     * Subtracts given vec2s from first and saves it into the first
     * @param vectors
     */
    Vector.SubOnFirst2 = function () {
        var vectors = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            vectors[_i] = arguments[_i];
        }
        var len = vectors.length;
        if (len > 0) {
            var result = vectors[0];
            var vector = void 0;
            for (var idx = 1; idx < len; idx++) {
                vector = vectors[idx];
                result.x -= vector.x;
                result.y -= vector.y;
            }
            return result;
        }
        return undefined;
    };
    /**
     * Subtracts given vec3s from first and saves it into the first
     * @param vectors
     */
    Vector.SubOnFirst3 = function () {
        var vectors = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            vectors[_i] = arguments[_i];
        }
        var len = vectors.length;
        if (len > 0) {
            var result = vectors[0];
            var vector = void 0;
            for (var idx = 1; idx < len; idx++) {
                vector = vectors[idx];
                result.x -= vector.x;
                result.y -= vector.y;
                result.z -= vector.z;
            }
            return result;
        }
        return undefined;
    };
    /**
     * Returns sum of given vec2s
     * @param vectors
     */
    Vector.Add2 = function () {
        var vectors = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            vectors[_i] = arguments[_i];
        }
        var result = Vector.CreateVec2();
        var len = vectors.length;
        for (var idx = 0; idx < len; idx++) {
            var vector = vectors[idx];
            result.x += vector.x;
            result.y += vector.y;
        }
        return result;
    };
    /**
     * Returns sum of given vec3s
     * @param vectors
     */
    Vector.Add3 = function () {
        var vectors = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            vectors[_i] = arguments[_i];
        }
        var result = Vector.CreateVec3();
        var len = vectors.length;
        for (var idx = 0; idx < len; idx++) {
            var vector = vectors[idx];
            result.x += vector.x;
            result.y += vector.y;
            result.z += vector.z;
        }
        return result;
    };
    /**
     * Sums up given vec2s and saves it into the first
     * @param vectors
     */
    Vector.AddOnFirst2 = function () {
        var vectors = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            vectors[_i] = arguments[_i];
        }
        var len = vectors.length;
        if (len > 0) {
            var result = vectors[0];
            var vector = void 0;
            for (var idx = 1; idx < len; idx++) {
                vector = vectors[idx];
                result.x += vector.x;
                result.y += vector.y;
            }
            return result;
        }
        return undefined;
    };
    /**
     * Sums up given vec3s and saves it into the first
     * @param vectors
     */
    Vector.AddOnFirst3 = function () {
        var vectors = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            vectors[_i] = arguments[_i];
        }
        var len = vectors.length;
        if (len > 0) {
            var result = vectors[0];
            var vector = void 0;
            for (var idx = 1; idx < len; idx++) {
                vector = vectors[idx];
                result.x += vector.x;
                result.y += vector.y;
                result.z += vector.z;
            }
            return result;
        }
        return undefined;
    };
    /**
     * Returns squared magnitude of vec2
     * @param vector
     */
    Vector.MagnitudeSquared2 = function (vector) {
        return vector.x * vector.x + vector.y * vector.y;
    };
    /**
     * Returns squared magnitude of vec3
     * @param vector
     */
    Vector.MagnitudeSquared3 = function (vector) {
        return vector.x * vector.x + vector.y * vector.y + vector.z * vector.z;
    };
    /**
     * Returns magnitude (length) of vec2
     * @param vector
     */
    Vector.Magnitude2 = function (vector) {
        return Math.sqrt(vector.x * vector.x + vector.y * vector.y);
    };
    /**
     * Returns magnitude (length) of vec3
     * @param vector
     */
    Vector.Magnitude3 = function (vector) {
        return Math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z);
    };
    /**
     * Returns first parameter with the maximum entries from other vec2 parameters
     * @param input
     * @oaram vectors
     */
    Vector.MaximizeInPlace2 = function (input) {
        var vectors = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            vectors[_i - 1] = arguments[_i];
        }
        for (var i = 1, il = vectors.length; i < il; ++i) {
            var v = vectors[i];
            input.x = Math.max(input.x, v.x);
            input.y = Math.max(input.y, v.y);
        }
        return input;
    };
    /**
     * Returns first parameter with the maximum entries from other vec3 parameters
     * @param input
     * @oaram vectors
     */
    Vector.MaximizeInPlace3 = function (input) {
        var vectors = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            vectors[_i - 1] = arguments[_i];
        }
        for (var i = 1, il = vectors.length; i < il; ++i) {
            var v = vectors[i];
            input.x = Math.max(input.x, v.x);
            input.y = Math.max(input.y, v.y);
            input.z = Math.max(input.z, v.z);
        }
        return input;
    };
    /**
     * Returns first parameter with the minimum entries from other vec2 parameters
     * @param input
     * @oaram vectors
     */
    Vector.MinimizeInPlace2 = function (input) {
        var vectors = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            vectors[_i - 1] = arguments[_i];
        }
        for (var i = 1, il = vectors.length; i < il; ++i) {
            var v = vectors[i];
            input.x = Math.min(input.x, v.x);
            input.y = Math.min(input.y, v.y);
        }
        return input;
    };
    /**
     * Returns first parameter with the minimum entries from other vec3 parameters
     * @param input
     * @oaram vectors
     */
    Vector.MinimizeInPlace3 = function (input) {
        var vectors = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            vectors[_i - 1] = arguments[_i];
        }
        for (var i = 1, il = vectors.length; i < il; ++i) {
            var v = vectors[i];
            input.x = Math.min(input.x, v.x);
            input.y = Math.min(input.y, v.y);
            input.z = Math.min(input.z, v.z);
        }
        return input;
    };
    /**
     * Returns inner product of first and second vec2
     * @param first
     * @param second
     */
    Vector.InnerProduct2 = function (first, second) {
        return first.x * second.x + first.y * second.y;
    };
    /**
     * Returns inner product of first and second vec3
     * @param first
     * @param second
     */
    Vector.InnerProduct3 = function (first, second) {
        return first.x * second.x + first.y * second.y + first.z * second.z;
    };
    /**
     * Returns cross product of first and second vec3
     * @param first
     * @param second
     */
    Vector.Cross3 = function (first, second) {
        var ax = first.x;
        var ay = first.y;
        var az = first.z;
        var bx = second.x;
        var by = second.y;
        var bz = second.z;
        return {
            x: ay * bz - az * by,
            y: az * bx - ax * bz,
            z: ax * by - ay * bx,
        };
    };
    /**
     * Returns distance between first and second vec2
     * @param first
     * @param second
     */
    Vector.Distance2 = function (first, second) {
        var x = first.x - second.x;
        var y = first.y - second.y;
        return Math.sqrt(x * x + y * y);
    };
    /**
     * Returns distance between first and second vec3
     * @param first
     * @param second
     */
    Vector.Distance3 = function (first, second) {
        var x = first.x - second.x;
        var y = first.y - second.y;
        var z = first.z - second.z;
        return Math.sqrt(x * x + y * y + z * z);
    };
    /**
     * Returns scaled vec2
     * @param scalar
     * @param vector
     */
    Vector.ScalarMultiply2 = function (scalar, vector) {
        return { x: scalar * vector.x, y: scalar * vector.y };
    };
    /**
     * Returns scaled vec3
     * @param scalar
     * @param vector
     */
    Vector.ScalarMultiply3 = function (scalar, vector) {
        return {
            x: scalar * vector.x,
            y: scalar * vector.y,
            z: scalar * vector.z,
        };
    };
    /**
     * Scales vec2 vector
     * @param scalar
     * @param vector
     */
    Vector.ScalarMultiplyInPlace2 = function (scalar, vector) {
        vector.x *= scalar;
        vector.y *= scalar;
        return vector;
    };
    /**
     * Scales vec3 vector
     * @param scalar
     * @param vector
     */
    Vector.ScalarMultiplyInPlace3 = function (scalar, vector) {
        vector.x *= scalar;
        vector.y *= scalar;
        vector.z *= scalar;
        return vector;
    };
    /**
     * Returns vec2 with offset
     * @param offset
     * @param vector
     */
    Vector.ScalarAdd2 = function (offset, vector) {
        return { x: offset + vector.x, y: offset + vector.y };
    };
    /**
     * Returns vec3 with offset
     * @param offset
     * @param vector
     */
    Vector.ScalarAdd3 = function (offset, vector) {
        return {
            x: offset + vector.x,
            y: offset + vector.y,
            z: offset + vector.z,
        };
    };
    /**
     * Adds offset on vec2 vector
     * @param offset
     * @param vector
     */
    Vector.ScalarAddInPlace2 = function (offset, vector) {
        vector.x += offset;
        vector.y += offset;
        return vector;
    };
    /**
     * Adds offset on vec3 vector
     * @param offset
     * @param vector
     */
    Vector.ScalarAddInPlace3 = function (offset, vector) {
        vector.x += offset;
        vector.y += offset;
        vector.z += offset;
        return vector;
    };
    /**
     * Returns a vec2 with negated components
     * @param vector
     */
    Vector.Reverse2 = function (vector) {
        return {
            x: -vector.x,
            y: -vector.y,
        };
    };
    /**
     * Returns a vec3 with negated components
     * @param vector
     */
    Vector.Reverse3 = function (vector) {
        return {
            x: -vector.x,
            y: -vector.y,
            z: -vector.z,
        };
    };
    /**
     * Negates components of vec2 vector
     * @param vector
     */
    Vector.ReverseInPlace2 = function (vector) {
        vector.x = -vector.x;
        vector.y = -vector.y;
        return vector;
    };
    /**
     * Negates components of vec3 vector
     * @param vector
     */
    Vector.ReverseInPlace3 = function (vector) {
        vector.x = -vector.x;
        vector.y = -vector.y;
        vector.z = -vector.z;
        return vector;
    };
    /**
     * Compare two vec2's on equality (with optional deviation)
     * @param first
     * @param second
     * @param deviation
     */
    Vector.IsEqual2 = function (first, second, deviation) {
        if (deviation === void 0) { deviation = 0; }
        var diff = Vector.Sub2(first, second);
        return Math.abs(diff.x) <= deviation && Math.abs(diff.y) <= deviation;
    };
    /**
     * Compare two vec3's on equality (with optional deviation)
     * @param first
     * @param second
     * @param deviation
     */
    Vector.IsEqual3 = function (first, second, deviation) {
        if (deviation === void 0) { deviation = 0; }
        var diff = Vector.Sub3(first, second);
        return (Math.abs(diff.x) <= deviation &&
            Math.abs(diff.y) <= deviation &&
            Math.abs(diff.z) <= deviation);
    };
    /**
     * calculate angle (in radians) between two direction vectors (vec2)
     * @param v
     * @param w
     */
    Vector.AngleBetweenVectors2 = function (v, w) {
        var magW = Math.sqrt(w.x * w.x + w.y * w.y);
        var magV = Math.sqrt(v.x * v.x + v.y * v.y);
        var dotVW = v.x * w.x + v.y * w.y;
        var theta = dotVW / (magW * magV);
        return Math.acos(MathUtils.clamp(theta, -1, 1));
    };
    /**
     * calculate angle (in radians) between two direction vectors (vec3)
     * @param v
     * @param w
     */
    Vector.AngleBetweenVectors3 = function (v, w) {
        var magW = Math.sqrt(w.x * w.x + w.y * w.y + w.z * w.z);
        var magV = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
        var dotVW = v.x * w.x + v.y * w.y + v.z * w.z;
        var theta = dotVW / (magW * magV);
        return Math.acos(MathUtils.clamp(theta, -1, 1));
    };
    Vector.ApplyMatrix4 = function (v, mat) {
        var oldX = v.x;
        var oldY = v.y;
        var oldZ = v.z;
        var e = mat.elements;
        var w = 1 / (e[3] * oldX + e[7] * oldY + e[11] * oldZ + e[15]);
        var x = (e[0] * oldX + e[4] * oldY + e[8] * oldZ + e[12]) * w;
        var y = (e[1] * oldX + e[5] * oldY + e[9] * oldZ + e[13]) * w;
        var z = (e[2] * oldX + e[6] * oldY + e[10] * oldZ + e[14]) * w;
        var result = Vector.CreateVec3(x, y, z);
        return result;
    };
    Vector.RotateVec3 = function (v, eulerRotation) {
        var quaternion = createQuaternionFromEuler(eulerRotation);
        makeRotationFromQuaternion(tmpMatrix4, quaternion);
        var result = Vector.ApplyMatrix4(v, tmpMatrix4);
        return result;
    };
    /** Convenience function to stringify vec2 */
    Vector.StringifyVec2 = function (v, precision) {
        if (precision === void 0) { precision = 3; }
        return ("{x: " +
            v.x.toFixed(precision) +
            ", y: " +
            v.y.toFixed(precision) +
            "}");
    };
    /** Convenience function to stringify vec3 */
    Vector.StringifyVec3 = function (v, precision) {
        if (precision === void 0) { precision = 3; }
        return ("{x: " +
            v.x.toFixed(precision) +
            ", y: " +
            v.y.toFixed(precision) +
            ", z: " +
            v.z.toFixed(precision) +
            "}");
    };
    /** Convenience function to check if vec2 has invalid entries (no Not-a-Number, on infinite values) */
    Vector.HasInvalidEntries2 = function (v) {
        var product = v.x * v.y;
        return isNaN(product) || !isFinite(product);
    };
    /** Convenience function to check if vec3 has invalid entries (no Not-a-Number, on infinite values) */
    Vector.HasInvalidEntries3 = function (v) {
        var product = v.x * v.y * v.z;
        return isNaN(product) || !isFinite(product);
    };
    return Vector;
}());
export { Vector };
