PageRenderTime 57ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/src/math/Quaternion.js

https://github.com/mrdoob/three.js
JavaScript | 689 lines | 409 code | 261 blank | 19 comment | 36 complexity | dddba7dcc9910e5b0f02b76bb207efee MD5 | raw file
Possible License(s): MIT
  1. import * as MathUtils from './MathUtils.js';
  2. class Quaternion {
  3. constructor( x = 0, y = 0, z = 0, w = 1 ) {
  4. this._x = x;
  5. this._y = y;
  6. this._z = z;
  7. this._w = w;
  8. }
  9. static slerp( qa, qb, qm, t ) {
  10. console.warn( 'THREE.Quaternion: Static .slerp() has been deprecated. Use qm.slerpQuaternions( qa, qb, t ) instead.' );
  11. return qm.slerpQuaternions( qa, qb, t );
  12. }
  13. static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {
  14. // fuzz-free, array-based Quaternion SLERP operation
  15. let x0 = src0[ srcOffset0 + 0 ],
  16. y0 = src0[ srcOffset0 + 1 ],
  17. z0 = src0[ srcOffset0 + 2 ],
  18. w0 = src0[ srcOffset0 + 3 ];
  19. const x1 = src1[ srcOffset1 + 0 ],
  20. y1 = src1[ srcOffset1 + 1 ],
  21. z1 = src1[ srcOffset1 + 2 ],
  22. w1 = src1[ srcOffset1 + 3 ];
  23. if ( t === 0 ) {
  24. dst[ dstOffset + 0 ] = x0;
  25. dst[ dstOffset + 1 ] = y0;
  26. dst[ dstOffset + 2 ] = z0;
  27. dst[ dstOffset + 3 ] = w0;
  28. return;
  29. }
  30. if ( t === 1 ) {
  31. dst[ dstOffset + 0 ] = x1;
  32. dst[ dstOffset + 1 ] = y1;
  33. dst[ dstOffset + 2 ] = z1;
  34. dst[ dstOffset + 3 ] = w1;
  35. return;
  36. }
  37. if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {
  38. let s = 1 - t;
  39. const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,
  40. dir = ( cos >= 0 ? 1 : - 1 ),
  41. sqrSin = 1 - cos * cos;
  42. // Skip the Slerp for tiny steps to avoid numeric problems:
  43. if ( sqrSin > Number.EPSILON ) {
  44. const sin = Math.sqrt( sqrSin ),
  45. len = Math.atan2( sin, cos * dir );
  46. s = Math.sin( s * len ) / sin;
  47. t = Math.sin( t * len ) / sin;
  48. }
  49. const tDir = t * dir;
  50. x0 = x0 * s + x1 * tDir;
  51. y0 = y0 * s + y1 * tDir;
  52. z0 = z0 * s + z1 * tDir;
  53. w0 = w0 * s + w1 * tDir;
  54. // Normalize in case we just did a lerp:
  55. if ( s === 1 - t ) {
  56. const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 );
  57. x0 *= f;
  58. y0 *= f;
  59. z0 *= f;
  60. w0 *= f;
  61. }
  62. }
  63. dst[ dstOffset ] = x0;
  64. dst[ dstOffset + 1 ] = y0;
  65. dst[ dstOffset + 2 ] = z0;
  66. dst[ dstOffset + 3 ] = w0;
  67. }
  68. static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) {
  69. const x0 = src0[ srcOffset0 ];
  70. const y0 = src0[ srcOffset0 + 1 ];
  71. const z0 = src0[ srcOffset0 + 2 ];
  72. const w0 = src0[ srcOffset0 + 3 ];
  73. const x1 = src1[ srcOffset1 ];
  74. const y1 = src1[ srcOffset1 + 1 ];
  75. const z1 = src1[ srcOffset1 + 2 ];
  76. const w1 = src1[ srcOffset1 + 3 ];
  77. dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1;
  78. dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1;
  79. dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1;
  80. dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
  81. return dst;
  82. }
  83. get x() {
  84. return this._x;
  85. }
  86. set x( value ) {
  87. this._x = value;
  88. this._onChangeCallback();
  89. }
  90. get y() {
  91. return this._y;
  92. }
  93. set y( value ) {
  94. this._y = value;
  95. this._onChangeCallback();
  96. }
  97. get z() {
  98. return this._z;
  99. }
  100. set z( value ) {
  101. this._z = value;
  102. this._onChangeCallback();
  103. }
  104. get w() {
  105. return this._w;
  106. }
  107. set w( value ) {
  108. this._w = value;
  109. this._onChangeCallback();
  110. }
  111. set( x, y, z, w ) {
  112. this._x = x;
  113. this._y = y;
  114. this._z = z;
  115. this._w = w;
  116. this._onChangeCallback();
  117. return this;
  118. }
  119. clone() {
  120. return new this.constructor( this._x, this._y, this._z, this._w );
  121. }
  122. copy( quaternion ) {
  123. this._x = quaternion.x;
  124. this._y = quaternion.y;
  125. this._z = quaternion.z;
  126. this._w = quaternion.w;
  127. this._onChangeCallback();
  128. return this;
  129. }
  130. setFromEuler( euler, update ) {
  131. if ( ! ( euler && euler.isEuler ) ) {
  132. throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' );
  133. }
  134. const x = euler._x, y = euler._y, z = euler._z, order = euler._order;
  135. // http://www.mathworks.com/matlabcentral/fileexchange/
  136. // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
  137. // content/SpinCalc.m
  138. const cos = Math.cos;
  139. const sin = Math.sin;
  140. const c1 = cos( x / 2 );
  141. const c2 = cos( y / 2 );
  142. const c3 = cos( z / 2 );
  143. const s1 = sin( x / 2 );
  144. const s2 = sin( y / 2 );
  145. const s3 = sin( z / 2 );
  146. switch ( order ) {
  147. case 'XYZ':
  148. this._x = s1 * c2 * c3 + c1 * s2 * s3;
  149. this._y = c1 * s2 * c3 - s1 * c2 * s3;
  150. this._z = c1 * c2 * s3 + s1 * s2 * c3;
  151. this._w = c1 * c2 * c3 - s1 * s2 * s3;
  152. break;
  153. case 'YXZ':
  154. this._x = s1 * c2 * c3 + c1 * s2 * s3;
  155. this._y = c1 * s2 * c3 - s1 * c2 * s3;
  156. this._z = c1 * c2 * s3 - s1 * s2 * c3;
  157. this._w = c1 * c2 * c3 + s1 * s2 * s3;
  158. break;
  159. case 'ZXY':
  160. this._x = s1 * c2 * c3 - c1 * s2 * s3;
  161. this._y = c1 * s2 * c3 + s1 * c2 * s3;
  162. this._z = c1 * c2 * s3 + s1 * s2 * c3;
  163. this._w = c1 * c2 * c3 - s1 * s2 * s3;
  164. break;
  165. case 'ZYX':
  166. this._x = s1 * c2 * c3 - c1 * s2 * s3;
  167. this._y = c1 * s2 * c3 + s1 * c2 * s3;
  168. this._z = c1 * c2 * s3 - s1 * s2 * c3;
  169. this._w = c1 * c2 * c3 + s1 * s2 * s3;
  170. break;
  171. case 'YZX':
  172. this._x = s1 * c2 * c3 + c1 * s2 * s3;
  173. this._y = c1 * s2 * c3 + s1 * c2 * s3;
  174. this._z = c1 * c2 * s3 - s1 * s2 * c3;
  175. this._w = c1 * c2 * c3 - s1 * s2 * s3;
  176. break;
  177. case 'XZY':
  178. this._x = s1 * c2 * c3 - c1 * s2 * s3;
  179. this._y = c1 * s2 * c3 - s1 * c2 * s3;
  180. this._z = c1 * c2 * s3 + s1 * s2 * c3;
  181. this._w = c1 * c2 * c3 + s1 * s2 * s3;
  182. break;
  183. default:
  184. console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order );
  185. }
  186. if ( update !== false ) this._onChangeCallback();
  187. return this;
  188. }
  189. setFromAxisAngle( axis, angle ) {
  190. // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
  191. // assumes axis is normalized
  192. const halfAngle = angle / 2, s = Math.sin( halfAngle );
  193. this._x = axis.x * s;
  194. this._y = axis.y * s;
  195. this._z = axis.z * s;
  196. this._w = Math.cos( halfAngle );
  197. this._onChangeCallback();
  198. return this;
  199. }
  200. setFromRotationMatrix( m ) {
  201. // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
  202. // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
  203. const te = m.elements,
  204. m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
  205. m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
  206. m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],
  207. trace = m11 + m22 + m33;
  208. if ( trace > 0 ) {
  209. const s = 0.5 / Math.sqrt( trace + 1.0 );
  210. this._w = 0.25 / s;
  211. this._x = ( m32 - m23 ) * s;
  212. this._y = ( m13 - m31 ) * s;
  213. this._z = ( m21 - m12 ) * s;
  214. } else if ( m11 > m22 && m11 > m33 ) {
  215. const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
  216. this._w = ( m32 - m23 ) / s;
  217. this._x = 0.25 * s;
  218. this._y = ( m12 + m21 ) / s;
  219. this._z = ( m13 + m31 ) / s;
  220. } else if ( m22 > m33 ) {
  221. const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
  222. this._w = ( m13 - m31 ) / s;
  223. this._x = ( m12 + m21 ) / s;
  224. this._y = 0.25 * s;
  225. this._z = ( m23 + m32 ) / s;
  226. } else {
  227. const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
  228. this._w = ( m21 - m12 ) / s;
  229. this._x = ( m13 + m31 ) / s;
  230. this._y = ( m23 + m32 ) / s;
  231. this._z = 0.25 * s;
  232. }
  233. this._onChangeCallback();
  234. return this;
  235. }
  236. setFromUnitVectors( vFrom, vTo ) {
  237. // assumes direction vectors vFrom and vTo are normalized
  238. let r = vFrom.dot( vTo ) + 1;
  239. if ( r < Number.EPSILON ) {
  240. // vFrom and vTo point in opposite directions
  241. r = 0;
  242. if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {
  243. this._x = - vFrom.y;
  244. this._y = vFrom.x;
  245. this._z = 0;
  246. this._w = r;
  247. } else {
  248. this._x = 0;
  249. this._y = - vFrom.z;
  250. this._z = vFrom.y;
  251. this._w = r;
  252. }
  253. } else {
  254. // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3
  255. this._x = vFrom.y * vTo.z - vFrom.z * vTo.y;
  256. this._y = vFrom.z * vTo.x - vFrom.x * vTo.z;
  257. this._z = vFrom.x * vTo.y - vFrom.y * vTo.x;
  258. this._w = r;
  259. }
  260. return this.normalize();
  261. }
  262. angleTo( q ) {
  263. return 2 * Math.acos( Math.abs( MathUtils.clamp( this.dot( q ), - 1, 1 ) ) );
  264. }
  265. rotateTowards( q, step ) {
  266. const angle = this.angleTo( q );
  267. if ( angle === 0 ) return this;
  268. const t = Math.min( 1, step / angle );
  269. this.slerp( q, t );
  270. return this;
  271. }
  272. identity() {
  273. return this.set( 0, 0, 0, 1 );
  274. }
  275. invert() {
  276. // quaternion is assumed to have unit length
  277. return this.conjugate();
  278. }
  279. conjugate() {
  280. this._x *= - 1;
  281. this._y *= - 1;
  282. this._z *= - 1;
  283. this._onChangeCallback();
  284. return this;
  285. }
  286. dot( v ) {
  287. return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;
  288. }
  289. lengthSq() {
  290. return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
  291. }
  292. length() {
  293. return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
  294. }
  295. normalize() {
  296. let l = this.length();
  297. if ( l === 0 ) {
  298. this._x = 0;
  299. this._y = 0;
  300. this._z = 0;
  301. this._w = 1;
  302. } else {
  303. l = 1 / l;
  304. this._x = this._x * l;
  305. this._y = this._y * l;
  306. this._z = this._z * l;
  307. this._w = this._w * l;
  308. }
  309. this._onChangeCallback();
  310. return this;
  311. }
  312. multiply( q, p ) {
  313. if ( p !== undefined ) {
  314. console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
  315. return this.multiplyQuaternions( q, p );
  316. }
  317. return this.multiplyQuaternions( this, q );
  318. }
  319. premultiply( q ) {
  320. return this.multiplyQuaternions( q, this );
  321. }
  322. multiplyQuaternions( a, b ) {
  323. // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
  324. const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
  325. const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
  326. this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
  327. this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
  328. this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
  329. this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
  330. this._onChangeCallback();
  331. return this;
  332. }
  333. slerp( qb, t ) {
  334. if ( t === 0 ) return this;
  335. if ( t === 1 ) return this.copy( qb );
  336. const x = this._x, y = this._y, z = this._z, w = this._w;
  337. // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
  338. let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;
  339. if ( cosHalfTheta < 0 ) {
  340. this._w = - qb._w;
  341. this._x = - qb._x;
  342. this._y = - qb._y;
  343. this._z = - qb._z;
  344. cosHalfTheta = - cosHalfTheta;
  345. } else {
  346. this.copy( qb );
  347. }
  348. if ( cosHalfTheta >= 1.0 ) {
  349. this._w = w;
  350. this._x = x;
  351. this._y = y;
  352. this._z = z;
  353. return this;
  354. }
  355. const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
  356. if ( sqrSinHalfTheta <= Number.EPSILON ) {
  357. const s = 1 - t;
  358. this._w = s * w + t * this._w;
  359. this._x = s * x + t * this._x;
  360. this._y = s * y + t * this._y;
  361. this._z = s * z + t * this._z;
  362. this.normalize();
  363. this._onChangeCallback();
  364. return this;
  365. }
  366. const sinHalfTheta = Math.sqrt( sqrSinHalfTheta );
  367. const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );
  368. const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
  369. ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
  370. this._w = ( w * ratioA + this._w * ratioB );
  371. this._x = ( x * ratioA + this._x * ratioB );
  372. this._y = ( y * ratioA + this._y * ratioB );
  373. this._z = ( z * ratioA + this._z * ratioB );
  374. this._onChangeCallback();
  375. return this;
  376. }
  377. slerpQuaternions( qa, qb, t ) {
  378. this.copy( qa ).slerp( qb, t );
  379. }
  380. random() {
  381. // Derived from http://planning.cs.uiuc.edu/node198.html
  382. // Note, this source uses w, x, y, z ordering,
  383. // so we swap the order below.
  384. const u1 = Math.random();
  385. const sqrt1u1 = Math.sqrt( 1 - u1 );
  386. const sqrtu1 = Math.sqrt( u1 );
  387. const u2 = 2 * Math.PI * Math.random();
  388. const u3 = 2 * Math.PI * Math.random();
  389. return this.set(
  390. sqrt1u1 * Math.cos( u2 ),
  391. sqrtu1 * Math.sin( u3 ),
  392. sqrtu1 * Math.cos( u3 ),
  393. sqrt1u1 * Math.sin( u2 ),
  394. );
  395. }
  396. equals( quaternion ) {
  397. return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
  398. }
  399. fromArray( array, offset = 0 ) {
  400. this._x = array[ offset ];
  401. this._y = array[ offset + 1 ];
  402. this._z = array[ offset + 2 ];
  403. this._w = array[ offset + 3 ];
  404. this._onChangeCallback();
  405. return this;
  406. }
  407. toArray( array = [], offset = 0 ) {
  408. array[ offset ] = this._x;
  409. array[ offset + 1 ] = this._y;
  410. array[ offset + 2 ] = this._z;
  411. array[ offset + 3 ] = this._w;
  412. return array;
  413. }
  414. fromBufferAttribute( attribute, index ) {
  415. this._x = attribute.getX( index );
  416. this._y = attribute.getY( index );
  417. this._z = attribute.getZ( index );
  418. this._w = attribute.getW( index );
  419. return this;
  420. }
  421. _onChange( callback ) {
  422. this._onChangeCallback = callback;
  423. return this;
  424. }
  425. _onChangeCallback() {}
  426. }
  427. Quaternion.prototype.isQuaternion = true;
  428. export { Quaternion };