Quaternion.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  1. using System;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Runtime.InteropServices;
  4. #nullable enable
  5. namespace Godot
  6. {
  7. /// <summary>
  8. /// A unit quaternion used for representing 3D rotations.
  9. /// Quaternions need to be normalized to be used for rotation.
  10. ///
  11. /// It is similar to <see cref="Basis"/>, which implements matrix
  12. /// representation of rotations, and can be parametrized using both
  13. /// an axis-angle pair or Euler angles. Basis stores rotation, scale,
  14. /// and shearing, while Quaternion only stores rotation.
  15. ///
  16. /// Due to its compactness and the way it is stored in memory, certain
  17. /// operations (obtaining axis-angle and performing SLERP, in particular)
  18. /// are more efficient and robust against floating-point errors.
  19. /// </summary>
  20. [Serializable]
  21. [StructLayout(LayoutKind.Sequential)]
  22. public struct Quaternion : IEquatable<Quaternion>
  23. {
  24. /// <summary>
  25. /// X component of the quaternion (imaginary <c>i</c> axis part).
  26. /// Quaternion components should usually not be manipulated directly.
  27. /// </summary>
  28. public real_t X;
  29. /// <summary>
  30. /// Y component of the quaternion (imaginary <c>j</c> axis part).
  31. /// Quaternion components should usually not be manipulated directly.
  32. /// </summary>
  33. public real_t Y;
  34. /// <summary>
  35. /// Z component of the quaternion (imaginary <c>k</c> axis part).
  36. /// Quaternion components should usually not be manipulated directly.
  37. /// </summary>
  38. public real_t Z;
  39. /// <summary>
  40. /// W component of the quaternion (real part).
  41. /// Quaternion components should usually not be manipulated directly.
  42. /// </summary>
  43. public real_t W;
  44. /// <summary>
  45. /// Access quaternion components using their index.
  46. /// </summary>
  47. /// <exception cref="ArgumentOutOfRangeException">
  48. /// <paramref name="index"/> is not 0, 1, 2 or 3.
  49. /// </exception>
  50. /// <value>
  51. /// <c>[0]</c> is equivalent to <see cref="X"/>,
  52. /// <c>[1]</c> is equivalent to <see cref="Y"/>,
  53. /// <c>[2]</c> is equivalent to <see cref="Z"/>,
  54. /// <c>[3]</c> is equivalent to <see cref="W"/>.
  55. /// </value>
  56. public real_t this[int index]
  57. {
  58. readonly get
  59. {
  60. switch (index)
  61. {
  62. case 0:
  63. return X;
  64. case 1:
  65. return Y;
  66. case 2:
  67. return Z;
  68. case 3:
  69. return W;
  70. default:
  71. throw new ArgumentOutOfRangeException(nameof(index));
  72. }
  73. }
  74. set
  75. {
  76. switch (index)
  77. {
  78. case 0:
  79. X = value;
  80. break;
  81. case 1:
  82. Y = value;
  83. break;
  84. case 2:
  85. Z = value;
  86. break;
  87. case 3:
  88. W = value;
  89. break;
  90. default:
  91. throw new ArgumentOutOfRangeException(nameof(index));
  92. }
  93. }
  94. }
  95. /// <summary>
  96. /// Returns the angle between this quaternion and <paramref name="to"/>.
  97. /// This is the magnitude of the angle you would need to rotate
  98. /// by to get from one to the other.
  99. ///
  100. /// Note: This method has an abnormally high amount
  101. /// of floating-point error, so methods such as
  102. /// <see cref="Mathf.IsZeroApprox(real_t)"/> will not work reliably.
  103. /// </summary>
  104. /// <param name="to">The other quaternion.</param>
  105. /// <returns>The angle between the quaternions.</returns>
  106. public readonly real_t AngleTo(Quaternion to)
  107. {
  108. real_t dot = Dot(to);
  109. return Mathf.Acos(Mathf.Clamp(dot * dot * 2 - 1, -1, 1));
  110. }
  111. /// <summary>
  112. /// Performs a spherical cubic interpolation between quaternions <paramref name="preA"/>, this quaternion,
  113. /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>.
  114. /// </summary>
  115. /// <param name="b">The destination quaternion.</param>
  116. /// <param name="preA">A quaternion before this quaternion.</param>
  117. /// <param name="postB">A quaternion after <paramref name="b"/>.</param>
  118. /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
  119. /// <returns>The interpolated quaternion.</returns>
  120. public readonly Quaternion SphericalCubicInterpolate(Quaternion b, Quaternion preA, Quaternion postB, real_t weight)
  121. {
  122. #if DEBUG
  123. if (!IsNormalized())
  124. {
  125. throw new InvalidOperationException("Quaternion is not normalized");
  126. }
  127. if (!b.IsNormalized())
  128. {
  129. throw new ArgumentException("Argument is not normalized", nameof(b));
  130. }
  131. #endif
  132. // Align flip phases.
  133. Quaternion fromQ = new Basis(this).GetRotationQuaternion();
  134. Quaternion preQ = new Basis(preA).GetRotationQuaternion();
  135. Quaternion toQ = new Basis(b).GetRotationQuaternion();
  136. Quaternion postQ = new Basis(postB).GetRotationQuaternion();
  137. // Flip quaternions to shortest path if necessary.
  138. bool flip1 = Math.Sign(fromQ.Dot(preQ)) < 0;
  139. preQ = flip1 ? -preQ : preQ;
  140. bool flip2 = Math.Sign(fromQ.Dot(toQ)) < 0;
  141. toQ = flip2 ? -toQ : toQ;
  142. bool flip3 = flip2 ? toQ.Dot(postQ) <= 0 : Math.Sign(toQ.Dot(postQ)) < 0;
  143. postQ = flip3 ? -postQ : postQ;
  144. // Calc by Expmap in fromQ space.
  145. Quaternion lnFrom = new Quaternion(0, 0, 0, 0);
  146. Quaternion lnTo = (fromQ.Inverse() * toQ).Log();
  147. Quaternion lnPre = (fromQ.Inverse() * preQ).Log();
  148. Quaternion lnPost = (fromQ.Inverse() * postQ).Log();
  149. Quaternion ln = new Quaternion(
  150. Mathf.CubicInterpolate(lnFrom.X, lnTo.X, lnPre.X, lnPost.X, weight),
  151. Mathf.CubicInterpolate(lnFrom.Y, lnTo.Y, lnPre.Y, lnPost.Y, weight),
  152. Mathf.CubicInterpolate(lnFrom.Z, lnTo.Z, lnPre.Z, lnPost.Z, weight),
  153. 0);
  154. Quaternion q1 = fromQ * ln.Exp();
  155. // Calc by Expmap in toQ space.
  156. lnFrom = (toQ.Inverse() * fromQ).Log();
  157. lnTo = new Quaternion(0, 0, 0, 0);
  158. lnPre = (toQ.Inverse() * preQ).Log();
  159. lnPost = (toQ.Inverse() * postQ).Log();
  160. ln = new Quaternion(
  161. Mathf.CubicInterpolate(lnFrom.X, lnTo.X, lnPre.X, lnPost.X, weight),
  162. Mathf.CubicInterpolate(lnFrom.Y, lnTo.Y, lnPre.Y, lnPost.Y, weight),
  163. Mathf.CubicInterpolate(lnFrom.Z, lnTo.Z, lnPre.Z, lnPost.Z, weight),
  164. 0);
  165. Quaternion q2 = toQ * ln.Exp();
  166. // To cancel error made by Expmap ambiguity, do blending.
  167. return q1.Slerp(q2, weight);
  168. }
  169. /// <summary>
  170. /// Performs a spherical cubic interpolation between quaternions <paramref name="preA"/>, this quaternion,
  171. /// <paramref name="b"/>, and <paramref name="postB"/>, by the given amount <paramref name="weight"/>.
  172. /// It can perform smoother interpolation than <see cref="SphericalCubicInterpolate"/>
  173. /// by the time values.
  174. /// </summary>
  175. /// <param name="b">The destination quaternion.</param>
  176. /// <param name="preA">A quaternion before this quaternion.</param>
  177. /// <param name="postB">A quaternion after <paramref name="b"/>.</param>
  178. /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
  179. /// <param name="bT"></param>
  180. /// <param name="preAT"></param>
  181. /// <param name="postBT"></param>
  182. /// <returns>The interpolated quaternion.</returns>
  183. public readonly Quaternion SphericalCubicInterpolateInTime(Quaternion b, Quaternion preA, Quaternion postB, real_t weight, real_t bT, real_t preAT, real_t postBT)
  184. {
  185. #if DEBUG
  186. if (!IsNormalized())
  187. {
  188. throw new InvalidOperationException("Quaternion is not normalized");
  189. }
  190. if (!b.IsNormalized())
  191. {
  192. throw new ArgumentException("Argument is not normalized", nameof(b));
  193. }
  194. #endif
  195. // Align flip phases.
  196. Quaternion fromQ = new Basis(this).GetRotationQuaternion();
  197. Quaternion preQ = new Basis(preA).GetRotationQuaternion();
  198. Quaternion toQ = new Basis(b).GetRotationQuaternion();
  199. Quaternion postQ = new Basis(postB).GetRotationQuaternion();
  200. // Flip quaternions to shortest path if necessary.
  201. bool flip1 = Math.Sign(fromQ.Dot(preQ)) < 0;
  202. preQ = flip1 ? -preQ : preQ;
  203. bool flip2 = Math.Sign(fromQ.Dot(toQ)) < 0;
  204. toQ = flip2 ? -toQ : toQ;
  205. bool flip3 = flip2 ? toQ.Dot(postQ) <= 0 : Math.Sign(toQ.Dot(postQ)) < 0;
  206. postQ = flip3 ? -postQ : postQ;
  207. // Calc by Expmap in fromQ space.
  208. Quaternion lnFrom = new Quaternion(0, 0, 0, 0);
  209. Quaternion lnTo = (fromQ.Inverse() * toQ).Log();
  210. Quaternion lnPre = (fromQ.Inverse() * preQ).Log();
  211. Quaternion lnPost = (fromQ.Inverse() * postQ).Log();
  212. Quaternion ln = new Quaternion(
  213. Mathf.CubicInterpolateInTime(lnFrom.X, lnTo.X, lnPre.X, lnPost.X, weight, bT, preAT, postBT),
  214. Mathf.CubicInterpolateInTime(lnFrom.Y, lnTo.Y, lnPre.Y, lnPost.Y, weight, bT, preAT, postBT),
  215. Mathf.CubicInterpolateInTime(lnFrom.Z, lnTo.Z, lnPre.Z, lnPost.Z, weight, bT, preAT, postBT),
  216. 0);
  217. Quaternion q1 = fromQ * ln.Exp();
  218. // Calc by Expmap in toQ space.
  219. lnFrom = (toQ.Inverse() * fromQ).Log();
  220. lnTo = new Quaternion(0, 0, 0, 0);
  221. lnPre = (toQ.Inverse() * preQ).Log();
  222. lnPost = (toQ.Inverse() * postQ).Log();
  223. ln = new Quaternion(
  224. Mathf.CubicInterpolateInTime(lnFrom.X, lnTo.X, lnPre.X, lnPost.X, weight, bT, preAT, postBT),
  225. Mathf.CubicInterpolateInTime(lnFrom.Y, lnTo.Y, lnPre.Y, lnPost.Y, weight, bT, preAT, postBT),
  226. Mathf.CubicInterpolateInTime(lnFrom.Z, lnTo.Z, lnPre.Z, lnPost.Z, weight, bT, preAT, postBT),
  227. 0);
  228. Quaternion q2 = toQ * ln.Exp();
  229. // To cancel error made by Expmap ambiguity, do blending.
  230. return q1.Slerp(q2, weight);
  231. }
  232. /// <summary>
  233. /// Returns the dot product of two quaternions.
  234. /// </summary>
  235. /// <param name="b">The other quaternion.</param>
  236. /// <returns>The dot product.</returns>
  237. public readonly real_t Dot(Quaternion b)
  238. {
  239. return (X * b.X) + (Y * b.Y) + (Z * b.Z) + (W * b.W);
  240. }
  241. public readonly Quaternion Exp()
  242. {
  243. Vector3 v = new Vector3(X, Y, Z);
  244. real_t theta = v.Length();
  245. v = v.Normalized();
  246. if (theta < Mathf.Epsilon || !v.IsNormalized())
  247. {
  248. return new Quaternion(0, 0, 0, 1);
  249. }
  250. return new Quaternion(v, theta);
  251. }
  252. public readonly real_t GetAngle()
  253. {
  254. return 2 * Mathf.Acos(W);
  255. }
  256. public readonly Vector3 GetAxis()
  257. {
  258. if (Mathf.Abs(W) > 1 - Mathf.Epsilon)
  259. {
  260. return new Vector3(X, Y, Z);
  261. }
  262. real_t r = 1 / Mathf.Sqrt(1 - W * W);
  263. return new Vector3(X * r, Y * r, Z * r);
  264. }
  265. /// <summary>
  266. /// Returns Euler angles (in the YXZ convention: when decomposing,
  267. /// first Z, then X, and Y last) corresponding to the rotation
  268. /// represented by the unit quaternion. Returned vector contains
  269. /// the rotation angles in the format (X angle, Y angle, Z angle).
  270. /// </summary>
  271. /// <returns>The Euler angle representation of this quaternion.</returns>
  272. public readonly Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz)
  273. {
  274. #if DEBUG
  275. if (!IsNormalized())
  276. {
  277. throw new InvalidOperationException("Quaternion is not normalized.");
  278. }
  279. #endif
  280. var basis = new Basis(this);
  281. return basis.GetEuler(order);
  282. }
  283. /// <summary>
  284. /// Returns the inverse of the quaternion.
  285. /// </summary>
  286. /// <returns>The inverse quaternion.</returns>
  287. public readonly Quaternion Inverse()
  288. {
  289. #if DEBUG
  290. if (!IsNormalized())
  291. {
  292. throw new InvalidOperationException("Quaternion is not normalized.");
  293. }
  294. #endif
  295. return new Quaternion(-X, -Y, -Z, W);
  296. }
  297. /// <summary>
  298. /// Returns <see langword="true"/> if this quaternion is finite, by calling
  299. /// <see cref="Mathf.IsFinite(real_t)"/> on each component.
  300. /// </summary>
  301. /// <returns>Whether this vector is finite or not.</returns>
  302. public readonly bool IsFinite()
  303. {
  304. return Mathf.IsFinite(X) && Mathf.IsFinite(Y) && Mathf.IsFinite(Z) && Mathf.IsFinite(W);
  305. }
  306. /// <summary>
  307. /// Returns whether the quaternion is normalized or not.
  308. /// </summary>
  309. /// <returns>A <see langword="bool"/> for whether the quaternion is normalized or not.</returns>
  310. public readonly bool IsNormalized()
  311. {
  312. return Mathf.Abs(LengthSquared() - 1) <= Mathf.Epsilon;
  313. }
  314. public readonly Quaternion Log()
  315. {
  316. Vector3 v = GetAxis() * GetAngle();
  317. return new Quaternion(v.X, v.Y, v.Z, 0);
  318. }
  319. /// <summary>
  320. /// Returns the length (magnitude) of the quaternion.
  321. /// </summary>
  322. /// <seealso cref="LengthSquared"/>
  323. /// <value>Equivalent to <c>Mathf.Sqrt(LengthSquared)</c>.</value>
  324. public readonly real_t Length()
  325. {
  326. return Mathf.Sqrt(LengthSquared());
  327. }
  328. /// <summary>
  329. /// Returns the squared length (squared magnitude) of the quaternion.
  330. /// This method runs faster than <see cref="Length"/>, so prefer it if
  331. /// you need to compare quaternions or need the squared length for some formula.
  332. /// </summary>
  333. /// <value>Equivalent to <c>Dot(this)</c>.</value>
  334. public readonly real_t LengthSquared()
  335. {
  336. return Dot(this);
  337. }
  338. /// <summary>
  339. /// Returns a copy of the quaternion, normalized to unit length.
  340. /// </summary>
  341. /// <returns>The normalized quaternion.</returns>
  342. public readonly Quaternion Normalized()
  343. {
  344. return this / Length();
  345. }
  346. /// <summary>
  347. /// Returns the result of the spherical linear interpolation between
  348. /// this quaternion and <paramref name="to"/> by amount <paramref name="weight"/>.
  349. ///
  350. /// Note: Both quaternions must be normalized.
  351. /// </summary>
  352. /// <param name="to">The destination quaternion for interpolation. Must be normalized.</param>
  353. /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
  354. /// <returns>The resulting quaternion of the interpolation.</returns>
  355. public readonly Quaternion Slerp(Quaternion to, real_t weight)
  356. {
  357. #if DEBUG
  358. if (!IsNormalized())
  359. {
  360. throw new InvalidOperationException("Quaternion is not normalized.");
  361. }
  362. if (!to.IsNormalized())
  363. {
  364. throw new ArgumentException("Argument is not normalized.", nameof(to));
  365. }
  366. #endif
  367. // Calculate cosine.
  368. real_t cosom = Dot(to);
  369. var to1 = new Quaternion();
  370. // Adjust signs if necessary.
  371. if (cosom < 0.0)
  372. {
  373. cosom = -cosom;
  374. to1 = -to;
  375. }
  376. else
  377. {
  378. to1 = to;
  379. }
  380. real_t sinom, scale0, scale1;
  381. // Calculate coefficients.
  382. if (1.0 - cosom > Mathf.Epsilon)
  383. {
  384. // Standard case (Slerp).
  385. real_t omega = Mathf.Acos(cosom);
  386. sinom = Mathf.Sin(omega);
  387. scale0 = Mathf.Sin((1.0f - weight) * omega) / sinom;
  388. scale1 = Mathf.Sin(weight * omega) / sinom;
  389. }
  390. else
  391. {
  392. // Quaternions are very close so we can do a linear interpolation.
  393. scale0 = 1.0f - weight;
  394. scale1 = weight;
  395. }
  396. // Calculate final values.
  397. return new Quaternion
  398. (
  399. (scale0 * X) + (scale1 * to1.X),
  400. (scale0 * Y) + (scale1 * to1.Y),
  401. (scale0 * Z) + (scale1 * to1.Z),
  402. (scale0 * W) + (scale1 * to1.W)
  403. );
  404. }
  405. /// <summary>
  406. /// Returns the result of the spherical linear interpolation between
  407. /// this quaternion and <paramref name="to"/> by amount <paramref name="weight"/>, but without
  408. /// checking if the rotation path is not bigger than 90 degrees.
  409. /// </summary>
  410. /// <param name="to">The destination quaternion for interpolation. Must be normalized.</param>
  411. /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
  412. /// <returns>The resulting quaternion of the interpolation.</returns>
  413. public readonly Quaternion Slerpni(Quaternion to, real_t weight)
  414. {
  415. #if DEBUG
  416. if (!IsNormalized())
  417. {
  418. throw new InvalidOperationException("Quaternion is not normalized");
  419. }
  420. if (!to.IsNormalized())
  421. {
  422. throw new ArgumentException("Argument is not normalized", nameof(to));
  423. }
  424. #endif
  425. real_t dot = Dot(to);
  426. if (Mathf.Abs(dot) > 0.9999f)
  427. {
  428. return this;
  429. }
  430. real_t theta = Mathf.Acos(dot);
  431. real_t sinT = 1.0f / Mathf.Sin(theta);
  432. real_t newFactor = Mathf.Sin(weight * theta) * sinT;
  433. real_t invFactor = Mathf.Sin((1.0f - weight) * theta) * sinT;
  434. return new Quaternion
  435. (
  436. (invFactor * X) + (newFactor * to.X),
  437. (invFactor * Y) + (newFactor * to.Y),
  438. (invFactor * Z) + (newFactor * to.Z),
  439. (invFactor * W) + (newFactor * to.W)
  440. );
  441. }
  442. // Constants
  443. private static readonly Quaternion _identity = new Quaternion(0, 0, 0, 1);
  444. /// <summary>
  445. /// The identity quaternion, representing no rotation.
  446. /// Equivalent to an identity <see cref="Basis"/> matrix. If a vector is transformed by
  447. /// an identity quaternion, it will not change.
  448. /// </summary>
  449. /// <value>Equivalent to <c>new Quaternion(0, 0, 0, 1)</c>.</value>
  450. public static Quaternion Identity { get { return _identity; } }
  451. /// <summary>
  452. /// Constructs a <see cref="Quaternion"/> defined by the given values.
  453. /// </summary>
  454. /// <param name="x">X component of the quaternion (imaginary <c>i</c> axis part).</param>
  455. /// <param name="y">Y component of the quaternion (imaginary <c>j</c> axis part).</param>
  456. /// <param name="z">Z component of the quaternion (imaginary <c>k</c> axis part).</param>
  457. /// <param name="w">W component of the quaternion (real part).</param>
  458. public Quaternion(real_t x, real_t y, real_t z, real_t w)
  459. {
  460. X = x;
  461. Y = y;
  462. Z = z;
  463. W = w;
  464. }
  465. /// <summary>
  466. /// Constructs a <see cref="Quaternion"/> from the given <see cref="Basis"/>.
  467. /// </summary>
  468. /// <param name="basis">The <see cref="Basis"/> to construct from.</param>
  469. public Quaternion(Basis basis)
  470. {
  471. this = basis.GetQuaternion();
  472. }
  473. /// <summary>
  474. /// Constructs a <see cref="Quaternion"/> that will rotate around the given axis
  475. /// by the specified angle. The axis must be a normalized vector.
  476. /// </summary>
  477. /// <param name="axis">The axis to rotate around. Must be normalized.</param>
  478. /// <param name="angle">The angle to rotate, in radians.</param>
  479. public Quaternion(Vector3 axis, real_t angle)
  480. {
  481. #if DEBUG
  482. if (!axis.IsNormalized())
  483. {
  484. throw new ArgumentException("Argument is not normalized.", nameof(axis));
  485. }
  486. #endif
  487. real_t d = axis.Length();
  488. if (d == 0f)
  489. {
  490. X = 0f;
  491. Y = 0f;
  492. Z = 0f;
  493. W = 0f;
  494. }
  495. else
  496. {
  497. (real_t sin, real_t cos) = Mathf.SinCos(angle * 0.5f);
  498. real_t s = sin / d;
  499. X = axis.X * s;
  500. Y = axis.Y * s;
  501. Z = axis.Z * s;
  502. W = cos;
  503. }
  504. }
  505. public Quaternion(Vector3 arcFrom, Vector3 arcTo)
  506. {
  507. Vector3 c = arcFrom.Cross(arcTo);
  508. real_t d = arcFrom.Dot(arcTo);
  509. if (d < -1.0f + Mathf.Epsilon)
  510. {
  511. X = 0f;
  512. Y = 1f;
  513. Z = 0f;
  514. W = 0f;
  515. }
  516. else
  517. {
  518. real_t s = Mathf.Sqrt((1.0f + d) * 2.0f);
  519. real_t rs = 1.0f / s;
  520. X = c.X * rs;
  521. Y = c.Y * rs;
  522. Z = c.Z * rs;
  523. W = s * 0.5f;
  524. }
  525. }
  526. /// <summary>
  527. /// Constructs a <see cref="Quaternion"/> that will perform a rotation specified by
  528. /// Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last),
  529. /// given in the vector format as (X angle, Y angle, Z angle).
  530. /// </summary>
  531. /// <param name="eulerYXZ">Euler angles that the quaternion will be rotated by.</param>
  532. public static Quaternion FromEuler(Vector3 eulerYXZ)
  533. {
  534. real_t halfA1 = eulerYXZ.Y * 0.5f;
  535. real_t halfA2 = eulerYXZ.X * 0.5f;
  536. real_t halfA3 = eulerYXZ.Z * 0.5f;
  537. // R = Y(a1).X(a2).Z(a3) convention for Euler angles.
  538. // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6)
  539. // a3 is the angle of the first rotation, following the notation in this reference.
  540. (real_t sinA1, real_t cosA1) = Mathf.SinCos(halfA1);
  541. (real_t sinA2, real_t cosA2) = Mathf.SinCos(halfA2);
  542. (real_t sinA3, real_t cosA3) = Mathf.SinCos(halfA3);
  543. return new Quaternion(
  544. (sinA1 * cosA2 * sinA3) + (cosA1 * sinA2 * cosA3),
  545. (sinA1 * cosA2 * cosA3) - (cosA1 * sinA2 * sinA3),
  546. (cosA1 * cosA2 * sinA3) - (sinA1 * sinA2 * cosA3),
  547. (sinA1 * sinA2 * sinA3) + (cosA1 * cosA2 * cosA3)
  548. );
  549. }
  550. /// <summary>
  551. /// Composes these two quaternions by multiplying them together.
  552. /// This has the effect of rotating the second quaternion
  553. /// (the child) by the first quaternion (the parent).
  554. /// </summary>
  555. /// <param name="left">The parent quaternion.</param>
  556. /// <param name="right">The child quaternion.</param>
  557. /// <returns>The composed quaternion.</returns>
  558. public static Quaternion operator *(Quaternion left, Quaternion right)
  559. {
  560. return new Quaternion
  561. (
  562. (left.W * right.X) + (left.X * right.W) + (left.Y * right.Z) - (left.Z * right.Y),
  563. (left.W * right.Y) + (left.Y * right.W) + (left.Z * right.X) - (left.X * right.Z),
  564. (left.W * right.Z) + (left.Z * right.W) + (left.X * right.Y) - (left.Y * right.X),
  565. (left.W * right.W) - (left.X * right.X) - (left.Y * right.Y) - (left.Z * right.Z)
  566. );
  567. }
  568. /// <summary>
  569. /// Returns a Vector3 rotated (multiplied) by the quaternion.
  570. /// </summary>
  571. /// <param name="quaternion">The quaternion to rotate by.</param>
  572. /// <param name="vector">A Vector3 to transform.</param>
  573. /// <returns>The rotated Vector3.</returns>
  574. public static Vector3 operator *(Quaternion quaternion, Vector3 vector)
  575. {
  576. #if DEBUG
  577. if (!quaternion.IsNormalized())
  578. {
  579. throw new InvalidOperationException("Quaternion is not normalized.");
  580. }
  581. #endif
  582. var u = new Vector3(quaternion.X, quaternion.Y, quaternion.Z);
  583. Vector3 uv = u.Cross(vector);
  584. return vector + (((uv * quaternion.W) + u.Cross(uv)) * 2);
  585. }
  586. /// <summary>
  587. /// Returns a Vector3 rotated (multiplied) by the inverse quaternion.
  588. /// <c>vector * quaternion</c> is equivalent to <c>quaternion.Inverse() * vector</c>. See <see cref="Inverse"/>.
  589. /// </summary>
  590. /// <param name="vector">A Vector3 to inversely rotate.</param>
  591. /// <param name="quaternion">The quaternion to rotate by.</param>
  592. /// <returns>The inversely rotated Vector3.</returns>
  593. public static Vector3 operator *(Vector3 vector, Quaternion quaternion)
  594. {
  595. return quaternion.Inverse() * vector;
  596. }
  597. /// <summary>
  598. /// Adds each component of the left <see cref="Quaternion"/>
  599. /// to the right <see cref="Quaternion"/>. This operation is not
  600. /// meaningful on its own, but it can be used as a part of a
  601. /// larger expression, such as approximating an intermediate
  602. /// rotation between two nearby rotations.
  603. /// </summary>
  604. /// <param name="left">The left quaternion to add.</param>
  605. /// <param name="right">The right quaternion to add.</param>
  606. /// <returns>The added quaternion.</returns>
  607. public static Quaternion operator +(Quaternion left, Quaternion right)
  608. {
  609. return new Quaternion(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W);
  610. }
  611. /// <summary>
  612. /// Subtracts each component of the left <see cref="Quaternion"/>
  613. /// by the right <see cref="Quaternion"/>. This operation is not
  614. /// meaningful on its own, but it can be used as a part of a
  615. /// larger expression.
  616. /// </summary>
  617. /// <param name="left">The left quaternion to subtract.</param>
  618. /// <param name="right">The right quaternion to subtract.</param>
  619. /// <returns>The subtracted quaternion.</returns>
  620. public static Quaternion operator -(Quaternion left, Quaternion right)
  621. {
  622. return new Quaternion(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W);
  623. }
  624. /// <summary>
  625. /// Returns the negative value of the <see cref="Quaternion"/>.
  626. /// This is the same as writing
  627. /// <c>new Quaternion(-q.X, -q.Y, -q.Z, -q.W)</c>. This operation
  628. /// results in a quaternion that represents the same rotation.
  629. /// </summary>
  630. /// <param name="quat">The quaternion to negate.</param>
  631. /// <returns>The negated quaternion.</returns>
  632. public static Quaternion operator -(Quaternion quat)
  633. {
  634. return new Quaternion(-quat.X, -quat.Y, -quat.Z, -quat.W);
  635. }
  636. /// <summary>
  637. /// Multiplies each component of the <see cref="Quaternion"/>
  638. /// by the given <see cref="real_t"/>. This operation is not
  639. /// meaningful on its own, but it can be used as a part of a
  640. /// larger expression.
  641. /// </summary>
  642. /// <param name="left">The quaternion to multiply.</param>
  643. /// <param name="right">The value to multiply by.</param>
  644. /// <returns>The multiplied quaternion.</returns>
  645. public static Quaternion operator *(Quaternion left, real_t right)
  646. {
  647. return new Quaternion(left.X * right, left.Y * right, left.Z * right, left.W * right);
  648. }
  649. /// <summary>
  650. /// Multiplies each component of the <see cref="Quaternion"/>
  651. /// by the given <see cref="real_t"/>. This operation is not
  652. /// meaningful on its own, but it can be used as a part of a
  653. /// larger expression.
  654. /// </summary>
  655. /// <param name="left">The value to multiply by.</param>
  656. /// <param name="right">The quaternion to multiply.</param>
  657. /// <returns>The multiplied quaternion.</returns>
  658. public static Quaternion operator *(real_t left, Quaternion right)
  659. {
  660. return new Quaternion(right.X * left, right.Y * left, right.Z * left, right.W * left);
  661. }
  662. /// <summary>
  663. /// Divides each component of the <see cref="Quaternion"/>
  664. /// by the given <see cref="real_t"/>. This operation is not
  665. /// meaningful on its own, but it can be used as a part of a
  666. /// larger expression.
  667. /// </summary>
  668. /// <param name="left">The quaternion to divide.</param>
  669. /// <param name="right">The value to divide by.</param>
  670. /// <returns>The divided quaternion.</returns>
  671. public static Quaternion operator /(Quaternion left, real_t right)
  672. {
  673. return left * (1.0f / right);
  674. }
  675. /// <summary>
  676. /// Returns <see langword="true"/> if the quaternions are exactly equal.
  677. /// Note: Due to floating-point precision errors, consider using
  678. /// <see cref="IsEqualApprox"/> instead, which is more reliable.
  679. /// </summary>
  680. /// <param name="left">The left quaternion.</param>
  681. /// <param name="right">The right quaternion.</param>
  682. /// <returns>Whether or not the quaternions are exactly equal.</returns>
  683. public static bool operator ==(Quaternion left, Quaternion right)
  684. {
  685. return left.Equals(right);
  686. }
  687. /// <summary>
  688. /// Returns <see langword="true"/> if the quaternions are not equal.
  689. /// Note: Due to floating-point precision errors, consider using
  690. /// <see cref="IsEqualApprox"/> instead, which is more reliable.
  691. /// </summary>
  692. /// <param name="left">The left quaternion.</param>
  693. /// <param name="right">The right quaternion.</param>
  694. /// <returns>Whether or not the quaternions are not equal.</returns>
  695. public static bool operator !=(Quaternion left, Quaternion right)
  696. {
  697. return !left.Equals(right);
  698. }
  699. /// <summary>
  700. /// Returns <see langword="true"/> if this quaternion and <paramref name="obj"/> are equal.
  701. /// </summary>
  702. /// <param name="obj">The other object to compare.</param>
  703. /// <returns>Whether or not the quaternion and the other object are exactly equal.</returns>
  704. public override readonly bool Equals([NotNullWhen(true)] object? obj)
  705. {
  706. return obj is Quaternion other && Equals(other);
  707. }
  708. /// <summary>
  709. /// Returns <see langword="true"/> if this quaternion and <paramref name="other"/> are equal.
  710. /// </summary>
  711. /// <param name="other">The other quaternion to compare.</param>
  712. /// <returns>Whether or not the quaternions are exactly equal.</returns>
  713. public readonly bool Equals(Quaternion other)
  714. {
  715. return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
  716. }
  717. /// <summary>
  718. /// Returns <see langword="true"/> if this quaternion and <paramref name="other"/> are approximately equal,
  719. /// by running <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component.
  720. /// </summary>
  721. /// <param name="other">The other quaternion to compare.</param>
  722. /// <returns>Whether or not the quaternions are approximately equal.</returns>
  723. public readonly bool IsEqualApprox(Quaternion other)
  724. {
  725. return Mathf.IsEqualApprox(X, other.X) && Mathf.IsEqualApprox(Y, other.Y) && Mathf.IsEqualApprox(Z, other.Z) && Mathf.IsEqualApprox(W, other.W);
  726. }
  727. /// <summary>
  728. /// Serves as the hash function for <see cref="Quaternion"/>.
  729. /// </summary>
  730. /// <returns>A hash code for this quaternion.</returns>
  731. public override readonly int GetHashCode()
  732. {
  733. return HashCode.Combine(X, Y, Z, W);
  734. }
  735. /// <summary>
  736. /// Converts this <see cref="Quaternion"/> to a string.
  737. /// </summary>
  738. /// <returns>A string representation of this quaternion.</returns>
  739. public override readonly string ToString()
  740. {
  741. return $"({X}, {Y}, {Z}, {W})";
  742. }
  743. /// <summary>
  744. /// Converts this <see cref="Quaternion"/> to a string with the given <paramref name="format"/>.
  745. /// </summary>
  746. /// <returns>A string representation of this quaternion.</returns>
  747. public readonly string ToString(string? format)
  748. {
  749. #pragma warning disable CA1305 // Disable warning: "Specify IFormatProvider"
  750. return $"({X.ToString(format)}, {Y.ToString(format)}, {Z.ToString(format)}, {W.ToString(format)})";
  751. #pragma warning restore CA1305
  752. }
  753. }
  754. }