quaternion_test.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /**
  2. * @license
  3. * Copyright The Closure Library Authors.
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.module('goog.vec.QuaternionTest');
  7. goog.setTestOnly();
  8. const Mat3 = goog.require('goog.vec.Mat3');
  9. const Mat4 = goog.require('goog.vec.Mat4');
  10. const Quaternion = goog.require('goog.vec.Quaternion');
  11. const Vec3 = goog.require('goog.vec.Vec3');
  12. const testSuite = goog.require('goog.testing.testSuite');
  13. const vec3f = goog.require('goog.vec.vec3f');
  14. testSuite({
  15. testCreateIdentityFloat32() {
  16. const q = Quaternion.createIdentityFloat32();
  17. assertElementsEquals([0, 0, 0, 1], q);
  18. },
  19. testInvert() {
  20. const q0 = Quaternion.createFloat32FromValues(1, 2, 3, 4);
  21. const q1 = Quaternion.createFloat32();
  22. Quaternion.invert(q0, q1);
  23. assertElementsRoughlyEqual([1, 2, 3, 4], q0, goog.vec.EPSILON);
  24. assertElementsRoughlyEqual(
  25. [-0.033333, -0.066666, -0.1, 0.133333], q1, goog.vec.EPSILON);
  26. },
  27. testConjugate() {
  28. const q0 = Quaternion.createFloat32FromValues(1, 2, 3, 4);
  29. const q1 = Quaternion.createFloat32();
  30. Quaternion.conjugate(q0, q1);
  31. assertElementsEquals([1, 2, 3, 4], q0);
  32. assertElementsEquals([-1, -2, -3, 4], q1);
  33. Quaternion.conjugate(q1, q1);
  34. assertElementsEquals([1, 2, 3, 4], q1);
  35. // Conjugate and inverse of a normalized quaternion should be equal.
  36. const q2 = Quaternion.createFloat32();
  37. const q3 = Quaternion.createFloat32();
  38. Quaternion.normalize(q0, q2);
  39. Quaternion.conjugate(q2, q2);
  40. Quaternion.normalize(q0, q3);
  41. Quaternion.invert(q3, q3);
  42. assertElementsRoughlyEqual(q2, q3, goog.vec.EPSILON);
  43. },
  44. testConcat() {
  45. const q0 = Quaternion.createFloat32FromValues(1, 2, 3, 4);
  46. const q1 = Quaternion.createFloat32FromValues(2, 3, 4, 5);
  47. const q2 = Quaternion.createFloat32();
  48. Quaternion.concat(q0, q1, q2);
  49. assertElementsEquals([12, 24, 30, 0], q2);
  50. Quaternion.concat(q0, q1, q0);
  51. assertElementsEquals([12, 24, 30, 0], q0);
  52. },
  53. testMakeIdentity() {
  54. const q = Quaternion.createFloat32FromValues(1, 2, 3, 4);
  55. Quaternion.makeIdentity(q);
  56. assertElementsEquals([0, 0, 0, 1], q);
  57. },
  58. testRotateX() {
  59. const q = Quaternion.createIdentityFloat32();
  60. Quaternion.rotateX(q, Math.PI / 2, q);
  61. const axis = Vec3.createFloat32();
  62. const angle = Quaternion.toAngleAxis(q, axis);
  63. assertElementsRoughlyEqual([1, 0, 0], axis, goog.vec.EPSILON);
  64. assertRoughlyEquals(Math.PI / 2, angle, goog.vec.EPSILON);
  65. },
  66. testRotateY() {
  67. const q = Quaternion.createIdentityFloat32();
  68. Quaternion.rotateY(q, Math.PI / 2, q);
  69. const axis = Vec3.createFloat32();
  70. const angle = Quaternion.toAngleAxis(q, axis);
  71. assertElementsRoughlyEqual([0, 1, 0], axis, goog.vec.EPSILON);
  72. assertRoughlyEquals(Math.PI / 2, angle, goog.vec.EPSILON);
  73. },
  74. testRotateZ() {
  75. const q = Quaternion.createIdentityFloat32();
  76. Quaternion.rotateZ(q, Math.PI / 2, q);
  77. const axis = Vec3.createFloat32();
  78. const angle = Quaternion.toAngleAxis(q, axis);
  79. assertElementsRoughlyEqual([0, 0, 1], axis, goog.vec.EPSILON);
  80. assertRoughlyEquals(Math.PI / 2, angle, goog.vec.EPSILON);
  81. },
  82. testTransformVec() {
  83. const q = Quaternion.createIdentityFloat32();
  84. Quaternion.rotateX(q, Math.PI / 2, q);
  85. const v0 = vec3f.setFromArray(vec3f.create(), [0, 0, 1]);
  86. const v1 = vec3f.create();
  87. Quaternion.transformVec(v0, q, v1);
  88. assertElementsRoughlyEqual([0, -1, 0], v1, goog.vec.EPSILON);
  89. },
  90. testSlerp() {
  91. const q0 = Quaternion.createFloat32FromValues(1, 2, 3, 4);
  92. const q1 = Quaternion.createFloat32FromValues(5, -6, 7, -8);
  93. const q2 = Quaternion.createFloat32();
  94. Quaternion.slerp(q0, q1, 0, q2);
  95. assertElementsEquals([5, -6, 7, -8], q2);
  96. Quaternion.normalize(q0, q0);
  97. Quaternion.normalize(q1, q1);
  98. Quaternion.slerp(q0, q0, .5, q2);
  99. assertElementsEquals(q0, q2);
  100. Quaternion.slerp(q0, q1, 0, q2);
  101. assertElementsEquals(q0, q2);
  102. Quaternion.slerp(q0, q1, 1, q2);
  103. if (q1[3] * q2[3] < 0) {
  104. Quaternion.negate(q2, q2);
  105. }
  106. assertElementsEquals(q1, q2);
  107. Quaternion.slerp(q0, q1, .3, q2);
  108. assertElementsRoughlyEqual(
  109. [-0.000501537327541, 0.4817612034640, 0.2398775270769, 0.842831337398],
  110. q2, goog.vec.EPSILON);
  111. Quaternion.slerp(q0, q1, .5, q2);
  112. assertElementsRoughlyEqual(
  113. [-0.1243045421171, 0.51879732466, 0.0107895780990, 0.845743047108], q2,
  114. goog.vec.EPSILON);
  115. Quaternion.slerp(q0, q1, .8, q0);
  116. assertElementsRoughlyEqual(
  117. [-0.291353561485, 0.506925588797, -0.3292443285721, 0.741442999653], q0,
  118. goog.vec.EPSILON);
  119. },
  120. testFromRotMatrix() {
  121. const m0 = Mat3.createFloat32FromValues(
  122. -0.408248, 0.8796528, -0.244016935, -0.4082482, 0.06315623, 0.9106836,
  123. 0.8164965, 0.47140452, 0.3333333);
  124. const q0 = Quaternion.createFloat32();
  125. Quaternion.fromRotationMatrix3(m0, q0);
  126. assertElementsRoughlyEqual(
  127. [
  128. 0.22094256606638, 0.53340203646030, 0.64777022739548,
  129. 0.497051689967954
  130. ],
  131. q0, goog.vec.EPSILON);
  132. const m1 = Mat3.createFloat32FromValues(
  133. -0.544310, 0, 0.838884, 0, 1, 0, -0.838884, 0, -0.544310);
  134. const q1 = Quaternion.createFloat32();
  135. Quaternion.fromRotationMatrix3(m1, q1);
  136. assertElementsRoughlyEqual(
  137. [0, -0.87872350215912, 0, 0.477331042289734], q1, goog.vec.EPSILON);
  138. const m2 = Mat4.createFloat32FromValues(
  139. -0.408248, 0.8796528, -0.244016935, 0, -0.4082482, 0.06315623,
  140. 0.9106836, 0, 0.8164965, 0.47140452, 0.3333333, 0, 0, 0, 0, 1);
  141. const q2 = Quaternion.createFloat32();
  142. Quaternion.fromRotationMatrix4(m2, q2);
  143. assertElementsRoughlyEqual(
  144. [
  145. 0.22094256606638, 0.53340203646030, 0.64777022739548,
  146. 0.497051689967954
  147. ],
  148. q2, goog.vec.EPSILON);
  149. const m3 = Mat4.createFloat32FromValues(
  150. -0.544310, 0, 0.838884, 0, 0, 1, 0, 0, -0.838884, 0, -0.544310, 0, 0, 0,
  151. 0, 1);
  152. const q3 = Quaternion.createFloat32();
  153. Quaternion.fromRotationMatrix4(m3, q3);
  154. assertElementsRoughlyEqual(
  155. [0, -0.87872350215912, 0, 0.477331042289734], q3, goog.vec.EPSILON);
  156. assertElementsRoughlyEqual(q0, q2, goog.vec.EPSILON);
  157. assertElementsRoughlyEqual(q1, q3, goog.vec.EPSILON);
  158. },
  159. testToRotMatrix() {
  160. const q0 = Quaternion.createFloat32FromValues(
  161. 0.22094256606638, 0.53340203646030, 0.64777022739548,
  162. 0.497051689967954);
  163. const m0 = Mat3.createFloat32();
  164. Quaternion.toRotationMatrix3(q0, m0);
  165. assertElementsRoughlyEqual(
  166. [
  167. -0.408248, 0.8796528, -0.244016935, -0.4082482, 0.06315623, 0.9106836,
  168. 0.8164965, 0.47140452, 0.3333333
  169. ],
  170. m0, goog.vec.EPSILON);
  171. const m1 = Mat4.createFloat32();
  172. Quaternion.toRotationMatrix4(q0, m1);
  173. assertElementsRoughlyEqual(
  174. [
  175. -0.408248, 0.8796528, -0.244016935, 0, -0.4082482, 0.06315623,
  176. 0.9106836, 0, 0.8164965, 0.47140452, 0.3333333, 0, 0, 0, 0, 1
  177. ],
  178. m1, goog.vec.EPSILON);
  179. },
  180. testToAngleAxis() {
  181. // Test the identity rotation.
  182. const q0 = Quaternion.createFloat32FromValues(0, 0, 0, 1);
  183. const axis = Vec3.createFloat32();
  184. let angle = Quaternion.toAngleAxis(q0, axis);
  185. assertRoughlyEquals(0.0, angle, goog.vec.EPSILON);
  186. assertElementsRoughlyEqual([1, 0, 0], axis, goog.vec.EPSILON);
  187. // Check equivalent representations of the same rotation.
  188. Quaternion.setFromValues(
  189. q0, -0.288675032, 0.622008682, -0.17254543, 0.70710678);
  190. angle = Quaternion.toAngleAxis(q0, axis);
  191. assertRoughlyEquals(Math.PI / 2, angle, goog.vec.EPSILON);
  192. assertElementsRoughlyEqual(
  193. [-0.408248, 0.8796528, -0.244016], axis, goog.vec.EPSILON);
  194. // The polar opposite unit quaternion is the same rotation, so we
  195. // check that the negated quaternion yields the negated angle and axis.
  196. Quaternion.negate(q0, q0);
  197. angle = Quaternion.toAngleAxis(q0, axis);
  198. assertRoughlyEquals(-Math.PI / 2, angle, goog.vec.EPSILON);
  199. assertElementsRoughlyEqual(
  200. [0.408248, -0.8796528, 0.244016], axis, goog.vec.EPSILON);
  201. // Verify that the inverse rotation yields the inverse axis.
  202. Quaternion.conjugate(q0, q0);
  203. angle = Quaternion.toAngleAxis(q0, axis);
  204. assertRoughlyEquals(-Math.PI / 2, angle, goog.vec.EPSILON);
  205. assertElementsRoughlyEqual(
  206. [-0.408248, 0.8796528, -0.244016], axis, goog.vec.EPSILON);
  207. },
  208. testFromAngleAxis() {
  209. // Test identity rotation (zero angle or multiples of TWO_PI).
  210. let angle = 0.0;
  211. const axis = Vec3.createFloat32FromValues(-0.408248, 0.8796528, -0.244016);
  212. const q0 = Quaternion.createFloat32();
  213. Quaternion.fromAngleAxis(angle, axis, q0);
  214. assertElementsRoughlyEqual([0, 0, 0, 1], q0, goog.vec.EPSILON);
  215. angle = 4 * Math.PI;
  216. Quaternion.fromAngleAxis(angle, axis, q0);
  217. assertElementsRoughlyEqual([0, 0, 0, 1], q0, goog.vec.EPSILON);
  218. // General test of various rotations around axes of different lengths.
  219. angle = Math.PI / 2;
  220. Quaternion.fromAngleAxis(angle, axis, q0);
  221. assertElementsRoughlyEqual(
  222. [-0.288675032, 0.622008682, -0.17254543, 0.70710678], q0,
  223. goog.vec.EPSILON);
  224. // Angle multiples of TWO_PI with a scaled axis should be the same.
  225. angle += 4 * Math.PI;
  226. Vec3.scale(axis, 7.0, axis);
  227. Quaternion.fromAngleAxis(angle, axis, q0);
  228. assertElementsRoughlyEqual(
  229. [-0.288675032, 0.622008682, -0.17254543, 0.70710678], q0,
  230. goog.vec.EPSILON);
  231. Vec3.setFromValues(axis, 1, 5, 8);
  232. Quaternion.fromAngleAxis(angle, axis, q0);
  233. assertElementsRoughlyEqual(
  234. [0.074535599, 0.372677996, 0.596284794, 0.70710678], q0,
  235. goog.vec.EPSILON);
  236. // Check equivalent representations of the same rotation.
  237. angle = Math.PI / 5;
  238. Vec3.setFromValues(axis, 5, -2, -10);
  239. Quaternion.fromAngleAxis(angle, axis, q0);
  240. assertElementsRoughlyEqual(
  241. [0.136037146, -0.0544148586, -0.27207429, 0.951056516], q0,
  242. goog.vec.EPSILON);
  243. // The negated angle and axis should yield the same rotation.
  244. angle = -Math.PI / 5;
  245. Vec3.negate(axis, axis);
  246. Quaternion.fromAngleAxis(angle, axis, q0);
  247. assertElementsRoughlyEqual(
  248. [0.136037146, -0.0544148586, -0.27207429, 0.951056516], q0,
  249. goog.vec.EPSILON);
  250. },
  251. });