Source: math/Quaternion.js

/**
 * jslint browser: true
 */

/**
 * Creates new quaternion
 * @param {Number} x
 * @param {Number} y
 * @param {Number} z
 * @param {Number} w
 * @class
 * @constructor
 */
Ayce.Quaternion = function (x, y, z, w) {
    this.x = x || 0;
    this.y = y || 0;
    this.z = z || 0;
    this.w = w || 1;
};

Ayce.Quaternion.prototype = {

    /**
     * Multiplies two quaternions
     * @param {Ayce.Quaternion} qa
     * @param {Ayce.Quaternion} qb
     */
    multiply: function(qa, qb){
        var w1 = qa.w;
        var x1 = qa.x;
        var y1 = qa.y;
        var z1 = qa.z;
        var w2 = qb.w;
        var x2 = qb.x;
        var y2 = qb.y;
        var z2 = qb.z;
        this.w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;
        this.x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;
        this.y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;
        this.z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;
        return this;
    },

    /**
     * Sets quaternion to given values
     * @param {Number} x
     * @param {Number} y
     * @param {Number} z
     * @param {Number} w
     */
    set: function(x, y, z, w){
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    },

    /**
     * Sets quaternion to rotation around axis
     * @param {Ayce.Vector3} axis
     * @param {Number} angle
     */
    fromAxisAngle: function (axis, angle) {
        axis.normalize();
        var sin_a = Math.sin(angle / 2);
        var cos_a = Math.cos(angle / 2);
        this.x = axis.x * sin_a;
        this.y = axis.y * sin_a;
        this.z = axis.z * sin_a;
        this.w = cos_a;
        this.normalize();
        return this;
    },

    /**
     * Sets Quaternion values based on rotation around euler angles (radians)
     * @param {Number} ax
     * @param {Number} ay
     * @param {Number} az
     */
    fromEulerAngles: function(ax, ay, az) {
        var halfX = ax * 0.5;
        var halfY = ay * 0.5;
        var halfZ = az * 0.5;
        var cosX = Math.cos(halfX);
        var sinX = Math.sin(halfX);
        var cosY = Math.cos(halfY);
        var sinY = Math.sin(halfY);
        var cosZ = Math.cos(halfZ);
        var sinZ = Math.sin(halfZ);
        this.w = cosX * cosY * cosZ + sinX * sinY * sinZ;
        this.x = sinX * cosY * cosZ - cosX * sinY * sinZ;
        this.y = cosX * sinY * cosZ + sinX * cosY * sinZ;
        this.z = cosX * cosY * sinZ - sinX * sinY * cosZ;
        return this;
    },

    /**
     * Normalizes Quaternion
     * @return {Ayce.Quaternion} quaternion
     */
    normalize: function () {
        var x = this.x;
        var y = this.y;
        var z = this.z;
        var w = this.w;

        var mag = 1.0 / Math.sqrt(x * x + y * y + z * z + w * w);
        this.x *= mag;
        this.y *= mag;
        this.z *= mag;
        this.w *= mag;
        
        return this;
    },

    /**
     * Sets quaternion to (0, 0, 0, 1)
     */
    reset: function () {
        this.x = 0;
        this.y = 0;
        this.z = 0;
        this.w = 1;
    },

    /**
     * TODO: Description
     * @param {Ayce.Vector3} vector
     * @return vector
     */
    rotatePoint: function(vector) {
        var x = this.x;
        var y = this.y;
        var z = this.z;
        var w = this.w;

        var x2 = vector.x;
        var y2 = vector.y;
        var z2 = vector.z;

        var w1 = -x * x2 - y * y2 - z * z2;
        var x1 = w * x2 + y * z2 - z * y2;
        var y1 = w * y2 - x * z2 + z * x2;
        var z1 = w * z2 + x * y2 - y * x2;

        vector.x = -w1 * x + x1 * w - y1 * z + z1 * y;
        vector.y = -w1 * y + x1 * z + y1 * w - z1 * x;
        vector.z = -w1 * z - x1 * y + y1 * x + z1 * w;
        return vector;
    },

    /**
     * TODO: Description
     * @param {Ayce.Vector3} vector
     * @param {Ayce.Vector3} target
     * @return {Ayce.Vector3} v
     */
    getRotatedPoint: function(vector, target) {
        var x = this.x;
        var y = this.y;
        var z = this.z;
        var w = this.w;

        var x1;
        var y1;
        var z1;
        var w1;

        var x2 = vector.x;
        var y2 = vector.y;
        var z2 = vector.z;

        w1 = -x * x2 - y * y2 - z * z2;
        x1 = w * x2 + y * z2 - z * y2;
        y1 = w * y2 - x * z2 + z * x2;
        z1 = w * z2 + x * y2 - y * x2;
        
        var v = target ? target : new Ayce.Vector3();
        v.x = -w1 * x + x1 * w - y1 * z + z1 * y;
        v.y = -w1 * y + x1 * z + y1 * w - z1 * x;
        v.z = -w1 * z - x1 * y + y1 * x + z1 * w;
        return v;
    },

    /**
     * TODO: Description
     * @param {Ayce.Matrix4} matrix
     */
    toRotationMatrix: function (matrix) {
        var x = this.x;
        var y = this.y;
        var z = this.z;
        var w = this.w;

        var xx = x * x;
        var xy = x * y;
        var xz = x * z;
        var xw = x * w;
        var yy = y * y;
        var yz = y * z;
        var yw = y * w;
        var zz = z * z;
        var zw = z * w;

        matrix.data[0] = 1 - 2 * (yy + zz);
        matrix.data[1] = 2 * (xy - zw);
        matrix.data[2] = 2 * (xz + yw);
        matrix.data[4] = 2 * (xy + zw);
        matrix.data[5] = 1 - 2 * (xx + zz);
        matrix.data[6] = 2 * (yz - xw);
        matrix.data[8] = 2 * (xz - yw);
        matrix.data[9] = 2 * (yz + xw);
        matrix.data[10] = 1 - 2 * (xx + yy);
        matrix.data[3] = matrix.data[7] = matrix.data[11] = matrix.data[12] = matrix.data[13] = matrix.data[14] = 0;
        matrix.data[15] = 1;
    },

    /**
     * Returns conjugated quaternion
     * @return {Ayce.Quaternion} quaternion
     */
    getConjugate: function(target){
        this.normalize();
        if(target){
            target.x = -this.x;
            target.y = -this.y;
            target.z = -this.z;
            target.w = this.w;
            return target;
        }
        else{
            return new Ayce.Quaternion(-this.x, -this.y, -this.z, this.w);
        }
    },

    /**
     * Returns copy of quaternion
     * @return {Ayce.Quaternion} quaternion
     */
    copy: function(){
        return new Ayce.Quaternion(this.x, this.y, this.z, this.w);
    },

    /**
     * Copies values from one quaternion to another
     * @param {} from
     * @param {} to
     */
    copyToQuaternion: function(from, to){
        to.x = from.x;
        to.y = from.y;
        to.z = from.z;
        to.w = from.w;
    },

    /**
     * TODO: Description
     * @param {} ga
     * @param {} gb
     * @param {} t
     */
    slerp: function(qa, qb, t){
        var w1 = qa.w;
        var x1 = qa.x;
        var y1 = qa.y;
        var z1 = qa.z;
        var w2 = qb.w;
        var x2 = qb.x;
        var y2 = qb.y;
        var z2 = qb.z;
        var dot = w1 * w2 + x1 * x2 + y1 * y2 + z1 * z2;
        
        if (dot < 0) {
            dot = -dot;
            w2 = -w2;
            x2 = -x2;
            y2 = -y2;
            z2 = -z2;
        }
        if (dot < 0.95) {
            var angle = Math.acos(dot);
            var s = 1 / Math.sin(angle);
            var s1 = Math.sin(angle * (1 - t)) * s;
            var s2 = Math.sin(angle * t) * s;
            this.w = w1 * s1 + w2 * s2;
            this.x = x1 * s1 + x2 * s2;
            this.y = y1 * s1 + y2 * s2;
            this.z = z1 * s1 + z2 * s2;
        }
        else {
            this.w = w1 + t * (w2 - w1);
            this.x = x1 + t * (x2 - x1);
            this.y = y1 + t * (y2 - y1);
            this.z = z1 + t * (z2 - z1);
            var len = 1.0 / Math.sqrt(this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z);
            this.w *= len;
            this.x *= len;
            this.y *= len;
            this.z *= len;
        }
    },

    /**
     * Returns forward vector
     * @returns {Ayce.Vector3} vector
     */
    getForwardVector: function(){
        return new Ayce.Vector3(
            2 * (this.x * this.z + this.w * this.y),
            2 * (this.y * this.x - this.w * this.x),
            1 - 2 * (this.x * this.x + this.y * this.y));
    },

    /**
     * Returns up vector
     * @returns {Ayce.Vector3} vector
     */
    getUpVector: function(){
        return new Ayce.Vector3(
            2 * (this.x * this.y - this.w * this.z),
            1 - 2 * (this.x * this.x + this.z * this.z),
            2 * (this.y * this.z + this.w * this.x));
    },

    /**
     * Returns right vector
     * @returns {Ayce.Vector3} vector
     */
    getRightVector: function(){
        return new Ayce.Vector3(
            1 - 2 * (this.y * this.y + this.z * this.z),
            2 * (this.x * this.y + this.w * this.z),
            2 * (this.x * this.z - this.w * this.y));
    }
};