diff --git a/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs b/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs index 25df3f2b..d921cff7 100644 --- a/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs +++ b/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using Microsoft.Xna.Framework; @@ -45,6 +46,28 @@ namespace MonoGame.Extended Orientation = orientation; } + /// + /// Gets a list of points defining the corner points of the oriented rectangle. + /// + public IReadOnlyList Points + { + get + { + var topLeft = -Radii; + var bottomLeft = -new Vector2(Radii.X, -Radii.Y); + var topRight = (Vector2)new Point2(Radii.X, -Radii.Y); + var bottomRight = Radii; + + return new List + { + Vector2.Transform(topRight, Orientation) + Center, + Vector2.Transform(topLeft, Orientation) + Center, + Vector2.Transform(bottomLeft, Orientation) + Center, + Vector2.Transform(bottomRight, Orientation) + Center + }; + } + } + /// /// Computes the from the specified /// transformed by . @@ -62,7 +85,6 @@ namespace MonoGame.Extended { PrimitivesHelper.TransformOrientedBoundingRectangle( ref rectangle.Center, - ref rectangle.Radii, ref rectangle.Orientation, ref transformMatrix); result.Center = rectangle.Center; @@ -127,6 +149,14 @@ namespace MonoGame.Extended return HashCode.Combine(Center, Radii, Orientation); } + public static explicit operator OrientedBoundingRectangle(RectangleF rectangle) + { + var radii = new Size2(rectangle.Width * 0.5f, rectangle.Height * 0.5f); + var centre = new Point2(rectangle.X + radii.Width, rectangle.Y + radii.Height); + + return new OrientedBoundingRectangle(centre, radii, Matrix2.Identity); + } + /// /// Returns a that represents this . /// diff --git a/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs b/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs index 4000ebd5..63152f16 100644 --- a/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs +++ b/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs @@ -74,17 +74,12 @@ namespace MonoGame.Extended [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void TransformOrientedBoundingRectangle( ref Point2 center, - ref Vector2 radii, ref Matrix2 orientation, ref Matrix2 transformMatrix) { // Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.4; Oriented Bounding Boxes (OBBs), pg 101-105. center = transformMatrix.Transform(center); - var xRadius = radii.X; - var yRadius = radii.Y; - radii.X = xRadius * Math.Abs(transformMatrix.M11) + yRadius * Math.Abs(transformMatrix.M12); - radii.Y = xRadius * Math.Abs(transformMatrix.M21) + yRadius * Math.Abs(transformMatrix.M22); orientation *= transformMatrix; // Reset the translation since orientation is only about rotation orientation.M31 = 0; diff --git a/src/cs/Tests/MonoGame.Extended.Tests/CollectionAssert.cs b/src/cs/Tests/MonoGame.Extended.Tests/CollectionAssert.cs new file mode 100644 index 00000000..15c105bd --- /dev/null +++ b/src/cs/Tests/MonoGame.Extended.Tests/CollectionAssert.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Xunit; + +namespace MonoGame.Extended.Tests; + +public static class CollectionAssert +{ + public static void Equal(IReadOnlyList expected, IReadOnlyList actual) + { + Assert.True(expected.Count == actual.Count, "The number of items in the collections does not match."); + + Assert.All(actual, x => Assert.Contains(x, expected)); + } +} diff --git a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs index 9d2357b4..be64af4a 100644 --- a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs +++ b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Numerics; using Microsoft.Xna.Framework; using Xunit; using Vector2 = Microsoft.Xna.Framework.Vector2; @@ -11,11 +10,20 @@ public class OrientedBoundingRectangleTests [Fact] public void Initializes_oriented_bounding_rectangle() { - var rect = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)); + var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)); - Assert.Equal(new Point2(1, 2), rect.Center); - Assert.Equal(new Vector2(3, 4), rect.Radii); - Assert.Equal(new Matrix2(5, 6, 7, 8, 9, 10), rect.Orientation); + Assert.Equal(new Point2(1, 2), rectangle.Center); + Assert.Equal(new Vector2(3, 4), rectangle.Radii); + Assert.Equal(new Matrix2(5, 6, 7, 8, 9, 10), rectangle.Orientation); + CollectionAssert.Equal( + new List + { + new(-3, -2), + new(-33, -38), + new(23, 26), + new(53, 62) + }, + rectangle.Points); } public static readonly IEnumerable _equalsComparisons = new[] @@ -49,7 +57,7 @@ public class OrientedBoundingRectangleTests [Fact] public void Center_point_is_not_translated() { - var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(), new Matrix2()); + var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(), Matrix2.Identity); var transform = Matrix2.Identity; var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); @@ -91,7 +99,7 @@ public class OrientedBoundingRectangleTests } [Fact] - public void Rotate_45_degrees_left() + public void Orientation_is_rotated_45_degrees_left() { var rectangle = new OrientedBoundingRectangle(new Point2(), new Size2(), Matrix2.Identity); var transform = Matrix2.CreateRotationZ(MathHelper.PiOver4); @@ -104,7 +112,7 @@ public class OrientedBoundingRectangleTests } [Fact] - public void Rotate_to_45_degrees_from_180() + public void Orientation_is_rotated_to_45_degrees_from_180() { var rectangle = new OrientedBoundingRectangle(new Point2(), new Size2(), Matrix2.CreateRotationZ(MathHelper.Pi)); var transform = Matrix2.CreateRotationZ(-3 * MathHelper.PiOver4); @@ -122,6 +130,44 @@ public class OrientedBoundingRectangleTests Assert.Equal(expectedOrientation.M32, result.Orientation.M32, 6); } + [Fact] + public void Points_are_same_as_center() + { + var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(), Matrix2.Identity); + var transform = Matrix2.Identity; + + var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + + CollectionAssert.Equal( + new List + { + new(1, 2), + new(1, 2), + new(1, 2), + new(1, 2) + }, + result.Points); + } + + [Fact] + public void Points_are_translated() + { + var rectangle = new OrientedBoundingRectangle(new Point2(0, 0), new Size2(2, 4), Matrix2.Identity); + var transform = Matrix2.CreateTranslation(10, 20); + + var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + + CollectionAssert.Equal( + new List + { + new(8, 16), + new(8, 24), + new(12, 24), + new(12, 16) + }, + result.Points); + } + [Fact] public void Applies_rotation_and_translation() { @@ -169,11 +215,20 @@ public class OrientedBoundingRectangleTests var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); - Assert.Equal(-2 + 10, result.Center.X, 6); - Assert.Equal(1 + 20, result.Center.Y, 6); - Assert.Equal(4, result.Radii.X, 6); - Assert.Equal(2, result.Radii.Y, 6); + Assert.Equal(8, result.Center.X, 6); + Assert.Equal(21, result.Center.Y, 6); + Assert.Equal(2, result.Radii.X, 6); + Assert.Equal(4, result.Radii.Y, 6); Assert.Equal(Matrix2.CreateRotationZ(MathHelper.PiOver2), result.Orientation); + CollectionAssert.Equal( + new List + { + new(4, 23), + new(4, 19), + new(12, 19), + new(12, 23) + }, + result.Points); } } }