/**
* @class Quaternion
* @memberof SQR
*
* @description Represents a quaternion with optionally setting the values directly.
*
* Just as a reminder, given an angle `a` and an axis `x,y,z`
* this is what the quaternion values are:
* @example
var q = new SQR.Quaternion();
var s = Math.sin(a / 2);
q.x = x * s;
q.y = y * s;
q.z = z * s;
q.w = Math.cos(a / 2);
*/
SQR.Quaternion = function(x, y, z, w) {
this.set(w, x, y, z);
}
/**
* Set value of the Quaternion directly.
*/
SQR.Quaternion.prototype.set = function(x, y, z, w) {
this.w = w || 1;
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
return this;
}
SQR.Quaternion.prototype.copyTo = function(p) {
p.x = this.x;
p.y = this.y;
p.z = this.z;
p.w = this.w;
return this;
}
/**
* Copy the values from another quaternion.
* @param q the quaternion to copy values from
*/
SQR.Quaternion.prototype.copyFrom = function(q) {
if(q instanceof Array || q instanceof Float32Array) {
this.x = q[0];
this.y = q[1];
this.z = q[2];
this.w = q[3];
} else {
this.x = q.x;
this.y = q.y;
this.z = q.z;
this.w = q.w;
}
return this;
}
/**
* Resets the quaternion values to identity.
*/
SQR.Quaternion.prototype.identity = function() {
this.set();
return this;
}
/**
* Multiplies rq (or this if no rq) by q
* @param q
* @param rq if not defined this is multiplied by q
*/
SQR.Quaternion.prototype.mul = function(q, rq) {
rq = rq || this;
var w = (rq.w * q.w - rq.x * q.x - rq.y * q.y - rq.z * q.z);
var x = (rq.w * q.x + rq.x * q.w + rq.y * q.z - rq.z * q.y);
var y = (rq.w * q.y - rq.x * q.z + rq.y * q.w + rq.z * q.x);
var z = (rq.w * q.z + rq.x * q.y - rq.y * q.x + rq.z * q.w);
rq.set(x, y, z, w);
rq.normalize();
return rq;
}
SQR.Quaternion.prototype.dot = function(q) {
return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
}
/**
* Sets the quaternion to point in the given direction.
* @param _dir the direction to look at
* @param _up the up vector
*/
SQR.Quaternion.prototype.lookAt = function(_dir, _up) {
var dir = SQR.Quaternion.__tv1;
var right = SQR.Quaternion.__tv2;
var up = SQR.Quaternion.__tv3;
_dir.copyTo(dir);
_up.copyTo(up);
dir.norm();
// If direction is back, the returned quaternion is flipped. Not sure why, but that fixes it.
if(dir.z == -1) {
dir.x = 0.0001;
dir.norm();
}
// Probably should do the orthonormalization but not sure how that works :)
// tangent.sub(up, forward.mul(SQR.V3.dot(forward, up))).norm();
right.cross(up, dir);
up.cross(dir, right);
this.w = Math.sqrt(1 + right.x + up.y + dir.z) * 0.5;
var rc = 4 * this.w;
this.x = (dir.y - up.z) / rc;
this.y = (right.z - dir.x) / rc;
this.z = (up.x - right.y) / rc;
this.normalize();
return this;
}
/**
* Creates a quaternion out of an angle axis representation.
* @param a angle in radians
* @param x x component of the axis
* @param y y component of the axis
* @param z z component of the axis
*/
SQR.Quaternion.prototype.fromAngleAxis = function(a, x, y, z) {
var s = Math.sin(a / 2);
this.x = x * s;
this.y = y * s;
this.z = z * s;
this.w = Math.cos(a / 2);
return this;
}
/**
* Returns the magniture of the quaternion.
*/
SQR.Quaternion.prototype.mag = function() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);
}
/**
* Normalizes the quaternion.
*/
SQR.Quaternion.prototype.normalize = function() {
var n = this.mag();
this.x /= n;
this.y /= n;
this.z /= n;
this.w /= n;
return this;
}
/**
* Negates the quaternion.
*/
SQR.Quaternion.prototype.neg = function() {
this.x *= -1;
this.y *= -1;
this.z *= -1;
this.w *= -1;
return this;
}
/**
* Inverses the quaternion.
*/
SQR.Quaternion.prototype.inverse = function() {
var d = this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
this.x = -this.x / d;
this.y = -this.y / d;
this.z = -this.z / d;
this.w = this.w / d;
}
/**
* That method doesn't do anything.
* Check {SQR.Matrix44.TQS()} to see how to turn a
* Quanternion into a matrix representation.
*
* @todo Implement (or not... not sure how much this is needed)
*/
SQR.Quaternion.prototype.toMatrix = function(m) {
throw "SQR.Quaternion.toMatrix() is not implemented. Check SQR.Matrix44.TQS()";
}
// This one is from three.js (used for reference )
// SQR.Quaternion.slerp2 = function(qa, qb, t, qr) {
// qr = qr || new SQR.Quaternion();
// if ( t === 0 ) return qr.copyFrom(qa);
// if ( t === 1 ) return qr.copyFrom(qb);
// var x = qa.x, y = qa.y, z = qa.z, w = qa.w;
// // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
// var cosHalfTheta = w * qb.w + x * qb.x + y * qb.y + z * qb.z;
// if (cosHalfTheta < 0) {
// qr.w = - qb.w;
// qr.x = - qb.x;
// qr.y = - qb.y;
// qr.z = - qb.z;
// cosHalfTheta = - cosHalfTheta;
// } else {
// qr.copyFrom( qb );
// }
// if (cosHalfTheta >= 1.0) {
// qr.w = w;
// qr.x = x;
// qr.y = y;
// qr.z = z;
// return qr;
// }
// var halfTheta = Math.acos( cosHalfTheta );
// var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );
// if (Math.abs( sinHalfTheta ) < 0.001) {
// qr.w = 0.5 * (w + qr.w);
// qr.x = 0.5 * (x + qr.x);
// qr.y = 0.5 * (y + qr.y);
// qr.z = 0.5 * (z + qr.z);
// return qr;
// }
// var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
// ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
// qr.w = (w * ratioA + qr.w * ratioB);
// qr.x = (x * ratioA + qr.x * ratioB);
// qr.y = (y * ratioA + qr.y * ratioB);
// qr.z = (z * ratioA + qr.z * ratioB);
// return qr;
// }
/**
* Returns a spherical linear interpolation between two quaternions.
* @param qa first quaternion
* @param qb second quaternion
* @param t interpolation value [0-1]
* @param qr the quaterion to store the results in and return. If omitted results are returned in a new quaternion object.
*/
SQR.Quaternion.slerp = function(qa, qb, t, qr) {
qr = qr || new SQR.Quaternion();
if (t === 0) return qr.copyFrom(qa);
if (t === 1) return qr.copyFrom(qb);
// Try taking the dot product of your two quaternions (i.e., the 4-D dot product),
// and if the dot product is negative, replace your quaterions q1 and q2 with -q1 and q2 before performing Slerp.
// http://stackoverflow.com/questions/2886606/flipping-issue-when-interpolating-rotations-using-quaternions
// (This is not working for me)
var cha = SQR.Quaternion.dot(qa, qb);
if(cha < 0) {
qa.neg();
cha = SQR.Quaternion.dot(qa, qb);
}
var ha = Math.acos(cha);
var sha = Math.sqrt(1 - cha * cha);
var ra = Math.sin((1 - t) * ha) / sha;
var rb = Math.sin(t * ha) / sha;
if (Math.abs(cha) >= 1) {
// If angle is 0 (i.e cos(a) = 1) just
// return the first quaternion
ra = 1;
rb = 0;
} else if (Math.abs(sha) < 0.001) {
// If angle is 180 deg (i.e. sin(a) = 0) there is
// an infinite amount of possible rotations between those 2
ra = 0.5;
rb = 0.5;
}
qr.w = (qa.w * ra + qb.w * rb);
qr.x = (qa.x * ra + qb.x * rb);
qr.y = (qa.y * ra + qb.y * rb);
qr.z = (qa.z * ra + qb.z * rb);
return qr;
}
SQR.Quaternion.dot = function(qa, qb) {
return qa.x * qb.x + qa.y * qb.y + qa.z * qb.z + qa.w * qb.w;
}
SQR.Quaternion.prototype.slerp = function(qa, qb, t) {
SQR.Quaternion.slerp(qa, qb, t, this);
return this;
}
SQR.Quaternion.__tv1 = new SQR.Quaternion();
SQR.Quaternion.__tv2 = new SQR.Quaternion();
SQR.Quaternion.__tv3 = new SQR.Quaternion();