Quaternion.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. /**
  2. * @author bhouston / http://exocortex.com
  3. * @author tschw
  4. */
  5. module( "Quaternion" );
  6. var orders = [ 'XYZ', 'YXZ', 'ZXY', 'ZYX', 'YZX', 'XZY' ];
  7. var eulerAngles = new THREE.Euler( 0.1, -0.3, 0.25 );
  8. var qSub = function ( a, b ) {
  9. var result = new THREE.Quaternion();
  10. result.copy( a );
  11. result.x -= b.x;
  12. result.y -= b.y;
  13. result.z -= b.z;
  14. result.w -= b.w;
  15. return result;
  16. };
  17. test( "constructor", function() {
  18. var a = new THREE.Quaternion();
  19. ok( a.x == 0, "Passed!" );
  20. ok( a.y == 0, "Passed!" );
  21. ok( a.z == 0, "Passed!" );
  22. ok( a.w == 1, "Passed!" );
  23. a = new THREE.Quaternion( x, y, z, w );
  24. ok( a.x === x, "Passed!" );
  25. ok( a.y === y, "Passed!" );
  26. ok( a.z === z, "Passed!" );
  27. ok( a.w === w, "Passed!" );
  28. });
  29. test( "copy", function() {
  30. var a = new THREE.Quaternion( x, y, z, w );
  31. var b = new THREE.Quaternion().copy( a );
  32. ok( b.x == x, "Passed!" );
  33. ok( b.y == y, "Passed!" );
  34. ok( b.z == z, "Passed!" );
  35. ok( b.w == w, "Passed!" );
  36. // ensure that it is a true copy
  37. a.x = 0;
  38. a.y = -1;
  39. a.z = 0;
  40. a.w = -1;
  41. ok( b.x == x, "Passed!" );
  42. ok( b.y == y, "Passed!" );
  43. });
  44. test( "set", function() {
  45. var a = new THREE.Quaternion();
  46. ok( a.x == 0, "Passed!" );
  47. ok( a.y == 0, "Passed!" );
  48. ok( a.z == 0, "Passed!" );
  49. ok( a.w == 1, "Passed!" );
  50. a.set( x, y, z, w );
  51. ok( a.x == x, "Passed!" );
  52. ok( a.y == y, "Passed!" );
  53. ok( a.z === z, "Passed!" );
  54. ok( a.w === w, "Passed!" );
  55. });
  56. test( "setFromAxisAngle", function() {
  57. // TODO: find cases to validate.
  58. ok( true, "Passed!" );
  59. var zero = new THREE.Quaternion();
  60. var a = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), 0 );
  61. ok( a.equals( zero ), "Passed!" );
  62. a = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), 0 );
  63. ok( a.equals( zero ), "Passed!" );
  64. a = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 0, 0, 1 ), 0 );
  65. ok( a.equals( zero ), "Passed!" );
  66. var b1 = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), Math.PI );
  67. ok( ! a.equals( b1 ), "Passed!" );
  68. var b2 = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), -Math.PI );
  69. ok( ! a.equals( b2 ), "Passed!" );
  70. b1.multiply( b2 );
  71. ok( a.equals( b1 ), "Passed!" );
  72. });
  73. test( "setFromEuler/setFromQuaternion", function() {
  74. var angles = [ new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ) ];
  75. // ensure euler conversion to/from Quaternion matches.
  76. for( var i = 0; i < orders.length; i ++ ) {
  77. for( var j = 0; j < angles.length; j ++ ) {
  78. var eulers2 = new THREE.Euler().setFromQuaternion( new THREE.Quaternion().setFromEuler( new THREE.Euler( angles[j].x, angles[j].y, angles[j].z, orders[i] ) ), orders[i] );
  79. var newAngle = new THREE.Vector3( eulers2.x, eulers2.y, eulers2.z );
  80. ok( newAngle.distanceTo( angles[j] ) < 0.001, "Passed!" );
  81. }
  82. }
  83. });
  84. test( "setFromEuler/setFromRotationMatrix", function() {
  85. // ensure euler conversion for Quaternion matches that of Matrix4
  86. for( var i = 0; i < orders.length; i ++ ) {
  87. var q = new THREE.Quaternion().setFromEuler( eulerAngles, orders[i] );
  88. var m = new THREE.Matrix4().makeRotationFromEuler( eulerAngles, orders[i] );
  89. var q2 = new THREE.Quaternion().setFromRotationMatrix( m );
  90. ok( qSub( q, q2 ).length() < 0.001, "Passed!" );
  91. }
  92. });
  93. test( "normalize/length/lengthSq", function() {
  94. var a = new THREE.Quaternion( x, y, z, w );
  95. var b = new THREE.Quaternion( -x, -y, -z, -w );
  96. ok( a.length() != 1, "Passed!");
  97. ok( a.lengthSq() != 1, "Passed!");
  98. a.normalize();
  99. ok( a.length() == 1, "Passed!");
  100. ok( a.lengthSq() == 1, "Passed!");
  101. a.set( 0, 0, 0, 0 );
  102. ok( a.lengthSq() == 0, "Passed!");
  103. ok( a.length() == 0, "Passed!");
  104. a.normalize();
  105. ok( a.lengthSq() == 1, "Passed!");
  106. ok( a.length() == 1, "Passed!");
  107. });
  108. test( "inverse/conjugate", function() {
  109. var a = new THREE.Quaternion( x, y, z, w );
  110. // TODO: add better validation here.
  111. var b = a.clone().conjugate();
  112. ok( a.x == -b.x, "Passed!" );
  113. ok( a.y == -b.y, "Passed!" );
  114. ok( a.z == -b.z, "Passed!" );
  115. ok( a.w == b.w, "Passed!" );
  116. });
  117. test( "multiplyQuaternions/multiply", function() {
  118. var angles = [ new THREE.Euler( 1, 0, 0 ), new THREE.Euler( 0, 1, 0 ), new THREE.Euler( 0, 0, 1 ) ];
  119. var q1 = new THREE.Quaternion().setFromEuler( angles[0], "XYZ" );
  120. var q2 = new THREE.Quaternion().setFromEuler( angles[1], "XYZ" );
  121. var q3 = new THREE.Quaternion().setFromEuler( angles[2], "XYZ" );
  122. var q = new THREE.Quaternion().multiplyQuaternions( q1, q2 ).multiply( q3 );
  123. var m1 = new THREE.Matrix4().makeRotationFromEuler( angles[0], "XYZ" );
  124. var m2 = new THREE.Matrix4().makeRotationFromEuler( angles[1], "XYZ" );
  125. var m3 = new THREE.Matrix4().makeRotationFromEuler( angles[2], "XYZ" );
  126. var m = new THREE.Matrix4().multiplyMatrices( m1, m2 ).multiply( m3 );
  127. var qFromM = new THREE.Quaternion().setFromRotationMatrix( m );
  128. ok( qSub( q, qFromM ).length() < 0.001, "Passed!" );
  129. });
  130. test( "multiplyVector3", function() {
  131. var angles = [ new THREE.Euler( 1, 0, 0 ), new THREE.Euler( 0, 1, 0 ), new THREE.Euler( 0, 0, 1 ) ];
  132. // ensure euler conversion for Quaternion matches that of Matrix4
  133. for( var i = 0; i < orders.length; i ++ ) {
  134. for( var j = 0; j < angles.length; j ++ ) {
  135. var q = new THREE.Quaternion().setFromEuler( angles[j], orders[i] );
  136. var m = new THREE.Matrix4().makeRotationFromEuler( angles[j], orders[i] );
  137. var v0 = new THREE.Vector3(1, 0, 0);
  138. var qv = v0.clone().applyQuaternion( q );
  139. var mv = v0.clone().applyMatrix4( m );
  140. ok( qv.distanceTo( mv ) < 0.001, "Passed!" );
  141. }
  142. }
  143. });
  144. test( "equals", function() {
  145. var a = new THREE.Quaternion( x, y, z, w );
  146. var b = new THREE.Quaternion( -x, -y, -z, -w );
  147. ok( a.x != b.x, "Passed!" );
  148. ok( a.y != b.y, "Passed!" );
  149. ok( ! a.equals( b ), "Passed!" );
  150. ok( ! b.equals( a ), "Passed!" );
  151. a.copy( b );
  152. ok( a.x == b.x, "Passed!" );
  153. ok( a.y == b.y, "Passed!" );
  154. ok( a.equals( b ), "Passed!" );
  155. ok( b.equals( a ), "Passed!" );
  156. });
  157. function doSlerpObject( aArr, bArr, t ) {
  158. var a = new THREE.Quaternion().fromArray( aArr ),
  159. b = new THREE.Quaternion().fromArray( bArr ),
  160. c = new THREE.Quaternion().fromArray( aArr );
  161. c.slerp( b, t );
  162. return {
  163. equals: function( x, y, z, w, maxError ) {
  164. if ( maxError === undefined ) maxError = Number.EPSILON;
  165. return Math.abs( x - c.x ) <= maxError &&
  166. Math.abs( y - c.y ) <= maxError &&
  167. Math.abs( z - c.z ) <= maxError &&
  168. Math.abs( w - c.w ) <= maxError;
  169. },
  170. length: c.length(),
  171. dotA: c.dot( a ),
  172. dotB: c.dot( b )
  173. };
  174. };
  175. function doSlerpArray( a, b, t ) {
  176. var result = [ 0, 0, 0, 0 ];
  177. THREE.Quaternion.slerpFlat( result, 0, a, 0, b, 0, t );
  178. function arrDot( a, b ) {
  179. return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] +
  180. a[ 2 ] * b[ 2 ] + a[ 3 ] * b[ 3 ];
  181. }
  182. return {
  183. equals: function( x, y, z, w, maxError ) {
  184. if ( maxError === undefined ) maxError = Number.EPSILON;
  185. return Math.abs( x - result[ 0 ] ) <= maxError &&
  186. Math.abs( y - result[ 1 ] ) <= maxError &&
  187. Math.abs( z - result[ 2 ] ) <= maxError &&
  188. Math.abs( w - result[ 3 ] ) <= maxError;
  189. },
  190. length: Math.sqrt( arrDot( result, result ) ),
  191. dotA: arrDot( result, a ),
  192. dotB: arrDot( result, b )
  193. };
  194. }
  195. function slerpTestSkeleton( doSlerp, maxError ) {
  196. var a, b, result;
  197. a = [
  198. 0.6753410084407496,
  199. 0.4087830051091744,
  200. 0.32856700410659473,
  201. 0.5185120064806223,
  202. ];
  203. b = [
  204. 0.6602792107657797,
  205. 0.43647413932562285,
  206. 0.35119011210236006,
  207. 0.5001871596632682
  208. ];
  209. var maxNormError = 0;
  210. function isNormal( result ) {
  211. var normError = Math.abs( 1 - result.length );
  212. maxNormError = Math.max( maxNormError, normError );
  213. return normError <= maxError;
  214. }
  215. result = doSlerp( a, b, 0 );
  216. ok( result.equals(
  217. a[ 0 ], a[ 1 ], a[ 2 ], a[ 3 ], 0 ), "Exactly A @ t = 0" );
  218. result = doSlerp( a, b, 1 );
  219. ok( result.equals(
  220. b[ 0 ], b[ 1 ], b[ 2 ], b[ 3 ], 0 ), "Exactly B @ t = 1" );
  221. result = doSlerp( a, b, 0.5 );
  222. ok( Math.abs( result.dotA - result.dotB ) <= Number.EPSILON, "Symmetry at 0.5" );
  223. ok( isNormal( result ), "Approximately normal (at 0.5)" );
  224. result = doSlerp( a, b, 0.25 );
  225. ok( result.dotA > result.dotB, "Interpolating at 0.25" );
  226. ok( isNormal( result ), "Approximately normal (at 0.25)" );
  227. result = doSlerp( a, b, 0.75 );
  228. ok( result.dotA < result.dotB, "Interpolating at 0.75" );
  229. ok( isNormal( result ), "Approximately normal (at 0.75)" );
  230. var D = Math.SQRT1_2;
  231. result = doSlerp( [ 1, 0, 0, 0 ], [ 0, 0, 1, 0 ], 0.5 );
  232. ok( result.equals( D, 0, D, 0 ), "X/Z diagonal from axes" );
  233. ok( isNormal( result ), "Approximately normal (X/Z diagonal)" );
  234. result = doSlerp( [ 0, D, 0, D ], [ 0, -D, 0, D ], 0.5 );
  235. ok( result.equals( 0, 0, 0, 1 ), "W-Unit from diagonals" );
  236. ok( isNormal( result ), "Approximately normal (W-Unit)" );
  237. }
  238. test( "slerp", function() {
  239. slerpTestSkeleton( doSlerpObject, Number.EPSILON );
  240. } );
  241. test( "slerpFlat", function() {
  242. slerpTestSkeleton( doSlerpArray, Number.EPSILON );
  243. } );