OrientedRectangle.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using Microsoft.Xna.Framework;
  5. namespace MonoGame.Extended
  6. {
  7. // Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.4; Bounding Volumes - Oriented Bounding Boxes (OBBs), pg 101.
  8. /// <summary>
  9. /// An oriented bounding rectangle is a rectangular block, much like a bounding rectangle
  10. /// <see cref="BoundingRectangle" /> but with an arbitrary orientation <see cref="Vector2" />.
  11. /// </summary>
  12. /// <seealso cref="IEquatable{T}" />
  13. [DebuggerDisplay($"{{{nameof(DebugDisplayString)},nq}}")]
  14. public struct OrientedRectangle : IEquatable<OrientedRectangle>, IShapeF
  15. {
  16. /// <summary>
  17. /// The centre position of this <see cref="OrientedRectangle" />.
  18. /// </summary>
  19. public Vector2 Center;
  20. /// <summary>
  21. /// The distance from the <see cref="Center" /> point along both axes to any point on the boundary of this
  22. /// <see cref="OrientedRectangle" />.
  23. /// </summary>
  24. ///
  25. public Vector2 Radii;
  26. /// <summary>
  27. /// The rotation matrix <see cref="Matrix3x2" /> of the bounding rectangle <see cref="OrientedRectangle" />.
  28. /// </summary>
  29. public Matrix3x2 Orientation;
  30. /// <summary>
  31. /// Initializes a new instance of the <see cref="BoundingRectangle" /> structure from the specified centre
  32. /// <see cref="Vector2" /> and the radii <see cref="SizeF" />.
  33. /// </summary>
  34. /// <param name="center">The centre <see cref="Vector2" />.</param>
  35. /// <param name="radii">The radii <see cref="Vector2" />.</param>
  36. /// <param name="orientation">The orientation <see cref="Matrix3x2" />.</param>
  37. public OrientedRectangle(Vector2 center, SizeF radii, Matrix3x2 orientation)
  38. {
  39. Center = center;
  40. Radii = radii;
  41. Orientation = orientation;
  42. }
  43. /// <summary>
  44. /// Gets a list of points defining the corner points of the oriented rectangle.
  45. /// </summary>
  46. public IReadOnlyList<Vector2> Points
  47. {
  48. get
  49. {
  50. var topLeft = -Radii;
  51. var bottomLeft = -new Vector2(Radii.X, -Radii.Y);
  52. var topRight = (Vector2)new Vector2(Radii.X, -Radii.Y);
  53. var bottomRight = Radii;
  54. return new List<Vector2>
  55. {
  56. Vector2.Transform(topRight, Orientation) + Center,
  57. Vector2.Transform(topLeft, Orientation) + Center,
  58. Vector2.Transform(bottomLeft, Orientation) + Center,
  59. Vector2.Transform(bottomRight, Orientation) + Center
  60. };
  61. }
  62. }
  63. public Vector2 Position
  64. {
  65. get => Vector2.Transform(-Radii, Orientation) + Center;
  66. set => throw new NotImplementedException();
  67. }
  68. public RectangleF BoundingRectangle => (RectangleF)this;
  69. /// <summary>
  70. /// Computes the <see cref="OrientedRectangle"/> from the specified <paramref name="rectangle"/>
  71. /// transformed by <paramref name="transformMatrix"/>.
  72. /// </summary>
  73. /// <param name="rectangle">The <see cref="OrientedRectangle"/> to transform.</param>
  74. /// <param name="transformMatrix">The <see cref="Matrix3x2"/> transformation.</param>
  75. /// <returns>A new <see cref="OrientedRectangle"/>.</returns>
  76. public static OrientedRectangle Transform(OrientedRectangle rectangle, ref Matrix3x2 transformMatrix)
  77. {
  78. Transform(ref rectangle, ref transformMatrix, out var result);
  79. return result;
  80. }
  81. private static void Transform(ref OrientedRectangle rectangle, ref Matrix3x2 transformMatrix, out OrientedRectangle result)
  82. {
  83. PrimitivesHelper.TransformOrientedRectangle(
  84. ref rectangle.Center,
  85. ref rectangle.Orientation,
  86. ref transformMatrix);
  87. result = new OrientedRectangle();
  88. result.Center = rectangle.Center;
  89. result.Radii = rectangle.Radii;
  90. result.Orientation = rectangle.Orientation;
  91. }
  92. /// <summary>
  93. /// Compares to two <see cref="OrientedRectangle"/> structures. The result specifies whether the
  94. /// the values of the <see cref="Center"/>, <see cref="Radii"/> and <see cref="Orientation"/> are
  95. /// equal.
  96. /// </summary>
  97. /// <param name="left">The left <see cref="OrientedRectangle" />.</param>
  98. /// <param name="right">The right <see cref="OrientedRectangle" />.</param>
  99. /// <returns><c>true</c> if left and right argument are equal; otherwise, <c>false</c>.</returns>
  100. public static bool operator ==(OrientedRectangle left, OrientedRectangle right)
  101. {
  102. return left.Equals(right);
  103. }
  104. /// <summary>
  105. /// Compares to two <see cref="OrientedRectangle"/> structures. The result specifies whether the
  106. /// the values of the <see cref="Center"/>, <see cref="Radii"/> or <see cref="Orientation"/> are
  107. /// unequal.
  108. /// </summary>
  109. /// <param name="left">The left <see cref="OrientedRectangle" />.</param>
  110. /// <param name="right">The right <see cref="OrientedRectangle" />.</param>
  111. /// <returns><c>true</c> if left and right argument are unequal; otherwise, <c>false</c>.</returns>
  112. public static bool operator !=(OrientedRectangle left, OrientedRectangle right)
  113. {
  114. return !left.Equals(right);
  115. }
  116. /// <summary>
  117. /// Determines whether two instances of <see cref="OrientedRectangle"/> are equal.
  118. /// </summary>
  119. /// <param name="other">The other <see cref="OrientedRectangle"/>.</param>
  120. /// <returns><c>true</c> if the specified <see cref="OrientedRectangle"/> is equal
  121. /// to the current <see cref="OrientedRectangle"/>; otherwise, <c>false</c>.</returns>
  122. public bool Equals(OrientedRectangle other)
  123. {
  124. return Center.Equals(other.Center) && Radii.Equals(other.Radii) && Orientation.Equals(other.Orientation);
  125. }
  126. /// <summary>
  127. /// Determines whether two instances of <see cref="OrientedRectangle"/> are equal.
  128. /// </summary>
  129. /// <param name="obj">The <see cref="OrientedRectangle"/> to compare to.</param>
  130. /// <returns><c>true</c> if the specified <see cref="OrientedRectangle"/> is equal
  131. /// to the current <see cref="OrientedRectangle"/>; otherwise, <c>false</c>.</returns>
  132. public override bool Equals(object obj)
  133. {
  134. return obj is OrientedRectangle other && Equals(other);
  135. }
  136. /// <summary>
  137. /// Returns a hash code for this object instance.
  138. /// </summary>
  139. /// <returns></returns>
  140. public override int GetHashCode()
  141. {
  142. return HashCode.Combine(Center, Radii, Orientation);
  143. }
  144. /// <summary>
  145. /// Performs an implicit conversion from a <see cref="RectangleF" /> to <see cref="OrientedRectangle" />.
  146. /// </summary>
  147. /// <param name="rectangle">The rectangle to convert.</param>
  148. /// <returns>The resulting <see cref="OrientedRectangle" />.</returns>
  149. public static explicit operator OrientedRectangle(RectangleF rectangle)
  150. {
  151. var radii = new SizeF(rectangle.Width * 0.5f, rectangle.Height * 0.5f);
  152. var centre = new Vector2(rectangle.X + radii.Width, rectangle.Y + radii.Height);
  153. return new OrientedRectangle(centre, radii, Matrix3x2.Identity);
  154. }
  155. public static explicit operator RectangleF(OrientedRectangle orientedRectangle)
  156. {
  157. var topLeft = -orientedRectangle.Radii;
  158. var rectangle = new RectangleF(topLeft, orientedRectangle.Radii * 2);
  159. var orientation = orientedRectangle.Orientation * Matrix3x2.CreateTranslation(orientedRectangle.Center);
  160. return RectangleF.Transform(rectangle, ref orientation);
  161. }
  162. /// <summary>
  163. /// See:
  164. /// https://www.flipcode.com/archives/2D_OBB_Intersection.shtml
  165. /// https://dyn4j.org/2010/01/sat
  166. /// </summary>
  167. /// <param name="rectangle"></param>
  168. /// <param name="other"></param>
  169. /// <returns></returns>
  170. /// <exception cref="NotImplementedException"></exception>
  171. public static (bool Intersects, Vector2 MinimumTranslationVector) Intersects(
  172. OrientedRectangle rectangle, OrientedRectangle other)
  173. {
  174. var corners = rectangle.Points;
  175. var otherCorners = other.Points;
  176. var allAxis = new[]
  177. {
  178. corners[1] - corners[0],
  179. corners[3] - corners[0],
  180. otherCorners[1] - otherCorners[0],
  181. otherCorners[3] - otherCorners[0],
  182. };
  183. var normalizedAxis = new[]
  184. {
  185. allAxis[0],
  186. allAxis[1],
  187. allAxis[2],
  188. allAxis[3]
  189. };
  190. var overlap = 0f;
  191. var minimumTranslationVector = Vector2.Zero;
  192. // Make the length of each axis 1/edge length, so we know any
  193. // dot product must be less than 1 to fall within the edge.
  194. for (var a = 0; a < normalizedAxis.Length; a++)
  195. {
  196. normalizedAxis[a] /= normalizedAxis[a].LengthSquared();
  197. }
  198. for (var a = 0; a < normalizedAxis.Length; a++)
  199. {
  200. var axisProjectedOnto = normalizedAxis[a];
  201. var originalAxis = allAxis[a];
  202. var p1 = Project(corners, axisProjectedOnto);
  203. var p2 = Project(otherCorners, axisProjectedOnto);
  204. if (!IsOverlapping(p1, p2))
  205. {
  206. // There was no intersection along this dimension;
  207. // the boxes cannot possibly overlap.
  208. return (false, Vector2.Zero);
  209. }
  210. var o = GetOverlap(p1, p2);
  211. if (o < overlap || overlap == 0f)
  212. {
  213. overlap = o;
  214. minimumTranslationVector = originalAxis * overlap;
  215. if (p1.Min > p2.Min)
  216. {
  217. minimumTranslationVector = -minimumTranslationVector;
  218. }
  219. }
  220. }
  221. // There was no dimension along which there is no intersection.
  222. // Therefore, the boxes overlap.
  223. return (true, minimumTranslationVector);
  224. (float Min, float Max) Project(IReadOnlyList<Vector2> vertices, Vector2 axis)
  225. {
  226. var t = vertices[0].Dot(axis);
  227. // Find the extent of box 2 on axis a
  228. var min = t;
  229. var max = t;
  230. for (var c = 1; c < 4; c++)
  231. {
  232. t = vertices[c].Dot(axis);
  233. if (t < min)
  234. {
  235. min = t;
  236. }
  237. else if (t > max)
  238. {
  239. max = t;
  240. }
  241. }
  242. return (min, max);
  243. }
  244. bool IsOverlapping((float Min, float Max) p1, (float Min, float Max) p2)
  245. {
  246. return p1.Min <= p2.Max && p1.Max >= p2.Min;
  247. }
  248. float GetOverlap((float Min, float Max) p1, (float Min, float Max) p2)
  249. {
  250. return Math.Min(p1.Max, p2.Max) - Math.Max(p1.Min, p2.Min);
  251. }
  252. }
  253. /// <summary>
  254. /// Returns a <see cref="string" /> that represents this <see cref="OrientedRectangle" />.
  255. /// </summary>
  256. /// <returns>
  257. /// A <see cref="string" /> that represents this <see cref="OrientedRectangle" />.
  258. /// </returns>
  259. public override string ToString()
  260. {
  261. return $"Centre: {Center}, Radii: {Radii}, Orientation: {Orientation}";
  262. }
  263. internal string DebugDisplayString => ToString();
  264. }
  265. }