WorldView.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. /*
  2. Copyright (c) 2017, Lars Brubaker, John Lewin
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are met:
  6. 1. Redistributions of source code must retain the above copyright notice, this
  7. list of conditions and the following disclaimer.
  8. 2. Redistributions in binary form must reproduce the above copyright notice,
  9. this list of conditions and the following disclaimer in the documentation
  10. and/or other materials provided with the distribution.
  11. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  12. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  13. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  14. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  15. ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  16. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  17. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  18. ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  19. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  20. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  21. The views and conclusions contained in the software and documentation are those
  22. of the authors and should not be interpreted as representing official policies,
  23. either expressed or implied, of the FreeBSD Project.
  24. */
  25. using System;
  26. namespace MatterHackers.VectorMath
  27. {
  28. public class WorldView
  29. {
  30. public const double DefaultPerspectiveVFOVDegrees = 45;
  31. public const double DefaultNearZ = 0.1;
  32. public const double DefaultFarZ = 100.0;
  33. // Force this minimum on near Z.
  34. // As the dynamic near plane will take this value inside an AABB, it'll be the same as the default for now.
  35. public const double PerspectiveProjectionMinimumNearZ = DefaultNearZ;
  36. // far / near >= this
  37. private const double PerspectiveProjectionMinimumFarNearRatio = 1.001;
  38. // Force this minimum distance between the near and far planes for the orthographic projection.
  39. private const double OrthographicProjectionMinimumNearFarGap = 0.001;
  40. // Derive from the perspective minimum near Z.
  41. public static readonly double OrthographicProjectionMinimumHeight = CalcPerspectiveHeight(PerspectiveProjectionMinimumNearZ, DefaultPerspectiveVFOVDegrees);
  42. private const double CameraZTranslationFudge = -7;
  43. public double Height { get; private set; } = 0;
  44. public double Width { get; private set; } = 0;
  45. public Vector2 ViewportSize { get { return new Vector2(Width, Height); } }
  46. /// <summary>
  47. /// The vertical FOV of the latest perspective projection. Untouched by orthographic projection.
  48. /// </summary>
  49. public double VFovDegrees { get; private set; } = 0;
  50. /// <summary>
  51. /// The horizontal FOV of the latest perspective projection. Untouched by orthographic projection.
  52. /// May not be symmetric due to center offset.
  53. /// </summary>
  54. public double HFovDegrees { get; private set; } = 0;
  55. /// <summary>
  56. /// The height of the near plane in viewspace in either projection mode. For orthographic, this is constant for all Z.
  57. /// This value is used to maintain orthographic scale during viewport resize.
  58. /// </summary>
  59. public double NearPlaneHeightInViewspace { get; private set; } = 0;
  60. public bool IsOrthographic { get; private set; } = false;
  61. public Matrix4X4 ModelviewMatrix { get; private set; } = Matrix4X4.Identity;
  62. public Matrix4X4 ProjectionMatrix { get; private set; } = Matrix4X4.Identity;
  63. public Matrix4X4 InverseModelviewMatrix { get; private set; } = Matrix4X4.Identity;
  64. public Matrix4X4 InverseProjectionMatrix { get; private set; } = Matrix4X4.Identity;
  65. /// <summary>
  66. /// Signed distance of the near plane from the eye along the forward axis. Positive for perspective projection.
  67. /// </summary>
  68. public double NearZ { get; private set; } = 0;
  69. /// <summary>
  70. /// Signed distance of the far plane from the eye along the forward axis. Positive for perspective projection.
  71. /// </summary>
  72. public double FarZ { get; private set; } = 0;
  73. private Matrix4X4 _rotationMatrix = Matrix4X4.Identity;
  74. private Matrix4X4 _translationMatrix = Matrix4X4.Identity;
  75. public Matrix4X4 RotationMatrix
  76. {
  77. get => _rotationMatrix;
  78. set
  79. {
  80. if (_rotationMatrix != value)
  81. {
  82. #if DEBUG
  83. if (!value.IsValid())
  84. {
  85. }
  86. #endif
  87. _rotationMatrix = value;
  88. OnTransformChanged(null);
  89. }
  90. }
  91. }
  92. public double Scale
  93. {
  94. get
  95. {
  96. var scaledUnitVector = Vector3.UnitX.TransformPosition(this.GetTransform4X4());
  97. return scaledUnitVector.Length;
  98. }
  99. set
  100. {
  101. if (Scale > 0)
  102. {
  103. double requiredChange = value / Scale;
  104. TranslationMatrix *= Matrix4X4.CreateScale(requiredChange);
  105. OnTransformChanged(null);
  106. }
  107. }
  108. }
  109. public Matrix4X4 TranslationMatrix
  110. {
  111. get => _translationMatrix;
  112. set
  113. {
  114. if (_translationMatrix != value)
  115. {
  116. #if DEBUG
  117. if (!value.IsValid())
  118. {
  119. }
  120. #endif
  121. _translationMatrix = value;
  122. OnTransformChanged(null);
  123. }
  124. }
  125. }
  126. public void CalculateModelviewMatrix()
  127. {
  128. this.ModelviewMatrix = this.GetTransform4X4() * Matrix4X4.CreateTranslation(0, 0, CameraZTranslationFudge);
  129. this.InverseModelviewMatrix = Matrix4X4.Invert(this.ModelviewMatrix);
  130. }
  131. public Vector3 EyePosition
  132. {
  133. get
  134. {
  135. return Vector3.Zero.Transform(InverseModelviewMatrix);
  136. }
  137. set
  138. {
  139. this.Translate(EyePosition - value);
  140. }
  141. }
  142. /// <summary>
  143. /// Initialises with a typical perspective projection and a default camera transform.
  144. /// </summary>
  145. /// <param name="width">Width of the viewport.</param>
  146. /// <param name="height">Height of the viewport.</param>
  147. public WorldView(double width, double height)
  148. {
  149. this.CalculatePerspectiveMatrix(width, height);
  150. this.CalculateModelviewMatrix();
  151. }
  152. /// <summary>
  153. /// Sets a typical perspective projection with an FOV of 45.
  154. /// </summary>
  155. /// <param name="width">Width of the viewport.</param>
  156. /// <param name="height">Height of the viewport.</param>
  157. /// <param name="zNear">Positive position of the near plane along the forward axis.</param>
  158. /// <param name="zFar">Positive position of the far plane along the forward axis.</param>
  159. public void CalculatePerspectiveMatrix(
  160. double width, double height,
  161. double zNear = DefaultNearZ, double zFar = DefaultFarZ)
  162. {
  163. CalculatePerspectiveMatrixOffCenter(width, height, 0, zNear, zFar);
  164. }
  165. public static void SanitisePerspectiveNearFar(ref double near, ref double far)
  166. {
  167. near = Math.Max(near, PerspectiveProjectionMinimumNearZ);
  168. far = Math.Max(far, near * PerspectiveProjectionMinimumFarNearRatio);
  169. }
  170. /// <param name="distance">Signed distance from the camera to the plane.</param>
  171. /// <param name="vfovDegrees">Symmetric vertical FOV in degrees between the top and bottom.</param>
  172. /// <returns>The visible height within the FOV. Positive for positive arguments.</returns>
  173. public static double CalcPerspectiveHeight(double distance, double vfovDegrees)
  174. {
  175. return distance * 2 * Math.Tan(MathHelper.DegreesToRadians(vfovDegrees) * 0.5);
  176. }
  177. /// <param name="height">Signed visible height within the FOV.</param>
  178. /// <param name="vfovDegrees">Symmetric vertical FOV in degrees between the top and bottom.</param>
  179. /// <returns>Distance from the camera to the plane. Positive for positive arguments.</returns>
  180. public static double CalcPerspectiveDistance(double height, double vfovDegrees)
  181. {
  182. return height * 0.5 / Math.Tan(MathHelper.DegreesToRadians(vfovDegrees) * 0.5);
  183. }
  184. /// <param name="distance">Signed distance from the camera to the plane.</param>
  185. /// <param name="height">Signed visible height within the FOV.</param>
  186. /// <returns>The resulting symmetric vertical FOV in degrees between the top and bottom. Positive for positive arguments.</returns>
  187. public static double CalcPerspectiveVFOVDegreesFromDistanceAndHeight(double distance, double height)
  188. {
  189. return MathHelper.RadiansToDegrees(Math.Atan(height * 0.5 / distance) * 2);
  190. }
  191. /// <summary>
  192. /// Sets a perspective projection with a given center adjustment and FOV.
  193. /// </summary>
  194. /// <param name="width">Width of the viewport.</param>
  195. /// <param name="height">Height of the viewport.</param>
  196. /// <param name="centerOffsetX">Offset of the right edge, to adjust the position of the viewport's center.</param>
  197. /// <param name="zNear">Positive position of the near plane along the forward axis.</param>
  198. /// <param name="zFar">Positive position of the far plane along the forward axis.</param>
  199. /// <param name="vfovDegrees">Vertical FOV in degrees.</param>
  200. public void CalculatePerspectiveMatrixOffCenter(
  201. double width, double height,
  202. double centerOffsetX,
  203. double zNear = DefaultNearZ, double zFar = DefaultFarZ,
  204. double vfovDegrees = DefaultPerspectiveVFOVDegrees)
  205. {
  206. width = Math.Max(1, width);
  207. height = Math.Max(1, height);
  208. SanitisePerspectiveNearFar(ref zNear, ref zFar);
  209. var screenDist = CalcPerspectiveDistance(height, vfovDegrees);
  210. var center = width / 2;
  211. var xAngleL = Math.Atan2(-center - centerOffsetX / 2, screenDist);
  212. var xAngleR = Math.Atan2(center - centerOffsetX / 2, screenDist);
  213. // calculate yMin and yMax at the near clip plane
  214. double yMax = CalcPerspectiveHeight(zNear, vfovDegrees) * 0.5;
  215. double yMin = -yMax;
  216. double xMax = zNear * Math.Tan(xAngleR);
  217. double xMin = zNear * Math.Tan(xAngleL);
  218. Matrix4X4.CreatePerspectiveOffCenter(xMin, xMax, yMin, yMax, zNear, zFar, out Matrix4X4 projectionMatrix);
  219. this.ProjectionMatrix = projectionMatrix;
  220. this.InverseProjectionMatrix = Matrix4X4.Invert(projectionMatrix);
  221. this.Width = width;
  222. this.Height = height;
  223. this.VFovDegrees = vfovDegrees;
  224. this.HFovDegrees = MathHelper.RadiansToDegrees(Math.Abs(xAngleR - xAngleL));
  225. this.NearPlaneHeightInViewspace = yMax * 2;
  226. this.NearZ = zNear;
  227. this.FarZ = zFar;
  228. this.IsOrthographic = false;
  229. }
  230. public static void SanitiseOrthographicNearFar(ref double near, ref double far)
  231. {
  232. far = Math.Max(far, near + WorldView.OrthographicProjectionMinimumNearFarGap);
  233. if (far <= near)
  234. {
  235. // zNear is so large that the addition didn't make a difference.
  236. // Hope it's not infinity and do something multiplicative instead.
  237. if (near >= 0)
  238. far = near * 2;
  239. else
  240. far = near / 2;
  241. }
  242. }
  243. /// <summary>
  244. /// Sets an orthographic projection with an explicit height in viewspace.
  245. /// </summary>
  246. /// <param name="width">Width of the viewport.</param>
  247. /// <param name="height">Height of the viewport.</param>
  248. /// <param name="centerOffsetX">Offset of the right edge, to adjust the position of the viewport's center.</param>
  249. /// <param name="heightInViewspace">The height of the projection in viewspace.</param>
  250. /// <param name="zNear">Signed position of the near plane along the forward axis.</param>
  251. /// <param name="zFar">Signed position of the far plane along the forward axis.</param>
  252. public void CalculateOrthogrphicMatrixOffCenterWithViewspaceHeight(
  253. double width, double height,
  254. double centerOffsetX,
  255. double heightInViewspace,
  256. double zNear = DefaultNearZ, double zFar = DefaultFarZ)
  257. {
  258. width = Math.Max(1, width);
  259. height = Math.Max(1, height);
  260. heightInViewspace = Math.Max(heightInViewspace, OrthographicProjectionMinimumHeight);
  261. SanitiseOrthographicNearFar(ref zNear, ref zFar);
  262. double effectiveViewWidth = Math.Max(1, width + centerOffsetX);
  263. double screenCenterX = effectiveViewWidth / 2;
  264. double xMax = heightInViewspace / height * (width - screenCenterX);
  265. double xMin = heightInViewspace / height * -screenCenterX;
  266. double yMax = heightInViewspace / 2;
  267. double yMin = -yMax;
  268. Matrix4X4.CreateOrthographicOffCenter(xMin, xMax, yMin, yMax, zNear, zFar, out Matrix4X4 projectionMatrix);
  269. this.Width = width;
  270. this.Height = height;
  271. this.ProjectionMatrix = projectionMatrix;
  272. this.InverseProjectionMatrix = Matrix4X4.Invert(projectionMatrix);
  273. this.NearPlaneHeightInViewspace = heightInViewspace;
  274. this.NearZ = zNear;
  275. this.FarZ = zFar;
  276. this.IsOrthographic = true;
  277. }
  278. /// <param name="worldPosition">Position in worldspace.</param>
  279. /// <returns>[0, 0]..[Width, Height] (+Y is up)</returns>
  280. public Vector2 GetScreenPosition(Vector3 worldPosition)
  281. {
  282. var viewspace = WorldspaceToViewspace(worldPosition);
  283. var ndc = ViewspaceToNDC(viewspace);
  284. return NDCToBottomScreenspace(ndc.Xy);
  285. }
  286. /// <param name="worldPosition">Position in worldspace.</param>
  287. /// <returns>NDC before the perspective divide (clip-space).</returns>
  288. public Vector3 WorldToScreenSpace(Vector3 worldPosition)
  289. {
  290. var viewPosition = worldPosition.Transform(ModelviewMatrix);
  291. return viewPosition.Transform(ProjectionMatrix);
  292. }
  293. public Vector3 ScreenToWorldSpace(Vector3 screenPosition)
  294. {
  295. throw new NotImplementedException();
  296. // this function is not working at this time (it is not the inverse of the WorldToScreenSpace, and needs to be)
  297. var xxx = screenPosition.Transform(InverseProjectionMatrix);
  298. return xxx.Transform(InverseModelviewMatrix);
  299. }
  300. /// <param name="screenPosition">Screenspace coordinate with bottom-left origin.</param>
  301. /// <returns>
  302. /// A ray in worldspace along all points at the given position.
  303. /// In perspective mode, the origin is EyePosition.
  304. /// In orthographic mode, the origin is on the near plane with infinite extent in both directions (Ray.minDistanceToConsider is -inf).
  305. /// </returns>
  306. public Ray GetRayForLocalBounds(Vector2 screenPosition)
  307. {
  308. var nearNDC = BottomScreenspaceToNDC(new Vector3(screenPosition, -1));
  309. var nearViewspacePosition = NDCToViewspace(nearNDC);
  310. if (IsOrthographic)
  311. {
  312. return new Ray(nearViewspacePosition.TransformPosition(InverseModelviewMatrix), -Vector3.UnitZ.TransformVector(InverseModelviewMatrix).GetNormal(),
  313. minDistanceToConsider: double.NegativeInfinity);
  314. }
  315. else
  316. {
  317. return new Ray(EyePosition, nearViewspacePosition.TransformVector(InverseModelviewMatrix).GetNormal());
  318. }
  319. }
  320. public Matrix4X4 GetTransform4X4()
  321. {
  322. return TranslationMatrix * RotationMatrix;
  323. }
  324. // This code just doesn't look right... (and appears to be unused)
  325. /*
  326. public Vector3 GetWorldPosition(Vector2 screenPosition)
  327. {
  328. var homoginizedScreenSpace = new Vector4((2.0f * (screenPosition.X / Width)) - 1,
  329. 1 - (2 * (screenPosition.Y / Height)),
  330. 1,
  331. 1);
  332. var unprojected = Vector4.Transform(homoginizedScreenSpace, InverseProjectionMatrix);
  333. var worldSpace2 = Vector4.Transform(unprojected, InverseModelviewMatrix);
  334. return new Vector3(worldSpace2);
  335. Matrix4X4 viewProjection = ModelviewMatrix * ProjectionMatrix;
  336. var viewProjectionInverse = Matrix4X4.Invert(viewProjection);
  337. var woldSpace = Vector4.Transform(homoginizedScreenSpace, viewProjectionInverse);
  338. double perspectiveDivide = 1 / woldSpace.W;
  339. woldSpace.X *= perspectiveDivide;
  340. woldSpace.Y *= perspectiveDivide;
  341. woldSpace.Z *= perspectiveDivide;
  342. return new Vector3(woldSpace);
  343. }
  344. */
  345. public double GetViewspaceHeightAtPosition(Vector3 viewspacePosition)
  346. {
  347. if (this.IsOrthographic)
  348. return NearPlaneHeightInViewspace;
  349. else
  350. return NearPlaneHeightInViewspace * viewspacePosition.Z / -NearZ;
  351. }
  352. /// <param name="worldPosition">Position in worldspace.</param>
  353. /// <returns>
  354. /// Units per screenspace X in worldspace at the given position.
  355. /// Always positive unless underflow or NaN occurs.
  356. /// The absolute value is taken and clamped to a minimum derived from the minimum allowed near plane.
  357. /// </returns>
  358. // NOTE: Original implementation always returns non-negative and callers depend on non-zero.
  359. public double GetWorldUnitsPerScreenPixelAtPosition(Vector3 worldPosition, double maxRatio = 5)
  360. {
  361. Vector3 viewspace = WorldspaceToViewspace(worldPosition);
  362. double viewspaceUnitsPerPixel = GetViewspaceHeightAtPosition(viewspace) / Height;
  363. double minMagnitude = GetViewspaceHeightAtPosition(new Vector3(0, 0, -PerspectiveProjectionMinimumNearZ)) / Height;
  364. viewspaceUnitsPerPixel = Math.Max(Math.Abs(viewspaceUnitsPerPixel), minMagnitude);
  365. double worldspaceXUnitsPerPixel = new Vector3(viewspaceUnitsPerPixel, 0, 0).TransformVector(InverseModelviewMatrix).Length;
  366. return Math.Min(worldspaceXUnitsPerPixel, maxRatio);
  367. }
  368. public void OnTransformChanged(EventArgs e)
  369. {
  370. this.CalculateModelviewMatrix();
  371. }
  372. public void Reset()
  373. {
  374. RotationMatrix = Matrix4X4.Identity;
  375. TranslationMatrix = Matrix4X4.Identity;
  376. }
  377. public void Rotate(Quaternion rotation)
  378. {
  379. RotationMatrix *= Matrix4X4.CreateRotation(rotation);
  380. }
  381. public void RotateAroundPosition(Vector3 worldPosition, Quaternion rotation)
  382. {
  383. var newRotation = RotationMatrix * Matrix4X4.CreateRotation(rotation);
  384. SetRotationHoldPosition(worldPosition, newRotation);
  385. }
  386. public void Translate(Vector3 deltaPosition)
  387. {
  388. TranslationMatrix = Matrix4X4.CreateTranslation(deltaPosition) * TranslationMatrix;
  389. }
  390. public void SetRotationHoldPosition(Vector3 worldPosition, Quaternion newRotation)
  391. {
  392. SetRotationHoldPosition(worldPosition, Matrix4X4.CreateRotation(newRotation));
  393. }
  394. public void SetRotationHoldPosition(Vector3 worldPosition, Matrix4X4 newRotation)
  395. {
  396. // remember where we started on the screen
  397. var cameraSpaceStart = worldPosition.Transform(this.ModelviewMatrix);
  398. // do the rotation
  399. this.RotationMatrix = newRotation;
  400. // move back to where we started
  401. var worldStartPostRotation = cameraSpaceStart.Transform(this.InverseModelviewMatrix);
  402. var delta = worldStartPostRotation - worldPosition;
  403. this.Translate(delta);
  404. }
  405. /// <param name="worldspacePosition">Position in worldspace.</param>
  406. /// <returns>[0..0]..[Width, Height], if on-screen (+Y is up)</returns>
  407. public Vector3 WorldspaceToBottomScreenspace(Vector3 worldspacePosition)
  408. {
  409. Vector3 viewspace = WorldspaceToViewspace(worldspacePosition);
  410. Vector3 ndc = ViewspaceToNDC(viewspace);
  411. return NDCToBottomScreenspace(ndc);
  412. }
  413. /// <param name="worldspacePosition">Worldspace</param>
  414. /// <returns>[l, b, -near]..[r, t, -far] (if ortho)</returns>
  415. private Vector3 WorldspaceToViewspace(Vector3 worldspacePosition)
  416. {
  417. return worldspacePosition.TransformPosition(ModelviewMatrix);
  418. }
  419. /// <param name="viewspacePosition">[l, b, -near]..[r, t, -far] (if ortho)</param>
  420. /// <returns>[-1, -1, -1]..[1, 1, 1]</returns>
  421. private Vector3 ViewspaceToNDC(Vector3 viewspacePosition)
  422. {
  423. var v = Vector4.Transform(new Vector4(viewspacePosition, 1), ProjectionMatrix);
  424. return v.Xyz / v.W;
  425. }
  426. /// <param name="ndcPosition">[-1, -1, -1]..[1, 1, 1]</param>
  427. /// <returns>[l, b, -near]..[r, t, -far] (if ortho)</returns>
  428. public Vector3 NDCToViewspace(Vector3 ndcPosition)
  429. {
  430. var v = Vector4.Transform(new Vector4(ndcPosition, 1), InverseProjectionMatrix);
  431. return v.Xyz / v.W;
  432. }
  433. /// <param name="ndcPosition">[-1, -1]..[1, 1] (+Y is up)</param>
  434. /// <returns>[0, 0]..[Width, Height] (+Y is down)</returns>
  435. private Vector2 NDCToTopScreenspace(Vector2 ndcPosition)
  436. {
  437. return Vector2.Multiply(Vector2.Multiply(ndcPosition - new Vector2(-1, 1), ViewportSize), new Vector2(0.5, -0.5));
  438. }
  439. /// <param name="ndcPosition">[-1, -1]..[1, 1] (+Y is up)</param>
  440. /// <returns>[0, 0]..[Width, Height] (+Y is up)</returns>
  441. private Vector2 NDCToBottomScreenspace(Vector2 ndcPosition)
  442. {
  443. return Vector2.Multiply(Vector2.Multiply(ndcPosition - new Vector2(-1, -1), ViewportSize), new Vector2(0.5, 0.5));
  444. }
  445. /// <param name="screenspacePosition">[0, 0]..[Width, Height] (+Y is up)</param>
  446. /// <returns>[-1, -1]..[1, 1] (+Y is up)</returns>
  447. private Vector2 BottomScreenspaceToNDC(Vector2 screenspacePosition)
  448. {
  449. return Vector2.Divide(Vector2.Multiply(screenspacePosition, new Vector2(2, 2)), ViewportSize) + new Vector2(-1, -1);
  450. }
  451. /// <param name="ndcPosition">[-1, -1, -1]..[1, 1, 1] (+Y is up)</param>
  452. /// <returns>[0, 0, -1]..[Width, Height, 1] (+Y is up)</returns>
  453. private Vector3 NDCToBottomScreenspace(Vector3 ndcPosition)
  454. {
  455. return new Vector3(NDCToBottomScreenspace(ndcPosition.Xy), ndcPosition.Z);
  456. }
  457. /// <param name="screenspacePosition">[0, 0, -1]..[Width, Height, 1] (+Y is up)</param>
  458. /// <returns>[-1, -1, -1]..[1, 1, 1] (+Y is up)</returns>
  459. private Vector3 BottomScreenspaceToNDC(Vector3 screenspacePosition)
  460. {
  461. return new Vector3(BottomScreenspaceToNDC(screenspacePosition.Xy), screenspacePosition.Z);
  462. }
  463. }
  464. }