OrientedBoundingBox2D.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. // Copyright (c) Craftwork Games. All rights reserved.
  2. // Licensed under the MIT license.
  3. // See LICENSE file in the project root for full license information.
  4. using System;
  5. using System.Diagnostics;
  6. using System.Runtime.InteropServices;
  7. using System.Runtime.Serialization;
  8. using Microsoft.Xna.Framework;
  9. namespace MonoGame.Extended
  10. {
  11. /// <summary>
  12. /// Represents an oriented bounding box in 2D space
  13. /// </summary>
  14. [DataContract]
  15. [DebuggerDisplay("{DebugDisplayString,nq}")]
  16. [StructLayout(LayoutKind.Sequential)]
  17. public struct OrientedBoundingBox2D : IEquatable<OrientedBoundingBox2D>
  18. {
  19. /// <summary>
  20. /// The number of corners in this oriented bounding box.
  21. /// </summary>
  22. public const int CornerCount = 4;
  23. #region Public Fields
  24. /// <summary>
  25. /// The center position of this oriented bounding box in 2D space.
  26. /// </summary>
  27. [DataMember]
  28. public Vector2 Center;
  29. /// <summary>
  30. /// The unit vector defining this box's local X-axis direction.
  31. /// </summary>
  32. [DataMember]
  33. public Vector2 AxisX;
  34. /// <summary>
  35. /// The unit vector defining this box's local Y-axis direction, perpendicular to the X-axis.
  36. /// </summary>
  37. [DataMember]
  38. public Vector2 AxisY;
  39. /// <summary>
  40. /// The half extents of this box, representing the distance from the center to each edge along the local axes.
  41. /// </summary>
  42. [DataMember]
  43. public Vector2 HalfExtents;
  44. #endregion
  45. #region Public Properties
  46. /// <summary>
  47. /// Gets the width of this box along its local X-axis.
  48. /// </summary>
  49. public readonly float Width => HalfExtents.X * 2.0f;
  50. /// <summary>
  51. /// Gets the height of this box along its local Y-axis.
  52. /// </summary>
  53. public readonly float Height => HalfExtents.Y * 2.0f;
  54. /// <summary>
  55. /// Gets the rotation angle of this box in radians, measured counter-clockwise from the positive world X-axis.
  56. /// </summary>
  57. public readonly float Rotation => MathF.Atan2(AxisX.Y, AxisX.X);
  58. /// <summary>
  59. /// Gets the area enclosed by this box in.
  60. /// </summary>
  61. public readonly float Area => Width * Height;
  62. #endregion
  63. #region Internal Properties
  64. internal string DebugDisplayString
  65. {
  66. get
  67. {
  68. return string.Concat(
  69. "Center( ", Center.ToString(), " ) \r\n",
  70. "Rotation( ", Rotation.ToString("F2"), " rad ) \r\n",
  71. "Size( ", Width.ToString("F2"), " x ", Height.ToString("F2"), " )"
  72. );
  73. }
  74. }
  75. #endregion
  76. #region Public Constructors
  77. /// <summary>
  78. /// Creates a new <see cref="OrientedBoundingBox2D"/> with the specified center, orientation, and extents.
  79. /// </summary>
  80. /// <param name="center">The center position of the box in 2D space.</param>
  81. /// <param name="axisX">
  82. /// The unit vector defining the local X-axis direction. Should be normalized for accurate calculations.
  83. /// </param>
  84. /// <param name="axisY">
  85. /// The unit vector defining the local Y-axis direction. Should be normalized and perpendicular to <paramref name="axisX"/>.
  86. /// </param>
  87. /// <param name="halfExtents">
  88. /// The half extents, representing the distance from the center to each edge along the local axes.
  89. /// </param>
  90. public OrientedBoundingBox2D(Vector2 center, Vector2 axisX, Vector2 axisY, Vector2 halfExtents)
  91. {
  92. Center = center;
  93. AxisX = axisX;
  94. AxisY = axisY;
  95. HalfExtents = halfExtents;
  96. }
  97. #endregion
  98. #region Public Methods
  99. /// <summary>
  100. /// Creates a new <see cref="OrientedBoundingBox2D"/> from a center, rotation angle, and extents.
  101. /// </summary>
  102. /// <param name="center">The center position of the box in 2D space.</param>
  103. /// <param name="rotation">
  104. /// The rotation angle in radians, measured counter-clockwise from the positive world X-axis.
  105. /// </param>
  106. /// <param name="halfExtents">
  107. /// The half extents, representing the distance from the center to each edge along the local axes.
  108. /// </param>
  109. /// <returns>
  110. /// A new <see cref="OrientedBoundingBox2D"/> with the specified center, rotation, and extents.
  111. /// </returns>
  112. public static OrientedBoundingBox2D CreateFromRotation(Vector2 center, float rotation, Vector2 halfExtents)
  113. {
  114. float cos = MathF.Cos(rotation);
  115. float sin = MathF.Sin(rotation);
  116. return new OrientedBoundingBox2D(
  117. center,
  118. new Vector2(cos, sin),
  119. new Vector2(-sin, cos),
  120. halfExtents
  121. );
  122. }
  123. /// <summary>
  124. /// Creates a new <see cref="OrientedBoundingBox2D"/> from an axis-aligned bounding box with zero rotation.
  125. /// </summary>
  126. /// <param name="box">The axis-aligned bounding box to convert.</param>
  127. /// <returns>
  128. /// A new <see cref="OrientedBoundingBox2D"/> with the same center and extents as the input box,
  129. /// aligned with the world axes.
  130. /// </returns>
  131. public static OrientedBoundingBox2D CreateFromBoundingBox2D(BoundingBox2D box)
  132. {
  133. return new OrientedBoundingBox2D(
  134. box.Center,
  135. new Vector2(1, 0),
  136. new Vector2(0, 1),
  137. box.HalfExtents
  138. );
  139. }
  140. /// <summary>
  141. /// Creates an <see cref="OrientedBoundingBox2D"/> that encloses two oriented bounding boxes.
  142. /// </summary>
  143. /// <param name="original">The first oriented bounding box to enclose.</param>
  144. /// <param name="additional">The second oriented bounding box to enclose.</param>
  145. /// <returns>
  146. /// A new <see cref="OrientedBoundingBox2D"/> that completely contains both input boxes.
  147. /// </returns>
  148. /// <remarks>
  149. /// Uses Principal Component Analysis (PCA) with power iteration to compute optimal axes for the merged box.
  150. /// The resulting box may not be the absolute minimum volume, but provides a good approximation that aligns
  151. /// with the principal direction of the combined corners.
  152. /// </remarks>
  153. public static OrientedBoundingBox2D CreateMerged(OrientedBoundingBox2D original, OrientedBoundingBox2D additional)
  154. {
  155. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  156. // Section 6.5.3 "Merging Two OBBs", Section 4.4.2 "PCA-based OBBs"
  157. // Derived PCA-based OBB merge using power iteration in place of the
  158. // Jacobi eigenvalue method described in the book.
  159. const int Iterations = 4;
  160. // Get all corners from both rectangles
  161. // Computing inline instead of calling GetCorners to avoid multiple array allocations and array copies
  162. Vector2 extX1 = original.AxisX * original.HalfExtents.X;
  163. Vector2 extY1 = original.AxisY * original.HalfExtents.Y;
  164. Vector2 extX2 = additional.AxisX * additional.HalfExtents.X;
  165. Vector2 extY2 = additional.AxisY * additional.HalfExtents.Y;
  166. Vector2[] allCorners = new Vector2[]
  167. {
  168. original.Center - extX1 - extY1,
  169. original.Center + extX1 - extY1,
  170. original.Center + extX1 + extY1,
  171. original.Center - extX1 + extY1,
  172. additional.Center - extX2 - extY2,
  173. additional.Center + extX2 - extY2,
  174. additional.Center + extX2 + extY2,
  175. additional.Center - extX2 + extY2,
  176. };
  177. // Compute the centroid
  178. Vector2 centroid = Vector2.Zero;
  179. for (int i = 0; i < allCorners.Length; i++)
  180. {
  181. centroid += allCorners[i];
  182. }
  183. centroid /= allCorners.Length;
  184. // Compute covariance matrix
  185. float cxx = 0;
  186. float cxy = 0;
  187. float cyy = 0;
  188. for (int i = 0; i < allCorners.Length; i++)
  189. {
  190. Vector2 p = allCorners[i] - centroid;
  191. cxx += p.X * p.X;
  192. cxy += p.X * p.Y;
  193. cyy += p.Y * p.Y;
  194. }
  195. // Find eigenvector of largest eigenvalue using power iteration.
  196. // to get the primary axis of the OBB
  197. Vector2 axisX = Vector2.UnitX;
  198. for (int iter = 0; iter < Iterations; iter++)
  199. {
  200. float newX = cxx * axisX.X + cxy * axisX.Y;
  201. float newY = cxy * axisX.X + cyy * axisX.Y;
  202. axisX = new Vector2(newX, newY);
  203. // Only sqrt if necessary
  204. float lenSq = axisX.LengthSquared();
  205. if (lenSq > Collision2D.Epsilon * Collision2D.Epsilon)
  206. {
  207. axisX /= MathF.Sqrt(lenSq);
  208. }
  209. }
  210. // Compute perpendicular axis
  211. Vector2 axisY = new Vector2(-axisX.Y, axisX.X);
  212. // Project all points onto both axes to find extents
  213. float minX = float.MaxValue;
  214. float maxX = float.MinValue;
  215. float minY = float.MaxValue;
  216. float maxY = float.MinValue;
  217. for (int i = 0; i < allCorners.Length; i++)
  218. {
  219. Vector2 p = allCorners[i] - centroid;
  220. float projX = Vector2.Dot(p, axisX);
  221. float projY = Vector2.Dot(p, axisY);
  222. minX = MathF.Min(minX, projX);
  223. maxX = MathF.Max(maxX, projX);
  224. minY = MathF.Min(minY, projY);
  225. maxY = MathF.Max(maxY, projY);
  226. }
  227. // Compute center and extents in the new OBB's local space
  228. float centerOffsetX = (minX + maxX) * 0.5f;
  229. float centerOffsetY = (minY + maxY) * 0.5f;
  230. Vector2 center = centroid + axisX * centerOffsetX + axisY * centerOffsetY;
  231. Vector2 halfExtents = new Vector2(
  232. (maxX - minX) * 0.5f,
  233. (maxY - minY) * 0.5f
  234. );
  235. return new OrientedBoundingBox2D(center, axisX, axisY, halfExtents);
  236. }
  237. /// <summary>
  238. /// Gets an array containing the four corner positions of this oriented bounding box.
  239. /// </summary>
  240. /// <returns>
  241. /// An array of 4 corner positions in order: top-left, top-right, bottom-right, bottom-left,
  242. /// relative to the box's local orientation.
  243. /// </returns>
  244. public readonly Vector2[] GetCorners()
  245. {
  246. Vector2 extX = AxisX * HalfExtents.X;
  247. Vector2 extY = AxisY * HalfExtents.Y;
  248. return new Vector2[]
  249. {
  250. Center - extX - extY, // Top-left
  251. Center + extX - extY, // Top-right
  252. Center + extX + extY, // Bottom-right
  253. Center - extX + extY // Bottom-left
  254. };
  255. }
  256. /// <summary>
  257. /// Writes the four corner positions of this oriented bounding box into an existing array.
  258. /// </summary>
  259. /// <param name="corners">
  260. /// The array to write corner positions into. Must have at least 4 elements.
  261. /// Corners are written in order: top-left, top-right, bottom-right, bottom-left.
  262. /// </param>
  263. /// <exception cref="ArgumentNullException">
  264. /// Thrown when <paramref name="corners"/> is <see langword="null"/>.
  265. /// </exception>
  266. /// <exception cref="ArgumentException">
  267. /// Thrown when <paramref name="corners"/> has fewer than 4 elements.
  268. /// </exception>
  269. public readonly void GetCorners(Vector2[] corners)
  270. {
  271. if (corners == null)
  272. {
  273. throw new ArgumentNullException(nameof(corners));
  274. }
  275. if (corners.Length < CornerCount)
  276. {
  277. throw new ArgumentException($"Array must have at least {CornerCount} elements", nameof(corners));
  278. }
  279. Vector2 extX = AxisX * HalfExtents.X;
  280. Vector2 extY = AxisY * HalfExtents.Y;
  281. corners[0] = Center - extX - extY; // Top-left
  282. corners[1] = Center + extX - extY; // Top-right
  283. corners[2] = Center + extX + extY; // Bottom-right
  284. corners[3] = Center - extX + extY; // Bottom-left
  285. }
  286. /// <inheritdoc/>
  287. public readonly bool Equals(OrientedBoundingBox2D other)
  288. {
  289. return Center.Equals(other.Center)
  290. && AxisX.Equals(other.AxisX)
  291. && AxisY.Equals(other.AxisY)
  292. && HalfExtents.Equals(other.HalfExtents);
  293. }
  294. /// <summary>
  295. /// Tests whether a point lies inside this oriented bounding box or on its boundary.
  296. /// </summary>
  297. /// <param name="point">The point to test in 2D space.</param>
  298. /// <returns>
  299. /// <see cref="ContainmentType.Contains"/> if the point is inside or on the boundary;
  300. /// otherwise, <see cref="ContainmentType.Disjoint"/> if the point is outside.
  301. /// </returns>
  302. public readonly ContainmentType Contains(Vector2 point)
  303. {
  304. return Collision2D.ContainsObbPoint(point, Center, AxisX, AxisY, HalfExtents);
  305. }
  306. /// <summary>
  307. /// Tests whether this oriented bounding box contains, intersects, or is separate from a bounding box.
  308. /// </summary>
  309. /// <param name="aabb">The bounding box to test against.</param>
  310. /// <returns>
  311. /// <see cref="ContainmentType.Contains"/> if the bounding box is completely inside this box;
  312. /// <see cref="ContainmentType.Intersects"/> if they partially overlap;
  313. /// or <see cref="ContainmentType.Disjoint"/> if they do not touch.
  314. /// </returns>
  315. public readonly ContainmentType Contains(BoundingBox2D aabb)
  316. {
  317. return Collision2D.ContainsObbAabb(Center, AxisX, AxisY, HalfExtents, aabb.Min, aabb.Max);
  318. }
  319. /// <summary>
  320. /// Tests whether this oriented bounding box contains, intersects, or is separate from a circle.
  321. /// </summary>
  322. /// <param name="circle">The circle to test against.</param>
  323. /// <returns>
  324. /// <see cref="ContainmentType.Contains"/> if the circle is completely inside this box;
  325. /// <see cref="ContainmentType.Intersects"/> if they partially overlap;
  326. /// or <see cref="ContainmentType.Disjoint"/> if they do not touch.
  327. /// </returns>
  328. public readonly ContainmentType Contains(BoundingCircle2D circle)
  329. {
  330. return Collision2D.ContainsObbCircle(Center, AxisX, AxisY, HalfExtents, circle.Center, circle.Radius);
  331. }
  332. /// <summary>
  333. /// Tests whether this oriented bounding box contains, intersects, or is separate from another oriented bounding box.
  334. /// </summary>
  335. /// <param name="other">The other oriented bounding box to test against.</param>
  336. /// <returns>
  337. /// <see cref="ContainmentType.Contains"/> if the other box is completely inside this one;
  338. /// <see cref="ContainmentType.Intersects"/> if they partially overlap;
  339. /// or <see cref="ContainmentType.Disjoint"/> if they do not touch.
  340. /// </returns>
  341. public readonly ContainmentType Contains(OrientedBoundingBox2D other)
  342. {
  343. return Collision2D.ContainsObbObb(Center, AxisX, AxisY, HalfExtents, other.Center, other.AxisX, other.AxisY, other.HalfExtents);
  344. }
  345. /// <summary>
  346. /// Tests whether this oriented bounding box contains, intersects, or is separate from a capsule.
  347. /// </summary>
  348. /// <param name="capsule">The capsule to test against.</param>
  349. /// <returns>
  350. /// <see cref="ContainmentType.Contains"/> if the capsule is completely inside this box;
  351. /// <see cref="ContainmentType.Intersects"/> if they partially overlap;
  352. /// or <see cref="ContainmentType.Disjoint"/> if they do not touch.
  353. /// </returns>
  354. public readonly ContainmentType Contains(BoundingCapsule2D capsule)
  355. {
  356. return Collision2D.ContainsObbCapsule(Center, AxisX, AxisY, HalfExtents, capsule.PointA, capsule.PointB, capsule.Radius);
  357. }
  358. /// <summary>
  359. /// Tests whether this oriented bounding box contains, intersects, or is separate from a polygon.
  360. /// </summary>
  361. /// <param name="polygon">The polygon to test against.</param>
  362. /// <returns>
  363. /// <see cref="ContainmentType.Contains"/> if the polygon is completely inside this box;
  364. /// <see cref="ContainmentType.Intersects"/> if they partially overlap;
  365. /// or <see cref="ContainmentType.Disjoint"/> if they do not touch.
  366. /// </returns>
  367. public readonly ContainmentType Contains(BoundingPolygon2D polygon)
  368. {
  369. return Collision2D.ContainsObbConvexPolygon(Center, AxisX, AxisY, HalfExtents, polygon.Vertices, polygon.Normals);
  370. }
  371. /// <summary>
  372. /// Tests whether this oriented bounding box intersects with a circle.
  373. /// </summary>
  374. /// <param name="circle">The circle to test against.</param>
  375. /// <returns>
  376. /// <see langword="true"/> if the box and circle overlap or touch; otherwise, <see langword="false"/>.
  377. /// </returns>
  378. public readonly bool Intersects(BoundingCircle2D circle)
  379. {
  380. return Collision2D.IntersectsCircleObb(circle.Center, circle.Radius, Center, AxisX, AxisY, HalfExtents);
  381. }
  382. /// <summary>
  383. /// Tests whether this oriented bounding box intersects with another oriented bounding box.
  384. /// </summary>
  385. /// <param name="other">The other oriented bounding box to test against.</param>
  386. /// <returns>
  387. /// <see langword="true"/> if the boxes overlap or touch; otherwise, <see langword="false"/>.
  388. /// </returns>
  389. public readonly bool Intersects(OrientedBoundingBox2D other)
  390. {
  391. return Collision2D.IntersectsObbObb(Center, AxisX, AxisY, HalfExtents, other.Center, other.AxisX, other.AxisY, other.HalfExtents);
  392. }
  393. /// <summary>
  394. /// Tests whether this oriented bounding box intersects with an axis-aligned bounding box.
  395. /// </summary>
  396. /// <param name="box">The bounding box to test against.</param>
  397. /// <returns>
  398. /// <see langword="true"/> if the boxes overlap or touch; otherwise, <see langword="false"/>.
  399. /// </returns>
  400. public readonly bool Intersects(BoundingBox2D box)
  401. {
  402. return Collision2D.IntersectsAabbObb(box.Center, box.HalfExtents, Center, AxisX, AxisY, HalfExtents);
  403. }
  404. /// <summary>
  405. /// Tests whether this oriented bounding box intersects with a capsule.
  406. /// </summary>
  407. /// <param name="capsule">The capsule to test against.</param>
  408. /// <returns>
  409. /// <see langword="true"/> if the box and capsule overlap or touch; otherwise, <see langword="false"/>.
  410. /// </returns>
  411. public readonly bool Intersects(BoundingCapsule2D capsule)
  412. {
  413. return Collision2D.IntersectsObbCapsule(Center, AxisX, AxisY, HalfExtents, capsule.PointA, capsule.PointB, capsule.Radius);
  414. }
  415. /// <summary>
  416. /// Tests whether this oriented bounding box intersects with a polygon.
  417. /// </summary>
  418. /// <param name="polygon">The polygon to test against.</param>
  419. /// <returns>
  420. /// <see langword="true"/> if the box and polygon overlap or touch; otherwise, <see langword="false"/>.
  421. /// </returns>
  422. public readonly bool Intersects(BoundingPolygon2D polygon)
  423. {
  424. return Collision2D.IntersectsObbConvexPolygon(Center, AxisX, AxisY, HalfExtents, polygon.Vertices, polygon.Normals);
  425. }
  426. /// <summary>
  427. /// Applies a matrix transformation to this oriented bounding box and creates a new transformed box.
  428. /// </summary>
  429. /// <param name="matrix">The transformation matrix to apply.</param>
  430. /// <returns>
  431. /// A new <see cref="OrientedBoundingBox2D"/> with the center transformed by the matrix,
  432. /// the axes rotated, and the extents scaled appropriately.
  433. /// </returns>
  434. /// <remarks>
  435. /// The transformation applies to all components: the center is transformed as a point,
  436. /// the local axes are rotated and normalized, and the half extents are scaled by the
  437. /// corresponding scale factors extracted from the transformation.
  438. /// </remarks>
  439. public readonly OrientedBoundingBox2D Transform(Matrix matrix)
  440. {
  441. Vector2 transformedCenter = Vector2.Transform(Center, matrix);
  442. // Transform the axes (rotation and scale)
  443. Vector2 transformedAxisX = Vector2.TransformNormal(AxisX, matrix);
  444. Vector2 transformedAxisY = Vector2.TransformNormal(AxisY, matrix);
  445. // Extract scale from transformed axes
  446. float scaleX = transformedAxisX.Length();
  447. float scaleY = transformedAxisY.Length();
  448. // Normalize axes
  449. transformedAxisX = scaleX > Collision2D.Epsilon ? transformedAxisX / scaleX : Vector2.UnitX;
  450. transformedAxisY = scaleY > Collision2D.Epsilon ? transformedAxisY / scaleY : Vector2.UnitY;
  451. // Scale the extents
  452. Vector2 transformedExtents = new Vector2(
  453. HalfExtents.X * scaleX,
  454. HalfExtents.Y * scaleY
  455. );
  456. return new OrientedBoundingBox2D(
  457. transformedCenter,
  458. transformedAxisX,
  459. transformedAxisY,
  460. transformedExtents
  461. );
  462. }
  463. /// <summary>
  464. /// Creates a new <see cref="OrientedBoundingBox2D"/> by translating this box by the specified offset.
  465. /// </summary>
  466. /// <param name="translation">The offset to translate the box by in 2D space.</param>
  467. /// <returns>
  468. /// A new <see cref="OrientedBoundingBox2D"/> at the translated position with the same orientation and extents.
  469. /// </returns>
  470. public readonly OrientedBoundingBox2D Translate(Vector2 translation)
  471. {
  472. return new OrientedBoundingBox2D(
  473. Center + translation,
  474. AxisX,
  475. AxisY,
  476. HalfExtents
  477. );
  478. }
  479. /// <summary>
  480. /// Deconstructs this oriented bounding box into its component values.
  481. /// </summary>
  482. /// <param name="center">
  483. /// When this method returns, contains the center position of this box in 2D space.
  484. /// </param>
  485. /// <param name="axisX">
  486. /// When this method returns, contains the unit vector defining this box's local X-axis direction.
  487. /// </param>
  488. /// <param name="axisY">
  489. /// When this method returns, contains the unit vector defining this box's local Y-axis direction.
  490. /// </param>
  491. /// <param name="halfExtents">
  492. /// When this method returns, contains the half extents of this box in world units.
  493. /// </param>
  494. public readonly void Deconstruct(out Vector2 center, out Vector2 axisX, out Vector2 axisY, out Vector2 halfExtents)
  495. {
  496. center = Center;
  497. axisX = AxisX;
  498. axisY = AxisY;
  499. halfExtents = HalfExtents;
  500. }
  501. /// <inheritdoc/>
  502. public override readonly bool Equals(object obj)
  503. {
  504. return obj is OrientedBoundingBox2D other && Equals(other);
  505. }
  506. /// <inheritdoc/>
  507. public override readonly int GetHashCode()
  508. {
  509. return Center.GetHashCode() ^
  510. AxisX.GetHashCode() ^
  511. AxisY.GetHashCode() ^
  512. HalfExtents.GetHashCode();
  513. }
  514. /// <inheritdoc/>
  515. public override readonly string ToString()
  516. {
  517. return $"{{Center:{Center} Rotation:{Rotation:F2} Size:{Width:F2}x{Height:F2}}}";
  518. }
  519. /// <summary/>
  520. public static bool operator ==(OrientedBoundingBox2D left, OrientedBoundingBox2D right)
  521. {
  522. return left.Equals(right);
  523. }
  524. /// <summary/>
  525. public static bool operator !=(OrientedBoundingBox2D left, OrientedBoundingBox2D right)
  526. {
  527. return !left.Equals(right);
  528. }
  529. #endregion
  530. }
  531. }