Quaternion.js

  1. (function() {
  2. 'use strict';
  3. var Vec3 = require('./Vec3');
  4. var Mat33 = require('./Mat33');
  5. var Mat44 = require('./Mat44');
  6. /**
  7. * Instantiates a Quaternion object.
  8. * @class Quaternion
  9. * @classdesc A quaternion representing an orientation.
  10. */
  11. function Quaternion() {
  12. switch ( arguments.length ) {
  13. case 1:
  14. // array or Quaternion argument
  15. var argument = arguments[0];
  16. if ( argument.w !== undefined ) {
  17. this.w = argument.w;
  18. } else if ( argument[0] !== undefined ) {
  19. this.w = argument[0];
  20. } else {
  21. this.w = 1.0;
  22. }
  23. this.x = argument.x || argument[1] || 0.0;
  24. this.y = argument.y || argument[2] || 0.0;
  25. this.z = argument.z || argument[3] || 0.0;
  26. break;
  27. case 4:
  28. // individual component arguments
  29. this.w = arguments[0];
  30. this.x = arguments[1];
  31. this.y = arguments[2];
  32. this.z = arguments[3];
  33. break;
  34. default:
  35. this.w = 1;
  36. this.x = 0;
  37. this.y = 0;
  38. this.z = 0;
  39. break;
  40. }
  41. }
  42. /**
  43. * Returns a quaternion that represents an oreintation matching
  44. * the identity matrix.
  45. * @memberof Quaternion
  46. *
  47. * @returns {Quaternion} The identity quaternion.
  48. */
  49. Quaternion.identity = function() {
  50. return new Quaternion( 1, 0, 0, 0 );
  51. };
  52. /**
  53. * Returns a new Quaternion with each component negated.
  54. * @memberof Quaternion
  55. *
  56. * @returns {Quaternion} The negated quaternion.
  57. */
  58. Quaternion.prototype.negate = function() {
  59. return new Quaternion( -this.w, -this.x, -this.y, -this.z );
  60. };
  61. /**
  62. * Concatenates the rotations of the two quaternions, returning
  63. * a new Quaternion object.
  64. * @memberof Quaternion
  65. *
  66. * @param {Quaternion|Array} that - The quaterion to concatenate.
  67. *
  68. * @returns {Quaternion} The resulting concatenated quaternion.
  69. */
  70. Quaternion.prototype.multQuat = function( that ) {
  71. that = ( that instanceof Array ) ? new Quaternion( that ) : that;
  72. var w = (that.w * this.w) - (that.x * this.x) - (that.y * this.y) - (that.z * this.z),
  73. x = this.y*that.z - this.z*that.y + this.w*that.x + this.x*that.w,
  74. y = this.z*that.x - this.x*that.z + this.w*that.y + this.y*that.w,
  75. z = this.x*that.y - this.y*that.x + this.w*that.z + this.z*that.w;
  76. return new Quaternion( w, x, y, z );
  77. };
  78. /**
  79. * Applies the orientation of the quaternion as a rotation
  80. * matrix to the provided vector, returning a new Vec3 object.
  81. * @memberof Quaternion
  82. *
  83. * @param {Vec3|Vec4|Array} that - The vector to rotate.
  84. *
  85. * @returns {Vec3} The resulting rotated vector.
  86. */
  87. Quaternion.prototype.rotate = function( that ) {
  88. that = ( that instanceof Array ) ? new Vec3( that ) : that;
  89. var vq = new Quaternion( 0, that.x, that.y, that.z ),
  90. r = this.multQuat( vq ).multQuat( this.inverse() );
  91. return new Vec3( r.x, r.y, r.z );
  92. };
  93. /**
  94. * Returns the x-axis of the rotation matrix that the quaternion represents.
  95. * @memberof Quaternion
  96. *
  97. * @returns {Vec3} The x-axis of the rotation matrix represented by the quaternion.
  98. */
  99. Quaternion.prototype.xAxis = function() {
  100. var yy = this.y*this.y,
  101. zz = this.z*this.z,
  102. xy = this.x*this.y,
  103. xz = this.x*this.z,
  104. yw = this.y*this.w,
  105. zw = this.z*this.w;
  106. return new Vec3(
  107. 1 - 2*yy - 2*zz,
  108. 2*xy + 2*zw,
  109. 2*xz - 2*yw ).normalize();
  110. };
  111. /**
  112. * Returns the y-axis of the rotation matrix that the quaternion represents.
  113. * @memberof Quaternion
  114. *
  115. * @returns {Vec3} The y-axis of the rotation matrix represented by the quaternion.
  116. */
  117. Quaternion.prototype.yAxis = function() {
  118. var xx = this.x*this.x,
  119. zz = this.z*this.z,
  120. xy = this.x*this.y,
  121. xw = this.x*this.w,
  122. yz = this.y*this.z,
  123. zw = this.z*this.w;
  124. return new Vec3(
  125. 2*xy - 2*zw,
  126. 1 - 2*xx - 2*zz,
  127. 2*yz + 2*xw ).normalize();
  128. };
  129. /**
  130. * Returns the z-axis of the rotation matrix that the quaternion represents.
  131. * @memberof Quaternion
  132. *
  133. * @returns {Vec3} The z-axis of the rotation matrix represented by the quaternion.
  134. */
  135. Quaternion.prototype.zAxis = function() {
  136. var xx = this.x*this.x,
  137. yy = this.y*this.y,
  138. xz = this.x*this.z,
  139. xw = this.x*this.w,
  140. yz = this.y*this.z,
  141. yw = this.y*this.w;
  142. return new Vec3(
  143. 2*xz + 2*yw,
  144. 2*yz - 2*xw,
  145. 1 - 2*xx - 2*yy ).normalize();
  146. };
  147. /**
  148. * Returns the axes of the rotation matrix that the quaternion represents.
  149. * @memberof Quaternion
  150. *
  151. * @returns {Object} The axes of the matrix represented by the quaternion.
  152. */
  153. Quaternion.prototype.axes = function() {
  154. var xx = this.x*this.x,
  155. yy = this.y*this.y,
  156. zz = this.z*this.z,
  157. xy = this.x*this.y,
  158. xz = this.x*this.z,
  159. xw = this.x*this.w,
  160. yz = this.y*this.z,
  161. yw = this.y*this.w,
  162. zw = this.z*this.w;
  163. return {
  164. x: new Vec3( 1 - 2*yy - 2*zz, 2*xy + 2*zw, 2*xz - 2*yw ),
  165. y: new Vec3( 2*xy - 2*zw, 1 - 2*xx - 2*zz, 2*yz + 2*xw ),
  166. z: new Vec3( 2*xz + 2*yw, 2*yz - 2*xw, 1 - 2*xx - 2*yy )
  167. };
  168. };
  169. /**
  170. * Returns the rotation matrix that the quaternion represents.
  171. * @memberof Quaternion
  172. *
  173. * @returns {Mat33} The rotation matrix represented by the quaternion.
  174. */
  175. Quaternion.prototype.matrix = function() {
  176. var xx = this.x*this.x,
  177. yy = this.y*this.y,
  178. zz = this.z*this.z,
  179. xy = this.x*this.y,
  180. xz = this.x*this.z,
  181. xw = this.x*this.w,
  182. yz = this.y*this.z,
  183. yw = this.y*this.w,
  184. zw = this.z*this.w;
  185. return new Mat33([
  186. 1 - 2*yy - 2*zz, 2*xy + 2*zw, 2*xz - 2*yw,
  187. 2*xy - 2*zw, 1 - 2*xx - 2*zz, 2*yz + 2*xw,
  188. 2*xz + 2*yw, 2*yz - 2*xw, 1 - 2*xx - 2*yy ]);
  189. };
  190. /**
  191. * Returns a quaternion representing the rotation defined by an axis
  192. * and an angle.
  193. * @memberof Quaternion
  194. *
  195. * @param {number} angle - The angle of the rotation, in radians.
  196. * @param {Vec3|Array} axis - The axis of the rotation.
  197. *
  198. * @returns {Quaternion} The quaternion representing the rotation.
  199. */
  200. Quaternion.rotation = function( angle, axis ) {
  201. if ( axis instanceof Array ) {
  202. axis = new Vec3( axis );
  203. }
  204. // normalize arguments
  205. axis = axis.normalize();
  206. // set quaternion for the equivolent rotation
  207. var modAngle = ( angle > 0 ) ? angle % (2*Math.PI) : angle % (-2*Math.PI),
  208. sina = Math.sin( modAngle/2 ),
  209. cosa = Math.cos( modAngle/2 );
  210. return new Quaternion(
  211. cosa,
  212. axis.x * sina,
  213. axis.y * sina,
  214. axis.z * sina ).normalize();
  215. };
  216. /**
  217. * Returns a rotation matrix to rotate a vector from one direction to
  218. * another.
  219. * @memberof Quaternion
  220. *
  221. * @param {Vec3} from - The starting direction.
  222. * @param {Vec3} to - The ending direction.
  223. *
  224. * @returns {Quaternion} The quaternion representing the rotation.
  225. */
  226. Quaternion.rotationFromTo = function( fromVec, toVec ) {
  227. fromVec = new Vec3( fromVec );
  228. toVec = new Vec3( toVec );
  229. var cross = fromVec.cross( toVec );
  230. var dot = fromVec.dot( toVec );
  231. var fLen = fromVec.length();
  232. var tLen = toVec.length();
  233. var w = Math.sqrt( ( fLen * fLen ) * ( tLen * tLen ) ) + dot;
  234. return new Quaternion( w, cross.x, cross.y, cross.z ).normalize();
  235. };
  236. /**
  237. * Returns a quaternion that has been spherically interpolated between
  238. * two provided quaternions for a given t value.
  239. * @memberof Quaternion
  240. *
  241. * @param {Quaternion} fromRot - The rotation at t = 0.
  242. * @param {Quaternion} toRot - The rotation at t = 1.
  243. * @param {number} t - The t value, from 0 to 1.
  244. *
  245. * @returns {Quaternion} The quaternion representing the interpolated rotation.
  246. */
  247. Quaternion.slerp = function( fromRot, toRot, t ) {
  248. if ( fromRot instanceof Array ) {
  249. fromRot = new Quaternion( fromRot );
  250. }
  251. if ( toRot instanceof Array ) {
  252. toRot = new Quaternion( toRot );
  253. }
  254. // calculate angle between
  255. var cosHalfTheta = ( fromRot.w * toRot.w ) +
  256. ( fromRot.x * toRot.x ) +
  257. ( fromRot.y * toRot.y ) +
  258. ( fromRot.z * toRot.z );
  259. // if fromRot=toRot or fromRot=-toRot then theta = 0 and we can return from
  260. if ( Math.abs( cosHalfTheta ) >= 1 ) {
  261. return new Quaternion(
  262. fromRot.w,
  263. fromRot.x,
  264. fromRot.y,
  265. fromRot.z );
  266. }
  267. // cosHalfTheta must be positive to return the shortest angle
  268. if ( cosHalfTheta < 0 ) {
  269. fromRot = fromRot.negate();
  270. cosHalfTheta = -cosHalfTheta;
  271. }
  272. var halfTheta = Math.acos( cosHalfTheta );
  273. var sinHalfTheta = Math.sqrt( 1 - cosHalfTheta * cosHalfTheta );
  274. var scaleFrom = Math.sin( ( 1.0 - t ) * halfTheta ) / sinHalfTheta;
  275. var scaleTo = Math.sin( t * halfTheta ) / sinHalfTheta;
  276. return new Quaternion(
  277. fromRot.w * scaleFrom + toRot.w * scaleTo,
  278. fromRot.x * scaleFrom + toRot.x * scaleTo,
  279. fromRot.y * scaleFrom + toRot.y * scaleTo,
  280. fromRot.z * scaleFrom + toRot.z * scaleTo );
  281. };
  282. /**
  283. * Returns true if the vector components match those of a provided vector.
  284. * An optional epsilon value may be provided.
  285. * @memberof Quaternion
  286. *
  287. * @param {Quaternion|Array} - The vector to calculate the dot product with.
  288. * @param {number} - The epsilon value. Optional.
  289. *
  290. * @returns {boolean} Whether or not the vector components match.
  291. */
  292. Quaternion.prototype.equals = function( that, epsilon ) {
  293. var w = that.w !== undefined ? that.w : that[0],
  294. x = that.x !== undefined ? that.x : that[1],
  295. y = that.y !== undefined ? that.y : that[2],
  296. z = that.z !== undefined ? that.z : that[3];
  297. epsilon = epsilon === undefined ? 0 : epsilon;
  298. return ( this.w === w || Math.abs( this.w - w ) <= epsilon ) &&
  299. ( this.x === x || Math.abs( this.x - x ) <= epsilon ) &&
  300. ( this.y === y || Math.abs( this.y - y ) <= epsilon ) &&
  301. ( this.z === z || Math.abs( this.z - z ) <= epsilon );
  302. };
  303. /**
  304. * Returns a new Quaternion of unit length.
  305. * @memberof Quaternion
  306. *
  307. * @returns {Quaternion} The quaternion of unit length.
  308. */
  309. Quaternion.prototype.normalize = function() {
  310. var mag = Math.sqrt(
  311. this.x*this.x +
  312. this.y*this.y +
  313. this.z*this.z +
  314. this.w*this.w );
  315. if ( mag !== 0 ) {
  316. return new Quaternion(
  317. this.w / mag,
  318. this.x / mag,
  319. this.y / mag,
  320. this.z / mag );
  321. }
  322. return new Quaternion();
  323. };
  324. /**
  325. * Returns the conjugate of the quaternion.
  326. * @memberof Quaternion
  327. *
  328. * @returns {Quaternion} The conjugate of the quaternion.
  329. */
  330. Quaternion.prototype.conjugate = function() {
  331. return new Quaternion( this.w, -this.x, -this.y, -this.z );
  332. };
  333. /**
  334. * Returns the inverse of the quaternion.
  335. * @memberof Quaternion
  336. *
  337. * @returns {Quaternion} The inverse of the quaternion.
  338. */
  339. Quaternion.prototype.inverse = function() {
  340. return this.conjugate();
  341. };
  342. /**
  343. * Returns a random Quaternion of unit length.
  344. * @memberof Quaternion
  345. *
  346. * @returns {Quaternion} A random vector of unit length.
  347. */
  348. Quaternion.random = function() {
  349. var axis = Vec3.random().normalize(),
  350. angle = Math.random();
  351. return Quaternion.rotation( angle, axis );
  352. };
  353. /**
  354. * Returns a string representation of the quaternion.
  355. * @memberof Quaternion
  356. *
  357. * @returns {String} The string representation of the quaternion.
  358. */
  359. Quaternion.prototype.toString = function() {
  360. return this.x + ', ' + this.y + ', ' + this.z + ', ' + this.w;
  361. };
  362. /**
  363. * Returns an array representation of the quaternion.
  364. * @memberof Quaternion
  365. *
  366. * @returns {Array} The quaternion as an array.
  367. */
  368. Quaternion.prototype.toArray = function() {
  369. return [ this.w, this.x, this.y, this.z ];
  370. };
  371. /**
  372. * Decomposes the matrix into the corresponding rotation, and scale components.
  373. * a scale.
  374. * @memberof Mat33
  375. *
  376. * @returns {Object} The decomposed components of the matrix.
  377. */
  378. Mat33.prototype.decompose = function() {
  379. // axis vectors
  380. var x = new Vec3( this.data[0], this.data[1], this.data[2] );
  381. var y = new Vec3( this.data[3], this.data[4], this.data[5] );
  382. var z = new Vec3( this.data[6], this.data[7], this.data[8] );
  383. // scale needs unnormalized vectors
  384. var scale = new Vec3( x.length(), y.length(), z.length() );
  385. // rotation needs normalized vectors
  386. x = x.normalize();
  387. y = y.normalize();
  388. z = z.normalize();
  389. var trace = x.x + y.y + z.z;
  390. var s;
  391. var rotation;
  392. if ( trace > 0 ) {
  393. s = 0.5 / Math.sqrt( trace + 1.0 );
  394. rotation = new Quaternion(
  395. 0.25 / s,
  396. ( y.z - z.y ) * s,
  397. ( z.x - x.z ) * s,
  398. ( x.y - y.x ) * s );
  399. } else if ( x.x > y.y && x.x > z.z ) {
  400. s = 2.0 * Math.sqrt( 1.0 + x.x - y.y - z.z );
  401. rotation = new Quaternion(
  402. ( y.z - z.y ) / s,
  403. 0.25 * s,
  404. ( y.x + x.y ) / s,
  405. ( z.x + x.z ) / s );
  406. } else if ( y.y > z.z ) {
  407. s = 2.0 * Math.sqrt( 1.0 + y.y - x.x - z.z );
  408. rotation = new Quaternion(
  409. ( z.x - x.z ) / s,
  410. ( y.x + x.y ) / s,
  411. 0.25 * s,
  412. ( z.y + y.z ) / s );
  413. } else {
  414. s = 2.0 * Math.sqrt( 1.0 + z.z - x.x - y.y );
  415. rotation = new Quaternion(
  416. ( x.y - y.x ) / s,
  417. ( z.x + x.z ) / s,
  418. ( z.y + y.z ) / s,
  419. 0.25 * s );
  420. }
  421. return {
  422. rotation: rotation,
  423. scale: scale
  424. };
  425. };
  426. /**
  427. * Decomposes the matrix into the corresponding rotation, translation, and scale components.
  428. * @memberof Mat44
  429. *
  430. * @returns {Object} The decomposed components of the matrix.
  431. */
  432. Mat44.prototype.decompose = function() {
  433. // translation
  434. var translation = new Vec3( this.data[12], this.data[13], this.data[14] );
  435. // axis vectors
  436. var x = new Vec3( this.data[0], this.data[1], this.data[2] );
  437. var y = new Vec3( this.data[4], this.data[5], this.data[6] );
  438. var z = new Vec3( this.data[8], this.data[9], this.data[10] );
  439. // scale needs unnormalized vectors
  440. var scale = new Vec3( x.length(), y.length(), z.length() );
  441. // rotation needs normalized vectors
  442. x = x.normalize();
  443. y = y.normalize();
  444. z = z.normalize();
  445. var trace = x.x + y.y + z.z;
  446. var s;
  447. var rotation;
  448. if ( trace > 0 ) {
  449. s = 0.5 / Math.sqrt( trace + 1.0 );
  450. rotation = new Quaternion(
  451. 0.25 / s,
  452. ( y.z - z.y ) * s,
  453. ( z.x - x.z ) * s,
  454. ( x.y - y.x ) * s );
  455. } else if ( x.x > y.y && x.x > z.z ) {
  456. s = 2.0 * Math.sqrt( 1.0 + x.x - y.y - z.z );
  457. rotation = new Quaternion(
  458. ( y.z - z.y ) / s,
  459. 0.25 * s,
  460. ( y.x + x.y ) / s,
  461. ( z.x + x.z ) / s );
  462. } else if ( y.y > z.z ) {
  463. s = 2.0 * Math.sqrt( 1.0 + y.y - x.x - z.z );
  464. rotation = new Quaternion(
  465. ( z.x - x.z ) / s,
  466. ( y.x + x.y ) / s,
  467. 0.25 * s,
  468. ( z.y + y.z ) / s );
  469. } else {
  470. s = 2.0 * Math.sqrt( 1.0 + z.z - x.x - y.y );
  471. rotation = new Quaternion(
  472. ( x.y - y.x ) / s,
  473. ( z.x + x.z ) / s,
  474. ( z.y + y.z ) / s,
  475. 0.25 * s );
  476. }
  477. return {
  478. rotation: rotation,
  479. scale: scale,
  480. translation: translation
  481. };
  482. };
  483. module.exports = Quaternion;
  484. }());