diff --git a/src/GameUtils/Types/Geometry/AABB.cs b/src/GameUtils/Types/Geometry/AABB.cs index 77e555b..93615f3 100644 --- a/src/GameUtils/Types/Geometry/AABB.cs +++ b/src/GameUtils/Types/Geometry/AABB.cs @@ -148,17 +148,43 @@ public bool Intersects(Line line, out Vector2[] intersectionPoints) #pragma warning disable S3267 // LINQ would reintroduce allocations on a hot collision path public bool Intersects(Polygon2D polygon) { + // 1. Fast bounds check: compute polygon bounds on the fly + if (polygon.Vertices.Length > 0) + { + var pMinX = polygon.Vertices[0].X; + var pMaxX = polygon.Vertices[0].X; + var pMinY = polygon.Vertices[0].Y; + var pMaxY = polygon.Vertices[0].Y; + + for (int i = 1; i < polygon.Vertices.Length; i++) + { + var v = polygon.Vertices[i]; + if (v.X < pMinX) pMinX = v.X; + if (v.X > pMaxX) pMaxX = v.X; + if (v.Y < pMinY) pMinY = v.Y; + if (v.Y > pMaxY) pMaxY = v.Y; + } + + if (pMinX > Max.X || pMaxX < Min.X || pMinY > Max.Y || pMaxY < Min.Y) + { + return false; + } + } + + // 2. Check if any polygon vertex is inside the AABB foreach (var v in polygon.Vertices) { if (Contains(v)) return true; } + // 3. Check if any polygon edge intersects the AABB foreach (var e in polygon.Edges) { if (Intersects(e)) return true; } - return false; + // 4. Check if the AABB is completely inside the polygon + return polygon.Contains(Center); } #pragma warning restore S3267 diff --git a/tests/GameUtils.Benchmarks/GameUtils.Benchmarks.csproj b/tests/GameUtils.Benchmarks/GameUtils.Benchmarks.csproj new file mode 100644 index 0000000..1a2a09d --- /dev/null +++ b/tests/GameUtils.Benchmarks/GameUtils.Benchmarks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + diff --git a/tests/GameUtils.Benchmarks/Program.cs b/tests/GameUtils.Benchmarks/Program.cs new file mode 100644 index 0000000..bbf6ad7 --- /dev/null +++ b/tests/GameUtils.Benchmarks/Program.cs @@ -0,0 +1,64 @@ +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Attributes; +using GameUtils.Types.Geometry; +using System.Numerics; +using System.Linq; + +namespace GameUtils.Benchmarks +{ + [MemoryDiagnoser] + public class AABBIntersectsBenchmark + { + private AABB _aabb; + private Polygon2D _polyInside; + private Polygon2D _polyIntersecting; + private Polygon2D _polyOutside; + private Polygon2D _polyContainsAABB; + + [GlobalSetup] + public void Setup() + { + _aabb = new AABB(new Vector2(10, 10), new Vector2(20, 20)); + + // Polygon completely inside AABB + _polyInside = new Polygon2D(new[] { + new Vector2(12, 12), new Vector2(18, 12), new Vector2(15, 18) + }); + + // Polygon intersecting AABB + _polyIntersecting = new Polygon2D(new[] { + new Vector2(5, 15), new Vector2(15, 15), new Vector2(10, 25) + }); + + // Polygon completely outside AABB + _polyOutside = new Polygon2D(new[] { + new Vector2(30, 30), new Vector2(40, 30), new Vector2(35, 40) + }); + + // Polygon containing the entire AABB + _polyContainsAABB = new Polygon2D(new[] { + new Vector2(0, 0), new Vector2(30, 0), new Vector2(30, 30), new Vector2(0, 30) + }); + } + + [Benchmark] + public bool IntersectsInside() => _aabb.Intersects(_polyInside); + + [Benchmark] + public bool IntersectsIntersecting() => _aabb.Intersects(_polyIntersecting); + + [Benchmark] + public bool IntersectsOutside() => _aabb.Intersects(_polyOutside); + + [Benchmark] + public bool IntersectsContainsAABB() => _aabb.Intersects(_polyContainsAABB); + } + + public class Program + { + public static void Main(string[] args) + { + var summary = BenchmarkRunner.Run(); + } + } +} diff --git a/tests/GameUtils.Tests/Geometry/AABBTests.cs b/tests/GameUtils.Tests/Geometry/AABBTests.cs new file mode 100644 index 0000000..039164b --- /dev/null +++ b/tests/GameUtils.Tests/Geometry/AABBTests.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using GameUtils.Types.Geometry; +using System.Numerics; + +namespace GameUtils.Tests.Geometry; + +[TestClass] +public class AABBTests +{ + [TestMethod] + public void Intersects_PolygonContainsAABB_ReturnsTrue() + { + var aabb = new AABB(new Vector2(10, 10), new Vector2(20, 20)); + var polyContainsAABB = new Polygon2D(new[] { + new Vector2(0, 0), new Vector2(30, 0), new Vector2(30, 30), new Vector2(0, 30) + }); + + Assert.IsTrue(aabb.Intersects(polyContainsAABB)); + } + + [TestMethod] + public void Intersects_PolygonInsideAABB_ReturnsTrue() + { + var aabb = new AABB(new Vector2(10, 10), new Vector2(20, 20)); + var polyInsideAABB = new Polygon2D(new[] { + new Vector2(12, 12), new Vector2(18, 12), new Vector2(15, 18) + }); + + Assert.IsTrue(aabb.Intersects(polyInsideAABB)); + } + + [TestMethod] + public void Intersects_PolygonIntersectingAABB_ReturnsTrue() + { + var aabb = new AABB(new Vector2(10, 10), new Vector2(20, 20)); + var polyIntersectingAABB = new Polygon2D(new[] { + new Vector2(5, 15), new Vector2(15, 15), new Vector2(10, 25) + }); + + Assert.IsTrue(aabb.Intersects(polyIntersectingAABB)); + } + + [TestMethod] + public void Intersects_PolygonOutsideAABB_ReturnsFalse() + { + var aabb = new AABB(new Vector2(10, 10), new Vector2(20, 20)); + var polyOutsideAABB = new Polygon2D(new[] { + new Vector2(30, 30), new Vector2(40, 30), new Vector2(35, 40) + }); + + Assert.IsFalse(aabb.Intersects(polyOutsideAABB)); + } +}