From 88a51a6f685f543b154c39f43f2e477f89d52b57 Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Mon, 5 Sep 2022 19:24:24 +0200 Subject: [PATCH 01/13] Add OrientedBoundingRectangle type --- .../Math/OrientedBoundingRectangle.cs | 143 ++++++++++++++ .../Math/PrimitivesHelper.cs | 20 ++ .../OrientedBoundingRectangleTests.cs | 179 ++++++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs create mode 100644 src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs diff --git a/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs b/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs new file mode 100644 index 00000000..25df3f2b --- /dev/null +++ b/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs @@ -0,0 +1,143 @@ +using System; +using System.Diagnostics; +using Microsoft.Xna.Framework; + +namespace MonoGame.Extended +{ + // Real-Time Collision Detection, Christer Ericson, 2005. Chapter 4.4; Bounding Volumes - Oriented Bounding Boxes (OBBs), pg 101. + + /// + /// An oriented bounding rectangle is a rectangular block, much like a bounding rectangle + /// but with an arbitrary orientation . + /// + /// + [DebuggerDisplay($"{{{nameof(DebugDisplayString)},nq}}")] + public struct OrientedBoundingRectangle : IEquatable + { + /// + /// The centre position of this . + /// + public Point2 Center; + + /// + /// The distance from the point along both axes to any point on the boundary of this + /// . + /// + /// + public Vector2 Radii; + + /// + /// The rotation matrix of the bounding rectangle . + /// + public Matrix2 Orientation; + + /// + /// Initializes a new instance of the structure from the specified centre + /// and the radii . + /// + /// The centre . + /// The radii . + /// The orientation . + public OrientedBoundingRectangle(Point2 center, Size2 radii, Matrix2 orientation) + { + Center = center; + Radii = radii; + Orientation = orientation; + } + + /// + /// Computes the from the specified + /// transformed by . + /// + /// The to transform. + /// The transformation. + /// A new . + public static OrientedBoundingRectangle Transform(OrientedBoundingRectangle rectangle, ref Matrix2 transformMatrix) + { + Transform(ref rectangle, ref transformMatrix, out var result); + return result; + } + + private static void Transform(ref OrientedBoundingRectangle rectangle, ref Matrix2 transformMatrix, out OrientedBoundingRectangle result) + { + PrimitivesHelper.TransformOrientedBoundingRectangle( + ref rectangle.Center, + ref rectangle.Radii, + ref rectangle.Orientation, + ref transformMatrix); + result.Center = rectangle.Center; + result.Radii = rectangle.Radii; + result.Orientation = rectangle.Orientation; + } + + /// + /// Compares to two structures. The result specifies whether the + /// the values of the , and are + /// equal. + /// + /// The left . + /// The right . + /// true if left and right argument are equal; otherwise, false. + public static bool operator ==(OrientedBoundingRectangle left, OrientedBoundingRectangle right) + { + return left.Equals(right); + } + + /// + /// Compares to two structures. The result specifies whether the + /// the values of the , or are + /// unequal. + /// + /// The left . + /// The right . + /// true if left and right argument are unequal; otherwise, false. + public static bool operator !=(OrientedBoundingRectangle left, OrientedBoundingRectangle right) + { + return !left.Equals(right); + } + + /// + /// Determines whether two instances of are equal. + /// + /// The other . + /// true if the specified is equal + /// to the current ; otherwise, false. + public bool Equals(OrientedBoundingRectangle other) + { + return Center.Equals(other.Center) && Radii.Equals(other.Radii) && Orientation.Equals(other.Orientation); + } + + /// + /// Determines whether two instances of are equal. + /// + /// The to compare to. + /// true if the specified is equal + /// to the current ; otherwise, false. + public override bool Equals(object obj) + { + return obj is OrientedBoundingRectangle other && Equals(other); + } + + /// + /// + /// + /// + public override int GetHashCode() + { + return HashCode.Combine(Center, Radii, Orientation); + } + + /// + /// Returns a that represents this . + /// + /// + /// A that represents this . + /// + public override string ToString() + { + return $"Centre: {Center}, Radii: {Radii}, Orientation: {Orientation}"; + } + + internal string DebugDisplayString => ToString(); + } +} diff --git a/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs b/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs index ef151bcb..4000ebd5 100644 --- a/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs +++ b/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs @@ -71,6 +71,26 @@ namespace MonoGame.Extended halfExtents.Y = xRadius * Math.Abs(transformMatrix.M21) + yRadius * Math.Abs(transformMatrix.M22); } + [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; + orientation.M32 = 0; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static float SquaredDistanceToPointFromRectangle(Point2 minimum, Point2 maximum, Point2 point) { diff --git a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs new file mode 100644 index 00000000..9d2357b4 --- /dev/null +++ b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs @@ -0,0 +1,179 @@ +using System.Collections.Generic; +using System.Numerics; +using Microsoft.Xna.Framework; +using Xunit; +using Vector2 = Microsoft.Xna.Framework.Vector2; + +namespace MonoGame.Extended.Tests.Primitives; + +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)); + + 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); + } + + public static readonly IEnumerable _equalsComparisons = new[] + { + new object[] + { + "empty compared with empty is true", + new OrientedBoundingRectangle(Point2.Zero, Size2.Empty, Matrix2.Identity), + new OrientedBoundingRectangle(Point2.Zero, Size2.Empty, Matrix2.Identity) + }, + new object[] + { + "initialized compared with initialized true", + new OrientedBoundingRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)), + new OrientedBoundingRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)) + } + }; + + [Theory] + [MemberData(nameof(_equalsComparisons))] +#pragma warning disable xUnit1026 + public void Equals_comparison(string name, OrientedBoundingRectangle first, OrientedBoundingRectangle second) +#pragma warning restore xUnit1026 + { + Assert.True(first == second); + Assert.False(first != second); + } + + public class Transform + { + [Fact] + public void Center_point_is_not_translated() + { + var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(), new Matrix2()); + var transform = Matrix2.Identity; + + var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + + Assert.Equal(new Point2(1, 2), result.Center); + } + + [Fact] + public void Center_point_is_translated() + { + var rectangle = new OrientedBoundingRectangle(new Point2(0, 0), new Size2(), new Matrix2()); + var transform = Matrix2.CreateTranslation(1, 2); + + var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + + Assert.Equal(new Point2(1, 2), result.Center); + } + + [Fact] + public void Radii_is_not_changed_by_identity_transform() + { + var rectangle = new OrientedBoundingRectangle(new Point2(), new Size2(10, 20), new Matrix2()); + var transform = Matrix2.Identity; + + var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + + Assert.Equal(new Vector2(10, 20), result.Radii); + } + + [Fact] + public void Radii_is_not_changed_by_translation() + { + var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(10, 20), new Matrix2()); + var transform = Matrix2.CreateTranslation(1, 2); + + var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + + Assert.Equal(new Vector2(10, 20), result.Radii); + } + + [Fact] + public void Rotate_45_degrees_left() + { + var rectangle = new OrientedBoundingRectangle(new Point2(), new Size2(), Matrix2.Identity); + var transform = Matrix2.CreateRotationZ(MathHelper.PiOver4); + + var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + + Assert.Equal(new Point2(), result.Center); + Assert.Equal(new Vector2(), result.Radii); + Assert.Equal(Matrix2.CreateRotationZ(MathHelper.PiOver4), result.Orientation); + } + + [Fact] + public void Rotate_to_45_degrees_from_180() + { + var rectangle = new OrientedBoundingRectangle(new Point2(), new Size2(), Matrix2.CreateRotationZ(MathHelper.Pi)); + var transform = Matrix2.CreateRotationZ(-3 * MathHelper.PiOver4); + var expectedOrientation = Matrix2.CreateRotationZ(MathHelper.PiOver4); + + var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + + Assert.Equal(new Point2(), result.Center); + Assert.Equal(new Vector2(), result.Radii); + Assert.Equal(expectedOrientation.M11, result.Orientation.M11, 6); + Assert.Equal(expectedOrientation.M12, result.Orientation.M12, 6); + Assert.Equal(expectedOrientation.M21, result.Orientation.M21, 6); + Assert.Equal(expectedOrientation.M22, result.Orientation.M22, 6); + Assert.Equal(expectedOrientation.M31, result.Orientation.M31, 6); + Assert.Equal(expectedOrientation.M32, result.Orientation.M32, 6); + } + + [Fact] + public void Applies_rotation_and_translation() + { + /* Rectangle with center point p, aligned in coordinate system with origin 0. + * + * : + * : + * +-+ + * | | + * |p| + * | | + * ...............0-+............ + * : + * : + * : + * + * Rotate around center point p, 90 degrees around origin 0. + * + * : + * : + * +---+ + * | p | + * ...........+---0.............. + * : + * : + * : + * + * Then translate rectangle by x=10 and y=20. + * : + * : +---+ + * : | p | + * y=21 - - - - - - - -> +---+ + * . + * : + * ...............0.............. + * : + * : + * : + */ + var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(2, 4), Matrix2.Identity); + var transform = + Matrix2.CreateRotationZ(MathHelper.PiOver2) + * + Matrix2.CreateTranslation(10, 20); + + 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(Matrix2.CreateRotationZ(MathHelper.PiOver2), result.Orientation); + } + } +} From db807cd659b3c89bbfc80c5eca05350a3d668c4a Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Wed, 7 Sep 2022 01:56:47 +0200 Subject: [PATCH 02/13] Add Points property to OrientedBoundingRectangle --- .../Math/OrientedBoundingRectangle.cs | 32 +++++++- .../Math/PrimitivesHelper.cs | 5 -- .../CollectionAssert.cs | 14 ++++ .../OrientedBoundingRectangleTests.cs | 79 ++++++++++++++++--- 4 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 src/cs/Tests/MonoGame.Extended.Tests/CollectionAssert.cs 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); } } } From b85f5262759b02f055f3345d257d8eaf2a41a5c2 Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Wed, 7 Feb 2024 21:55:52 +0100 Subject: [PATCH 03/13] Ignore Visual Studio cache --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4c156a7f..ea7d8869 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ # cake build output artifacts + +# Visual Studio +.vs/* From 135507e8504d92793db233ed63f5884461b8916a Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Mon, 19 Sep 2022 21:18:33 +0200 Subject: [PATCH 04/13] Be explicit about conversion of geometries Conversion from circle to rectangle should not pass unnoted and be explicit. --- src/cs/MonoGame.Extended/Math/CircleF.cs | 20 ++++++++++---------- src/cs/MonoGame.Extended/Math/RectangleF.cs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/cs/MonoGame.Extended/Math/CircleF.cs b/src/cs/MonoGame.Extended/Math/CircleF.cs index d5b464af..70f93074 100644 --- a/src/cs/MonoGame.Extended/Math/CircleF.cs +++ b/src/cs/MonoGame.Extended/Math/CircleF.cs @@ -425,13 +425,13 @@ namespace MonoGame.Extended } /// - /// Performs an implicit conversion from a to a . + /// Performs an explicit conversion from a to a . /// /// The circle. /// /// The resulting . /// - public static implicit operator Rectangle(CircleF circle) + public static explicit operator Rectangle(CircleF circle) { var diameter = (int) circle.Diameter; return new Rectangle((int) (circle.Center.X - circle.Radius), (int) (circle.Center.Y - circle.Radius), @@ -446,17 +446,17 @@ namespace MonoGame.Extended /// public Rectangle ToRectangle() { - return this; + return (Rectangle)this; } /// - /// Performs an implicit conversion from a to a . + /// Performs an explicit conversion from a to a . /// /// The rectangle. /// /// The resulting . /// - public static implicit operator CircleF(Rectangle rectangle) + public static explicit operator CircleF(Rectangle rectangle) { var halfWidth = rectangle.Width / 2; var halfHeight = rectangle.Height / 2; @@ -465,13 +465,13 @@ namespace MonoGame.Extended } /// - /// Performs an implicit conversion from a to a . + /// Performs an explicit conversion from a to a . /// /// The circle. /// /// The resulting . /// - public static implicit operator RectangleF(CircleF circle) + public static explicit operator RectangleF(CircleF circle) { var diameter = circle.Diameter; return new RectangleF(circle.Center.X - circle.Radius, circle.Center.Y - circle.Radius, diameter, diameter); @@ -485,17 +485,17 @@ namespace MonoGame.Extended /// public RectangleF ToRectangleF() { - return this; + return (RectangleF)this; } /// - /// Performs an implicit conversion from a to a . + /// Performs an explicit conversion from a to a . /// /// The rectangle. /// /// The resulting . /// - public static implicit operator CircleF(RectangleF rectangle) + public static explicit operator CircleF(RectangleF rectangle) { var halfWidth = rectangle.Width * 0.5f; var halfHeight = rectangle.Height * 0.5f; diff --git a/src/cs/MonoGame.Extended/Math/RectangleF.cs b/src/cs/MonoGame.Extended/Math/RectangleF.cs index 7cd467e5..1a1d6859 100644 --- a/src/cs/MonoGame.Extended/Math/RectangleF.cs +++ b/src/cs/MonoGame.Extended/Math/RectangleF.cs @@ -664,7 +664,7 @@ namespace MonoGame.Extended } /// - /// Performs an implicit conversion from a to a . + /// Performs an explicit conversion from a to a . /// /// The rectangle. /// From 82303e48b18ac66980479a78eeada200be87b874 Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Mon, 19 Sep 2022 21:55:00 +0200 Subject: [PATCH 05/13] OrientedBoundedRectangle implements ShapeF In that way the OrientedBoundedRectangle can take part of collision tests. --- .../Math/OrientedBoundingRectangle.cs | 37 +++++++- src/cs/MonoGame.Extended/Math/ShapeF.cs | 90 +++++++++++++++---- .../Primitives/ShapeTests.cs | 45 ++++++++-- 3 files changed, 146 insertions(+), 26 deletions(-) diff --git a/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs b/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs index d921cff7..6b12846f 100644 --- a/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs +++ b/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs @@ -13,7 +13,7 @@ namespace MonoGame.Extended /// /// [DebuggerDisplay($"{{{nameof(DebugDisplayString)},nq}}")] - public struct OrientedBoundingRectangle : IEquatable + public struct OrientedBoundingRectangle : IEquatable, IShapeF { /// /// The centre position of this . @@ -68,6 +68,14 @@ namespace MonoGame.Extended } } + public Point2 Position + { + get => Vector2.Transform(-Radii, Orientation) + Center; + set => throw new NotImplementedException(); + } + + public RectangleF BoundingRectangle => (RectangleF)this; + /// /// Computes the from the specified /// transformed by . @@ -87,6 +95,7 @@ namespace MonoGame.Extended ref rectangle.Center, ref rectangle.Orientation, ref transformMatrix); + result = new OrientedBoundingRectangle(); result.Center = rectangle.Center; result.Radii = rectangle.Radii; result.Orientation = rectangle.Orientation; @@ -141,7 +150,7 @@ namespace MonoGame.Extended } /// - /// + /// Returns a hash code for this object instance. /// /// public override int GetHashCode() @@ -149,6 +158,11 @@ namespace MonoGame.Extended return HashCode.Combine(Center, Radii, Orientation); } + /// + /// Performs an implicit conversion from a to . + /// + /// The rectangle to convert. + /// The resulting . public static explicit operator OrientedBoundingRectangle(RectangleF rectangle) { var radii = new Size2(rectangle.Width * 0.5f, rectangle.Height * 0.5f); @@ -157,6 +171,25 @@ namespace MonoGame.Extended return new OrientedBoundingRectangle(centre, radii, Matrix2.Identity); } + public static explicit operator RectangleF(OrientedBoundingRectangle orientedBoundingRectangle) + { + var topLeft = orientedBoundingRectangle.Center - orientedBoundingRectangle.Radii; + var rectangle = new RectangleF(topLeft, orientedBoundingRectangle.Radii * 2); + return RectangleF.Transform(rectangle, ref orientedBoundingRectangle.Orientation); + } + + /// + /// + /// + /// + /// + /// + /// + public static bool Intersects(OrientedBoundingRectangle rectangle, OrientedBoundingRectangle otherRectangle) + { + throw new NotImplementedException(); + } + /// /// Returns a that represents this . /// diff --git a/src/cs/MonoGame.Extended/Math/ShapeF.cs b/src/cs/MonoGame.Extended/Math/ShapeF.cs index bf8d61a3..76b8feb7 100644 --- a/src/cs/MonoGame.Extended/Math/ShapeF.cs +++ b/src/cs/MonoGame.Extended/Math/ShapeF.cs @@ -1,4 +1,6 @@ -namespace MonoGame.Extended +using System; + +namespace MonoGame.Extended { /// /// Base class for shapes. @@ -32,26 +34,46 @@ /// True if the two shapes intersect. public static bool Intersects(this IShapeF shapeA, IShapeF shapeB) { - var intersects = false; + return shapeA switch + { + CircleF circleA => IntersectsInternal(circleA, shapeB), + RectangleF rectangleA => IntersectsInternal(rectangleA, shapeB), + OrientedBoundingRectangle orientedBoundingRectangleA => IntersectsInternal(orientedBoundingRectangleA, shapeB), + _ => throw new ArgumentOutOfRangeException(nameof(shapeA)) + }; + } - if (shapeA is RectangleF rectangleA && shapeB is RectangleF rectangleB) - { - intersects = rectangleA.Intersects(rectangleB); - } - else if (shapeA is CircleF circleA && shapeB is CircleF circleB) - { - intersects = circleA.Intersects(circleB); - } - else if (shapeA is RectangleF rect1 && shapeB is CircleF circ1) - { - return Intersects(circ1, rect1); - } - else if (shapeA is CircleF circ2 && shapeB is RectangleF rect2) - { - return Intersects(circ2, rect2); - } + private static bool IntersectsInternal(CircleF circle, IShapeF shape) + { + return shape switch + { + CircleF otherCircle => CircleF.Intersects(circle, otherCircle), + RectangleF otherRectangle => Intersects(circle, otherRectangle), + OrientedBoundingRectangle otherOrientedBoundingRectangle => Intersects(circle, otherOrientedBoundingRectangle), + _ => throw new ArgumentOutOfRangeException(nameof(shape)) + }; + } - return intersects; + private static bool IntersectsInternal(RectangleF rectangle, IShapeF shape) + { + return shape switch + { + CircleF otherCircle => Intersects(otherCircle, rectangle), + RectangleF otherRectangle => RectangleF.Intersects(rectangle, otherRectangle), + OrientedBoundingRectangle otherOrientedBoundingRectangle => Intersects(rectangle, otherOrientedBoundingRectangle), + _ => throw new ArgumentOutOfRangeException(nameof(shape)) + }; + } + + private static bool IntersectsInternal(OrientedBoundingRectangle orientedBoundingRectangle, IShapeF shape) + { + return shape switch + { + CircleF circleB => Intersects(circleB, orientedBoundingRectangle), + RectangleF rectangleB => Intersects(rectangleB, orientedBoundingRectangle), + OrientedBoundingRectangle orientedBoundingRectangleB => OrientedBoundingRectangle.Intersects(orientedBoundingRectangle, orientedBoundingRectangleB), + _ => throw new ArgumentOutOfRangeException(nameof(shape)) + }; } /// @@ -65,5 +87,35 @@ var closestPoint = rectangle.ClosestPointTo(circle.Center); return circle.Contains(closestPoint); } + + /// + /// + /// + /// + /// + /// + /// + public static bool Intersects(CircleF circle, OrientedBoundingRectangle orientedBoundingRectangle) + { + var rotation = Matrix2.CreateRotationZ(-orientedBoundingRectangle.Orientation.Rotation); + var circleCenterInRectangleSpace = rotation.Transform(orientedBoundingRectangle.Center - circle.Center); + var circleInRectangleSpace = new CircleF(circleCenterInRectangleSpace, circle.Radius); + var rectangleInLocalSpace = OrientedBoundingRectangle.Transform(orientedBoundingRectangle, ref rotation); + rectangleInLocalSpace.Center = Point2.Zero; + var rectangle = (BoundingRectangle)new RectangleF(0, 0, rectangleInLocalSpace.Radii.X, rectangleInLocalSpace.Radii.Y); + return circleInRectangleSpace.Intersects(rectangle); + } + + /// + /// + /// + /// + /// + /// + /// + public static bool Intersects(RectangleF rectangleF, OrientedBoundingRectangle orientedBoundingRectangle) + { + throw new NotImplementedException(); + } } } diff --git a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs index bb5e318b..6b876021 100644 --- a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs +++ b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs @@ -1,8 +1,11 @@ -using Xunit; +using Microsoft.Xna.Framework; +using Xunit; -namespace MonoGame.Extended.Tests.Primitives +namespace MonoGame.Extended.Tests.Primitives; + +public class ShapeTests { - public class ShapeTests + public class CircleFTests { [Fact] public void CircCircIntersectionSameCircleTest() @@ -30,7 +33,10 @@ namespace MonoGame.Extended.Tests.Primitives Assert.False(shape1.Intersects(shape2)); } + } + public class RectangleFTests + { [Fact] public void RectRectSameRectTest() { @@ -68,7 +74,6 @@ namespace MonoGame.Extended.Tests.Primitives Assert.True(shape2.Intersects(shape1)); } - [Fact] public void RectCircOnEdgeTest() { @@ -79,4 +84,34 @@ namespace MonoGame.Extended.Tests.Primitives Assert.True(shape2.Intersects(shape1)); } } -} \ No newline at end of file + + public class OrientedBoundingRectangleTests + { + [Fact] + public void Axis_aligned_rectangle_overlaps_circle() + { + IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity); + var circle = new CircleF(Point2.Zero, 1); + + Assert.True(rectangle.Intersects(circle)); + } + + [Fact] + public void Axis_aligned_rectangle_does_not_intersect_circle_in_top_left() + { + IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity); + var circle = new CircleF(new Point2(1, 1), 1); + + Assert.False(rectangle.Intersects(circle)); + } + + [Fact] + public void Rectangle_rotated_45_degrees_does_not_intersect_circle_in_bottom_right() + { + IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.CreateRotationZ(MathHelper.PiOver4)); + var circle = new CircleF(new Point2(1, -1), 1.4f); + + Assert.False(rectangle.Intersects(circle)); + } + } +} From c1873c2dec0fa35cbdcca0545e05f146230932ba Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Mon, 21 Nov 2022 22:57:55 +0100 Subject: [PATCH 06/13] Add ascii art to describe intersection tests --- .../Primitives/ShapeTests.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs index 6b876021..c666a5ef 100644 --- a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs +++ b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs @@ -90,17 +90,36 @@ public class ShapeTests [Fact] public void Axis_aligned_rectangle_overlaps_circle() { + /* + * : + * : + * +*+ + * ...........* *......... + * +*+ + * : + * : + */ IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity); var circle = new CircleF(Point2.Zero, 1); Assert.True(rectangle.Intersects(circle)); } + [Fact] public void Axis_aligned_rectangle_does_not_intersect_circle_in_top_left() { + /* + * * : + * * *: + * *+-+ + * ...........| |......... + * +-+ + * : + * : + */ IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity); - var circle = new CircleF(new Point2(1, 1), 1); + var circle = new CircleF(new Point2(-1, 1), 1); Assert.False(rectangle.Intersects(circle)); } @@ -108,6 +127,15 @@ public class ShapeTests [Fact] public void Rectangle_rotated_45_degrees_does_not_intersect_circle_in_bottom_right() { + /* + * : + * : + * +-. + * .........../ / ........ + * +./* * + * * * + * :* * + */ IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.CreateRotationZ(MathHelper.PiOver4)); var circle = new CircleF(new Point2(1, -1), 1.4f); From 0a2ce121349016e6b752b60e0f88ff24276d7046 Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Sun, 6 Aug 2023 19:32:01 +0200 Subject: [PATCH 07/13] Update circle and oriented bound box intersection --- src/cs/MonoGame.Extended/Math/ShapeF.cs | 21 ++++++++----------- .../Primitives/ShapeTests.cs | 7 +++---- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/cs/MonoGame.Extended/Math/ShapeF.cs b/src/cs/MonoGame.Extended/Math/ShapeF.cs index 76b8feb7..efdc98c0 100644 --- a/src/cs/MonoGame.Extended/Math/ShapeF.cs +++ b/src/cs/MonoGame.Extended/Math/ShapeF.cs @@ -89,30 +89,27 @@ namespace MonoGame.Extended } /// - /// + /// Checks whether a and intersects. /// - /// - /// - /// - /// + /// to use in intersection test. + /// to use in intersection test. + /// True if the circle and oriented bounded rectangle intersects, otherwise false. public static bool Intersects(CircleF circle, OrientedBoundingRectangle orientedBoundingRectangle) { var rotation = Matrix2.CreateRotationZ(-orientedBoundingRectangle.Orientation.Rotation); - var circleCenterInRectangleSpace = rotation.Transform(orientedBoundingRectangle.Center - circle.Center); + var circleCenterInRectangleSpace = rotation.Transform(circle.Center - orientedBoundingRectangle.Center); var circleInRectangleSpace = new CircleF(circleCenterInRectangleSpace, circle.Radius); var rectangleInLocalSpace = OrientedBoundingRectangle.Transform(orientedBoundingRectangle, ref rotation); - rectangleInLocalSpace.Center = Point2.Zero; - var rectangle = (BoundingRectangle)new RectangleF(0, 0, rectangleInLocalSpace.Radii.X, rectangleInLocalSpace.Radii.Y); - return circleInRectangleSpace.Intersects(rectangle); + var boundingRectangle = new BoundingRectangle(rectangleInLocalSpace.Center, rectangleInLocalSpace.Radii); + return circleInRectangleSpace.Intersects(boundingRectangle); } /// - /// + /// Checks if a and intersects. /// /// /// - /// - /// + /// True if objects are intersecting, otherwise false. public static bool Intersects(RectangleF rectangleF, OrientedBoundingRectangle orientedBoundingRectangle) { throw new NotImplementedException(); diff --git a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs index c666a5ef..c8ad9021 100644 --- a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs +++ b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs @@ -88,7 +88,7 @@ public class ShapeTests public class OrientedBoundingRectangleTests { [Fact] - public void Axis_aligned_rectangle_overlaps_circle() + public void Axis_aligned_rectangle_intersects_circle() { /* * : @@ -105,7 +105,6 @@ public class ShapeTests Assert.True(rectangle.Intersects(circle)); } - [Fact] public void Axis_aligned_rectangle_does_not_intersect_circle_in_top_left() { @@ -119,7 +118,7 @@ public class ShapeTests * : */ IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity); - var circle = new CircleF(new Point2(-1, 1), 1); + var circle = new CircleF(new Point2(-2, 1), .99f); Assert.False(rectangle.Intersects(circle)); } @@ -136,7 +135,7 @@ public class ShapeTests * * * * :* * */ - IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.CreateRotationZ(MathHelper.PiOver4)); + IShapeF rectangle = new OrientedBoundingRectangle(new Point2(-1, 1), new Size2(2.8f, 2.8f), Matrix2.CreateRotationZ(MathHelper.PiOver4)); var circle = new CircleF(new Point2(1, -1), 1.4f); Assert.False(rectangle.Intersects(circle)); From 9007c6bd553b2e122610df106dfe1830f54f02a3 Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Sun, 11 Feb 2024 19:26:54 +0100 Subject: [PATCH 08/13] Update dependencies --- .../MonoGame.Extended.Tweening.Tests.csproj | 6 +++--- .../MonoGame.Extended.Collisions.Tests.csproj | 6 +++--- .../MonoGame.Extended.Content.Pipeline.Tests.Tiled.csproj | 6 +++--- .../MonoGame.Extended.Content.Pipeline.Tests.csproj | 6 +++--- .../MonoGame.Extended.Entities.Tests.csproj | 6 +++--- .../MonoGame.Extended.Gui.Tests.csproj | 6 +++--- .../MonoGame.Extended.Tests/MonoGame.Extended.Tests.csproj | 6 +++--- .../MonoGame.Extended.Tiled.Tests.csproj | 6 +++--- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/MonoGame.Extended.Tweening.Tests/MonoGame.Extended.Tweening.Tests.csproj b/MonoGame.Extended.Tweening.Tests/MonoGame.Extended.Tweening.Tests.csproj index cfe82093..9a2d97fe 100644 --- a/MonoGame.Extended.Tweening.Tests/MonoGame.Extended.Tweening.Tests.csproj +++ b/MonoGame.Extended.Tweening.Tests/MonoGame.Extended.Tweening.Tests.csproj @@ -10,9 +10,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/cs/Tests/MonoGame.Extended.Collisions.Tests/MonoGame.Extended.Collisions.Tests.csproj b/src/cs/Tests/MonoGame.Extended.Collisions.Tests/MonoGame.Extended.Collisions.Tests.csproj index 729c105c..a032112e 100644 --- a/src/cs/Tests/MonoGame.Extended.Collisions.Tests/MonoGame.Extended.Collisions.Tests.csproj +++ b/src/cs/Tests/MonoGame.Extended.Collisions.Tests/MonoGame.Extended.Collisions.Tests.csproj @@ -8,9 +8,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/cs/Tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/MonoGame.Extended.Content.Pipeline.Tests.Tiled.csproj b/src/cs/Tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/MonoGame.Extended.Content.Pipeline.Tests.Tiled.csproj index 06a67b14..63b7d40c 100644 --- a/src/cs/Tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/MonoGame.Extended.Content.Pipeline.Tests.Tiled.csproj +++ b/src/cs/Tests/MonoGame.Extended.Content.Pipeline.Tests.Tiled/MonoGame.Extended.Content.Pipeline.Tests.Tiled.csproj @@ -40,9 +40,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/cs/Tests/MonoGame.Extended.Content.Pipeline.Tests/MonoGame.Extended.Content.Pipeline.Tests.csproj b/src/cs/Tests/MonoGame.Extended.Content.Pipeline.Tests/MonoGame.Extended.Content.Pipeline.Tests.csproj index 2a3f9cdc..7b503377 100644 --- a/src/cs/Tests/MonoGame.Extended.Content.Pipeline.Tests/MonoGame.Extended.Content.Pipeline.Tests.csproj +++ b/src/cs/Tests/MonoGame.Extended.Content.Pipeline.Tests/MonoGame.Extended.Content.Pipeline.Tests.csproj @@ -8,9 +8,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/cs/Tests/MonoGame.Extended.Entities.Tests/MonoGame.Extended.Entities.Tests.csproj b/src/cs/Tests/MonoGame.Extended.Entities.Tests/MonoGame.Extended.Entities.Tests.csproj index 73d4479f..ec4254e8 100644 --- a/src/cs/Tests/MonoGame.Extended.Entities.Tests/MonoGame.Extended.Entities.Tests.csproj +++ b/src/cs/Tests/MonoGame.Extended.Entities.Tests/MonoGame.Extended.Entities.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/cs/Tests/MonoGame.Extended.Gui.Tests/MonoGame.Extended.Gui.Tests.csproj b/src/cs/Tests/MonoGame.Extended.Gui.Tests/MonoGame.Extended.Gui.Tests.csproj index 2dca7dd8..b6a995be 100644 --- a/src/cs/Tests/MonoGame.Extended.Gui.Tests/MonoGame.Extended.Gui.Tests.csproj +++ b/src/cs/Tests/MonoGame.Extended.Gui.Tests/MonoGame.Extended.Gui.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/cs/Tests/MonoGame.Extended.Tests/MonoGame.Extended.Tests.csproj b/src/cs/Tests/MonoGame.Extended.Tests/MonoGame.Extended.Tests.csproj index 66ec6d54..18f78f1c 100644 --- a/src/cs/Tests/MonoGame.Extended.Tests/MonoGame.Extended.Tests.csproj +++ b/src/cs/Tests/MonoGame.Extended.Tests/MonoGame.Extended.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/cs/Tests/MonoGame.Extended.Tiled.Tests/MonoGame.Extended.Tiled.Tests.csproj b/src/cs/Tests/MonoGame.Extended.Tiled.Tests/MonoGame.Extended.Tiled.Tests.csproj index a6fc12ae..675552ab 100644 --- a/src/cs/Tests/MonoGame.Extended.Tiled.Tests/MonoGame.Extended.Tiled.Tests.csproj +++ b/src/cs/Tests/MonoGame.Extended.Tiled.Tests/MonoGame.Extended.Tiled.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 1d878727ec377e83e727b006751c89cc55c3f9f2 Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Sat, 24 Feb 2024 00:40:30 +0100 Subject: [PATCH 09/13] Add Intersects method for OBB --- .../Math/OrientedBoundingRectangle.cs | 67 +++++++++++++++++-- src/cs/MonoGame.Extended/Math/ShapeF.cs | 2 +- .../Primitives/ShapeTests.cs | 36 ++++++++++ 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs b/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs index 6b12846f..14c52f47 100644 --- a/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs +++ b/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs @@ -88,7 +88,7 @@ namespace MonoGame.Extended Transform(ref rectangle, ref transformMatrix, out var result); return result; } - + private static void Transform(ref OrientedBoundingRectangle rectangle, ref Matrix2 transformMatrix, out OrientedBoundingRectangle result) { PrimitivesHelper.TransformOrientedBoundingRectangle( @@ -179,15 +179,72 @@ namespace MonoGame.Extended } /// - /// + /// See https://www.flipcode.com/archives/2D_OBB_Intersection.shtml /// /// - /// + /// /// /// - public static bool Intersects(OrientedBoundingRectangle rectangle, OrientedBoundingRectangle otherRectangle) + public static bool Intersects(OrientedBoundingRectangle rectangle, OrientedBoundingRectangle other) { - throw new NotImplementedException(); + var corners = rectangle.Points; + var otherCorners = other.Points; + return IntersectsOneWay(corners, otherCorners) && IntersectsOneWay(otherCorners, corners); + + bool IntersectsOneWay(IReadOnlyList source, IReadOnlyList target) + { + var axis = new[] + { + source[1] - source[0], + source[3] - source[0] + }; + var origin = new float[2]; + + // Make the length of each axis 1/edge length so we know any + // dot product must be less than 1 to fall within the edge. + for (var a = 0; a < 2; a++) + { + axis[a] /= axis[a].LengthSquared(); + origin[a] = source[0].Dot(axis[a]); + } + + for (var a = 0; a < 2; a++) { + + var t = target[0].Dot(axis[a]); + + // Find the extent of box 2 on axis a + var tMin = t; + var tMax = t; + + for (var c = 1; c < 4; c++) + { + t = target[c].Dot(axis[a]); + + if (t < tMin) + { + tMin = t; + } + else if (t > tMax) + { + tMax = t; + } + } + + // We have to subtract off the origin + + // See if [tMin, tMax] intersects [0, 1] + if (tMin > 1 + origin[a] || tMax < origin[a]) + { + // There was no intersection along this dimension; + // the boxes cannot possibly overlap. + return false; + } + } + + // There was no dimension along which there is no intersection. + // Therefore the boxes overlap. + return true; + } } /// diff --git a/src/cs/MonoGame.Extended/Math/ShapeF.cs b/src/cs/MonoGame.Extended/Math/ShapeF.cs index efdc98c0..fe1dca76 100644 --- a/src/cs/MonoGame.Extended/Math/ShapeF.cs +++ b/src/cs/MonoGame.Extended/Math/ShapeF.cs @@ -112,7 +112,7 @@ namespace MonoGame.Extended /// True if objects are intersecting, otherwise false. public static bool Intersects(RectangleF rectangleF, OrientedBoundingRectangle orientedBoundingRectangle) { - throw new NotImplementedException(); + return OrientedBoundingRectangle.Intersects(orientedBoundingRectangle, (OrientedBoundingRectangle)rectangleF); } } } diff --git a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs index c8ad9021..f310d54e 100644 --- a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs +++ b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs @@ -140,5 +140,41 @@ public class ShapeTests Assert.False(rectangle.Intersects(circle)); } + + [Fact] + public void Axis_aligned_rectangle_does_not_intersect_rectangle() + { + /* + * : + * : + * +-+ + * ..........| |**....... + * +-+ * + * :** + * : + */ + IShapeF rectangle = new OrientedBoundingRectangle(new Point2(-1, 0), new Size2(1, 1), Matrix2.Identity); + var rect = new RectangleF(new Point2(0.001f, 0), new Size2(2, 2)); + + Assert.False(rectangle.Intersects(rect)); + } + + [Fact] + public void Axis_aligned_rectangle_intersects_rectangle() + { + /* + * : + * : + * +-+ + * ..........| |**....... + * +-+ * + * :** + * : + */ + IShapeF rectangle = new OrientedBoundingRectangle(new Point2(-1, 0), new Size2(1, 1), Matrix2.Identity); + var rect = new RectangleF(new Point2(0, 0), new Size2(2, 2)); + + Assert.True(rectangle.Intersects(rect)); + } } } From 6a380e381bf9fb370b2fb875ccab14987c4db8b5 Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Thu, 29 Feb 2024 22:11:57 +0100 Subject: [PATCH 10/13] Rename to OrientedRectangle --- ...ndingRectangle.cs => OrientedRectangle.cs} | 84 +++++++++---------- .../Math/PrimitivesHelper.cs | 2 +- src/cs/MonoGame.Extended/Math/ShapeF.cs | 34 ++++---- ...ngleTests.cs => OrientedRectangleTests.cs} | 52 ++++++------ .../Primitives/ShapeTests.cs | 12 +-- 5 files changed, 92 insertions(+), 92 deletions(-) rename src/cs/MonoGame.Extended/Math/{OrientedBoundingRectangle.cs => OrientedRectangle.cs} (70%) rename src/cs/Tests/MonoGame.Extended.Tests/Primitives/{OrientedBoundingRectangleTests.cs => OrientedRectangleTests.cs} (71%) diff --git a/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs b/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs similarity index 70% rename from src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs rename to src/cs/MonoGame.Extended/Math/OrientedRectangle.cs index 14c52f47..e1911b62 100644 --- a/src/cs/MonoGame.Extended/Math/OrientedBoundingRectangle.cs +++ b/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs @@ -13,22 +13,22 @@ namespace MonoGame.Extended /// /// [DebuggerDisplay($"{{{nameof(DebugDisplayString)},nq}}")] - public struct OrientedBoundingRectangle : IEquatable, IShapeF + public struct OrientedRectangle : IEquatable, IShapeF { /// - /// The centre position of this . + /// The centre position of this . /// public Point2 Center; /// /// The distance from the point along both axes to any point on the boundary of this - /// . + /// . /// /// public Vector2 Radii; /// - /// The rotation matrix of the bounding rectangle . + /// The rotation matrix of the bounding rectangle . /// public Matrix2 Orientation; @@ -39,7 +39,7 @@ namespace MonoGame.Extended /// The centre . /// The radii . /// The orientation . - public OrientedBoundingRectangle(Point2 center, Size2 radii, Matrix2 orientation) + public OrientedRectangle(Point2 center, Size2 radii, Matrix2 orientation) { Center = center; Radii = radii; @@ -77,76 +77,76 @@ namespace MonoGame.Extended public RectangleF BoundingRectangle => (RectangleF)this; /// - /// Computes the from the specified + /// Computes the from the specified /// transformed by . /// - /// The to transform. + /// The to transform. /// The transformation. - /// A new . - public static OrientedBoundingRectangle Transform(OrientedBoundingRectangle rectangle, ref Matrix2 transformMatrix) + /// A new . + public static OrientedRectangle Transform(OrientedRectangle rectangle, ref Matrix2 transformMatrix) { Transform(ref rectangle, ref transformMatrix, out var result); return result; } - - private static void Transform(ref OrientedBoundingRectangle rectangle, ref Matrix2 transformMatrix, out OrientedBoundingRectangle result) + + private static void Transform(ref OrientedRectangle rectangle, ref Matrix2 transformMatrix, out OrientedRectangle result) { - PrimitivesHelper.TransformOrientedBoundingRectangle( + PrimitivesHelper.TransformOrientedRectangle( ref rectangle.Center, ref rectangle.Orientation, ref transformMatrix); - result = new OrientedBoundingRectangle(); + result = new OrientedRectangle(); result.Center = rectangle.Center; result.Radii = rectangle.Radii; result.Orientation = rectangle.Orientation; } /// - /// Compares to two structures. The result specifies whether the + /// Compares to two structures. The result specifies whether the /// the values of the , and are /// equal. /// - /// The left . - /// The right . + /// The left . + /// The right . /// true if left and right argument are equal; otherwise, false. - public static bool operator ==(OrientedBoundingRectangle left, OrientedBoundingRectangle right) + public static bool operator ==(OrientedRectangle left, OrientedRectangle right) { return left.Equals(right); } /// - /// Compares to two structures. The result specifies whether the + /// Compares to two structures. The result specifies whether the /// the values of the , or are /// unequal. /// - /// The left . - /// The right . + /// The left . + /// The right . /// true if left and right argument are unequal; otherwise, false. - public static bool operator !=(OrientedBoundingRectangle left, OrientedBoundingRectangle right) + public static bool operator !=(OrientedRectangle left, OrientedRectangle right) { return !left.Equals(right); } /// - /// Determines whether two instances of are equal. + /// Determines whether two instances of are equal. /// - /// The other . - /// true if the specified is equal - /// to the current ; otherwise, false. - public bool Equals(OrientedBoundingRectangle other) + /// The other . + /// true if the specified is equal + /// to the current ; otherwise, false. + public bool Equals(OrientedRectangle other) { return Center.Equals(other.Center) && Radii.Equals(other.Radii) && Orientation.Equals(other.Orientation); } /// - /// Determines whether two instances of are equal. + /// Determines whether two instances of are equal. /// - /// The to compare to. - /// true if the specified is equal - /// to the current ; otherwise, false. + /// The to compare to. + /// true if the specified is equal + /// to the current ; otherwise, false. public override bool Equals(object obj) { - return obj is OrientedBoundingRectangle other && Equals(other); + return obj is OrientedRectangle other && Equals(other); } /// @@ -159,23 +159,23 @@ namespace MonoGame.Extended } /// - /// Performs an implicit conversion from a to . + /// Performs an implicit conversion from a to . /// /// The rectangle to convert. - /// The resulting . - public static explicit operator OrientedBoundingRectangle(RectangleF rectangle) + /// The resulting . + public static explicit operator OrientedRectangle(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); + return new OrientedRectangle(centre, radii, Matrix2.Identity); } - public static explicit operator RectangleF(OrientedBoundingRectangle orientedBoundingRectangle) + public static explicit operator RectangleF(OrientedRectangle orientedRectangle) { - var topLeft = orientedBoundingRectangle.Center - orientedBoundingRectangle.Radii; - var rectangle = new RectangleF(topLeft, orientedBoundingRectangle.Radii * 2); - return RectangleF.Transform(rectangle, ref orientedBoundingRectangle.Orientation); + var topLeft = orientedRectangle.Center - orientedRectangle.Radii; + var rectangle = new RectangleF(topLeft, orientedRectangle.Radii * 2); + return RectangleF.Transform(rectangle, ref orientedRectangle.Orientation); } /// @@ -185,7 +185,7 @@ namespace MonoGame.Extended /// /// /// - public static bool Intersects(OrientedBoundingRectangle rectangle, OrientedBoundingRectangle other) + public static bool Intersects(OrientedRectangle rectangle, OrientedRectangle other) { var corners = rectangle.Points; var otherCorners = other.Points; @@ -248,10 +248,10 @@ namespace MonoGame.Extended } /// - /// Returns a that represents this . + /// Returns a that represents this . /// /// - /// A that represents this . + /// A that represents this . /// public override string ToString() { diff --git a/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs b/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs index 63152f16..ed082708 100644 --- a/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs +++ b/src/cs/MonoGame.Extended/Math/PrimitivesHelper.cs @@ -72,7 +72,7 @@ namespace MonoGame.Extended } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void TransformOrientedBoundingRectangle( + internal static void TransformOrientedRectangle( ref Point2 center, ref Matrix2 orientation, ref Matrix2 transformMatrix) diff --git a/src/cs/MonoGame.Extended/Math/ShapeF.cs b/src/cs/MonoGame.Extended/Math/ShapeF.cs index fe1dca76..979477f3 100644 --- a/src/cs/MonoGame.Extended/Math/ShapeF.cs +++ b/src/cs/MonoGame.Extended/Math/ShapeF.cs @@ -38,7 +38,7 @@ namespace MonoGame.Extended { CircleF circleA => IntersectsInternal(circleA, shapeB), RectangleF rectangleA => IntersectsInternal(rectangleA, shapeB), - OrientedBoundingRectangle orientedBoundingRectangleA => IntersectsInternal(orientedBoundingRectangleA, shapeB), + OrientedRectangle orientedRectangleA => IntersectsInternal(orientedRectangleA, shapeB), _ => throw new ArgumentOutOfRangeException(nameof(shapeA)) }; } @@ -49,7 +49,7 @@ namespace MonoGame.Extended { CircleF otherCircle => CircleF.Intersects(circle, otherCircle), RectangleF otherRectangle => Intersects(circle, otherRectangle), - OrientedBoundingRectangle otherOrientedBoundingRectangle => Intersects(circle, otherOrientedBoundingRectangle), + OrientedRectangle otherOrientedRectangle => Intersects(circle, otherOrientedRectangle), _ => throw new ArgumentOutOfRangeException(nameof(shape)) }; } @@ -60,18 +60,18 @@ namespace MonoGame.Extended { CircleF otherCircle => Intersects(otherCircle, rectangle), RectangleF otherRectangle => RectangleF.Intersects(rectangle, otherRectangle), - OrientedBoundingRectangle otherOrientedBoundingRectangle => Intersects(rectangle, otherOrientedBoundingRectangle), + OrientedRectangle otherOrientedRectangle => Intersects(rectangle, otherOrientedRectangle), _ => throw new ArgumentOutOfRangeException(nameof(shape)) }; } - private static bool IntersectsInternal(OrientedBoundingRectangle orientedBoundingRectangle, IShapeF shape) + private static bool IntersectsInternal(OrientedRectangle orientedRectangle, IShapeF shape) { return shape switch { - CircleF circleB => Intersects(circleB, orientedBoundingRectangle), - RectangleF rectangleB => Intersects(rectangleB, orientedBoundingRectangle), - OrientedBoundingRectangle orientedBoundingRectangleB => OrientedBoundingRectangle.Intersects(orientedBoundingRectangle, orientedBoundingRectangleB), + CircleF circleB => Intersects(circleB, orientedRectangle), + RectangleF rectangleB => Intersects(rectangleB, orientedRectangle), + OrientedRectangle orientedRectangleB => OrientedRectangle.Intersects(orientedRectangle, orientedRectangleB), _ => throw new ArgumentOutOfRangeException(nameof(shape)) }; } @@ -89,30 +89,30 @@ namespace MonoGame.Extended } /// - /// Checks whether a and intersects. + /// Checks whether a and intersects. /// /// to use in intersection test. - /// to use in intersection test. + /// to use in intersection test. /// True if the circle and oriented bounded rectangle intersects, otherwise false. - public static bool Intersects(CircleF circle, OrientedBoundingRectangle orientedBoundingRectangle) + public static bool Intersects(CircleF circle, OrientedRectangle orientedRectangle) { - var rotation = Matrix2.CreateRotationZ(-orientedBoundingRectangle.Orientation.Rotation); - var circleCenterInRectangleSpace = rotation.Transform(circle.Center - orientedBoundingRectangle.Center); + var rotation = Matrix2.CreateRotationZ(-orientedRectangle.Orientation.Rotation); + var circleCenterInRectangleSpace = rotation.Transform(circle.Center - orientedRectangle.Center); var circleInRectangleSpace = new CircleF(circleCenterInRectangleSpace, circle.Radius); - var rectangleInLocalSpace = OrientedBoundingRectangle.Transform(orientedBoundingRectangle, ref rotation); + var rectangleInLocalSpace = OrientedRectangle.Transform(orientedRectangle, ref rotation); var boundingRectangle = new BoundingRectangle(rectangleInLocalSpace.Center, rectangleInLocalSpace.Radii); return circleInRectangleSpace.Intersects(boundingRectangle); } /// - /// Checks if a and intersects. + /// Checks if a and intersects. /// /// - /// + /// /// True if objects are intersecting, otherwise false. - public static bool Intersects(RectangleF rectangleF, OrientedBoundingRectangle orientedBoundingRectangle) + public static bool Intersects(RectangleF rectangleF, OrientedRectangle orientedRectangle) { - return OrientedBoundingRectangle.Intersects(orientedBoundingRectangle, (OrientedBoundingRectangle)rectangleF); + return OrientedRectangle.Intersects(orientedRectangle, (OrientedRectangle)rectangleF); } } } diff --git a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedRectangleTests.cs similarity index 71% rename from src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs rename to src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedRectangleTests.cs index be64af4a..e3b40c8c 100644 --- a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedBoundingRectangleTests.cs +++ b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/OrientedRectangleTests.cs @@ -5,12 +5,12 @@ using Vector2 = Microsoft.Xna.Framework.Vector2; namespace MonoGame.Extended.Tests.Primitives; -public class OrientedBoundingRectangleTests +public class OrientedRectangleTests { [Fact] - public void Initializes_oriented_bounding_rectangle() + public void Initializes_oriented_rectangle() { - var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)); + var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)); Assert.Equal(new Point2(1, 2), rectangle.Center); Assert.Equal(new Vector2(3, 4), rectangle.Radii); @@ -31,21 +31,21 @@ public class OrientedBoundingRectangleTests new object[] { "empty compared with empty is true", - new OrientedBoundingRectangle(Point2.Zero, Size2.Empty, Matrix2.Identity), - new OrientedBoundingRectangle(Point2.Zero, Size2.Empty, Matrix2.Identity) + new OrientedRectangle(Point2.Zero, Size2.Empty, Matrix2.Identity), + new OrientedRectangle(Point2.Zero, Size2.Empty, Matrix2.Identity) }, new object[] { "initialized compared with initialized true", - new OrientedBoundingRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)), - new OrientedBoundingRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)) + new OrientedRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)), + new OrientedRectangle(new Point2(1, 2), new Size2(3, 4), new Matrix2(5, 6, 7, 8, 9, 10)) } }; [Theory] [MemberData(nameof(_equalsComparisons))] #pragma warning disable xUnit1026 - public void Equals_comparison(string name, OrientedBoundingRectangle first, OrientedBoundingRectangle second) + public void Equals_comparison(string name, OrientedRectangle first, OrientedRectangle second) #pragma warning restore xUnit1026 { Assert.True(first == second); @@ -57,10 +57,10 @@ public class OrientedBoundingRectangleTests [Fact] public void Center_point_is_not_translated() { - var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(), Matrix2.Identity); + var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(), Matrix2.Identity); var transform = Matrix2.Identity; - var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + var result = OrientedRectangle.Transform(rectangle, ref transform); Assert.Equal(new Point2(1, 2), result.Center); } @@ -68,10 +68,10 @@ public class OrientedBoundingRectangleTests [Fact] public void Center_point_is_translated() { - var rectangle = new OrientedBoundingRectangle(new Point2(0, 0), new Size2(), new Matrix2()); + var rectangle = new OrientedRectangle(new Point2(0, 0), new Size2(), new Matrix2()); var transform = Matrix2.CreateTranslation(1, 2); - var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + var result = OrientedRectangle.Transform(rectangle, ref transform); Assert.Equal(new Point2(1, 2), result.Center); } @@ -79,10 +79,10 @@ public class OrientedBoundingRectangleTests [Fact] public void Radii_is_not_changed_by_identity_transform() { - var rectangle = new OrientedBoundingRectangle(new Point2(), new Size2(10, 20), new Matrix2()); + var rectangle = new OrientedRectangle(new Point2(), new Size2(10, 20), new Matrix2()); var transform = Matrix2.Identity; - var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + var result = OrientedRectangle.Transform(rectangle, ref transform); Assert.Equal(new Vector2(10, 20), result.Radii); } @@ -90,10 +90,10 @@ public class OrientedBoundingRectangleTests [Fact] public void Radii_is_not_changed_by_translation() { - var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(10, 20), new Matrix2()); + var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(10, 20), new Matrix2()); var transform = Matrix2.CreateTranslation(1, 2); - var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + var result = OrientedRectangle.Transform(rectangle, ref transform); Assert.Equal(new Vector2(10, 20), result.Radii); } @@ -101,10 +101,10 @@ public class OrientedBoundingRectangleTests [Fact] public void Orientation_is_rotated_45_degrees_left() { - var rectangle = new OrientedBoundingRectangle(new Point2(), new Size2(), Matrix2.Identity); + var rectangle = new OrientedRectangle(new Point2(), new Size2(), Matrix2.Identity); var transform = Matrix2.CreateRotationZ(MathHelper.PiOver4); - var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + var result = OrientedRectangle.Transform(rectangle, ref transform); Assert.Equal(new Point2(), result.Center); Assert.Equal(new Vector2(), result.Radii); @@ -114,11 +114,11 @@ public class OrientedBoundingRectangleTests [Fact] public void Orientation_is_rotated_to_45_degrees_from_180() { - var rectangle = new OrientedBoundingRectangle(new Point2(), new Size2(), Matrix2.CreateRotationZ(MathHelper.Pi)); + var rectangle = new OrientedRectangle(new Point2(), new Size2(), Matrix2.CreateRotationZ(MathHelper.Pi)); var transform = Matrix2.CreateRotationZ(-3 * MathHelper.PiOver4); var expectedOrientation = Matrix2.CreateRotationZ(MathHelper.PiOver4); - var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + var result = OrientedRectangle.Transform(rectangle, ref transform); Assert.Equal(new Point2(), result.Center); Assert.Equal(new Vector2(), result.Radii); @@ -133,10 +133,10 @@ public class OrientedBoundingRectangleTests [Fact] public void Points_are_same_as_center() { - var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(), Matrix2.Identity); + var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(), Matrix2.Identity); var transform = Matrix2.Identity; - var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + var result = OrientedRectangle.Transform(rectangle, ref transform); CollectionAssert.Equal( new List @@ -152,10 +152,10 @@ public class OrientedBoundingRectangleTests [Fact] public void Points_are_translated() { - var rectangle = new OrientedBoundingRectangle(new Point2(0, 0), new Size2(2, 4), Matrix2.Identity); + var rectangle = new OrientedRectangle(new Point2(0, 0), new Size2(2, 4), Matrix2.Identity); var transform = Matrix2.CreateTranslation(10, 20); - var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + var result = OrientedRectangle.Transform(rectangle, ref transform); CollectionAssert.Equal( new List @@ -207,13 +207,13 @@ public class OrientedBoundingRectangleTests * : * : */ - var rectangle = new OrientedBoundingRectangle(new Point2(1, 2), new Size2(2, 4), Matrix2.Identity); + var rectangle = new OrientedRectangle(new Point2(1, 2), new Size2(2, 4), Matrix2.Identity); var transform = Matrix2.CreateRotationZ(MathHelper.PiOver2) * Matrix2.CreateTranslation(10, 20); - var result = OrientedBoundingRectangle.Transform(rectangle, ref transform); + var result = OrientedRectangle.Transform(rectangle, ref transform); Assert.Equal(8, result.Center.X, 6); Assert.Equal(21, result.Center.Y, 6); diff --git a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs index f310d54e..dcc6f3af 100644 --- a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs +++ b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs @@ -85,7 +85,7 @@ public class ShapeTests } } - public class OrientedBoundingRectangleTests + public class OrientedRectangleTests { [Fact] public void Axis_aligned_rectangle_intersects_circle() @@ -99,7 +99,7 @@ public class ShapeTests * : * : */ - IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity); + IShapeF rectangle = new OrientedRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity); var circle = new CircleF(Point2.Zero, 1); Assert.True(rectangle.Intersects(circle)); @@ -117,7 +117,7 @@ public class ShapeTests * : * : */ - IShapeF rectangle = new OrientedBoundingRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity); + IShapeF rectangle = new OrientedRectangle(Point2.Zero, new Size2(1, 1), Matrix2.Identity); var circle = new CircleF(new Point2(-2, 1), .99f); Assert.False(rectangle.Intersects(circle)); @@ -135,7 +135,7 @@ public class ShapeTests * * * * :* * */ - IShapeF rectangle = new OrientedBoundingRectangle(new Point2(-1, 1), new Size2(2.8f, 2.8f), Matrix2.CreateRotationZ(MathHelper.PiOver4)); + IShapeF rectangle = new OrientedRectangle(new Point2(-1, 1), new Size2(2.8f, 2.8f), Matrix2.CreateRotationZ(MathHelper.PiOver4)); var circle = new CircleF(new Point2(1, -1), 1.4f); Assert.False(rectangle.Intersects(circle)); @@ -153,7 +153,7 @@ public class ShapeTests * :** * : */ - IShapeF rectangle = new OrientedBoundingRectangle(new Point2(-1, 0), new Size2(1, 1), Matrix2.Identity); + IShapeF rectangle = new OrientedRectangle(new Point2(-1, 0), new Size2(1, 1), Matrix2.Identity); var rect = new RectangleF(new Point2(0.001f, 0), new Size2(2, 2)); Assert.False(rectangle.Intersects(rect)); @@ -171,7 +171,7 @@ public class ShapeTests * :** * : */ - IShapeF rectangle = new OrientedBoundingRectangle(new Point2(-1, 0), new Size2(1, 1), Matrix2.Identity); + IShapeF rectangle = new OrientedRectangle(new Point2(-1, 0), new Size2(1, 1), Matrix2.Identity); var rect = new RectangleF(new Point2(0, 0), new Size2(2, 2)); Assert.True(rectangle.Intersects(rect)); From bf1159d9f3979193be185c16856768678585be97 Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Fri, 8 Mar 2024 18:16:03 +0100 Subject: [PATCH 11/13] Properly intersect circle and oriented rect --- .../CollisionComponent.cs | 101 ++++++++++++------ .../Math/OrientedRectangle.cs | 5 +- src/cs/MonoGame.Extended/Math/ShapeF.cs | 58 +++------- .../Primitives/ShapeTests.cs | 2 +- 4 files changed, 87 insertions(+), 79 deletions(-) diff --git a/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs b/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs index 9677c14f..d9211682 100644 --- a/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs +++ b/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs @@ -175,44 +175,22 @@ namespace MonoGame.Extended.Collisions /// The distance vector from the edge of b to a's Position private static Vector2 CalculatePenetrationVector(IShapeF a, IShapeF b) { - switch (a) - { - case RectangleF rectA when b is RectangleF rectB: - return PenetrationVector(rectA, rectB); - case CircleF circA when b is CircleF circB: - return PenetrationVector(circA, circB); - case CircleF circA when b is RectangleF rectB: - return PenetrationVector(circA, rectB); - case RectangleF rectA when b is CircleF circB: - return PenetrationVector(rectA, circB); - } + return a switch + { + CircleF circleA when b is CircleF circleB => PenetrationVector(circleA, circleB), + CircleF circleA when b is RectangleF rectangleB => PenetrationVector(circleA, rectangleB), + CircleF circleA when b is OrientedRectangle orientedRectangleB => PenetrationVector(circleA, orientedRectangleB), - throw new NotSupportedException("Shapes must be either a CircleF or RectangleF"); - } + RectangleF rectangleA when b is CircleF circleB => PenetrationVector(rectangleA, circleB), + RectangleF rectangleA when b is RectangleF rectangleB => PenetrationVector(rectangleA, rectangleB), + RectangleF rectangleA when b is OrientedRectangle orientedRectangleB => PenetrationVector(rectangleA, orientedRectangleB), - private static Vector2 PenetrationVector(RectangleF rect1, RectangleF rect2) - { - var intersectingRectangle = RectangleF.Intersection(rect1, rect2); - Debug.Assert(!intersectingRectangle.IsEmpty, - "Violation of: !intersect.IsEmpty; Rectangles must intersect to calculate a penetration vector."); + OrientedRectangle orientedRectangleA when b is CircleF circleB => PenetrationVector(orientedRectangleA, circleB), + OrientedRectangle orientedRectangleA when b is RectangleF rectangleB => PenetrationVector(orientedRectangleA, rectangleB), + OrientedRectangle orientedRectangleA when b is OrientedRectangle orientedRectangleB => PenetrationVector(orientedRectangleA, orientedRectangleB), - Vector2 penetration; - if (intersectingRectangle.Width < intersectingRectangle.Height) - { - var d = rect1.Center.X < rect2.Center.X - ? intersectingRectangle.Width - : -intersectingRectangle.Width; - penetration = new Vector2(d, 0); - } - else - { - var d = rect1.Center.Y < rect2.Center.Y - ? intersectingRectangle.Height - : -intersectingRectangle.Height; - penetration = new Vector2(0, d); - } - - return penetration; + _ => throw new ArgumentOutOfRangeException(nameof(a)) + }; } private static Vector2 PenetrationVector(CircleF circ1, CircleF circ2) @@ -287,11 +265,64 @@ namespace MonoGame.Extended.Collisions } } + private static Vector2 PenetrationVector(CircleF circleA, OrientedRectangle orientedRectangleB) + { + return new Vector2(); + throw new NotImplementedException(); + } + private static Vector2 PenetrationVector(RectangleF rect, CircleF circ) { return -PenetrationVector(circ, rect); } + private static Vector2 PenetrationVector(RectangleF rect1, RectangleF rect2) + { + var intersectingRectangle = RectangleF.Intersection(rect1, rect2); + Debug.Assert(!intersectingRectangle.IsEmpty, + "Violation of: !intersect.IsEmpty; Rectangles must intersect to calculate a penetration vector."); + + Vector2 penetration; + if (intersectingRectangle.Width < intersectingRectangle.Height) + { + var d = rect1.Center.X < rect2.Center.X + ? intersectingRectangle.Width + : -intersectingRectangle.Width; + penetration = new Vector2(d, 0); + } + else + { + var d = rect1.Center.Y < rect2.Center.Y + ? intersectingRectangle.Height + : -intersectingRectangle.Height; + penetration = new Vector2(0, d); + } + + return penetration; + } + + private static Vector2 PenetrationVector(RectangleF rectangleA, OrientedRectangle orientedRectangleB) + { + return new Vector2(); + throw new NotImplementedException(); + } + + private static Vector2 PenetrationVector(OrientedRectangle orientedRectangleA, CircleF circleB) + { + return -PenetrationVector(circleB, orientedRectangleA); + } + + private static Vector2 PenetrationVector(OrientedRectangle orientedRectangleA, RectangleF rectangleB) + { + return -PenetrationVector(rectangleB, orientedRectangleA); + } + + private static Vector2 PenetrationVector(OrientedRectangle orientedRectangleA, OrientedRectangle orientedRectangleB) + { + return new Vector2(); + throw new NotImplementedException(); + } + #endregion } } diff --git a/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs b/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs index e1911b62..d4057704 100644 --- a/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs +++ b/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs @@ -173,9 +173,10 @@ namespace MonoGame.Extended public static explicit operator RectangleF(OrientedRectangle orientedRectangle) { - var topLeft = orientedRectangle.Center - orientedRectangle.Radii; + var topLeft = -orientedRectangle.Radii; var rectangle = new RectangleF(topLeft, orientedRectangle.Radii * 2); - return RectangleF.Transform(rectangle, ref orientedRectangle.Orientation); + var orientation = orientedRectangle.Orientation * Matrix2.CreateTranslation(orientedRectangle.Center); + return RectangleF.Transform(rectangle, ref orientation); } /// diff --git a/src/cs/MonoGame.Extended/Math/ShapeF.cs b/src/cs/MonoGame.Extended/Math/ShapeF.cs index 979477f3..274a0a40 100644 --- a/src/cs/MonoGame.Extended/Math/ShapeF.cs +++ b/src/cs/MonoGame.Extended/Math/ShapeF.cs @@ -29,50 +29,27 @@ namespace MonoGame.Extended /// /// Check if two shapes intersect. /// - /// The first shape. - /// The second shape. + /// The first shape. + /// The second shape. /// True if the two shapes intersect. - public static bool Intersects(this IShapeF shapeA, IShapeF shapeB) + public static bool Intersects(this IShapeF a, IShapeF b) { - return shapeA switch + return a switch { - CircleF circleA => IntersectsInternal(circleA, shapeB), - RectangleF rectangleA => IntersectsInternal(rectangleA, shapeB), - OrientedRectangle orientedRectangleA => IntersectsInternal(orientedRectangleA, shapeB), - _ => throw new ArgumentOutOfRangeException(nameof(shapeA)) - }; - } + CircleF circleA when b is CircleF circleB => circleA.Intersects(circleB), + CircleF circleA when b is RectangleF rectangleB => circleA.Intersects(rectangleB), + CircleF circleA when b is OrientedRectangle orientedRectangleB => Intersects(circleA, orientedRectangleB), - private static bool IntersectsInternal(CircleF circle, IShapeF shape) - { - return shape switch - { - CircleF otherCircle => CircleF.Intersects(circle, otherCircle), - RectangleF otherRectangle => Intersects(circle, otherRectangle), - OrientedRectangle otherOrientedRectangle => Intersects(circle, otherOrientedRectangle), - _ => throw new ArgumentOutOfRangeException(nameof(shape)) - }; - } + RectangleF rectangleA when b is CircleF circleB => Intersects(circleB, rectangleA), + RectangleF rectangleA when b is RectangleF rectangleB => Intersects(rectangleA, rectangleB), + RectangleF rectangleA when b is OrientedRectangle orientedRectangleB => Intersects(rectangleA, orientedRectangleB), - private static bool IntersectsInternal(RectangleF rectangle, IShapeF shape) - { - return shape switch - { - CircleF otherCircle => Intersects(otherCircle, rectangle), - RectangleF otherRectangle => RectangleF.Intersects(rectangle, otherRectangle), - OrientedRectangle otherOrientedRectangle => Intersects(rectangle, otherOrientedRectangle), - _ => throw new ArgumentOutOfRangeException(nameof(shape)) - }; - } + OrientedRectangle orientedRectangleA when b is CircleF circleB => Intersects(circleB, orientedRectangleA), + OrientedRectangle orientedRectangleA when b is RectangleF rectangleB => Intersects(rectangleB, orientedRectangleA), + OrientedRectangle orientedRectangleA when b is OrientedRectangle orientedRectangleB + => OrientedRectangle.Intersects(orientedRectangleA, orientedRectangleB), - private static bool IntersectsInternal(OrientedRectangle orientedRectangle, IShapeF shape) - { - return shape switch - { - CircleF circleB => Intersects(circleB, orientedRectangle), - RectangleF rectangleB => Intersects(rectangleB, orientedRectangle), - OrientedRectangle orientedRectangleB => OrientedRectangle.Intersects(orientedRectangle, orientedRectangleB), - _ => throw new ArgumentOutOfRangeException(nameof(shape)) + _ => throw new ArgumentOutOfRangeException(nameof(a)) }; } @@ -96,11 +73,10 @@ namespace MonoGame.Extended /// True if the circle and oriented bounded rectangle intersects, otherwise false. public static bool Intersects(CircleF circle, OrientedRectangle orientedRectangle) { - var rotation = Matrix2.CreateRotationZ(-orientedRectangle.Orientation.Rotation); + var rotation = Matrix2.CreateRotationZ(orientedRectangle.Orientation.Rotation); var circleCenterInRectangleSpace = rotation.Transform(circle.Center - orientedRectangle.Center); var circleInRectangleSpace = new CircleF(circleCenterInRectangleSpace, circle.Radius); - var rectangleInLocalSpace = OrientedRectangle.Transform(orientedRectangle, ref rotation); - var boundingRectangle = new BoundingRectangle(rectangleInLocalSpace.Center, rectangleInLocalSpace.Radii); + var boundingRectangle = new BoundingRectangle(new Point2(), orientedRectangle.Radii); return circleInRectangleSpace.Intersects(boundingRectangle); } diff --git a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs index dcc6f3af..37b2fc4c 100644 --- a/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs +++ b/src/cs/Tests/MonoGame.Extended.Tests/Primitives/ShapeTests.cs @@ -135,7 +135,7 @@ public class ShapeTests * * * * :* * */ - IShapeF rectangle = new OrientedRectangle(new Point2(-1, 1), new Size2(2.8f, 2.8f), Matrix2.CreateRotationZ(MathHelper.PiOver4)); + IShapeF rectangle = new OrientedRectangle(new Point2(-1, 1), new Size2(1.42f, 1.42f), Matrix2.CreateRotationZ(-MathHelper.PiOver4)); var circle = new CircleF(new Point2(1, -1), 1.4f); Assert.False(rectangle.Intersects(circle)); From 377df892471fd282843b5521b4f8e095f7e7bfb2 Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Sun, 10 Mar 2024 21:23:21 +0100 Subject: [PATCH 12/13] Calc coll vector for circle and oriented rect --- .../CollisionComponent.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs b/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs index d9211682..56206d13 100644 --- a/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs +++ b/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs @@ -267,8 +267,16 @@ namespace MonoGame.Extended.Collisions private static Vector2 PenetrationVector(CircleF circleA, OrientedRectangle orientedRectangleB) { - return new Vector2(); - throw new NotImplementedException(); + var rotation = Matrix2.CreateRotationZ(orientedRectangleB.Orientation.Rotation); + var circleCenterInRectangleSpace = rotation.Transform(circleA.Center - orientedRectangleB.Center); + var circleInRectangleSpace = new CircleF(circleCenterInRectangleSpace, circleA.Radius); + var boundingRectangle = new BoundingRectangle(new Point2(), orientedRectangleB.Radii); + + var penetrationVector = PenetrationVector(circleInRectangleSpace, boundingRectangle); + var inverseRotation = Matrix2.CreateRotationZ(-orientedRectangleB.Orientation.Rotation); + var transformedPenetration = inverseRotation.Transform(penetrationVector); + + return transformedPenetration; } private static Vector2 PenetrationVector(RectangleF rect, CircleF circ) From a342fedfd7ea3a1b7b8c0ad9a3d75a3c01e7b74e Mon Sep 17 00:00:00 2001 From: Andreas Torebring Date: Fri, 15 Mar 2024 21:27:14 +0100 Subject: [PATCH 13/13] Calc coll vector for oriented rects --- .../CollisionComponent.cs | 7 +- .../Math/OrientedRectangle.cs | 124 ++++++++++++------ src/cs/MonoGame.Extended/Math/ShapeF.cs | 9 +- 3 files changed, 89 insertions(+), 51 deletions(-) diff --git a/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs b/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs index 56206d13..4193d674 100644 --- a/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs +++ b/src/cs/MonoGame.Extended.Collisions/CollisionComponent.cs @@ -311,8 +311,7 @@ namespace MonoGame.Extended.Collisions private static Vector2 PenetrationVector(RectangleF rectangleA, OrientedRectangle orientedRectangleB) { - return new Vector2(); - throw new NotImplementedException(); + return PenetrationVector((OrientedRectangle)rectangleA, orientedRectangleB); } private static Vector2 PenetrationVector(OrientedRectangle orientedRectangleA, CircleF circleB) @@ -327,8 +326,8 @@ namespace MonoGame.Extended.Collisions private static Vector2 PenetrationVector(OrientedRectangle orientedRectangleA, OrientedRectangle orientedRectangleB) { - return new Vector2(); - throw new NotImplementedException(); + return OrientedRectangle.Intersects(orientedRectangleA, orientedRectangleB) + .MinimumTranslationVector; } #endregion diff --git a/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs b/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs index d4057704..1439e9f7 100644 --- a/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs +++ b/src/cs/MonoGame.Extended/Math/OrientedRectangle.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using Microsoft.Xna.Framework; +using SharpDX.Direct3D; namespace MonoGame.Extended { @@ -180,71 +181,108 @@ namespace MonoGame.Extended } /// - /// See https://www.flipcode.com/archives/2D_OBB_Intersection.shtml + /// See: + /// https://www.flipcode.com/archives/2D_OBB_Intersection.shtml + /// https://dyn4j.org/2010/01/sat /// /// /// /// /// - public static bool Intersects(OrientedRectangle rectangle, OrientedRectangle other) + public static (bool Intersects, Vector2 MinimumTranslationVector) Intersects( + OrientedRectangle rectangle, OrientedRectangle other) { var corners = rectangle.Points; var otherCorners = other.Points; - return IntersectsOneWay(corners, otherCorners) && IntersectsOneWay(otherCorners, corners); - bool IntersectsOneWay(IReadOnlyList source, IReadOnlyList target) + var allAxis = new[] { - var axis = new[] - { - source[1] - source[0], - source[3] - source[0] - }; - var origin = new float[2]; + corners[1] - corners[0], + corners[3] - corners[0], + otherCorners[1] - otherCorners[0], + otherCorners[3] - otherCorners[0], + }; + var normalizedAxis = new[] + { + allAxis[0], + allAxis[1], + allAxis[2], + allAxis[3] + }; + var overlap = 0f; + var minimumTranslationVector = Vector2.Zero; - // Make the length of each axis 1/edge length so we know any - // dot product must be less than 1 to fall within the edge. - for (var a = 0; a < 2; a++) + // Make the length of each axis 1/edge length, so we know any + // dot product must be less than 1 to fall within the edge. + for (var a = 0; a < normalizedAxis.Length; a++) + { + normalizedAxis[a] /= normalizedAxis[a].LengthSquared(); + } + + for (var a = 0; a < normalizedAxis.Length; a++) + { + var axisProjectedOnto = normalizedAxis[a]; + var originalAxis = allAxis[a]; + + var p1 = Project(corners, axisProjectedOnto); + var p2 = Project(otherCorners, axisProjectedOnto); + + if (!IsOverlapping(p1, p2)) { - axis[a] /= axis[a].LengthSquared(); - origin[a] = source[0].Dot(axis[a]); + // There was no intersection along this dimension; + // the boxes cannot possibly overlap. + return (false, Vector2.Zero); } - for (var a = 0; a < 2; a++) { - - var t = target[0].Dot(axis[a]); - - // Find the extent of box 2 on axis a - var tMin = t; - var tMax = t; - - for (var c = 1; c < 4; c++) + var o = GetOverlap(p1, p2); + if (o < overlap || overlap == 0f) + { + overlap = o; + minimumTranslationVector = originalAxis * overlap; + if (p1.Min > p2.Min) { - t = target[c].Dot(axis[a]); - - if (t < tMin) - { - tMin = t; - } - else if (t > tMax) - { - tMax = t; - } + minimumTranslationVector = -minimumTranslationVector; } + } + } - // We have to subtract off the origin + // There was no dimension along which there is no intersection. + // Therefore, the boxes overlap. + return (true, minimumTranslationVector); - // See if [tMin, tMax] intersects [0, 1] - if (tMin > 1 + origin[a] || tMax < origin[a]) + (float Min, float Max) Project(IReadOnlyList vertices, Vector2 axis) + { + var t = vertices[0].Dot(axis); + + // Find the extent of box 2 on axis a + var min = t; + var max = t; + + for (var c = 1; c < 4; c++) + { + t = vertices[c].Dot(axis); + + if (t < min) { - // There was no intersection along this dimension; - // the boxes cannot possibly overlap. - return false; + min = t; + } + else if (t > max) + { + max = t; } } - // There was no dimension along which there is no intersection. - // Therefore the boxes overlap. - return true; + return (min, max); + } + + bool IsOverlapping((float Min, float Max) p1, (float Min, float Max) p2) + { + return p1.Min <= p2.Max && p1.Max >= p2.Min; + } + + float GetOverlap((float Min, float Max) p1, (float Min, float Max) p2) + { + return Math.Min(p1.Max, p2.Max) - Math.Max(p1.Min, p2.Min); } } diff --git a/src/cs/MonoGame.Extended/Math/ShapeF.cs b/src/cs/MonoGame.Extended/Math/ShapeF.cs index 274a0a40..4ffe452a 100644 --- a/src/cs/MonoGame.Extended/Math/ShapeF.cs +++ b/src/cs/MonoGame.Extended/Math/ShapeF.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.Xna.Framework; namespace MonoGame.Extended { @@ -42,12 +43,12 @@ namespace MonoGame.Extended RectangleF rectangleA when b is CircleF circleB => Intersects(circleB, rectangleA), RectangleF rectangleA when b is RectangleF rectangleB => Intersects(rectangleA, rectangleB), - RectangleF rectangleA when b is OrientedRectangle orientedRectangleB => Intersects(rectangleA, orientedRectangleB), + RectangleF rectangleA when b is OrientedRectangle orientedRectangleB => Intersects(rectangleA, orientedRectangleB).Intersects, OrientedRectangle orientedRectangleA when b is CircleF circleB => Intersects(circleB, orientedRectangleA), - OrientedRectangle orientedRectangleA when b is RectangleF rectangleB => Intersects(rectangleB, orientedRectangleA), + OrientedRectangle orientedRectangleA when b is RectangleF rectangleB => Intersects(rectangleB, orientedRectangleA).Intersects, OrientedRectangle orientedRectangleA when b is OrientedRectangle orientedRectangleB - => OrientedRectangle.Intersects(orientedRectangleA, orientedRectangleB), + => OrientedRectangle.Intersects(orientedRectangleA, orientedRectangleB).Intersects, _ => throw new ArgumentOutOfRangeException(nameof(a)) }; @@ -86,7 +87,7 @@ namespace MonoGame.Extended /// /// /// True if objects are intersecting, otherwise false. - public static bool Intersects(RectangleF rectangleF, OrientedRectangle orientedRectangle) + public static (bool Intersects, Vector2 MinimumTranslationVector) Intersects(RectangleF rectangleF, OrientedRectangle orientedRectangle) { return OrientedRectangle.Intersects(orientedRectangle, (OrientedRectangle)rectangleF); }