Projection.cs 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. using System;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Globalization;
  4. using System.Runtime.InteropServices;
  5. #nullable enable
  6. namespace Godot
  7. {
  8. /// <summary>
  9. /// A 4x4 matrix used for 3D projective transformations. It can represent transformations such as
  10. /// translation, rotation, scaling, shearing, and perspective division. It consists of four
  11. /// <see cref="Vector4"/> columns.
  12. /// For purely linear transformations (translation, rotation, and scale), it is recommended to use
  13. /// <see cref="Transform3D"/>, as it is more performant and has a lower memory footprint.
  14. /// Used internally as <see cref="Camera3D"/>'s projection matrix.
  15. /// </summary>
  16. [Serializable]
  17. [StructLayout(LayoutKind.Sequential)]
  18. public struct Projection : IEquatable<Projection>
  19. {
  20. /// <summary>
  21. /// Enumerated index values for the planes.
  22. /// </summary>
  23. public enum Planes
  24. {
  25. /// <summary>
  26. /// The projection's near plane.
  27. /// </summary>
  28. Near,
  29. /// <summary>
  30. /// The projection's far plane.
  31. /// </summary>
  32. Far,
  33. /// <summary>
  34. /// The projection's left plane.
  35. /// </summary>
  36. Left,
  37. /// <summary>
  38. /// The projection's top plane.
  39. /// </summary>
  40. Top,
  41. /// <summary>
  42. /// The projection's right plane.
  43. /// </summary>
  44. Right,
  45. /// <summary>
  46. /// The projection's bottom plane.
  47. /// </summary>
  48. Bottom,
  49. }
  50. /// <summary>
  51. /// The projection's X column. Also accessible by using the index position <c>[0]</c>.
  52. /// </summary>
  53. public Vector4 X;
  54. /// <summary>
  55. /// The projection's Y column. Also accessible by using the index position <c>[1]</c>.
  56. /// </summary>
  57. public Vector4 Y;
  58. /// <summary>
  59. /// The projection's Z column. Also accessible by using the index position <c>[2]</c>.
  60. /// </summary>
  61. public Vector4 Z;
  62. /// <summary>
  63. /// The projection's W column. Also accessible by using the index position <c>[3]</c>.
  64. /// </summary>
  65. public Vector4 W;
  66. /// <summary>
  67. /// Access whole columns in the form of <see cref="Vector4"/>.
  68. /// </summary>
  69. /// <param name="column">Which column vector.</param>
  70. /// <exception cref="ArgumentOutOfRangeException">
  71. /// <paramref name="column"/> is not 0, 1, 2 or 3.
  72. /// </exception>
  73. public Vector4 this[int column]
  74. {
  75. readonly get
  76. {
  77. switch (column)
  78. {
  79. case 0:
  80. return X;
  81. case 1:
  82. return Y;
  83. case 2:
  84. return Z;
  85. case 3:
  86. return W;
  87. default:
  88. throw new ArgumentOutOfRangeException(nameof(column));
  89. }
  90. }
  91. set
  92. {
  93. switch (column)
  94. {
  95. case 0:
  96. X = value;
  97. return;
  98. case 1:
  99. Y = value;
  100. return;
  101. case 2:
  102. Z = value;
  103. return;
  104. case 3:
  105. W = value;
  106. return;
  107. default:
  108. throw new ArgumentOutOfRangeException(nameof(column));
  109. }
  110. }
  111. }
  112. /// <summary>
  113. /// Access single values.
  114. /// </summary>
  115. /// <param name="column">Which column vector.</param>
  116. /// <param name="row">Which row of the column.</param>
  117. /// <exception cref="ArgumentOutOfRangeException">
  118. /// <paramref name="column"/> or <paramref name="row"/> are not 0, 1, 2 or 3.
  119. /// </exception>
  120. public real_t this[int column, int row]
  121. {
  122. readonly get
  123. {
  124. switch (column)
  125. {
  126. case 0:
  127. return X[row];
  128. case 1:
  129. return Y[row];
  130. case 2:
  131. return Z[row];
  132. case 3:
  133. return W[row];
  134. default:
  135. throw new ArgumentOutOfRangeException(nameof(column));
  136. }
  137. }
  138. set
  139. {
  140. switch (column)
  141. {
  142. case 0:
  143. X[row] = value;
  144. return;
  145. case 1:
  146. Y[row] = value;
  147. return;
  148. case 2:
  149. Z[row] = value;
  150. return;
  151. case 3:
  152. W[row] = value;
  153. return;
  154. default:
  155. throw new ArgumentOutOfRangeException(nameof(column));
  156. }
  157. }
  158. }
  159. /// <summary>
  160. /// Creates a new <see cref="Projection"/> that projects positions from a depth range of
  161. /// <c>-1</c> to <c>1</c> to one that ranges from <c>0</c> to <c>1</c>, and flips the projected
  162. /// positions vertically, according to <paramref name="flipY"/>.
  163. /// </summary>
  164. /// <param name="flipY">If the projection should be flipped vertically.</param>
  165. /// <returns>The created projection.</returns>
  166. public static Projection CreateDepthCorrection(bool flipY)
  167. {
  168. return new Projection(
  169. new Vector4(1, 0, 0, 0),
  170. new Vector4(0, flipY ? -1 : 1, 0, 0),
  171. new Vector4(0, 0, (real_t)0.5, 0),
  172. new Vector4(0, 0, (real_t)0.5, 1)
  173. );
  174. }
  175. /// <summary>
  176. /// Creates a new <see cref="Projection"/> that scales a given projection to fit around
  177. /// a given <see cref="Aabb"/> in projection space.
  178. /// </summary>
  179. /// <param name="aabb">The Aabb to fit the projection around.</param>
  180. /// <returns>The created projection.</returns>
  181. public static Projection CreateFitAabb(Aabb aabb)
  182. {
  183. Vector3 min = aabb.Position;
  184. Vector3 max = aabb.Position + aabb.Size;
  185. return new Projection(
  186. new Vector4(2 / (max.X - min.X), 0, 0, 0),
  187. new Vector4(0, 2 / (max.Y - min.Y), 0, 0),
  188. new Vector4(0, 0, 2 / (max.Z - min.Z), 0),
  189. new Vector4(-(max.X + min.X) / (max.X - min.X), -(max.Y + min.Y) / (max.Y - min.Y), -(max.Z + min.Z) / (max.Z - min.Z), 1)
  190. );
  191. }
  192. /// <summary>
  193. /// Creates a new <see cref="Projection"/> for projecting positions onto a head-mounted display with
  194. /// the given X:Y aspect ratio, distance between eyes, display width, distance to lens, oversampling factor,
  195. /// and depth clipping planes.
  196. /// <paramref name="eye"/> creates the projection for the left eye when set to 1,
  197. /// or the right eye when set to 2.
  198. /// </summary>
  199. /// <param name="eye">
  200. /// The eye to create the projection for.
  201. /// The left eye when set to 1, the right eye when set to 2.
  202. /// </param>
  203. /// <param name="aspect">The aspect ratio.</param>
  204. /// <param name="intraocularDist">The distance between the eyes.</param>
  205. /// <param name="displayWidth">The display width.</param>
  206. /// <param name="displayToLens">The distance to the lens.</param>
  207. /// <param name="oversample">The oversampling factor.</param>
  208. /// <param name="zNear">The near clipping distance.</param>
  209. /// <param name="zFar">The far clipping distance.</param>
  210. /// <returns>The created projection.</returns>
  211. public static Projection CreateForHmd(int eye, real_t aspect, real_t intraocularDist, real_t displayWidth, real_t displayToLens, real_t oversample, real_t zNear, real_t zFar)
  212. {
  213. real_t f1 = (intraocularDist * (real_t)0.5) / displayToLens;
  214. real_t f2 = ((displayWidth - intraocularDist) * (real_t)0.5) / displayToLens;
  215. real_t f3 = (displayWidth / (real_t)4.0) / displayToLens;
  216. real_t add = ((f1 + f2) * (oversample - (real_t)1.0)) / (real_t)2.0;
  217. f1 += add;
  218. f2 += add;
  219. f3 *= oversample;
  220. f3 /= aspect;
  221. switch (eye)
  222. {
  223. case 1:
  224. return CreateFrustum(-f2 * zNear, f1 * zNear, -f3 * zNear, f3 * zNear, zNear, zFar);
  225. case 2:
  226. return CreateFrustum(-f1 * zNear, f2 * zNear, -f3 * zNear, f3 * zNear, zNear, zFar);
  227. default:
  228. return Zero;
  229. }
  230. }
  231. /// <summary>
  232. /// Creates a new <see cref="Projection"/> that projects positions in a frustum with
  233. /// the given clipping planes.
  234. /// </summary>
  235. /// <param name="left">The left clipping distance.</param>
  236. /// <param name="right">The right clipping distance.</param>
  237. /// <param name="bottom">The bottom clipping distance.</param>
  238. /// <param name="top">The top clipping distance.</param>
  239. /// <param name="depthNear">The near clipping distance.</param>
  240. /// <param name="depthFar">The far clipping distance.</param>
  241. /// <returns>The created projection.</returns>
  242. public static Projection CreateFrustum(real_t left, real_t right, real_t bottom, real_t top, real_t depthNear, real_t depthFar)
  243. {
  244. if (right <= left)
  245. {
  246. throw new ArgumentException("right is less or equal to left.");
  247. }
  248. if (top <= bottom)
  249. {
  250. throw new ArgumentException("top is less or equal to bottom.");
  251. }
  252. if (depthFar <= depthNear)
  253. {
  254. throw new ArgumentException("far is less or equal to near.");
  255. }
  256. real_t x = 2 * depthNear / (right - left);
  257. real_t y = 2 * depthNear / (top - bottom);
  258. real_t a = (right + left) / (right - left);
  259. real_t b = (top + bottom) / (top - bottom);
  260. real_t c = -(depthFar + depthNear) / (depthFar - depthNear);
  261. real_t d = -2 * depthFar * depthNear / (depthFar - depthNear);
  262. return new Projection(
  263. new Vector4(x, 0, 0, 0),
  264. new Vector4(0, y, 0, 0),
  265. new Vector4(a, b, c, -1),
  266. new Vector4(0, 0, d, 0)
  267. );
  268. }
  269. /// <summary>
  270. /// Creates a new <see cref="Projection"/> that projects positions in a frustum with
  271. /// the given size, X:Y aspect ratio, offset, and clipping planes.
  272. /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal.
  273. /// </summary>
  274. /// <param name="size">The frustum size.</param>
  275. /// <param name="aspect">The aspect ratio.</param>
  276. /// <param name="offset">The offset to apply.</param>
  277. /// <param name="depthNear">The near clipping distance.</param>
  278. /// <param name="depthFar">The far clipping distance.</param>
  279. /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param>
  280. /// <returns>The created projection.</returns>
  281. public static Projection CreateFrustumAspect(real_t size, real_t aspect, Vector2 offset, real_t depthNear, real_t depthFar, bool flipFov)
  282. {
  283. if (!flipFov)
  284. {
  285. size *= aspect;
  286. }
  287. return CreateFrustum(-size / 2 + offset.X, +size / 2 + offset.X, -size / aspect / 2 + offset.Y, +size / aspect / 2 + offset.Y, depthNear, depthFar);
  288. }
  289. /// <summary>
  290. /// Creates a new <see cref="Projection"/> that projects positions into the given <see cref="Rect2"/>.
  291. /// </summary>
  292. /// <param name="rect">The Rect2 to project positions into.</param>
  293. /// <returns>The created projection.</returns>
  294. public static Projection CreateLightAtlasRect(Rect2 rect)
  295. {
  296. return new Projection(
  297. new Vector4(rect.Size.X, 0, 0, 0),
  298. new Vector4(0, rect.Size.Y, 0, 0),
  299. new Vector4(0, 0, 1, 0),
  300. new Vector4(rect.Position.X, rect.Position.Y, 0, 1)
  301. );
  302. }
  303. /// <summary>
  304. /// Creates a new <see cref="Projection"/> that projects positions using an orthogonal projection with
  305. /// the given clipping planes.
  306. /// </summary>
  307. /// <param name="left">The left clipping distance.</param>
  308. /// <param name="right">The right clipping distance.</param>
  309. /// <param name="bottom">The bottom clipping distance.</param>
  310. /// <param name="top">The top clipping distance.</param>
  311. /// <param name="zNear">The near clipping distance.</param>
  312. /// <param name="zFar">The far clipping distance.</param>
  313. /// <returns>The created projection.</returns>
  314. public static Projection CreateOrthogonal(real_t left, real_t right, real_t bottom, real_t top, real_t zNear, real_t zFar)
  315. {
  316. Projection proj = Projection.Identity;
  317. proj.X.X = (real_t)2.0 / (right - left);
  318. proj.W.X = -((right + left) / (right - left));
  319. proj.Y.Y = (real_t)2.0 / (top - bottom);
  320. proj.W.Y = -((top + bottom) / (top - bottom));
  321. proj.Z.Z = (real_t)(-2.0) / (zFar - zNear);
  322. proj.W.Z = -((zFar + zNear) / (zFar - zNear));
  323. proj.W.W = (real_t)1.0;
  324. return proj;
  325. }
  326. /// <summary>
  327. /// Creates a new <see cref="Projection"/> that projects positions using an orthogonal projection with
  328. /// the given size, X:Y aspect ratio, and clipping planes.
  329. /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal.
  330. /// </summary>
  331. /// <param name="size">The frustum size.</param>
  332. /// <param name="aspect">The aspect ratio.</param>
  333. /// <param name="zNear">The near clipping distance.</param>
  334. /// <param name="zFar">The far clipping distance.</param>
  335. /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param>
  336. /// <returns>The created projection.</returns>
  337. public static Projection CreateOrthogonalAspect(real_t size, real_t aspect, real_t zNear, real_t zFar, bool flipFov)
  338. {
  339. if (!flipFov)
  340. {
  341. size *= aspect;
  342. }
  343. return CreateOrthogonal(-size / 2, +size / 2, -size / aspect / 2, +size / aspect / 2, zNear, zFar);
  344. }
  345. /// <summary>
  346. /// Creates a new <see cref="Projection"/> that projects positions using a perspective projection with
  347. /// the given Y-axis field of view (in degrees), X:Y aspect ratio, and clipping planes.
  348. /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal.
  349. /// </summary>
  350. /// <param name="fovyDegrees">The vertical field of view (in degrees).</param>
  351. /// <param name="aspect">The aspect ratio.</param>
  352. /// <param name="zNear">The near clipping distance.</param>
  353. /// <param name="zFar">The far clipping distance.</param>
  354. /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param>
  355. /// <returns>The created projection.</returns>
  356. public static Projection CreatePerspective(real_t fovyDegrees, real_t aspect, real_t zNear, real_t zFar, bool flipFov)
  357. {
  358. if (flipFov)
  359. {
  360. fovyDegrees = GetFovy(fovyDegrees, (real_t)1.0 / aspect);
  361. }
  362. real_t radians = Mathf.DegToRad(fovyDegrees / (real_t)2.0);
  363. real_t deltaZ = zFar - zNear;
  364. (real_t sin, real_t cos) = Mathf.SinCos(radians);
  365. if ((deltaZ == 0) || (sin == 0) || (aspect == 0))
  366. {
  367. return Zero;
  368. }
  369. real_t cotangent = cos / sin;
  370. Projection proj = Projection.Identity;
  371. proj.X.X = cotangent / aspect;
  372. proj.Y.Y = cotangent;
  373. proj.Z.Z = -(zFar + zNear) / deltaZ;
  374. proj.Z.W = -1;
  375. proj.W.Z = -2 * zNear * zFar / deltaZ;
  376. proj.W.W = 0;
  377. return proj;
  378. }
  379. /// <summary>
  380. /// Creates a new <see cref="Projection"/> that projects positions using a perspective projection with
  381. /// the given Y-axis field of view (in degrees), X:Y aspect ratio, and clipping distances.
  382. /// The projection is adjusted for a head-mounted display with the given distance between eyes and distance
  383. /// to a point that can be focused on.
  384. /// <paramref name="eye"/> creates the projection for the left eye when set to 1,
  385. /// or the right eye when set to 2.
  386. /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal.
  387. /// </summary>
  388. /// <param name="fovyDegrees">The vertical field of view (in degrees).</param>
  389. /// <param name="aspect">The aspect ratio.</param>
  390. /// <param name="zNear">The near clipping distance.</param>
  391. /// <param name="zFar">The far clipping distance.</param>
  392. /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param>
  393. /// <param name="eye">
  394. /// The eye to create the projection for.
  395. /// The left eye when set to 1, the right eye when set to 2.
  396. /// </param>
  397. /// <param name="intraocularDist">The distance between the eyes.</param>
  398. /// <param name="convergenceDist">The distance to a point of convergence that can be focused on.</param>
  399. /// <returns>The created projection.</returns>
  400. public static Projection CreatePerspectiveHmd(real_t fovyDegrees, real_t aspect, real_t zNear, real_t zFar, bool flipFov, int eye, real_t intraocularDist, real_t convergenceDist)
  401. {
  402. if (flipFov)
  403. {
  404. fovyDegrees = GetFovy(fovyDegrees, (real_t)1.0 / aspect);
  405. }
  406. real_t ymax = zNear * Mathf.Tan(Mathf.DegToRad(fovyDegrees / (real_t)2.0));
  407. real_t xmax = ymax * aspect;
  408. real_t frustumshift = (intraocularDist / (real_t)2.0) * zNear / convergenceDist;
  409. real_t left;
  410. real_t right;
  411. real_t modeltranslation;
  412. switch (eye)
  413. {
  414. case 1:
  415. left = -xmax + frustumshift;
  416. right = xmax + frustumshift;
  417. modeltranslation = intraocularDist / (real_t)2.0;
  418. break;
  419. case 2:
  420. left = -xmax - frustumshift;
  421. right = xmax - frustumshift;
  422. modeltranslation = -intraocularDist / (real_t)2.0;
  423. break;
  424. default:
  425. left = -xmax;
  426. right = xmax;
  427. modeltranslation = (real_t)0.0;
  428. break;
  429. }
  430. Projection proj = CreateFrustum(left, right, -ymax, ymax, zNear, zFar);
  431. Projection cm = Projection.Identity;
  432. cm.W.X = modeltranslation;
  433. return proj * cm;
  434. }
  435. /// <summary>
  436. /// Returns a scalar value that is the signed factor by which areas are scaled by this matrix.
  437. /// If the sign is negative, the matrix flips the orientation of the area.
  438. /// The determinant can be used to calculate the invertibility of a matrix or solve linear systems
  439. /// of equations involving the matrix, among other applications.
  440. /// </summary>
  441. /// <returns>The determinant calculated from this projection.</returns>
  442. public readonly real_t Determinant()
  443. {
  444. return X.W * Y.Z * Z.Y * W.X - X.Z * Y.W * Z.Y * W.X -
  445. X.W * Y.Y * Z.Z * W.X + X.Y * Y.W * Z.Z * W.X +
  446. X.Z * Y.Y * Z.W * W.X - X.Y * Y.Z * Z.W * W.X -
  447. X.W * Y.Z * Z.X * W.Y + X.Z * Y.W * Z.X * W.Y +
  448. X.W * Y.X * Z.Z * W.Y - X.X * Y.W * Z.Z * W.Y -
  449. X.Z * Y.X * Z.W * W.Y + X.X * Y.Z * Z.W * W.Y +
  450. X.W * Y.Y * Z.X * W.Z - X.Y * Y.W * Z.X * W.Z -
  451. X.W * Y.X * Z.Y * W.Z + X.X * Y.W * Z.Y * W.Z +
  452. X.Y * Y.X * Z.W * W.Z - X.X * Y.Y * Z.W * W.Z -
  453. X.Z * Y.Y * Z.X * W.W + X.Y * Y.Z * Z.X * W.W +
  454. X.Z * Y.X * Z.Y * W.W - X.X * Y.Z * Z.Y * W.W -
  455. X.Y * Y.X * Z.Z * W.W + X.X * Y.Y * Z.Z * W.W;
  456. }
  457. /// <summary>
  458. /// Returns the X:Y aspect ratio of this <see cref="Projection"/>'s viewport.
  459. /// </summary>
  460. /// <returns>The aspect ratio from this projection's viewport.</returns>
  461. public readonly real_t GetAspect()
  462. {
  463. Vector2 vpHe = GetViewportHalfExtents();
  464. return vpHe.X / vpHe.Y;
  465. }
  466. /// <summary>
  467. /// Returns the horizontal field of view of the projection (in degrees).
  468. /// </summary>
  469. /// <returns>The horizontal field of view of this projection.</returns>
  470. public readonly real_t GetFov()
  471. {
  472. Plane rightPlane = new Plane(X.W - X.X, Y.W - Y.X, Z.W - Z.X, -W.W + W.X).Normalized();
  473. if (Z.X == 0 && Z.Y == 0)
  474. {
  475. return Mathf.RadToDeg(Mathf.Acos(Mathf.Abs(rightPlane.Normal.X))) * (real_t)2.0;
  476. }
  477. else
  478. {
  479. Plane leftPlane = new Plane(X.W + X.X, Y.W + Y.X, Z.W + Z.X, W.W + W.X).Normalized();
  480. return Mathf.RadToDeg(Mathf.Acos(Mathf.Abs(leftPlane.Normal.X))) + Mathf.RadToDeg(Mathf.Acos(Mathf.Abs(rightPlane.Normal.X)));
  481. }
  482. }
  483. /// <summary>
  484. /// Returns the vertical field of view of the projection (in degrees) associated with
  485. /// the given horizontal field of view (in degrees) and aspect ratio.
  486. /// </summary>
  487. /// <param name="fovx">The horizontal field of view (in degrees).</param>
  488. /// <param name="aspect">The aspect ratio.</param>
  489. /// <returns>The vertical field of view of this projection.</returns>
  490. public static real_t GetFovy(real_t fovx, real_t aspect)
  491. {
  492. return Mathf.RadToDeg(Mathf.Atan(aspect * Mathf.Tan(Mathf.DegToRad(fovx) * (real_t)0.5)) * (real_t)2.0);
  493. }
  494. /// <summary>
  495. /// Returns the factor by which the visible level of detail is scaled by this <see cref="Projection"/>.
  496. /// </summary>
  497. /// <returns>The level of detail factor for this projection.</returns>
  498. public readonly real_t GetLodMultiplier()
  499. {
  500. if (IsOrthogonal())
  501. {
  502. return GetViewportHalfExtents().X;
  503. }
  504. else
  505. {
  506. real_t zn = GetZNear();
  507. real_t width = GetViewportHalfExtents().X * (real_t)2.0;
  508. return (real_t)1.0 / (zn / width);
  509. }
  510. }
  511. /// <summary>
  512. /// Returns the number of pixels with the given pixel width displayed per meter, after
  513. /// this <see cref="Projection"/> is applied.
  514. /// </summary>
  515. /// <param name="forPixelWidth">The width for each pixel (in meters).</param>
  516. /// <returns>The number of pixels per meter.</returns>
  517. public readonly int GetPixelsPerMeter(int forPixelWidth)
  518. {
  519. Vector3 result = this * new Vector3(1, 0, -1);
  520. return (int)((result.X * (real_t)0.5 + (real_t)0.5) * forPixelWidth);
  521. }
  522. /// <summary>
  523. /// Returns the clipping plane of this <see cref="Projection"/> whose index is given
  524. /// by <paramref name="plane"/>.
  525. /// <paramref name="plane"/> should be equal to one of <see cref="Planes.Near"/>,
  526. /// <see cref="Planes.Far"/>, <see cref="Planes.Left"/>, <see cref="Planes.Top"/>,
  527. /// <see cref="Planes.Right"/>, or <see cref="Planes.Bottom"/>.
  528. /// </summary>
  529. /// <param name="plane">The kind of clipping plane to get from the projection.</param>
  530. /// <returns>The clipping plane of this projection.</returns>
  531. public readonly Plane GetProjectionPlane(Planes plane)
  532. {
  533. Plane newPlane = plane switch
  534. {
  535. Planes.Near => new Plane(X.W + X.Z, Y.W + Y.Z, Z.W + Z.Z, W.W + W.Z),
  536. Planes.Far => new Plane(X.W - X.Z, Y.W - Y.Z, Z.W - Z.Z, W.W - W.Z),
  537. Planes.Left => new Plane(X.W + X.X, Y.W + Y.X, Z.W + Z.X, W.W + W.X),
  538. Planes.Top => new Plane(X.W - X.Y, Y.W - Y.Y, Z.W - Z.Y, W.W - W.Y),
  539. Planes.Right => new Plane(X.W - X.X, Y.W - Y.X, Z.W - Z.X, W.W - W.X),
  540. Planes.Bottom => new Plane(X.W + X.Y, Y.W + Y.Y, Z.W + Z.Y, W.W + W.Y),
  541. _ => new Plane(),
  542. };
  543. newPlane.Normal = -newPlane.Normal;
  544. return newPlane.Normalized();
  545. }
  546. /// <summary>
  547. /// Returns the dimensions of the far clipping plane of the projection, divided by two.
  548. /// </summary>
  549. /// <returns>The half extents for this projection's far plane.</returns>
  550. public readonly Vector2 GetFarPlaneHalfExtents()
  551. {
  552. var res = GetProjectionPlane(Planes.Far).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top));
  553. return res is null ? default : new Vector2(res.Value.X, res.Value.Y);
  554. }
  555. /// <summary>
  556. /// Returns the dimensions of the viewport plane that this <see cref="Projection"/>
  557. /// projects positions onto, divided by two.
  558. /// </summary>
  559. /// <returns>The half extents for this projection's viewport plane.</returns>
  560. public readonly Vector2 GetViewportHalfExtents()
  561. {
  562. var res = GetProjectionPlane(Planes.Near).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top));
  563. return res is null ? default : new Vector2(res.Value.X, res.Value.Y);
  564. }
  565. /// <summary>
  566. /// Returns the distance for this <see cref="Projection"/> beyond which positions are clipped.
  567. /// </summary>
  568. /// <returns>The distance beyond which positions are clipped.</returns>
  569. public readonly real_t GetZFar()
  570. {
  571. return GetProjectionPlane(Planes.Far).D;
  572. }
  573. /// <summary>
  574. /// Returns the distance for this <see cref="Projection"/> before which positions are clipped.
  575. /// </summary>
  576. /// <returns>The distance before which positions are clipped.</returns>
  577. public readonly real_t GetZNear()
  578. {
  579. return -GetProjectionPlane(Planes.Near).D;
  580. }
  581. /// <summary>
  582. /// Returns a copy of this <see cref="Projection"/> with the signs of the values of the Y column flipped.
  583. /// </summary>
  584. /// <returns>The flipped projection.</returns>
  585. public readonly Projection FlippedY()
  586. {
  587. Projection proj = this;
  588. proj.Y = -proj.Y;
  589. return proj;
  590. }
  591. /// <summary>
  592. /// Returns a <see cref="Projection"/> with the near clipping distance adjusted to be
  593. /// <paramref name="newZNear"/>.
  594. /// Note: The original <see cref="Projection"/> must be a perspective projection.
  595. /// </summary>
  596. /// <param name="newZNear">The near clipping distance to adjust the projection to.</param>
  597. /// <returns>The adjusted projection.</returns>
  598. public readonly Projection PerspectiveZNearAdjusted(real_t newZNear)
  599. {
  600. Projection proj = this;
  601. real_t zFar = GetZFar();
  602. real_t zNear = newZNear;
  603. real_t deltaZ = zFar - zNear;
  604. proj.Z.Z = -(zFar + zNear) / deltaZ;
  605. proj.W.Z = -2 * zNear * zFar / deltaZ;
  606. return proj;
  607. }
  608. /// <summary>
  609. /// Returns a <see cref="Projection"/> with the X and Y values from the given <see cref="Vector2"/>
  610. /// added to the first and second values of the final column respectively.
  611. /// </summary>
  612. /// <param name="offset">The offset to apply to the projection.</param>
  613. /// <returns>The offsetted projection.</returns>
  614. public readonly Projection JitterOffseted(Vector2 offset)
  615. {
  616. Projection proj = this;
  617. proj.W.X += offset.X;
  618. proj.W.Y += offset.Y;
  619. return proj;
  620. }
  621. /// <summary>
  622. /// Returns a <see cref="Projection"/> that performs the inverse of this <see cref="Projection"/>'s
  623. /// projective transformation.
  624. /// </summary>
  625. /// <returns>The inverted projection.</returns>
  626. public readonly Projection Inverse()
  627. {
  628. Projection proj = this;
  629. int i, j, k;
  630. int[] pvt_i = new int[4];
  631. int[] pvt_j = new int[4]; /* Locations of pivot matrix */
  632. real_t pvt_val; /* Value of current pivot element */
  633. real_t hold; /* Temporary storage */
  634. real_t determinant = 1.0f;
  635. for (k = 0; k < 4; k++)
  636. {
  637. /* Locate k'th pivot element */
  638. pvt_val = proj[k][k]; /* Initialize for search */
  639. pvt_i[k] = k;
  640. pvt_j[k] = k;
  641. for (i = k; i < 4; i++)
  642. {
  643. for (j = k; j < 4; j++)
  644. {
  645. if (Mathf.Abs(proj[i][j]) > Mathf.Abs(pvt_val))
  646. {
  647. pvt_i[k] = i;
  648. pvt_j[k] = j;
  649. pvt_val = proj[i][j];
  650. }
  651. }
  652. }
  653. /* Product of pivots, gives determinant when finished */
  654. determinant *= pvt_val;
  655. if (Mathf.IsZeroApprox(determinant))
  656. {
  657. return Zero;
  658. }
  659. /* "Interchange" rows (with sign change stuff) */
  660. i = pvt_i[k];
  661. if (i != k)
  662. { /* If rows are different */
  663. for (j = 0; j < 4; j++)
  664. {
  665. hold = -proj[k][j];
  666. proj[k, j] = proj[i][j];
  667. proj[i, j] = hold;
  668. }
  669. }
  670. /* "Interchange" columns */
  671. j = pvt_j[k];
  672. if (j != k)
  673. { /* If columns are different */
  674. for (i = 0; i < 4; i++)
  675. {
  676. hold = -proj[i][k];
  677. proj[i, k] = proj[i][j];
  678. proj[i, j] = hold;
  679. }
  680. }
  681. /* Divide column by minus pivot value */
  682. for (i = 0; i < 4; i++)
  683. {
  684. if (i != k)
  685. {
  686. proj[i, k] /= (-pvt_val);
  687. }
  688. }
  689. /* Reduce the matrix */
  690. for (i = 0; i < 4; i++)
  691. {
  692. hold = proj[i][k];
  693. for (j = 0; j < 4; j++)
  694. {
  695. if (i != k && j != k)
  696. {
  697. proj[i, j] += hold * proj[k][j];
  698. }
  699. }
  700. }
  701. /* Divide row by pivot */
  702. for (j = 0; j < 4; j++)
  703. {
  704. if (j != k)
  705. {
  706. proj[k, j] /= pvt_val;
  707. }
  708. }
  709. /* Replace pivot by reciprocal (at last we can touch it). */
  710. proj[k, k] = (real_t)1.0 / pvt_val;
  711. }
  712. /* That was most of the work, one final pass of row/column interchange */
  713. /* to finish */
  714. for (k = 4 - 2; k >= 0; k--)
  715. { /* Don't need to work with 1 by 1 corner*/
  716. i = pvt_j[k]; /* Rows to swap correspond to pivot COLUMN */
  717. if (i != k)
  718. { /* If rows are different */
  719. for (j = 0; j < 4; j++)
  720. {
  721. hold = proj[k][j];
  722. proj[k, j] = -proj[i][j];
  723. proj[i, j] = hold;
  724. }
  725. }
  726. j = pvt_i[k]; /* Columns to swap correspond to pivot ROW */
  727. if (j != k)
  728. { /* If columns are different */
  729. for (i = 0; i < 4; i++)
  730. {
  731. hold = proj[i][k];
  732. proj[i, k] = -proj[i][j];
  733. proj[i, j] = hold;
  734. }
  735. }
  736. }
  737. return proj;
  738. }
  739. /// <summary>
  740. /// Returns <see langword="true"/> if this <see cref="Projection"/> performs an orthogonal projection.
  741. /// </summary>
  742. /// <returns>If the projection performs an orthogonal projection.</returns>
  743. public readonly bool IsOrthogonal()
  744. {
  745. return W.W == (real_t)1.0;
  746. }
  747. // Constants
  748. private static readonly Projection _zero = new Projection(
  749. new Vector4(0, 0, 0, 0),
  750. new Vector4(0, 0, 0, 0),
  751. new Vector4(0, 0, 0, 0),
  752. new Vector4(0, 0, 0, 0)
  753. );
  754. private static readonly Projection _identity = new Projection(
  755. new Vector4(1, 0, 0, 0),
  756. new Vector4(0, 1, 0, 0),
  757. new Vector4(0, 0, 1, 0),
  758. new Vector4(0, 0, 0, 1)
  759. );
  760. /// <summary>
  761. /// Zero projection, a projection with all components set to <c>0</c>.
  762. /// </summary>
  763. /// <value>Equivalent to <c>new Projection(Vector4.Zero, Vector4.Zero, Vector4.Zero, Vector4.Zero)</c>.</value>
  764. public static Projection Zero { get { return _zero; } }
  765. /// <summary>
  766. /// The identity projection, with no distortion applied.
  767. /// This is used as a replacement for <c>Projection()</c> in GDScript.
  768. /// Do not use <c>new Projection()</c> with no arguments in C#, because it sets all values to zero.
  769. /// </summary>
  770. /// <value>Equivalent to <c>new Projection(new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1))</c>.</value>
  771. public static Projection Identity { get { return _identity; } }
  772. /// <summary>
  773. /// Constructs a projection from 4 vectors (matrix columns).
  774. /// </summary>
  775. /// <param name="x">The X column, or column index 0.</param>
  776. /// <param name="y">The Y column, or column index 1.</param>
  777. /// <param name="z">The Z column, or column index 2.</param>
  778. /// <param name="w">The W column, or column index 3.</param>
  779. public Projection(Vector4 x, Vector4 y, Vector4 z, Vector4 w)
  780. {
  781. X = x;
  782. Y = y;
  783. Z = z;
  784. W = w;
  785. }
  786. /// <summary>
  787. /// Constructs a projection from 16 scalars.
  788. /// </summary>
  789. /// <param name="xx">The X column vector's X component, accessed via <c>p.X.X</c> or <c>[0][0]</c>.</param>
  790. /// <param name="xy">The X column vector's Y component, accessed via <c>p.X.Y</c> or <c>[0][1]</c>.</param>
  791. /// <param name="xz">The X column vector's Z component, accessed via <c>p.X.Z</c> or <c>[0][2]</c>.</param>
  792. /// <param name="xw">The X column vector's W component, accessed via <c>p.X.W</c> or <c>[0][3]</c>.</param>
  793. /// <param name="yx">The Y column vector's X component, accessed via <c>p.Y.X</c> or <c>[1][0]</c>.</param>
  794. /// <param name="yy">The Y column vector's Y component, accessed via <c>p.Y.Y</c> or <c>[1][1]</c>.</param>
  795. /// <param name="yz">The Y column vector's Z component, accessed via <c>p.Y.Z</c> or <c>[1][2]</c>.</param>
  796. /// <param name="yw">The Y column vector's W component, accessed via <c>p.Y.W</c> or <c>[1][3]</c>.</param>
  797. /// <param name="zx">The Z column vector's X component, accessed via <c>p.Z.X</c> or <c>[2][0]</c>.</param>
  798. /// <param name="zy">The Z column vector's Y component, accessed via <c>p.Z.Y</c> or <c>[2][1]</c>.</param>
  799. /// <param name="zz">The Z column vector's Z component, accessed via <c>p.Z.Z</c> or <c>[2][2]</c>.</param>
  800. /// <param name="zw">The Z column vector's W component, accessed via <c>p.Z.W</c> or <c>[2][3]</c>.</param>
  801. /// <param name="wx">The W column vector's X component, accessed via <c>p.W.X</c> or <c>[3][0]</c>.</param>
  802. /// <param name="wy">The W column vector's Y component, accessed via <c>p.W.Y</c> or <c>[3][1]</c>.</param>
  803. /// <param name="wz">The W column vector's Z component, accessed via <c>p.W.Z</c> or <c>[3][2]</c>.</param>
  804. /// <param name="ww">The W column vector's W component, accessed via <c>p.W.W</c> or <c>[3][3]</c>.</param>
  805. public Projection(real_t xx, real_t xy, real_t xz, real_t xw, real_t yx, real_t yy, real_t yz, real_t yw, real_t zx, real_t zy, real_t zz, real_t zw, real_t wx, real_t wy, real_t wz, real_t ww)
  806. {
  807. X = new Vector4(xx, xy, xz, xw);
  808. Y = new Vector4(yx, yy, yz, yw);
  809. Z = new Vector4(zx, zy, zz, zw);
  810. W = new Vector4(wx, wy, wz, ww);
  811. }
  812. /// <summary>
  813. /// Constructs a new <see cref="Projection"/> from a <see cref="Transform3D"/>.
  814. /// </summary>
  815. /// <param name="transform">The <see cref="Transform3D"/>.</param>
  816. public Projection(Transform3D transform)
  817. {
  818. X = new Vector4(transform.Basis.Row0.X, transform.Basis.Row1.X, transform.Basis.Row2.X, 0);
  819. Y = new Vector4(transform.Basis.Row0.Y, transform.Basis.Row1.Y, transform.Basis.Row2.Y, 0);
  820. Z = new Vector4(transform.Basis.Row0.Z, transform.Basis.Row1.Z, transform.Basis.Row2.Z, 0);
  821. W = new Vector4(transform.Origin.X, transform.Origin.Y, transform.Origin.Z, 1);
  822. }
  823. /// <summary>
  824. /// Composes these two projections by multiplying them
  825. /// together. This has the effect of applying the right
  826. /// and then the left projection.
  827. /// </summary>
  828. /// <param name="left">The parent transform.</param>
  829. /// <param name="right">The child transform.</param>
  830. /// <returns>The composed projection.</returns>
  831. public static Projection operator *(Projection left, Projection right)
  832. {
  833. return new Projection(
  834. new Vector4(
  835. left.X.X * right.X.X + left.Y.X * right.X.Y + left.Z.X * right.X.Z + left.W.X * right.X.W,
  836. left.X.Y * right.X.X + left.Y.Y * right.X.Y + left.Z.Y * right.X.Z + left.W.Y * right.X.W,
  837. left.X.Z * right.X.X + left.Y.Z * right.X.Y + left.Z.Z * right.X.Z + left.W.Z * right.X.W,
  838. left.X.W * right.X.X + left.Y.W * right.X.Y + left.Z.W * right.X.Z + left.W.W * right.X.W
  839. ), new Vector4(
  840. left.X.X * right.Y.X + left.Y.X * right.Y.Y + left.Z.X * right.Y.Z + left.W.X * right.Y.W,
  841. left.X.Y * right.Y.X + left.Y.Y * right.Y.Y + left.Z.Y * right.Y.Z + left.W.Y * right.Y.W,
  842. left.X.Z * right.Y.X + left.Y.Z * right.Y.Y + left.Z.Z * right.Y.Z + left.W.Z * right.Y.W,
  843. left.X.W * right.Y.X + left.Y.W * right.Y.Y + left.Z.W * right.Y.Z + left.W.W * right.Y.W
  844. ), new Vector4(
  845. left.X.X * right.Z.X + left.Y.X * right.Z.Y + left.Z.X * right.Z.Z + left.W.X * right.Z.W,
  846. left.X.Y * right.Z.X + left.Y.Y * right.Z.Y + left.Z.Y * right.Z.Z + left.W.Y * right.Z.W,
  847. left.X.Z * right.Z.X + left.Y.Z * right.Z.Y + left.Z.Z * right.Z.Z + left.W.Z * right.Z.W,
  848. left.X.W * right.Z.X + left.Y.W * right.Z.Y + left.Z.W * right.Z.Z + left.W.W * right.Z.W
  849. ), new Vector4(
  850. left.X.X * right.W.X + left.Y.X * right.W.Y + left.Z.X * right.W.Z + left.W.X * right.W.W,
  851. left.X.Y * right.W.X + left.Y.Y * right.W.Y + left.Z.Y * right.W.Z + left.W.Y * right.W.W,
  852. left.X.Z * right.W.X + left.Y.Z * right.W.Y + left.Z.Z * right.W.Z + left.W.Z * right.W.W,
  853. left.X.W * right.W.X + left.Y.W * right.W.Y + left.Z.W * right.W.Z + left.W.W * right.W.W
  854. )
  855. );
  856. }
  857. /// <summary>
  858. /// Returns a Vector4 transformed (multiplied) by the projection.
  859. /// </summary>
  860. /// <param name="proj">The projection to apply.</param>
  861. /// <param name="vector">A Vector4 to transform.</param>
  862. /// <returns>The transformed Vector4.</returns>
  863. public static Vector4 operator *(Projection proj, Vector4 vector)
  864. {
  865. return new Vector4(
  866. proj.X.X * vector.X + proj.Y.X * vector.Y + proj.Z.X * vector.Z + proj.W.X * vector.W,
  867. proj.X.Y * vector.X + proj.Y.Y * vector.Y + proj.Z.Y * vector.Z + proj.W.Y * vector.W,
  868. proj.X.Z * vector.X + proj.Y.Z * vector.Y + proj.Z.Z * vector.Z + proj.W.Z * vector.W,
  869. proj.X.W * vector.X + proj.Y.W * vector.Y + proj.Z.W * vector.Z + proj.W.W * vector.W
  870. );
  871. }
  872. /// <summary>
  873. /// Returns a Vector4 transformed (multiplied) by the transpose of the projection.
  874. /// For transforming by inverse of a projection <c>projection.Inverse() * vector</c> can be used instead. See <see cref="Inverse"/>.
  875. /// </summary>
  876. /// <param name="proj">The projection to apply.</param>
  877. /// <param name="vector">A Vector4 to transform.</param>
  878. /// <returns>The inversely transformed Vector4.</returns>
  879. public static Vector4 operator *(Vector4 vector, Projection proj)
  880. {
  881. return new Vector4(
  882. proj.X.X * vector.X + proj.X.Y * vector.Y + proj.X.Z * vector.Z + proj.X.W * vector.W,
  883. proj.Y.X * vector.X + proj.Y.Y * vector.Y + proj.Y.Z * vector.Z + proj.Y.W * vector.W,
  884. proj.Z.X * vector.X + proj.Z.Y * vector.Y + proj.Z.Z * vector.Z + proj.Z.W * vector.W,
  885. proj.W.X * vector.X + proj.W.Y * vector.Y + proj.W.Z * vector.Z + proj.W.W * vector.W
  886. );
  887. }
  888. /// <summary>
  889. /// Returns a Vector3 transformed (multiplied) by the projection.
  890. /// </summary>
  891. /// <param name="proj">The projection to apply.</param>
  892. /// <param name="vector">A Vector3 to transform.</param>
  893. /// <returns>The transformed Vector3.</returns>
  894. public static Vector3 operator *(Projection proj, Vector3 vector)
  895. {
  896. Vector3 ret = new Vector3(
  897. proj.X.X * vector.X + proj.Y.X * vector.Y + proj.Z.X * vector.Z + proj.W.X,
  898. proj.X.Y * vector.X + proj.Y.Y * vector.Y + proj.Z.Y * vector.Z + proj.W.Y,
  899. proj.X.Z * vector.X + proj.Y.Z * vector.Y + proj.Z.Z * vector.Z + proj.W.Z
  900. );
  901. return ret / (proj.X.W * vector.X + proj.Y.W * vector.Y + proj.Z.W * vector.Z + proj.W.W);
  902. }
  903. /// <summary>
  904. /// Returns <see langword="true"/> if the projections are exactly equal.
  905. /// </summary>
  906. /// <param name="left">The left projection.</param>
  907. /// <param name="right">The right projection.</param>
  908. /// <returns>Whether or not the projections are exactly equal.</returns>
  909. public static bool operator ==(Projection left, Projection right)
  910. {
  911. return left.Equals(right);
  912. }
  913. /// <summary>
  914. /// Returns <see langword="true"/> if the projections are not exactly equal.
  915. /// </summary>
  916. /// <param name="left">The left projection.</param>
  917. /// <param name="right">The right projection.</param>
  918. /// <returns>Whether or not the projections are not exactly equal.</returns>
  919. public static bool operator !=(Projection left, Projection right)
  920. {
  921. return !left.Equals(right);
  922. }
  923. /// <summary>
  924. /// Constructs a new <see cref="Transform3D"/> from the <see cref="Projection"/>.
  925. /// </summary>
  926. /// <param name="proj">The <see cref="Projection"/>.</param>
  927. public static explicit operator Transform3D(Projection proj)
  928. {
  929. return new Transform3D(
  930. new Basis(
  931. new Vector3(proj.X.X, proj.X.Y, proj.X.Z),
  932. new Vector3(proj.Y.X, proj.Y.Y, proj.Y.Z),
  933. new Vector3(proj.Z.X, proj.Z.Y, proj.Z.Z)
  934. ),
  935. new Vector3(proj.W.X, proj.W.Y, proj.W.Z)
  936. );
  937. }
  938. /// <summary>
  939. /// Returns <see langword="true"/> if the projection is exactly equal
  940. /// to the given object (<paramref name="obj"/>).
  941. /// </summary>
  942. /// <param name="obj">The object to compare with.</param>
  943. /// <returns>Whether or not the vector and the object are equal.</returns>
  944. public override readonly bool Equals([NotNullWhen(true)] object? obj)
  945. {
  946. return obj is Projection other && Equals(other);
  947. }
  948. /// <summary>
  949. /// Returns <see langword="true"/> if the projections are exactly equal.
  950. /// </summary>
  951. /// <param name="other">The other projection.</param>
  952. /// <returns>Whether or not the projections are exactly equal.</returns>
  953. public readonly bool Equals(Projection other)
  954. {
  955. return X == other.X && Y == other.Y && Z == other.Z && W == other.W;
  956. }
  957. /// <summary>
  958. /// Serves as the hash function for <see cref="Projection"/>.
  959. /// </summary>
  960. /// <returns>A hash code for this projection.</returns>
  961. public override readonly int GetHashCode()
  962. {
  963. return HashCode.Combine(X, Y, Z, W);
  964. }
  965. /// <summary>
  966. /// Converts this <see cref="Projection"/> to a string.
  967. /// </summary>
  968. /// <returns>A string representation of this projection.</returns>
  969. public override readonly string ToString() => ToString(null);
  970. /// <summary>
  971. /// Converts this <see cref="Projection"/> to a string with the given <paramref name="format"/>.
  972. /// </summary>
  973. /// <returns>A string representation of this projection.</returns>
  974. public readonly string ToString(string? format)
  975. {
  976. return $"{X.X.ToString(format, CultureInfo.InvariantCulture)}, {X.Y.ToString(format, CultureInfo.InvariantCulture)}, {X.Z.ToString(format, CultureInfo.InvariantCulture)}, {X.W.ToString(format, CultureInfo.InvariantCulture)}\n" +
  977. $"{Y.X.ToString(format, CultureInfo.InvariantCulture)}, {Y.Y.ToString(format, CultureInfo.InvariantCulture)}, {Y.Z.ToString(format, CultureInfo.InvariantCulture)}, {Y.W.ToString(format, CultureInfo.InvariantCulture)}\n" +
  978. $"{Z.X.ToString(format, CultureInfo.InvariantCulture)}, {Z.Y.ToString(format, CultureInfo.InvariantCulture)}, {Z.Z.ToString(format, CultureInfo.InvariantCulture)}, {Z.W.ToString(format, CultureInfo.InvariantCulture)}\n" +
  979. $"{W.X.ToString(format, CultureInfo.InvariantCulture)}, {W.Y.ToString(format, CultureInfo.InvariantCulture)}, {W.Z.ToString(format, CultureInfo.InvariantCulture)}, {W.W.ToString(format, CultureInfo.InvariantCulture)}\n";
  980. }
  981. }
  982. }