Vector2Extensions.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using Microsoft.Xna.Framework;
  4. namespace MonoGame.Extended
  5. {
  6. public static class Vector2Extensions
  7. {
  8. /// <summary>
  9. /// Computes the 2D pseudo cross product (perp-dot product) of two vectors.
  10. /// </summary>
  11. /// <param name="value1">The first vector.</param>
  12. /// <param name="value2">The second vector.</param>
  13. /// <returns>
  14. /// A scalar value representing twice the signed area of the parallelogram formed by the vectors.
  15. /// Positive if <paramref name="value2"/> is clockwise from <paramref name="value1"/>,
  16. /// negative if clockwise, zero if parallel.
  17. /// </returns>
  18. public static float PerpDot(Vector2 value1, Vector2 value2)
  19. {
  20. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  21. // Section 3.3.5 The Cross Product
  22. return (value1.X * value2.Y) - (value1.Y * value2.X);
  23. }
  24. /// <summary>
  25. /// Computes the 2D pseudo cross products (perp-dot product) of two vectors.
  26. /// </summary>
  27. /// <param name="value1">The first vector.</param>
  28. /// <param name="value2">The second vector.</param>
  29. /// <param name="result">
  30. /// A scalar value representing twice the signed area of the parallelogram formed by the vectors.
  31. /// Positive if <paramref name="value2"/> is clockwise from <paramref name="value1"/>,
  32. /// negative if clockwise, zero if parallel.
  33. /// </param>
  34. public static void PerpDot(ref Vector2 value1, ref Vector2 value2, out float result)
  35. {
  36. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  37. // Section 3.3.5 The Cross Product
  38. result = (value1.X * value2.Y) - (value1.Y * value2.X);
  39. }
  40. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  41. public static Vector2 SetX(this Vector2 vector2, float x) => new Vector2(x, vector2.Y);
  42. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  43. public static Vector2 SetY(this Vector2 vector2, float y) => new Vector2(vector2.X, y);
  44. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  45. public static Vector2 Translate(this Vector2 vector2, float x, float y) => new Vector2(vector2.X + x, vector2.Y + y);
  46. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  47. public static SizeF ToSize(this Vector2 value) => new SizeF(value.X, value.Y);
  48. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  49. public static SizeF ToAbsoluteSize(this Vector2 value)
  50. {
  51. var x = Math.Abs(value.X);
  52. var y = Math.Abs(value.Y);
  53. return new SizeF(x, y);
  54. }
  55. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  56. public static Vector2 Round(this Vector2 value, int digits, MidpointRounding mode)
  57. {
  58. var x = (float)Math.Round(value.X, digits, mode);
  59. var y = (float)Math.Round(value.Y, digits, mode);
  60. return new Vector2(x, y);
  61. }
  62. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  63. public static Vector2 Round(this Vector2 value, int digits)
  64. {
  65. var x = (float)Math.Round(value.X, digits);
  66. var y = (float)Math.Round(value.Y, digits);
  67. return new Vector2(x, y);
  68. }
  69. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  70. public static Vector2 Round(this Vector2 value)
  71. {
  72. var x = (float)Math.Round(value.X);
  73. var y = (float)Math.Round(value.Y);
  74. return new Vector2(x, y);
  75. }
  76. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  77. public static bool EqualsWithTolerence(this Vector2 value, Vector2 otherValue, float tolerance = 0.00001f)
  78. {
  79. return Math.Abs(value.X - otherValue.X) <= tolerance && (Math.Abs(value.Y - otherValue.Y) <= tolerance);
  80. }
  81. #if FNA || KNI
  82. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  83. #else
  84. [Obsolete("Use native Vector2.Rotate provided by MonoGame instead. This will be removed in a future release.", false)]
  85. #endif
  86. public static Vector2 Rotate(this Vector2 value, float radians)
  87. {
  88. var cos = (float) Math.Cos(radians);
  89. var sin = (float) Math.Sin(radians);
  90. return new Vector2(value.X*cos - value.Y*sin, value.X*sin + value.Y*cos);
  91. }
  92. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  93. public static Vector2 NormalizedCopy(this Vector2 value)
  94. {
  95. var newVector2 = new Vector2(value.X, value.Y);
  96. newVector2.Normalize();
  97. return newVector2;
  98. }
  99. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  100. public static Vector2 PerpendicularClockwise(this Vector2 value) => new Vector2(value.Y, -value.X);
  101. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  102. public static Vector2 PerpendicularCounterClockwise(this Vector2 value) => new Vector2(-value.Y, value.X);
  103. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  104. public static Vector2 Truncate(this Vector2 value, float maxLength)
  105. {
  106. if (value.LengthSquared() > maxLength*maxLength)
  107. return value.NormalizedCopy()*maxLength;
  108. return value;
  109. }
  110. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  111. public static bool IsNaN(this Vector2 value) => float.IsNaN(value.X) || float.IsNaN(value.Y);
  112. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  113. public static float ToAngle(this Vector2 value) => (float) Math.Atan2(value.X, -value.Y);
  114. /// <summary>
  115. /// Calculates the dot product of two vectors. If the two vectors are unit vectors, the dot product returns a floating
  116. /// point value between -1 and 1 that can be used to determine some properties of the angle between two vectors. For
  117. /// example, it can show whether the vectors are orthogonal, parallel, or have an acute or obtuse angle between them.
  118. /// </summary>
  119. /// <param name="vector1">The first vector.</param>
  120. /// <param name="vector2">The second vector.</param>
  121. /// <returns>The dot product of the two vectors.</returns>
  122. /// <remarks>
  123. /// <para>The dot product is also known as the inner product.</para>
  124. /// <para>
  125. /// For any two vectors, the dot product is defined as: <c>(vector1.X * vector2.X) + (vector1.Y * vector2.Y).</c>
  126. /// The result of this calculation, plus or minus some margin to account for floating point error, is equal to:
  127. /// <c>Length(vector1) * Length(vector2) * System.Math.Cos(theta)</c>, where <c>theta</c> is the angle between the
  128. /// two vectors.
  129. /// </para>
  130. /// <para>
  131. /// If <paramref name="vector1" /> and <paramref name="vector2" /> are unit vectors, the length of each
  132. /// vector will be equal to 1. So, when <paramref name="vector1" /> and <paramref name="vector2" /> are unit
  133. /// vectors, the dot product is simply equal to the cosine of the angle between the two vectors. For example, both
  134. /// <c>cos</c> values in the following calcuations would be equal in value:
  135. /// <c>vector1.Normalize(); vector2.Normalize(); var cos = vector1.Dot(vector2)</c>,
  136. /// <c>var cos = System.Math.Cos(theta)</c>, where <c>theta</c> is angle in radians betwen the two vectors.
  137. /// </para>
  138. /// <para>
  139. /// If <paramref name="vector1" /> and <paramref name="vector2" /> are unit vectors, without knowing the value of
  140. /// <c>theta</c> or using a potentially processor-intensive trigonometric function, the value of the dot product
  141. /// can tell us the
  142. /// following things:
  143. /// <list type="bullet">
  144. /// <item>
  145. /// <description>
  146. /// If <c>vector1.Dot(vector2) &gt; 0</c>, the angle between the two vectors
  147. /// is less than 90 degrees.
  148. /// </description>
  149. /// </item>
  150. /// <item>
  151. /// <description>
  152. /// If <c>vector1.Dot(vector2) &lt; 0</c>, the angle between the two vectors
  153. /// is more than 90 degrees.
  154. /// </description>
  155. /// </item>
  156. /// <item>
  157. /// <description>
  158. /// If <c>vector1.Dot(vector2) == 0</c>, the angle between the two vectors
  159. /// is 90 degrees; that is, the vectors are othogonal.
  160. /// </description>
  161. /// </item>
  162. /// <item>
  163. /// <description>
  164. /// If <c>vector1.Dot(vector2) == 1</c>, the angle between the two vectors
  165. /// is 0 degrees; that is, the vectors point in the same direction and are parallel.
  166. /// </description>
  167. /// </item>
  168. /// <item>
  169. /// <description>
  170. /// If <c>vector1.Dot(vector2) == -1</c>, the angle between the two vectors
  171. /// is 180 degrees; that is, the vectors point in opposite directions and are parallel.
  172. /// </description>
  173. /// </item>
  174. /// </list>
  175. /// </para>
  176. /// <note type="caution">
  177. /// Because of floating point error, two orthogonal vectors may not return a dot product that is exactly zero. It
  178. /// might be zero plus some amount of floating point error. In your code, you will want to determine what amount of
  179. /// error is acceptable in your calculation, and take that into account when you do your comparisons.
  180. /// </note>
  181. /// </remarks>
  182. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  183. public static float Dot(this Vector2 vector1, Vector2 vector2)
  184. {
  185. return vector1.X*vector2.X + vector1.Y*vector2.Y;
  186. }
  187. /// <summary>
  188. /// Calculates the scalar projection of one vector onto another. The scalar projection returns the length of the
  189. /// orthogonal projection of the first vector onto a straight line parallel to the second vector, with a negative value
  190. /// if the projection has an opposite direction with respect to the second vector.
  191. /// </summary>
  192. /// <param name="vector1">The first vector.</param>
  193. /// <param name="vector2">The second vector.</param>
  194. /// <returns>The scalar projection of <paramref name="vector1" /> onto <paramref name="vector2" />.</returns>
  195. /// <remarks>
  196. /// <para>
  197. /// The scalar projection is also known as the scalar resolute of the first vector in the direction of the second
  198. /// vector.
  199. /// </para>
  200. /// <para>
  201. /// For any two vectors, the scalar projection is defined as: <c>vector1.Dot(vector2) / Length(vector2)</c>. The
  202. /// result of this calculation, plus or minus some margin to account for floating point error, is equal to:
  203. /// <c>Length(vector1) * System.Math.Cos(theta)</c>, where <c>theta</c> is the angle in radians between
  204. /// <paramref name="vector1" /> and <paramref name="vector2" />.
  205. /// </para>
  206. /// <para>
  207. /// The value of the scalar projection can tell us the following things:
  208. /// <list type="bullet">
  209. /// <item>
  210. /// <description>
  211. /// If <c>vector1.ScalarProjectOnto(vector2) &gt;= 0</c>, the angle between <paramref name="vector1" />
  212. /// and <paramref name="vector2" /> is between 0 degrees (exclusive) and 90 degrees (inclusive).
  213. /// </description>
  214. /// </item>
  215. /// <item>
  216. /// <description>
  217. /// If <c>vector1.ScalarProjectOnto(vector2) &lt; 0</c>, the angle between <paramref name="vector1" />
  218. /// and <paramref name="vector2" /> is between 90 degrees (exclusive) and 180 degrees (inclusive).
  219. /// </description>
  220. /// </item>
  221. /// </list>
  222. /// </para>
  223. /// </remarks>
  224. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  225. public static float ScalarProjectOnto(this Vector2 vector1, Vector2 vector2)
  226. {
  227. var dotNumerator = vector1.X*vector2.X + vector1.Y*vector2.Y;
  228. var lengthSquaredDenominator = vector2.X*vector2.X + vector2.Y*vector2.Y;
  229. return dotNumerator/(float) Math.Sqrt(lengthSquaredDenominator);
  230. }
  231. /// <summary>
  232. /// Calculates the vector projection of one vector onto another. The vector projection returns the orthogonal
  233. /// projection of the first vector onto a straight line parallel to the second vector.
  234. /// </summary>
  235. /// <param name="vector1">The first vector.</param>
  236. /// <param name="vector2">The second vector.</param>
  237. /// <returns>The vector projection of <paramref name="vector1" /> onto <paramref name="vector2" />.</returns>
  238. /// <remarks>
  239. /// <para>
  240. /// The vector projection is also known as the vector component or vector resolute of the first vector in the
  241. /// direction of the second vector.
  242. /// </para>
  243. /// <para>
  244. /// For any two vectors, the vector projection is defined as:
  245. /// <c>( vector1.Dot(vector2) / Length(vector2)^2 ) * vector2</c>.
  246. /// The
  247. /// result of this calculation, plus or minus some margin to account for floating point error, is equal to:
  248. /// <c>( Length(vector1) * System.Math.Cos(theta) ) * vector2 / Length(vector2)</c>, where <c>theta</c> is the
  249. /// angle in radians between <paramref name="vector1" /> and <paramref name="vector2" />.
  250. /// </para>
  251. /// <para>
  252. /// This function is easier to compute than <see cref="ScalarProjectOnto" /> since it does not use a square root.
  253. /// When the vector projection and the scalar projection is required, consider using this function; the scalar
  254. /// projection can be obtained by taking the length of the projection vector.
  255. /// </para>
  256. /// </remarks>
  257. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  258. public static Vector2 ProjectOnto(this Vector2 vector1, Vector2 vector2)
  259. {
  260. var dotNumerator = vector1.X*vector2.X + vector1.Y*vector2.Y;
  261. var lengthSquaredDenominator = vector2.X*vector2.X + vector2.Y*vector2.Y;
  262. return dotNumerator/lengthSquaredDenominator*vector2;
  263. }
  264. #if FNA
  265. // MomoGame compatibility layer
  266. /// <summary>
  267. /// Gets a Point representation for this Vector2.
  268. /// </summary>
  269. /// <returns>A Point representation for this Vector2.</returns>
  270. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  271. public static Point ToPoint(this Vector2 value)
  272. {
  273. return new Point((int)value.X, (int)value.Y);
  274. }
  275. /// <summary>
  276. /// Gets a Vector2 representation for this Point.
  277. /// </summary>
  278. /// <returns>A Vector2 representation for this Point.</returns>
  279. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  280. public static Vector2 ToVector2(this Point value)
  281. {
  282. return new Vector2(value.X, value.Y);
  283. }
  284. #endif
  285. }
  286. }