PageRenderTime 46ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Datatypes/Quaternion.cs

#
C# | 979 lines | 548 code | 90 blank | 341 comment | 20 complexity | 904220030f204be120611f8cd6a1a4ba MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Diagnostics;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Runtime.InteropServices;
  6. using Delta.Utilities.Helpers;
  7. using NUnit.Framework;
  8. namespace Delta.Utilities.Datatypes
  9. {
  10. /// <summary>
  11. /// Quaternion, contains X, Y, Z and W values. Mostly used for cameras.
  12. /// Note: Sometimes used as a replacement for Vector4 because we only have
  13. /// Point for Vector2 and Vector for Vector3.
  14. /// </summary>
  15. [Serializable]
  16. [StructLayout(LayoutKind.Explicit)]
  17. [DebuggerDisplay("Quaternion(Vector=({Vector.X}, {Vector.Y}, {Vector.Z})" +
  18. ", W={W})")]
  19. public struct Quaternion : ISaveLoadBinary, IEquatable<Quaternion>
  20. {
  21. #region Constants
  22. /// <summary>
  23. /// Represents the size in bytes of a Quaternion (4 * 4 = 16 bytes).
  24. /// </summary>
  25. public const int DataSize = 4 * 4;
  26. #endregion
  27. #region Static
  28. /// <summary>
  29. /// Returns a identity quaternion (all values 0, except W is 1)
  30. /// </summary>
  31. public static readonly Quaternion Identity =
  32. new Quaternion(0, 0, 0, 1);
  33. #endregion
  34. #region Framework Union Defines (Public)
  35. #endregion
  36. #region Public
  37. /// <summary>
  38. /// X value of the Vector.
  39. /// </summary>
  40. [FieldOffset(0)]
  41. public float X;
  42. /// <summary>
  43. /// Y value of the Vector.
  44. /// </summary>
  45. [FieldOffset(4)]
  46. public float Y;
  47. /// <summary>
  48. /// Z value of the Vector.
  49. /// </summary>
  50. [FieldOffset(8)]
  51. public float Z;
  52. /// <summary>
  53. /// W value of the Vector.
  54. /// </summary>
  55. [FieldOffset(12)]
  56. public float W;
  57. /// <summary>
  58. /// We can also use X, Y, Z as a vector (same data)
  59. /// </summary>
  60. [FieldOffset(0)]
  61. public Vector Vector;
  62. #endregion
  63. #region Constructor
  64. /// <summary>
  65. /// Create quaternion
  66. /// </summary>
  67. /// <param name="scalarPart">scalarPart</param>
  68. /// <param name="vectorPart">vectorPart</param>
  69. public Quaternion(Vector vectorPart, float scalarPart)
  70. : this()
  71. {
  72. Vector = vectorPart;
  73. W = scalarPart;
  74. }
  75. /// <summary>
  76. /// Create quaternion
  77. /// </summary>
  78. /// <param name="setW">setW</param>
  79. /// <param name="setX">setX</param>
  80. /// <param name="setY">setY</param>
  81. /// <param name="setZ">setZ</param>
  82. public Quaternion(float setX, float setY, float setZ, float setW)
  83. : this()
  84. {
  85. X = setX;
  86. Y = setY;
  87. Z = setZ;
  88. W = setW;
  89. }
  90. #endregion
  91. #region Equals
  92. /// <summary>
  93. /// Check if another quaternion has the same values.
  94. /// </summary>
  95. /// <param name="other">Other quaternion to compare against.</param>
  96. /// <returns>True if the other quaternion has the same values.</returns>
  97. public bool Equals(Quaternion other)
  98. {
  99. return this == other;
  100. }
  101. /// <summary>
  102. /// Check if another object is an quaternion and has the same values.
  103. /// </summary>
  104. /// <param name="obj">Other object to compare against.</param>
  105. /// <returns>True if the other quaternion has the same values.</returns>
  106. public override bool Equals(object obj)
  107. {
  108. return obj is Quaternion &&
  109. Equals((Quaternion)obj);
  110. }
  111. #endregion
  112. #region GetHashCode
  113. /// <summary>
  114. /// Get hash code
  115. /// </summary>
  116. /// <returns>Hash code</returns>
  117. public override int GetHashCode()
  118. {
  119. return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode() ^
  120. W.GetHashCode();
  121. }
  122. #endregion
  123. #region ToString
  124. /// <summary>
  125. /// To string
  126. /// </summary>
  127. /// <returns>string</returns>
  128. public override string ToString()
  129. {
  130. CultureInfo culture = CultureInfo.CurrentCulture;
  131. return "{X:" + X.ToString(culture) +
  132. " Y:" + Y.ToString(culture) +
  133. " Z:" + Z.ToString(culture) +
  134. " W:" + W.ToString(culture) + "}";
  135. }
  136. #endregion
  137. #region Conjugate
  138. /// <summary>
  139. /// Conjugate
  140. /// </summary>
  141. public void Conjugate()
  142. {
  143. X = -X;
  144. Y = -Y;
  145. Z = -Z;
  146. }
  147. #endregion
  148. #region CreateFromAxisAngle
  149. /// <summary>
  150. /// Create from axis angle
  151. /// </summary>
  152. /// <param name="angle">angle</param>
  153. /// <param name="axis">axis</param>
  154. /// <returns>Quaternion with the rotation around the axis</returns>
  155. public static Quaternion CreateFromAxisAngle(Vector axis, float angle)
  156. {
  157. // Create the resulting quaternion by applying the angle
  158. return new Quaternion(
  159. MathHelper.Sin(angle * 0.5f) * axis,
  160. MathHelper.Cos(angle * 0.5f));
  161. }
  162. #endregion
  163. #region CreateFromYawPitchRoll
  164. /// <summary>
  165. /// Create from yaw pitch roll
  166. /// </summary>
  167. /// <param name="pitch">pitch</param>
  168. /// <param name="roll">roll</param>
  169. /// <param name="yaw">yaw</param>
  170. /// <returns>Quaternion with the rotation around yaw, pitch, roll</returns>
  171. public static Quaternion CreateFromYawPitchRoll(float yaw, float pitch,
  172. float roll)
  173. {
  174. //Note: Still needs to be refactored!
  175. yaw *= 0.5f;
  176. pitch *= 0.5f;
  177. roll *= 0.5f;
  178. float rollSin = MathHelper.Sin(roll);
  179. float rollCos = MathHelper.Cos(roll);
  180. float pitchSin = MathHelper.Sin(pitch);
  181. float pitchCos = MathHelper.Cos(pitch);
  182. float yawSin = MathHelper.Sin(yaw);
  183. float yawCos = MathHelper.Cos(yaw);
  184. // create the resulting quaternion
  185. Quaternion result = new Quaternion(
  186. // X
  187. ((yawCos * pitchSin) * rollCos) + ((yawSin * pitchCos) * rollSin),
  188. // Y
  189. ((yawCos * pitchCos) * rollSin) - ((yawSin * pitchSin) * rollCos),
  190. // Z
  191. ((yawSin * pitchCos) * rollCos) - ((yawCos * pitchSin) * rollSin),
  192. // W
  193. ((yawCos * pitchCos) * rollCos) + ((yawSin * pitchSin) * rollSin));
  194. return result;
  195. }
  196. #endregion
  197. #region Concatenate
  198. /// <summary>
  199. /// Concatenate
  200. /// </summary>
  201. /// <param name="value1">Value 1</param>
  202. /// <param name="value2">Value 2</param>
  203. /// <returns>Concatenated quaternion</returns>
  204. public static Quaternion Concatenate(Quaternion value1, Quaternion value2)
  205. {
  206. float x1 = value1.X;
  207. float y1 = value1.Y;
  208. float z1 = value1.Z;
  209. float w1 = value1.W;
  210. float x2 = value2.X;
  211. float y2 = value2.Y;
  212. float z2 = value2.Z;
  213. float w2 = value2.W;
  214. float num12 = (y2 * z1) - (z2 * y1);
  215. float num11 = (z2 * x1) - (x2 * z1);
  216. float num10 = (x2 * y1) - (y2 * x1);
  217. float num9 = (x2 * x1) + (y2 * y1) + (z2 * z1);
  218. // create the resulting quaternion
  219. return new Quaternion(
  220. // X
  221. (x2 * w1) + (x1 * w2) + num12,
  222. // Y
  223. (y2 * w1) + (y1 * w2) + num11,
  224. // Z
  225. (z2 * w1) + (z1 * w2) + num10,
  226. // W
  227. (w2 * w1) - num9);
  228. }
  229. #endregion
  230. #region CreateFromRotationMatrix
  231. /// <summary>
  232. /// Create from rotation matrix
  233. /// </summary>
  234. /// <param name="matrix">matrix</param>
  235. /// <returns>Quaternion</returns>
  236. public static Quaternion CreateFromRotationMatrix(Matrix matrix)
  237. {
  238. float num8 = matrix.M11 + matrix.M22 + matrix.M33;
  239. Quaternion quaternion = new Quaternion();
  240. if (num8 > 0f)
  241. {
  242. float num = MathHelper.Sqrt(num8 + 1f);
  243. quaternion.W = num * 0.5f;
  244. num = 0.5f / num;
  245. quaternion.X = (matrix.M23 - matrix.M32) * num;
  246. quaternion.Y = (matrix.M31 - matrix.M13) * num;
  247. quaternion.Z = (matrix.M12 - matrix.M21) * num;
  248. return quaternion;
  249. }
  250. if ((matrix.M11 >= matrix.M22) && (matrix.M11 >= matrix.M33))
  251. {
  252. float num7 = MathHelper.Sqrt(1f + matrix.M11 - matrix.M22 - matrix.M33);
  253. float num4 = 0.5f / num7;
  254. quaternion.X = 0.5f * num7;
  255. quaternion.Y = (matrix.M12 + matrix.M21) * num4;
  256. quaternion.Z = (matrix.M13 + matrix.M31) * num4;
  257. quaternion.W = (matrix.M23 - matrix.M32) * num4;
  258. return quaternion;
  259. }
  260. if (matrix.M22 > matrix.M33)
  261. {
  262. float num6 = MathHelper.Sqrt(1f + matrix.M22 - matrix.M11 - matrix.M33);
  263. float num3 = 0.5f / num6;
  264. quaternion.X = (matrix.M21 + matrix.M12) * num3;
  265. quaternion.Y = 0.5f * num6;
  266. quaternion.Z = (matrix.M32 + matrix.M23) * num3;
  267. quaternion.W = (matrix.M31 - matrix.M13) * num3;
  268. return quaternion;
  269. }
  270. float num5 = MathHelper.Sqrt(1f + matrix.M33 - matrix.M11 - matrix.M22);
  271. float num2 = 0.5f / num5;
  272. quaternion.X = (matrix.M31 + matrix.M13) * num2;
  273. quaternion.Y = (matrix.M32 + matrix.M23) * num2;
  274. quaternion.Z = 0.5f * num5;
  275. quaternion.W = (matrix.M12 - matrix.M21) * num2;
  276. return quaternion;
  277. }
  278. #endregion
  279. #region Length
  280. /// <summary>
  281. /// Length, also called Magnitude for Quaternions sometimes.
  282. /// </summary>
  283. /// <returns>Length</returns>
  284. public float Length()
  285. {
  286. return MathHelper.Sqrt(X * X + Y * Y + Z * Z + W * W);
  287. }
  288. #endregion
  289. #region LengthSquared
  290. /// <summary>
  291. /// Length squared, also called SquareMagnitude for Quaternions sometimes.
  292. /// </summary>
  293. /// <returns>Squared length</returns>
  294. public float LengthSquared()
  295. {
  296. return X * X + Y * Y + Z * Z + W * W;
  297. }
  298. #endregion
  299. #region Normalize
  300. /// <summary>
  301. /// Normalize this quaternion.
  302. /// </summary>
  303. public void Normalize()
  304. {
  305. float length = Length();
  306. if (length < float.Epsilon ||
  307. MathHelper.Abs(length - 1.0f) < float.Epsilon)
  308. {
  309. return;
  310. }
  311. float invertedLength = 1.0f / length;
  312. Vector *= invertedLength;
  313. W *= invertedLength;
  314. }
  315. #endregion
  316. #region Invert
  317. /// <summary>
  318. /// Invert this quaternion
  319. /// </summary>
  320. public void Invert()
  321. {
  322. float lengthSquared = LengthSquared();
  323. if (lengthSquared < float.Epsilon)
  324. {
  325. return;
  326. }
  327. float invertedLengthSquared = -1.0f / lengthSquared;
  328. Vector *= invertedLengthSquared;
  329. W *= -invertedLengthSquared;
  330. }
  331. #endregion
  332. #region Lerp
  333. /// <summary>
  334. /// Used to interpolate between two quaternions. This is not just
  335. /// quat1*amount+quat2*(1-amount) because we need to rotate correctly.
  336. /// </summary>
  337. /// <param name="quat1">Value 1</param>
  338. /// <param name="quat2">Value 2</param>
  339. /// <param name="amount">Interpolation amount</param>
  340. /// <returns>Interpolated quaternion</returns>
  341. public static Quaternion Lerp(Quaternion quat1, Quaternion quat2,
  342. float amount)
  343. {
  344. float cos = Dot(quat1, quat2);
  345. float sin = MathHelper.Sqrt(MathHelper.Abs(1.0f - cos * cos));
  346. if (MathHelper.Abs(sin) < float.Epsilon)
  347. {
  348. return quat1;
  349. }
  350. float angle = MathHelper.Atan(sin, cos);
  351. float invertedSin = 1.0f / sin;
  352. float c0 = MathHelper.Sin((1 - amount) * angle) * invertedSin;
  353. float c1 = MathHelper.Sin(amount * angle) * invertedSin;
  354. return (quat1 * c0) + (quat2 * c1);
  355. }
  356. #endregion
  357. #region Dot
  358. /// <summary>
  359. /// Dot product of two quaternions
  360. /// </summary>
  361. /// <param name="quaternion1">Quaternion 1</param>
  362. /// <param name="quaternion2">Quaternion 2</param>
  363. /// <returns>Dot product result</returns>
  364. public static float Dot(Quaternion quaternion1, Quaternion quaternion2)
  365. {
  366. return (quaternion1.X * quaternion2.X) +
  367. (quaternion1.Y * quaternion2.Y) +
  368. (quaternion1.Z * quaternion2.Z) +
  369. (quaternion1.W * quaternion2.W);
  370. }
  371. #endregion
  372. #region Operators
  373. #region Equation
  374. /// <summary>
  375. /// Operator for equality
  376. /// </summary>
  377. /// <param name="quaternion1">Quaternion 1</param>
  378. /// <param name="quaternion2">Quaternion 2</param>
  379. /// <returns>True if both quaternions are equal</returns>
  380. public static bool operator ==(Quaternion quaternion1,
  381. Quaternion quaternion2)
  382. {
  383. return (quaternion1.X == quaternion2.X) &&
  384. (quaternion1.Y == quaternion2.Y) &&
  385. (quaternion1.Z == quaternion2.Z) &&
  386. (quaternion1.W == quaternion2.W);
  387. }
  388. /// <summary>
  389. /// Operator for inequality
  390. /// </summary>
  391. /// <param name="quaternion1">Quaternion 1</param>
  392. /// <param name="quaternion2">Quaternion 2</param>
  393. /// <returns>True if both quaternions are not equal</returns>
  394. public static bool operator !=(Quaternion quaternion1,
  395. Quaternion quaternion2)
  396. {
  397. return (quaternion1.X != quaternion2.X) ||
  398. (quaternion1.Y != quaternion2.Y) ||
  399. (quaternion1.Z != quaternion2.Z) ||
  400. (quaternion1.W != quaternion2.W);
  401. }
  402. #endregion
  403. #region Negation
  404. /// <summary>
  405. /// Operator for unary negation
  406. /// </summary>
  407. /// <param name="value">Quaternion to negate</param>
  408. /// <returns>Negated value</returns>
  409. public static Quaternion operator -(Quaternion value)
  410. {
  411. return new Quaternion(-value.Vector, -value.W);
  412. }
  413. #endregion
  414. #region Addition
  415. /// <summary>
  416. /// Operator for addition
  417. /// </summary>
  418. /// <param name="value1">Quaternion 1</param>
  419. /// <param name="value2">Quaternion 2</param>
  420. /// <returns>Added quaternion</returns>
  421. public static Quaternion operator +(Quaternion value1, Quaternion value2)
  422. {
  423. return new Quaternion(value1.Vector + value2.Vector,
  424. value1.W + value2.W);
  425. }
  426. #endregion
  427. #region Multiply
  428. /// <summary>
  429. /// Operator for multiply
  430. /// </summary>
  431. /// <param name="value1">Quaternion 1</param>
  432. /// <param name="value2">Quaternion 2</param>
  433. /// <returns>Multiplied quaternion</returns>
  434. public static Quaternion operator *(Quaternion value1, Quaternion value2)
  435. {
  436. return new Quaternion(Vector.Cross(value1.Vector, value2.Vector) +
  437. (value1.W * value2.Vector) + (value1.Vector * value2.W),
  438. (value1.W * value2.W) - Vector.Dot(value1.Vector, value2.Vector));
  439. }
  440. /// <summary>
  441. /// Operator for multiply
  442. /// </summary>
  443. /// <param name="value">Quaternion to multiply with</param>
  444. /// <param name="scalar">Scalar to multiply with</param>
  445. /// <returns>Multiplied quaternion</returns>
  446. public static Quaternion operator *(Quaternion value, float scalar)
  447. {
  448. return new Quaternion(value.Vector * scalar, value.W * scalar);
  449. }
  450. /// <summary>
  451. /// Operator for multiply
  452. /// </summary>
  453. /// <param name="scalar">Scalar to multiply with</param>
  454. /// <param name="value">Quaternion to multiply with</param>
  455. /// <returns>Multiplied quaternion</returns>
  456. public static Quaternion operator *(float scalar, Quaternion value)
  457. {
  458. return new Quaternion(value.Vector * scalar, value.W * scalar);
  459. }
  460. #endregion
  461. #region Division
  462. /// <summary>
  463. /// Operator for division
  464. /// </summary>
  465. /// <param name="value1">Quaternion 1</param>
  466. /// <param name="value2">Quaternion 2</param>
  467. /// <returns>Divided quaternion</returns>
  468. public static Quaternion operator /(Quaternion value1, Quaternion value2)
  469. {
  470. value2.Invert();
  471. return value1 * value2;
  472. }
  473. /// <summary>
  474. /// Operator for division
  475. /// </summary>
  476. /// <param name="value">Quaternion</param>
  477. /// <param name="scalar">Scalar to divide through</param>
  478. /// <returns>Divided quaternion</returns>
  479. public static Quaternion operator /(Quaternion value, float scalar)
  480. {
  481. float invertedScalar = 1.0f / scalar;
  482. return new Quaternion(value.Vector * invertedScalar,
  483. value.W * invertedScalar);
  484. }
  485. #endregion
  486. #region Cast operators
  487. /// <summary>
  488. /// Operator to explicitly convert a quaternion to a matrix.
  489. /// </summary>
  490. /// <param name="quat">Quaternion to convert</param>
  491. /// <returns>Matrix from quaternion</returns>
  492. public static explicit operator Matrix(Quaternion quat)
  493. {
  494. Matrix ret = Matrix.Identity;
  495. // A cast to a matrix only works with normalized quaternions!
  496. quat.Normalize();
  497. float xs = 2.0f * quat.X;
  498. float ys = 2.0f * quat.Y;
  499. float zs = 2.0f * quat.Z;
  500. float wx = quat.W * xs;
  501. float wy = quat.W * ys;
  502. float wz = quat.W * zs;
  503. float xx = quat.X * xs;
  504. float xy = quat.X * ys;
  505. float xz = quat.X * zs;
  506. float yy = quat.Y * ys;
  507. float yz = quat.Y * zs;
  508. float zz = quat.Z * zs;
  509. ret.M11 = 1.0f - yy - zz;
  510. ret.M21 = xy - wz;
  511. ret.M31 = xz + wy;
  512. ret.M41 = 0.0f;
  513. ret.M12 = xy + wz;
  514. ret.M22 = 1.0f - xx - zz;
  515. ret.M32 = yz - wx;
  516. ret.M42 = 0.0f;
  517. ret.M13 = xz - wy;
  518. ret.M23 = yz + wx;
  519. ret.M33 = 1.0f - xx - yy;
  520. ret.M43 = 0.0f;
  521. return ret;
  522. }
  523. #endregion
  524. #endregion
  525. #region ISaveLoadBinary Methods
  526. #region Save
  527. /// <summary>
  528. /// Saves the point to a stream.
  529. /// </summary>
  530. /// <param name="writer">The stream that will be used.</param>
  531. public void Save(BinaryWriter writer)
  532. {
  533. writer.Write(X);
  534. writer.Write(Y);
  535. writer.Write(Z);
  536. writer.Write(W);
  537. }
  538. #endregion
  539. #region Load
  540. /// <summary>
  541. /// Load the point values from a stream.
  542. /// </summary>
  543. /// <param name="reader">The stream that will be used.</param>
  544. public void Load(BinaryReader reader)
  545. {
  546. X = reader.ReadSingle();
  547. Y = reader.ReadSingle();
  548. Z = reader.ReadSingle();
  549. W = reader.ReadSingle();
  550. }
  551. #endregion
  552. #endregion
  553. /// <summary>
  554. /// Tests
  555. /// </summary>
  556. internal class QuaternionTests
  557. {
  558. #region Helpers
  559. /// <summary>
  560. /// Length test
  561. ///
  562. /// Calculation:
  563. /// 10 * 10 = 100
  564. /// + 40 * 40 = 1600
  565. /// + 4 * 4 = 16
  566. /// + 2 * 2 = 4
  567. /// ----------------
  568. /// _____
  569. /// -/1720'
  570. /// 41.47288
  571. /// </summary>
  572. public static void Length()
  573. {
  574. Quaternion quat = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  575. Assert.NearlyEqual(quat.Length(), 41.47288f);
  576. }
  577. #endregion
  578. #region Equality (Static)
  579. /// <summary>
  580. /// Equality test
  581. /// </summary>
  582. [Test]
  583. public static void Equality()
  584. {
  585. Quaternion quat = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  586. Quaternion quat1 = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  587. Quaternion quat2 = new Quaternion(new Vector(5f, 0f, 43f), 21f);
  588. // using the Equals methods
  589. Assert.True(quat.Equals(quat));
  590. Assert.True(quat.Equals(quat1));
  591. Assert.True(quat2.Equals(quat2));
  592. Assert.True(quat.Equals((object)quat));
  593. Assert.False(quat.Equals(quat2));
  594. Assert.False(quat.Equals(1234));
  595. // using the operators
  596. Assert.True(quat1 == quat);
  597. Assert.True(quat == quat1);
  598. Assert.True(quat1 != quat2);
  599. Assert.False(quat1 == quat2);
  600. }
  601. #endregion
  602. #region ArithmeticOperators (Static)
  603. /// <summary>
  604. /// ArithmeticOperators test
  605. /// </summary>
  606. [Test]
  607. public static void ArithmeticOperators()
  608. {
  609. Quaternion quat1 = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  610. Quaternion quat2 = new Quaternion(new Vector(5f, 1f, 43f), 21f);
  611. // Multiply with scalar
  612. Quaternion result = quat1 * 3f;
  613. Assert.NearlyEqual(result.X, 30f);
  614. Assert.NearlyEqual(result.Y, 120f);
  615. Assert.NearlyEqual(result.Z, 12f);
  616. Assert.NearlyEqual(result.W, 6f);
  617. result = 3f * quat1;
  618. Assert.NearlyEqual(result.X, 30f);
  619. Assert.NearlyEqual(result.Y, 120f);
  620. Assert.NearlyEqual(result.Z, 12f);
  621. Assert.NearlyEqual(result.W, 6f);
  622. // Multiply two quaternions
  623. // vec1 = Cross(Vector1, Vector2)
  624. // vec1.X = (40 * 43) - ( 4 * 1) = 1716
  625. // vec1.Y = ( 4 * 5) - (10 * 43) = -410
  626. // vec1.Z = (10 * 1) - (40 * 5) = -190
  627. // vec2 = (2 * {5, 1, 43}) = {10, 1, 86}
  628. // vec3 = (21 * {10, 40, 4}) = {210, 840, 84}
  629. // X = vec1.X + vec2.X + vec3.X = 1936
  630. // Y = vec1.Y + vec2.Y + vec3.Y = 432
  631. // Z = vec1.Z + vec2.Z + vec3.Z = -20
  632. // W = (W1 * W2) - Dot(Vector1, Vector2)
  633. // W = 42 - (10 * 5 + 40 * 1 + 4 * 43) = -220
  634. result = quat1 * quat2;
  635. Assert.NearlyEqual(result.X, 1936f);
  636. Assert.NearlyEqual(result.Y, 432f);
  637. Assert.NearlyEqual(result.Z, -20f);
  638. Assert.NearlyEqual(result.W, -220);
  639. // Negation of a quaternion
  640. result = -quat1;
  641. Assert.NearlyEqual(result.X, -10f);
  642. Assert.NearlyEqual(result.Y, -40f);
  643. Assert.NearlyEqual(result.Z, -4f);
  644. Assert.NearlyEqual(result.W, -2f);
  645. // Addition of two quternions
  646. result = quat1 + quat2;
  647. Assert.NearlyEqual(result.X, 15f);
  648. Assert.NearlyEqual(result.Y, 41f);
  649. Assert.NearlyEqual(result.Z, 47f);
  650. Assert.NearlyEqual(result.W, 23f);
  651. // Division with scalar
  652. // invScalar = 1 / 4 = 0.25
  653. // XYZ = {10, 40, 4} * invScalar = {2.5, 10, 1}
  654. // W = 2 * invScalar = 0.5
  655. result = quat1 / 4f;
  656. Assert.NearlyEqual(result.X, 2.5f);
  657. Assert.NearlyEqual(result.Y, 10f);
  658. Assert.NearlyEqual(result.Z, 1f);
  659. Assert.NearlyEqual(result.W, 0.5f);
  660. }
  661. #endregion
  662. #region LengthSquared (Static)
  663. /// <summary>
  664. /// LengthSquared test
  665. ///
  666. /// Calculation:
  667. /// 10 * 10 = 100
  668. /// + 40 * 40 = 1600
  669. /// + 4 * 4 = 16
  670. /// + 2 * 2 = 4
  671. /// ----------------
  672. /// 1720
  673. /// </summary>
  674. [Test]
  675. public static void LengthSquared()
  676. {
  677. Quaternion quat = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  678. Assert.NearlyEqual(quat.LengthSquared(), 1720);
  679. }
  680. #endregion
  681. #region Normalize (Static)
  682. /// <summary>
  683. /// Normalize test
  684. ///
  685. /// Calculation:
  686. /// length = 41.47288 (see test above)
  687. /// invLength = 1 / 41.47288 = 0.02411
  688. /// X = 10 * invLength = 0.2411
  689. /// Y = 40 * invLength = 0.9644
  690. /// Z = 4 * invLength = 0.09644
  691. /// W = 2 * invLength = 0.04822
  692. /// </summary>
  693. [Test]
  694. public static void Normalize()
  695. {
  696. Quaternion quat = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  697. quat.Normalize();
  698. Assert.NearlyEqual(quat.X, 0.2411f);
  699. Assert.NearlyEqual(quat.Y, 0.9644f);
  700. Assert.NearlyEqual(quat.Z, 0.09644f);
  701. Assert.NearlyEqual(quat.W, 0.04822f);
  702. }
  703. #endregion
  704. #region Invert (Static)
  705. /// <summary>
  706. /// Invert test
  707. ///
  708. /// Calculation:
  709. /// length = 1720 (see test above)
  710. ///
  711. /// invLength = -1 / 1720 = -0.0005814
  712. /// X = 10 * invLength = -0.005814
  713. /// Y = 40 * invLength = -0.023256
  714. /// Z = 4 * invLength = -0.0023256
  715. /// W = 2 * -invLength = 0.0011628
  716. /// </summary>
  717. [Test]
  718. public static void Invert()
  719. {
  720. Quaternion quat = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  721. quat.Invert();
  722. Assert.NearlyEqual(quat.X, -0.005814f);
  723. Assert.NearlyEqual(quat.Y, -0.023256f);
  724. Assert.NearlyEqual(quat.Z, -0.0023256f);
  725. Assert.NearlyEqual(quat.W, 0.0011628f);
  726. }
  727. #endregion
  728. #region Dot (Static)
  729. /// <summary>
  730. /// Dot test
  731. ///
  732. /// Calculation:
  733. /// (x1 * x2) + (y1 * y2) + (z1 * z2) + (w1 * w2)
  734. /// 10 * 5 = 50
  735. /// + 40 * 2 = 80
  736. /// + 4 * 50 = 200
  737. /// + 2 * 12 = 24
  738. /// ---------------
  739. /// 354
  740. /// </summary>
  741. [Test]
  742. public static void Dot()
  743. {
  744. Quaternion quat1 = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  745. Quaternion quat2 = new Quaternion(new Vector(5f, 2f, 50f), 12f);
  746. float dot = Quaternion.Dot(quat1, quat2);
  747. Assert.NearlyEqual(dot, 354f);
  748. }
  749. #endregion
  750. #region Lerp (Static)
  751. /// <summary>
  752. /// Lerp test, will just interpolate two quaternions.
  753. /// </summary>
  754. [Test]
  755. public static void Lerp()
  756. {
  757. Quaternion quat1 = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  758. Quaternion quat2 = new Quaternion(new Vector(5f, 2f, 50f), 12f);
  759. Quaternion lerp = Quaternion.Lerp(quat1, quat2, 0.5f);
  760. Assert.NearlyEqual(lerp.X, 0.01621542f);
  761. Assert.NearlyEqual(lerp.Y, 0.04540319f);
  762. Assert.NearlyEqual(lerp.Z, 0.05837553f);
  763. Assert.NearlyEqual(lerp.W, 0.0151344f);
  764. }
  765. #endregion
  766. #region Conjugate (Static)
  767. /// <summary>
  768. /// Conjugate test
  769. /// </summary>
  770. [Test]
  771. public static void Conjugate()
  772. {
  773. Quaternion quat = new Quaternion(new Vector(10f, 40f, 4f), 2f);
  774. quat.Conjugate();
  775. Assert.NearlyEqual(quat.X, -10f);
  776. Assert.NearlyEqual(quat.Y, -40f);
  777. Assert.NearlyEqual(quat.Z, -4f);
  778. Assert.NearlyEqual(quat.W, 2f);
  779. }
  780. #endregion
  781. #region CreateFromRotationMatrix (Static)
  782. /// <summary>
  783. /// CreateFromRotationMatrix test
  784. /// </summary>
  785. [Test]
  786. public static void CreateFromRotationMatrix()
  787. {
  788. Matrix rotMatrixX = Matrix.CreateRotationX(4f);
  789. Matrix rotMatrixY = Matrix.CreateRotationY(10f);
  790. Matrix rotMatrixZ = Matrix.CreateRotationZ(2f);
  791. Matrix rotMatrixCombined = Matrix.Identity;
  792. Matrix.Multiply(ref rotMatrixX, ref rotMatrixY,
  793. ref rotMatrixCombined);
  794. Matrix.Multiply(ref rotMatrixCombined, ref rotMatrixZ,
  795. ref rotMatrixCombined);
  796. Quaternion quat =
  797. Quaternion.CreateFromRotationMatrix(rotMatrixCombined);
  798. Assert.NearlyEqual(quat.X, 0.03324125f);
  799. Assert.NearlyEqual(quat.Y, 0.08769615f);
  800. Assert.NearlyEqual(quat.Z, 0.01433417f);
  801. Assert.NearlyEqual(quat.W, 0.9954893f);
  802. }
  803. #endregion
  804. #region CreateFromYawPitchRoll (Static)
  805. /// <summary>
  806. /// CreateFromYawPitchRoll test
  807. ///
  808. /// Calculation:
  809. /// yaw = 40 * 0.5 = 20
  810. /// pitch = 2 * 0.5 = 1
  811. /// roll = 238 * 0.5 = 119
  812. ///
  813. /// rollSin = Sin(roll) = 0.342020
  814. /// rollCos = Cos(roll) = 0.939692
  815. /// pitchSin = Sin(pitch) = 0.017452
  816. /// pitchCos = Cos(pitch) = 0.999847
  817. /// yawSin = Sin(yaw) = 0.874619
  818. /// yawCos = Cos(yaw) = -0.484809
  819. ///
  820. /// X = ((yawCos * pitchSin) * rollCos) +
  821. /// ((yawSin * pitchCos) * rollSin)
  822. /// = ((-0.484809 * 0.017452) * 0.939692) +
  823. /// ((0.874619 * 0.999847) * 0.342020)
  824. /// = 0.291140794875046
  825. ///
  826. /// Y = ((yawSin * pitchCos) * rollCos) -
  827. /// ((yawCos * pitchSin) * rollSin)
  828. /// = ((0.874619 * 0.999847) * 0.939692) -
  829. /// ((-0.484809 * 0.017452) * 0.342020)
  830. /// = 0.824640523317155
  831. ///
  832. /// Z = ((yawCos * pitchCos) * rollSin) -
  833. /// ((yawSin * pitchSin) * rollCos)
  834. /// = ((-0.484809 * 0.999847) * 0.342020) -
  835. /// ((0.874619 * 0.017452) * 0.939692)
  836. /// = -0.180132323055428
  837. ///
  838. /// W = ((yawCos * pitchCos) * rollCos) +
  839. /// ((yawSin * pitchSin) * rollSin)
  840. /// = ((-0.484809 * 0.999847) * 0.939692) +
  841. /// ((0.874619 * 0.017452) * 0.342020)
  842. /// = -0.450280894197248
  843. /// </summary>
  844. [Test]
  845. public static void CreateFromYawPitchRoll()
  846. {
  847. Quaternion quat = Quaternion.CreateFromYawPitchRoll(40f, 2f, 238f);
  848. Assert.NearlyEqual(quat.X, 0.291140f);
  849. Assert.NearlyEqual(quat.Y, 0.824640f);
  850. Assert.NearlyEqual(quat.Z, -0.180132f);
  851. Assert.NearlyEqual(quat.W, -0.450280f);
  852. }
  853. #endregion
  854. #region CreateFromAxisAngle (Static)
  855. /// <summary>
  856. /// CreateFromAxisAngle test
  857. ///
  858. /// Calculation:
  859. /// sinAngle = Sin(4.5) = 0.0784590957278449
  860. /// X = 45 * sinAngle = 3.53065
  861. /// Y = 2 * sinAngle = 0.1569
  862. /// Z = 14 * sinAngle = 1.09842
  863. /// W = Cos(4.5) = 0.99691
  864. /// </summary>
  865. [Test]
  866. public static void CreateFromAxisAngle()
  867. {
  868. Quaternion quat = Quaternion.CreateFromAxisAngle(
  869. new Vector(45f, 2f, 14f), 9f);
  870. Assert.NearlyEqual(quat.X, 3.53065f);
  871. Assert.NearlyEqual(quat.Y, 0.1569f);
  872. Assert.NearlyEqual(quat.Z, 1.09842f);
  873. Assert.NearlyEqual(quat.W, 0.99691f);
  874. }
  875. #endregion
  876. #region SizeOf
  877. /// <summary>
  878. /// Checks if the size of Point is exactly 8 bytes (2 floats: X and Y)
  879. /// </summary>
  880. [Test]
  881. public void SizeOf()
  882. {
  883. // Quaternion has 4 floats: X, Y, Z, and W
  884. Assert.Equal(4 * 4, Marshal.SizeOf(typeof(Quaternion)));
  885. }
  886. #endregion
  887. }
  888. }
  889. }