import quat from '../glmatrix/quat';
import mat3 from '../glmatrix/mat3';
/**
* @constructor
* @alias clay.Quaternion
* @param {number} x
* @param {number} y
* @param {number} z
* @param {number} w
*/
var Quaternion = function (x, y, z, w) {
x = x || 0;
y = y || 0;
z = z || 0;
w = w === undefined ? 1 : w;
/**
* Storage of Quaternion, read and write of x, y, z, w will change the values in array
* All methods also operate on the array instead of x, y, z, w components
* @name array
* @type {Float32Array}
* @memberOf clay.Quaternion#
*/
this.array = quat.fromValues(x, y, z, w);
/**
* Dirty flag is used by the Node to determine
* if the matrix is updated to latest
* @name _dirty
* @type {boolean}
* @memberOf clay.Quaternion#
*/
this._dirty = true;
};
Quaternion.prototype = {
constructor: Quaternion,
/**
* Add b to self
* @param {clay.Quaternion} b
* @return {clay.Quaternion}
*/
add: function (b) {
quat.add(this.array, this.array, b.array);
this._dirty = true;
return this;
},
/**
* Calculate the w component from x, y, z component
* @return {clay.Quaternion}
*/
calculateW: function () {
quat.calculateW(this.array, this.array);
this._dirty = true;
return this;
},
/**
* Set x, y and z components
* @param {number} x
* @param {number} y
* @param {number} z
* @param {number} w
* @return {clay.Quaternion}
*/
set: function (x, y, z, w) {
this.array[0] = x;
this.array[1] = y;
this.array[2] = z;
this.array[3] = w;
this._dirty = true;
return this;
},
/**
* Set x, y, z and w components from array
* @param {Float32Array|number[]} arr
* @return {clay.Quaternion}
*/
setArray: function (arr) {
this.array[0] = arr[0];
this.array[1] = arr[1];
this.array[2] = arr[2];
this.array[3] = arr[3];
this._dirty = true;
return this;
},
/**
* Clone a new Quaternion
* @return {clay.Quaternion}
*/
clone: function () {
return new Quaternion(this.x, this.y, this.z, this.w);
},
/**
* Calculates the conjugate of self If the quaternion is normalized,
* this function is faster than invert and produces the same result.
*
* @return {clay.Quaternion}
*/
conjugate: function () {
quat.conjugate(this.array, this.array);
this._dirty = true;
return this;
},
/**
* Copy from b
* @param {clay.Quaternion} b
* @return {clay.Quaternion}
*/
copy: function (b) {
quat.copy(this.array, b.array);
this._dirty = true;
return this;
},
/**
* Dot product of self and b
* @param {clay.Quaternion} b
* @return {number}
*/
dot: function (b) {
return quat.dot(this.array, b.array);
},
/**
* Set from the given 3x3 rotation matrix
* @param {clay.Matrix3} m
* @return {clay.Quaternion}
*/
fromMat3: function (m) {
quat.fromMat3(this.array, m.array);
this._dirty = true;
return this;
},
/**
* Set from the given 4x4 rotation matrix
* The 4th column and 4th row will be droped
* @param {clay.Matrix4} m
* @return {clay.Quaternion}
*/
fromMat4: (function () {
var m3 = mat3.create();
return function (m) {
mat3.fromMat4(m3, m.array);
// TODO Not like mat4, mat3 in glmatrix seems to be row-based
mat3.transpose(m3, m3);
quat.fromMat3(this.array, m3);
this._dirty = true;
return this;
};
})(),
/**
* Set to identity quaternion
* @return {clay.Quaternion}
*/
identity: function () {
quat.identity(this.array);
this._dirty = true;
return this;
},
/**
* Invert self
* @return {clay.Quaternion}
*/
invert: function () {
quat.invert(this.array, this.array);
this._dirty = true;
return this;
},
/**
* Alias of length
* @return {number}
*/
len: function () {
return quat.len(this.array);
},
/**
* Calculate the length
* @return {number}
*/
length: function () {
return quat.length(this.array);
},
/**
* Linear interpolation between a and b
* @param {clay.Quaternion} a
* @param {clay.Quaternion} b
* @param {number} t
* @return {clay.Quaternion}
*/
lerp: function (a, b, t) {
quat.lerp(this.array, a.array, b.array, t);
this._dirty = true;
return this;
},
/**
* Alias for multiply
* @param {clay.Quaternion} b
* @return {clay.Quaternion}
*/
mul: function (b) {
quat.mul(this.array, this.array, b.array);
this._dirty = true;
return this;
},
/**
* Alias for multiplyLeft
* @param {clay.Quaternion} a
* @return {clay.Quaternion}
*/
mulLeft: function (a) {
quat.multiply(this.array, a.array, this.array);
this._dirty = true;
return this;
},
/**
* Mutiply self and b
* @param {clay.Quaternion} b
* @return {clay.Quaternion}
*/
multiply: function (b) {
quat.multiply(this.array, this.array, b.array);
this._dirty = true;
return this;
},
/**
* Mutiply a and self
* Quaternion mutiply is not commutative, so the result of mutiplyLeft is different with multiply.
* @param {clay.Quaternion} a
* @return {clay.Quaternion}
*/
multiplyLeft: function (a) {
quat.multiply(this.array, a.array, this.array);
this._dirty = true;
return this;
},
/**
* Normalize self
* @return {clay.Quaternion}
*/
normalize: function () {
quat.normalize(this.array, this.array);
this._dirty = true;
return this;
},
/**
* Rotate self by a given radian about X axis
* @param {number} rad
* @return {clay.Quaternion}
*/
rotateX: function (rad) {
quat.rotateX(this.array, this.array, rad);
this._dirty = true;
return this;
},
/**
* Rotate self by a given radian about Y axis
* @param {number} rad
* @return {clay.Quaternion}
*/
rotateY: function (rad) {
quat.rotateY(this.array, this.array, rad);
this._dirty = true;
return this;
},
/**
* Rotate self by a given radian about Z axis
* @param {number} rad
* @return {clay.Quaternion}
*/
rotateZ: function (rad) {
quat.rotateZ(this.array, this.array, rad);
this._dirty = true;
return this;
},
/**
* Sets self to represent the shortest rotation from Vector3 a to Vector3 b.
* a and b needs to be normalized
* @param {clay.Vector3} a
* @param {clay.Vector3} b
* @return {clay.Quaternion}
*/
rotationTo: function (a, b) {
quat.rotationTo(this.array, a.array, b.array);
this._dirty = true;
return this;
},
/**
* Sets self with values corresponding to the given axes
* @param {clay.Vector3} view
* @param {clay.Vector3} right
* @param {clay.Vector3} up
* @return {clay.Quaternion}
*/
setAxes: function (view, right, up) {
quat.setAxes(this.array, view.array, right.array, up.array);
this._dirty = true;
return this;
},
/**
* Sets self with a rotation axis and rotation angle
* @param {clay.Vector3} axis
* @param {number} rad
* @return {clay.Quaternion}
*/
setAxisAngle: function (axis, rad) {
quat.setAxisAngle(this.array, axis.array, rad);
this._dirty = true;
return this;
},
/**
* Perform spherical linear interpolation between a and b
* @param {clay.Quaternion} a
* @param {clay.Quaternion} b
* @param {number} t
* @return {clay.Quaternion}
*/
slerp: function (a, b, t) {
quat.slerp(this.array, a.array, b.array, t);
this._dirty = true;
return this;
},
/**
* Alias for squaredLength
* @return {number}
*/
sqrLen: function () {
return quat.sqrLen(this.array);
},
/**
* Squared length of self
* @return {number}
*/
squaredLength: function () {
return quat.squaredLength(this.array);
},
/**
* Set from euler
* @param {clay.Vector3} v
* @param {String} order
*/
fromEuler: function (v, order) {
return Quaternion.fromEuler(this, v, order);
},
toString: function () {
return '[' + Array.prototype.join.call(this.array, ',') + ']';
},
toArray: function () {
return Array.prototype.slice.call(this.array);
}
};
var defineProperty = Object.defineProperty;
// Getter and Setter
if (defineProperty) {
var proto = Quaternion.prototype;
/**
* @name x
* @type {number}
* @memberOf clay.Quaternion
* @instance
*/
defineProperty(proto, 'x', {
get: function () {
return this.array[0];
},
set: function (value) {
this.array[0] = value;
this._dirty = true;
}
});
/**
* @name y
* @type {number}
* @memberOf clay.Quaternion
* @instance
*/
defineProperty(proto, 'y', {
get: function () {
return this.array[1];
},
set: function (value) {
this.array[1] = value;
this._dirty = true;
}
});
/**
* @name z
* @type {number}
* @memberOf clay.Quaternion
* @instance
*/
defineProperty(proto, 'z', {
get: function () {
return this.array[2];
},
set: function (value) {
this.array[2] = value;
this._dirty = true;
}
});
/**
* @name w
* @type {number}
* @memberOf clay.Quaternion
* @instance
*/
defineProperty(proto, 'w', {
get: function () {
return this.array[3];
},
set: function (value) {
this.array[3] = value;
this._dirty = true;
}
});
}
// Supply methods that are not in place
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @param {clay.Quaternion} b
* @return {clay.Quaternion}
*/
Quaternion.add = function (out, a, b) {
quat.add(out.array, a.array, b.array);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {number} x
* @param {number} y
* @param {number} z
* @param {number} w
* @return {clay.Quaternion}
*/
Quaternion.set = function (out, x, y, z, w) {
quat.set(out.array, x, y, z, w);
out._dirty = true;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} b
* @return {clay.Quaternion}
*/
Quaternion.copy = function (out, b) {
quat.copy(out.array, b.array);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @return {clay.Quaternion}
*/
Quaternion.calculateW = function (out, a) {
quat.calculateW(out.array, a.array);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @return {clay.Quaternion}
*/
Quaternion.conjugate = function (out, a) {
quat.conjugate(out.array, a.array);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @return {clay.Quaternion}
*/
Quaternion.identity = function (out) {
quat.identity(out.array);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @return {clay.Quaternion}
*/
Quaternion.invert = function (out, a) {
quat.invert(out.array, a.array);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} a
* @param {clay.Quaternion} b
* @return {number}
*/
Quaternion.dot = function (a, b) {
return quat.dot(a.array, b.array);
};
/**
* @param {clay.Quaternion} a
* @return {number}
*/
Quaternion.len = function (a) {
return quat.length(a.array);
};
// Quaternion.length = Quaternion.len;
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @param {clay.Quaternion} b
* @param {number} t
* @return {clay.Quaternion}
*/
Quaternion.lerp = function (out, a, b, t) {
quat.lerp(out.array, a.array, b.array, t);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @param {clay.Quaternion} b
* @param {number} t
* @return {clay.Quaternion}
*/
Quaternion.slerp = function (out, a, b, t) {
quat.slerp(out.array, a.array, b.array, t);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @param {clay.Quaternion} b
* @return {clay.Quaternion}
*/
Quaternion.mul = function (out, a, b) {
quat.multiply(out.array, a.array, b.array);
out._dirty = true;
return out;
};
/**
* @function
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @param {clay.Quaternion} b
* @return {clay.Quaternion}
*/
Quaternion.multiply = Quaternion.mul;
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @param {number} rad
* @return {clay.Quaternion}
*/
Quaternion.rotateX = function (out, a, rad) {
quat.rotateX(out.array, a.array, rad);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @param {number} rad
* @return {clay.Quaternion}
*/
Quaternion.rotateY = function (out, a, rad) {
quat.rotateY(out.array, a.array, rad);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @param {number} rad
* @return {clay.Quaternion}
*/
Quaternion.rotateZ = function (out, a, rad) {
quat.rotateZ(out.array, a.array, rad);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Vector3} axis
* @param {number} rad
* @return {clay.Quaternion}
*/
Quaternion.setAxisAngle = function (out, axis, rad) {
quat.setAxisAngle(out.array, axis.array, rad);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Quaternion} a
* @return {clay.Quaternion}
*/
Quaternion.normalize = function (out, a) {
quat.normalize(out.array, a.array);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} a
* @return {number}
*/
Quaternion.sqrLen = function (a) {
return quat.sqrLen(a.array);
};
/**
* @function
* @param {clay.Quaternion} a
* @return {number}
*/
Quaternion.squaredLength = Quaternion.sqrLen;
/**
* @param {clay.Quaternion} out
* @param {clay.Matrix3} m
* @return {clay.Quaternion}
*/
Quaternion.fromMat3 = function (out, m) {
quat.fromMat3(out.array, m.array);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Vector3} view
* @param {clay.Vector3} right
* @param {clay.Vector3} up
* @return {clay.Quaternion}
*/
Quaternion.setAxes = function (out, view, right, up) {
quat.setAxes(out.array, view.array, right.array, up.array);
out._dirty = true;
return out;
};
/**
* @param {clay.Quaternion} out
* @param {clay.Vector3} a
* @param {clay.Vector3} b
* @return {clay.Quaternion}
*/
Quaternion.rotationTo = function (out, a, b) {
quat.rotationTo(out.array, a.array, b.array);
out._dirty = true;
return out;
};
/**
* Set quaternion from euler
* @param {clay.Quaternion} out
* @param {clay.Vector3} v
* @param {String} order
*/
Quaternion.fromEuler = function (out, v, order) {
out._dirty = true;
v = v.array;
var target = out.array;
var c1 = Math.cos(v[0] / 2);
var c2 = Math.cos(v[1] / 2);
var c3 = Math.cos(v[2] / 2);
var s1 = Math.sin(v[0] / 2);
var s2 = Math.sin(v[1] / 2);
var s3 = Math.sin(v[2] / 2);
var order = (order || 'XYZ').toUpperCase();
// http://www.mathworks.com/matlabcentral/fileexchange/
// 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
// content/SpinCalc.m
switch (order) {
case 'XYZ':
target[0] = s1 * c2 * c3 + c1 * s2 * s3;
target[1] = c1 * s2 * c3 - s1 * c2 * s3;
target[2] = c1 * c2 * s3 + s1 * s2 * c3;
target[3] = c1 * c2 * c3 - s1 * s2 * s3;
break;
case 'YXZ':
target[0] = s1 * c2 * c3 + c1 * s2 * s3;
target[1] = c1 * s2 * c3 - s1 * c2 * s3;
target[2] = c1 * c2 * s3 - s1 * s2 * c3;
target[3] = c1 * c2 * c3 + s1 * s2 * s3;
break;
case 'ZXY':
target[0] = s1 * c2 * c3 - c1 * s2 * s3;
target[1] = c1 * s2 * c3 + s1 * c2 * s3;
target[2] = c1 * c2 * s3 + s1 * s2 * c3;
target[3] = c1 * c2 * c3 - s1 * s2 * s3;
break;
case 'ZYX':
target[0] = s1 * c2 * c3 - c1 * s2 * s3;
target[1] = c1 * s2 * c3 + s1 * c2 * s3;
target[2] = c1 * c2 * s3 - s1 * s2 * c3;
target[3] = c1 * c2 * c3 + s1 * s2 * s3;
break;
case 'YZX':
target[0] = s1 * c2 * c3 + c1 * s2 * s3;
target[1] = c1 * s2 * c3 + s1 * c2 * s3;
target[2] = c1 * c2 * s3 - s1 * s2 * c3;
target[3] = c1 * c2 * c3 - s1 * s2 * s3;
break;
case 'XZY':
target[0] = s1 * c2 * c3 - c1 * s2 * s3;
target[1] = c1 * s2 * c3 - s1 * c2 * s3;
target[2] = c1 * c2 * s3 + s1 * s2 * c3;
target[3] = c1 * c2 * c3 + s1 * s2 * s3;
break;
}
};
export default Quaternion;