"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var Euler_1 = require("./Euler");
var Vector3_1 = require("./Vector3");
var Vector4_1 = require("./Vector4");
var MathUtils = tslib_1.__importStar(require("./MathUtils"));
var Quaternion = /** @class */ (function (_super) {
    tslib_1.__extends(Quaternion, _super);
    function Quaternion(_x, _y, _z, _w) {
        return _super.call(this, _x, _y, _z, _w) || this;
    }
    //#region Create
    /**
     * Factory acting as constructor for vec4
     * @param x
     * @param y
     * @param z
     * @param w
     */
    Quaternion.create = function (x, y, z, w) {
        if (x === void 0) { x = 0; }
        if (y === void 0) { y = 0; }
        if (z === void 0) { z = 0; }
        if (w === void 0) { w = 1; }
        return { x: x, y: y, z: z, w: w };
    };
    /**
     * Conversion function form Euler to Quaternion, taken from ThreeJS
     * @param euler euler (only order 'xyz' is implemented)
     */
    Quaternion.createFromEuler = function (euler) {
        // http://www.mathworks.com/matlabcentral/fileexchange/
        // 	20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
        //	content/SpinCalc.m
        // tslint:disable-next-line: one-variable-per-declaration
        var x, y, z, w;
        var order = euler.order;
        var c1 = Math.cos(euler.x / 2);
        var c2 = Math.cos(euler.y / 2);
        var c3 = Math.cos(euler.z / 2);
        var s1 = Math.sin(euler.x / 2);
        var s2 = Math.sin(euler.y / 2);
        var s3 = Math.sin(euler.z / 2);
        if (order === 'xyz') {
            x = s1 * c2 * c3 + c1 * s2 * s3;
            y = c1 * s2 * c3 - s1 * c2 * s3;
            z = c1 * c2 * s3 + s1 * s2 * c3;
            w = c1 * c2 * c3 - s1 * s2 * s3;
        }
        else if (order === 'yxz') {
            x = s1 * c2 * c3 + c1 * s2 * s3;
            y = c1 * s2 * c3 - s1 * c2 * s3;
            z = c1 * c2 * s3 - s1 * s2 * c3;
            w = c1 * c2 * c3 + s1 * s2 * s3;
        }
        else if (order === 'zxy') {
            x = s1 * c2 * c3 - c1 * s2 * s3;
            y = c1 * s2 * c3 + s1 * c2 * s3;
            z = c1 * c2 * s3 + s1 * s2 * c3;
            w = c1 * c2 * c3 - s1 * s2 * s3;
        }
        else if (order === 'zyx') {
            x = s1 * c2 * c3 - c1 * s2 * s3;
            y = c1 * s2 * c3 + s1 * c2 * s3;
            z = c1 * c2 * s3 - s1 * s2 * c3;
            w = c1 * c2 * c3 + s1 * s2 * s3;
        }
        else if (order === 'yzx') {
            x = s1 * c2 * c3 + c1 * s2 * s3;
            y = c1 * s2 * c3 + s1 * c2 * s3;
            z = c1 * c2 * s3 - s1 * s2 * c3;
            w = c1 * c2 * c3 - s1 * s2 * s3;
        }
        else if (order === 'xzy') {
            x = s1 * c2 * c3 - c1 * s2 * s3;
            y = c1 * s2 * c3 - s1 * c2 * s3;
            z = c1 * c2 * s3 + s1 * s2 * c3;
            w = c1 * c2 * c3 + s1 * s2 * s3;
        }
        else {
            throw new Error("expected order, got none");
        }
        var result = Quaternion.create(x, y, z, w);
        return result;
    };
    /**
     * Sets this quaternion from rotation component of m.
     * assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
     * @param m Matrix4
     */
    //prettier-ignore
    Quaternion.createFromRotationMatrix = function (m) {
        // see: https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2015/01/matrix-to-quat.pdf
        // note: the matrix is transposed there
        var te = m.elements;
        // tslint:disable: one-variable-per-declaration
        var m00 = te[0][0], m01 = te[0][1], m02 = te[0][2];
        var m10 = te[1][0], m11 = te[1][1], m12 = te[1][2];
        var m20 = te[2][0], m21 = te[2][1], m22 = te[2][2];
        var t;
        var q;
        if (m22 < 0) { // is |(x,y)| bigger than |(z,w)|?
            if (m00 > m11) { // is |x| bigger than |y|?
                // use x-form
                t = 1 + m00 - m11 - m22;
                q = new Quaternion(t, m10 + m01, m02 + m20, m21 - m12);
            }
            else {
                // use y-form
                t = 1 - m00 + m11 - m22;
                q = new Quaternion(m10 + m01, t, m21 + m12, m02 - m20);
            }
        }
        else {
            if (m00 < -m11) { // is |z| bigger than |w|?
                // use z-form
                t = 1 - m00 - m11 + m22;
                q = new Quaternion(m02 + m20, m21 + m12, t, m10 - m01);
            }
            else {
                // use w-form
                t = 1 + m00 + m11 + m22;
                q = new Quaternion(m21 - m12, m02 - m20, m10 - m01, t);
            }
        }
        return Quaternion.scalarMultiply(0.5 / Math.sqrt(t), q);
    };
    /**
     * Build a unit quaternion representing the rotation
     * from u to v. The input vectors must be normalised.
     * @param u Vector3
     * @param v Vector3
     * @returns normalized Quat. if it can't be normalized, it returns {0, 0, 0, 1}
     */
    Quaternion.createFromUnitVectors = function (u, v) {
        // same as createFromVectors() but with unit vectors
        // function is from three.js
        var quat = Quaternion.create();
        var EPS = 0.000001;
        var r = Vector3_1.Vector3.innerProduct(u, v) + 1;
        if (r < EPS) {
            r = 0;
            if (Math.abs(u.x) > Math.abs(u.z)) {
                quat.x = -u.y;
                quat.y = u.x;
                quat.z = 0;
                quat.w = r;
            }
            else {
                quat.x = 0;
                quat.y = -u.z;
                quat.z = u.y;
                quat.w = r;
            }
        }
        else {
            var cross = Vector3_1.Vector3.crossProduct(u, v);
            quat.x = cross.x;
            quat.y = cross.y;
            quat.z = cross.z;
            quat.w = r;
        }
        var result = Quaternion.normalize(quat);
        if (result !== undefined) {
            return result;
        }
        return Quaternion.create();
    };
    /**
     * Build a unit quaternion representing the rotation
     * from u to v. The input vectors don't need to be normalised.
     * @param u Vector3
     * @param v Vector3
     * @returns normalized Quat. if it can't be normalized, it returns {0, 0, 0, 1}
     */
    Quaternion.createFromVectors = function (u, v) {
        // see:
        // http://lolengine.net/blog/2013/09/18/beautiful-maths-quaternion-from-vectors
        // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
        var normUnormV = Math.sqrt(Vector3_1.Vector3.innerProduct(u, u) * Vector3_1.Vector3.innerProduct(v, v));
        var realPart = normUnormV + Vector3_1.Vector3.innerProduct(u, v);
        var w;
        var EPS = 0.000001;
        if (realPart < EPS * normUnormV) {
            /* If u and v are exactly opposite, rotate 180 degrees
             * around an arbitrary orthogonal axis. Axis normalisation
             * can happen later, when we normalise the quaternion.
             */
            realPart = 0.0;
            w = (Math.abs(u.x) > Math.abs(u.z))
                ? Vector3_1.Vector3.create(-u.y, u.x, 0.0)
                : Vector3_1.Vector3.create(0.0, -u.z, u.y);
        }
        else {
            /* Otherwise, build quaternion the standard way. */
            w = Vector3_1.Vector3.crossProduct(u, v);
        }
        var result = Vector4_1.Vector4.normalize(Vector4_1.Vector4.create(w.x, w.y, w.z, realPart));
        if (result !== undefined) {
            return result;
        }
        return Quaternion.create();
    };
    /**
     * Creates a Quaternion for a rotation around given axis
     * @param axis Vector3
     * @param angle angle in radians
     */
    Quaternion.CreateFromAxisAngle = function (axis, angle) {
        var halfAngle = angle * 0.5;
        var s = Math.sin(halfAngle);
        var q = Quaternion.create();
        q.x = axis.x * s;
        q.y = axis.y * s;
        q.z = axis.z * s;
        q.w = Math.cos(halfAngle);
        return q;
    };
    /**
     * Evaluates a rotation needed to be applied to an object positioned at sourcePoint to face destPoint
     * @param sourcePoint Vector3 - Coordinates of source point
     * @param destPoint Vector3 - Coordinates of destination point
     */
    /*
    public static lookAt(sourcePoint: Vector3, destPoint: Vector3, forwardVec?: Vector3): Quaternion {
        // from: https://stackoverflow.com/questions/12435671/quaternion-lookat-function
        if (!forwardVec) {
            forwardVec = {x: 0, y: 0, z: 1};
        }
        let newForwardVec = Vector3.sub(destPoint, sourcePoint);
        Vector3.normalizeInPlace(newForwardVec);

        let dot = Vector3.innerProduct(forwardVec, newForwardVec);

        if (Math.abs(dot - (-1.0)) < 0.000001) {
            // vector a and b point exactly in the opposite direction,
            // so it is a 180 degrees turn around the up-axis
            return new Quaternion(0, 1, 0, Math.PI);
        }
        if (Math.abs(dot - (1.0)) < 0.000001) {
            // vector a and b point exactly in the same direction
            // so we return the identity quaternion
            return Quaternion.create();
        }

        let rotAngle = Math.acos(dot);
        let rotAxis = Vector3.crossProduct(forwardVec, newForwardVec);
        Vector3.normalizeInPlace(rotAxis);
        return Quaternion.CreateFromAxisAngle(rotAxis, rotAngle);
    }
    */
    /*
    public static lookAt(target: Vector3, current: Vector3, eye: Vector3, up: Vector3): Quaternion {
        // from: https://www.euclideanspace.com/maths/algebra/vectors/lookat/index.htm

        // turn vectors into unit vectors
        let n1 = Vector3.normalize(Vector3.sub(current, eye));
        let n2 = Vector3.normalize(Vector3.sub(target, eye));
        if ( n1 === undefined || n2 === undefined ) {
            return new Quaternion( 0, 1, 0, 0 );
        }
        let d: number = Vector3.innerProduct(n1, n2);
        // if no noticable rotation is available return zero rotation
        // this way we avoid Cross product artifacts
        if ( d > 0.9998 ) { return new Quaternion( 0, 1, 0, 0 ); }
        // in this case there are 2 lines on the same axis
        if ( d < -0.9998 ) {
            n1 = Vector3.rotate( n1, {x: 0.5, y: 0, z: 0, order: "xyz"} );
            // there are an infinite number of normals
            // in this case. Anyone of these normals will be
            // a valid rotation (180 degrees). so rotate the curr axis by 0.5 radians
            // this way we get one of these normals
        }
        let axis: Vector3 = n1;
        axis = Vector3.crossProduct(axis, n2);
        let pointToTarget: Quaternion = new Quaternion(axis.x, axis.y, axis.z, 1.0 + d);
        Quaternion.normalizeInPlace(pointToTarget);
        // now twist around the target vector, so that the 'up' vector points along the z axis
        let projectionMatrix: Matrix3 = Matrix3.create();
        let a: number = pointToTarget.x;
        let b: number = pointToTarget.y;
        let c: number = pointToTarget.z;
        projectionMatrix.elements[0][0] =  b * b + c * c;
        projectionMatrix.elements[0][1] = -a * b;
        projectionMatrix.elements[0][2] = -a * c;
        projectionMatrix.elements[1][0] = -b * a;
        projectionMatrix.elements[1][1] =  a * a + c * c;
        projectionMatrix.elements[1][2] = -b * c;
        projectionMatrix.elements[2][0] = -c * a;
        projectionMatrix.elements[2][1] = -c * b;
        projectionMatrix.elements[2][2] =  a * a + b * b;
        let upProjected: Vector3 = Matrix3.multiplyWithVector3(up, projectionMatrix);
        let yaxisProjected: Vector3 = Matrix3.multiplyWithVector3({x: 0, y: 1, z: 0}, projectionMatrix);
        d = Vector3.innerProduct(upProjected, yaxisProjected);
        // so the axis of twist is n2 and the angle is arcos(d)
        //convert this to quat as follows
        let s = Math.sqrt(1.0 - d * d);
        Vector3.scalarMultiplyInPlace(s, n2);
        let twist: Quaternion = new Quaternion(n2.x, n2.y, n2.z, d);
        return Quaternion.multiply(pointToTarget, twist);
    }
    */
    //#endregion Create
    //#region Angle
    /**
     * Returns the angle between two Quaternions
     * @param q1 Quaternion
     * @param q2 Quaternion
     */
    Quaternion.angleBetween = function (q1, q2) {
        return 2 * Math.acos(Math.abs(MathUtils.clamp(Vector4_1.Vector4.innerProduct(q1, q2), -1, 1)));
    };
    //#endregion Angle
    //#region Algebra (Add + Multiplication)
    /**
     * Adds two Quaternions and returns a new Quaternion.
     * @param q1 Quaternion
     * @param q2 Quaternion
     */
    Quaternion.add = function (q1, q2) {
        var result = Quaternion.create();
        result.x = q1.x + q2.x;
        result.y = q1.y + q2.y;
        result.z = q1.z + q2.z;
        result.w = q1.w + q2.w;
        return result;
    };
    /**
     * Multiplies two Quaternions and returns a new Quaternion.
     * @param q1 Quaternion
     * @param q2 Quaternion
     */
    Quaternion.multiply = function (q1, q2) {
        var result = Quaternion.create();
        result.x = q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x;
        result.y = -q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y;
        result.z = q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z;
        result.w = -q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w;
        return result;
    };
    //#endregion Algebra
    //#region Utilities
    Quaternion.stringify = function (q) {
        var e = Euler_1.Euler.createFromQuaternion(q);
        return "\n" +
            ("Quaternion: (w: " + q.w + ", x: " + q.x + ", y: " + q.y + ", z: " + q.z + ")\n") +
            ("Euler (Deg): (x: " + e.x * MathUtils.Rad2Deg + ", y: " + e.y * MathUtils.Rad2Deg + ", z: " + e.z * MathUtils.Rad2Deg + ")");
    };
    return Quaternion;
}(Vector4_1.Vector4));
exports.Quaternion = Quaternion;
