text: /**
   * CSS 3D engine
   *
   * @category    css3d
   * @package     css3d.quaternion
   * @author      Jan Fischer, bitWorking <info@bitworking.de>
   * @copyright   2014 Jan Fischer
   * @license     http://www.opensource.org/licenses/mit-license.html  MIT License
   */
  
  /**
   * 
   * @name css3d.quaternion
   * @class
   * @param {Number} x
   * @param {Number} y
   * @param {Number} z
   * @param {Number} w
   * @returns {css3d.quaternion}
   */
  css3d.quaternion = (function()
  {
  
      /**
       * 
       * @param {Number} x
       * @param {Number} y
       * @param {Number} z
       * @param {Number} w
       * @returns {css3d.quaternion}
       */
      var quaternion = function(x, y, z, w)
      {
          /**
           * x value
           * @type {Number}
           * @memberof! css3d.quaternion
           * @instance
           */
          this.x = x || 0;
          /**
           * y value
           * @type {Number}
           * @memberof! css3d.quaternion
           * @instance
           */
          this.y = y || 0;
          /**
           * z value
           * @type {Number}
           * @memberof! css3d.quaternion
           * @instance
           */
          this.z = z || 0;
          /**
           * w value
           * @type {Number}
           * @memberof! css3d.quaternion
           * @instance
           */
          this.w = w || 1;
  
          this.TOLERANCE = 0.00001;
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @returns {css3d.quaternion}
       */
      quaternion.prototype.normalize = function()
      {
          var mag2 = this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z;
          if (Math.abs(mag2) > this.TOLERANCE && Math.abs(mag2 - 1) > this.TOLERANCE) {
              var mag = Math.sqrt(mag2);
              this.x /= mag;
              this.y /= mag;
              this.z /= mag;
              this.w /= mag;
          }
          return this;
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @returns {css3d.quaternion}
       */
      quaternion.prototype.getConjugate = function()
      {
          return new css3d.quaternion(-this.x, -this.y, -this.z, this.w);
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @param {css3d.quaternion} rq
       * @returns {css3d.quaternion}
       */
      quaternion.prototype.multiply = function(rq)
      {
          return new css3d.quaternion(
              this.w * rq.x + this.x * rq.w + this.y * rq.z - this.z * rq.y,
              this.w * rq.y + this.y * rq.w + this.z * rq.x - this.x * rq.z,
              this.w * rq.z + this.z * rq.w + this.x * rq.y - this.y * rq.x,
              this.w * rq.w - this.x * rq.x - this.y * rq.y - this.z * rq.z
          );
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @param {css3d.vector3} v
       * @returns {css3d.vector3}
       */
      quaternion.prototype.multiplyVector = function(v)
      {
          var vn = new css3d.vector3(v.x, v.y, v.z);
      	vn.normalize();
  
          var vecQuat = new css3d.quaternion();
      	vecQuat.x = vn.x;
      	vecQuat.y = vn.y;
      	vecQuat.z = vn.z;
      	vecQuat.w = 0;
          
      	var resQuat = vecQuat.multiply(this.getConjugate());
      	resQuat = this.multiply(resQuat);
  
      	return new css3d.vector3(resQuat.x, resQuat.y, resQuat.z);
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @param {css3d.vector3} axis
       * @param {Number} angle
       * @returns {css3d.quaternion}
       */
      quaternion.prototype.fromAxisAngle = function(axis, angle)
      {
          angle *= 0.5;
          var sinAngle = Math.sin(angle);
  
          return new css3d.quaternion(
              axis.x * sinAngle,
              axis.y * sinAngle,
              axis.z * sinAngle,
              Math.cos(angle)
          );
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @param {Number} x
       * @param {Number} y
       * @param {Number} z
       * @returns {css3d.quaternion}
       */
      quaternion.prototype.fromXYZ = function(x, y, z)
      {
          x *= 0.5;
          y *= 0.5;
          z *= 0.5;
  
          var sinp = Math.sin(y);
          var siny = Math.sin(z);
          var sinr = Math.sin(x);
          var cosp = Math.cos(y);
          var cosy = Math.cos(z);
          var cosr = Math.cos(x);
  
          return new css3d.quaternion(
              sinr * cosp * cosy - cosr * sinp * siny,
              cosr * sinp * cosy + sinr * cosp * siny,
              cosr * cosp * siny - sinr * sinp * cosy,
              cosr * cosp * cosy + sinr * sinp * siny
          ).normalize();
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @param {Array} m
       * @returns {css3d.quaternion}
       */
      quaternion.prototype.fromMatrix4 = function(m)
      {
          var tr = m[0] + m[5] + m[10];
  
          var q = new css3d.quaternion();
  
          if (tr > 0) {
              var s = Math.sqrt(tr+1) * 2;
              q.w = 0.25 * s;
              q.x = (m[9] - m[6]) / s;
              q.y = (m[2] - m[8]) / s;
              q.z = (m[4] - m[1]) / s;
          }
          else if ((m[0] > m[5])&(m[0] > m[10])) {
              var s = Math.sqrt(1 + m[0] - m[5] - m[10]) * 2;
              q.w = (m[9] - m[6]) / s;
              q.x = 0.25 * s;
              q.y = (m[1] + m[4]) / s;
              q.z = (m[2] + m[8]) / s;
          }
          else if (m[5] > m[10]) {
              var s = Math.sqrt(1 + m[5] - m[0] - m[10]) * 2;
              q.w = (m[2] - m[8]) / s;
              q.x = (m[1] + m[4]) / s;
              q.y = 0.25 * s;
              q.z = (m[6] + m[9]) / s;
          }
          else {
              var s = Math.sqrt(1 + m[10] - m[0] - m[5]) * 2;
              q.w = (m[4] - m[1]) / s;
              q.x = (m[2] + m[8]) / s;
              q.y = (m[6] + m[9]) / s;
              q.z = 0.25 * s;
          }
          return q;
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @returns {Array}
       */
      quaternion.prototype.toMatrix4 = function()
      {
          var x2 = this.x * this.x;
          var y2 = this.y * this.y;
          var z2 = this.z * this.z;
          var xy = this.x * this.y;
          var xz = this.x * this.z;
          var yz = this.y * this.z;
          var wx = this.w * this.x;
          var wy = this.w * this.y;
          var wz = this.w * this.z;
  
          // TODO: transpose ?
          return [
              1 - 2 * (y2 + z2), 2 * (xy - wz)    , 2 * (xz + wy)    , 0,
              2 * (xy + wz)    , 1 - 2 * (x2 + z2), 2 * (yz - wx)    , 0,
              2 * (xz - wy)    , 2 * (yz + wx)    , 1 - 2 * (x2 + y2), 0,
              0                , 0                , 0                , 1
          ];
      };
  
      /**
       * http://nic-gamedev.blogspot.de/2011/11/quaternion-math-getting-local-axis.html
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @returns {css3d.vector3}
       */
      quaternion.prototype.right = function()
      {
          return new css3d.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)
          );
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @returns {css3d.vector3}
       */
      quaternion.prototype.up = function()
      {
          return new css3d.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)
          );
      };
  
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @returns {css3d.vector3}
       */
      quaternion.prototype.forward = function()
      {
          return new css3d.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)
          );
      };
  
      /**
       * if t=0 then qm=qa, if t=1 then qm=qb
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @param {css3d.quaternion} qa
       * @param {css3d.quaternion} qb
       * @param {Number} t 0-1
       * @returns {css3d.quaternion}
       */
      quaternion.prototype.slerp = function(qa, qb, t)
      {
          // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
  
      	// quaternion to return
      	var qm = new css3d.quaternion();
      	// Calculate angle between them.
      	var cosHalfTheta = qa.w * qb.w + qa.x * qb.x + qa.y * qb.y + qa.z * qb.z;
  
          if (cosHalfTheta < 0) {
              qb.w = -qb.w; qb.x = -qb.x; qb.y = -qb.y; qb.z = qb.z;
              cosHalfTheta = -cosHalfTheta;
          }
  
      	// if qa=qb or qa=-qb then theta = 0 and we can return qa
      	if (Math.abs(cosHalfTheta) >= 1) {
              qm.w = qa.w;qm.x = qa.x;qm.y = qa.y;qm.z = qa.z;            
              return qm;
      	}
  
      	// Calculate temporary values.
      	var halfTheta = Math.acos(cosHalfTheta);
      	var sinHalfTheta = Math.sqrt(1 - cosHalfTheta*cosHalfTheta);
  
      	// if theta = 180 degrees then result is not fully defined
      	// we could rotate around any axis normal to qa or qb
      	if (Math.abs(sinHalfTheta) < 0.001) {
              qm.w = (qa.w * 0.5 + qb.w * 0.5);
              qm.x = (qa.x * 0.5 + qb.x * 0.5);
              qm.y = (qa.y * 0.5 + qb.y * 0.5);
              qm.z = (qa.z * 0.5 + qb.z * 0.5);            
              return qm;
      	}
  
      	var ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta;
      	var ratioB = Math.sin(t * halfTheta) / sinHalfTheta;
  
      	//calculate Quaternion.
      	qm.w = (qa.w * ratioA + qb.w * ratioB);
      	qm.x = (qa.x * ratioA + qb.x * ratioB);
      	qm.y = (qa.y * ratioA + qb.y * ratioB);
      	qm.z = (qa.z * ratioA + qb.z * ratioB);        
      	return qm;
      };
      
      /**
       * 
       * @memberof! css3d.quaternion
       * @instance
       * @returns {Object} {'axis':{css3d.vector3}, 'angle':{Number}}
       */
      quaternion.prototype.toAxisAngle = function()
      {
          // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
          
          if (this.w > 1) this.normalize();
          var angle = 2 * Math.acos(this.w);
          var s = Math.sqrt(1-this.w*this.w);
          var axis = new css3d.vector3();
          if (s < 0.001) {            
              axis.x = this.x;
              axis.y = this.y;
              axis.z = this.z;
          } 
          else {
              axis.x = this.x / s;
              axis.y = this.y / s;
              axis.z = this.z / s;
          }
          return {
              'axis': axis,
              'angle': angle
          };
      };
  
  
      return quaternion;
  
  }());