Browse Source

Add unit tests for primitives and bounding volumes

Christopher Whitley 1 month ago
parent
commit
d035ad2bb8

+ 351 - 0
tests/MonoGame.Extended.Tests/BoundingBox2DTest.cs

@@ -0,0 +1,351 @@
+// MonoGame - Copyright (C) MonoGame Foundation, Inc
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+    public sealed class BoundingBox2DTest
+    {
+        #region Constructor Tests
+
+        [Fact]
+        public void Constructor()
+        {
+            var min = new Vector2(1, 2);
+            var max = new Vector2(10, 20);
+
+            var box = new BoundingBox2D(min, max);
+
+            Assert.Equal(min, box.Min);
+            Assert.Equal(max, box.Max);
+        }
+
+        #endregion
+
+        #region Computed Property Tests
+
+        [Fact]
+        public void Center_ReturnsMiddlePoint()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+
+            var center = box.Center;
+
+            Assert.Equal(new Vector2(5, 10), center);
+        }
+
+        [Fact]
+        public void Size_ReturnsWidthAndHeight()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+
+            var size = box.Size;
+
+            Assert.Equal(new Vector2(10, 20), size);
+        }
+
+        [Fact]
+        public void HalfExtents_ReturnsHalfSize()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+
+            var halfExtents = box.HalfExtents;
+
+            Assert.Equal(new Vector2(5, 10), halfExtents);
+        }
+
+        [Fact]
+        public void Width_ReturnsHorizontalExtent()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+
+            float width = box.Width;
+
+            Assert.Equal(10.0f, width, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Height_ReturnsVerticalExtent()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+
+            float height = box.Height;
+
+            Assert.Equal(20.0f, height, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Area_ReturnsWidthTimesHeight()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+
+            float area = box.Area;
+
+            Assert.Equal(200.0f, area, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region Factory Method Tests
+
+        [Fact]
+        public void CreateFromMinMax()
+        {
+            var min = new Vector2(1, 2);
+            var max = new Vector2(10, 20);
+
+            var box = BoundingBox2D.CreateFromMinMax(min, max);
+
+            Assert.Equal(min, box.Min);
+            Assert.Equal(max, box.Max);
+        }
+
+        [Fact]
+        public void CreateFromCenterAndExtents()
+        {
+            var center = new Vector2(5, 10);
+            var halfExtents = new Vector2(5, 10);
+
+            var box = BoundingBox2D.CreateFromCenterAndExtents(center, halfExtents);
+
+            Assert.Equal(new Vector2(0, 0), box.Min);
+            Assert.Equal(new Vector2(10, 20), box.Max);
+        }
+
+        [Fact]
+        public void CreateFromPositionAndSize()
+        {
+            var position = new Vector2(1, 2);
+            var size = new Vector2(9, 18);
+
+            var box = BoundingBox2D.CreateFromPositionAndSize(position, size);
+
+            Assert.Equal(new Vector2(1, 2), box.Min);
+            Assert.Equal(new Vector2(10, 20), box.Max);
+        }
+
+        [Fact]
+        public void CreateFromPoints_SinglePoint()
+        {
+            var points = new[] { new Vector2(5, 5) };
+
+            var box = BoundingBox2D.CreateFromPoints(points);
+
+            Assert.Equal(new Vector2(5, 5), box.Min);
+            Assert.Equal(new Vector2(5, 5), box.Max);
+        }
+
+        [Fact]
+        public void CreateFromPoints_MultiplePoints()
+        {
+            var points = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 5),
+                new Vector2(5, 15),
+                new Vector2(-5, 3)
+            };
+
+            var box = BoundingBox2D.CreateFromPoints(points);
+
+            Assert.Equal(new Vector2(-5, 0), box.Min);
+            Assert.Equal(new Vector2(10, 15), box.Max);
+        }
+
+        [Fact]
+        public void CreateFromPoints_ThrowsWhenNull()
+        {
+            Assert.Throws<ArgumentNullException>(() => BoundingBox2D.CreateFromPoints(null));
+        }
+
+        [Fact]
+        public void CreateFromPoints_ThrowsWhenEmpty()
+        {
+            Assert.Throws<ArgumentException>(() => BoundingBox2D.CreateFromPoints(new Vector2[0]));
+        }
+
+        [Fact]
+        public void CreateMerged_EnclosesBoth()
+        {
+            var box1 = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 10));
+            var box2 = new BoundingBox2D(new Vector2(5, 5), new Vector2(15, 15));
+
+            var merged = BoundingBox2D.CreateMerged(box1, box2);
+
+            Assert.Equal(new Vector2(0, 0), merged.Min);
+            Assert.Equal(new Vector2(15, 15), merged.Max);
+        }
+
+        [Fact]
+        public void CreateMerged_OneBoxContainsOther()
+        {
+            var box1 = new BoundingBox2D(new Vector2(0, 0), new Vector2(20, 20));
+            var box2 = new BoundingBox2D(new Vector2(5, 5), new Vector2(15, 15));
+
+            var merged = BoundingBox2D.CreateMerged(box1, box2);
+
+            Assert.Equal(box1.Min, merged.Min);
+            Assert.Equal(box1.Max, merged.Max);
+        }
+
+        #endregion
+
+        #region GetCorners Tests (Type-Specific Method)
+
+        [Fact]
+        public void GetCorners_ReturnsArray()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+
+            var corners = box.GetCorners();
+
+            Assert.Equal(4, corners.Length);
+            Assert.Contains(new Vector2(0, 0), corners);
+            Assert.Contains(new Vector2(10, 0), corners);
+            Assert.Contains(new Vector2(10, 20), corners);
+            Assert.Contains(new Vector2(0, 20), corners);
+        }
+
+        [Fact]
+        public void GetCorners_FillsExistingArray()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+            var corners = new Vector2[4];
+
+            box.GetCorners(corners);
+
+            Assert.Contains(new Vector2(0, 0), corners);
+            Assert.Contains(new Vector2(10, 0), corners);
+            Assert.Contains(new Vector2(10, 20), corners);
+            Assert.Contains(new Vector2(0, 20), corners);
+        }
+
+        [Fact]
+        public void GetCorners_ThrowsWhenArrayNull()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+
+            Assert.Throws<ArgumentNullException>(() => box.GetCorners(null));
+        }
+
+        [Fact]
+        public void GetCorners_ThrowsWhenArrayTooSmall()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+            var corners = new Vector2[3];
+
+            Assert.Throws<ArgumentException>(() => box.GetCorners(corners));
+        }
+
+        #endregion
+
+        #region Transform Tests
+
+        [Fact]
+        public void Transform_Translation()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 10));
+            var matrix = Matrix.CreateTranslation(5, 10, 0);
+
+            var transformed = box.Transform(matrix);
+
+            Assert.Equal(new Vector2(5, 10), transformed.Min);
+            Assert.Equal(new Vector2(15, 20), transformed.Max);
+        }
+
+        [Fact]
+        public void Transform_Scale()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 10));
+            var matrix = Matrix.CreateScale(2.0f);
+
+            var transformed = box.Transform(matrix);
+
+            Assert.Equal(new Vector2(0, 0), transformed.Min);
+            Assert.Equal(new Vector2(20, 20), transformed.Max);
+        }
+
+        [Fact]
+        public void Transform_Rotation()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 0));
+            var matrix = Matrix.CreateRotationZ(MathHelper.PiOver2);
+
+            var transformed = box.Transform(matrix);
+
+            Assert.Equal(0, transformed.Min.X, Collision2D.Epsilon);
+            Assert.Equal(0, transformed.Min.Y, Collision2D.Epsilon);
+            Assert.Equal(0, transformed.Max.X, Collision2D.Epsilon);
+            Assert.Equal(10, transformed.Max.Y, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Translate_OffsetsPosition()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 10));
+            var offset = new Vector2(5, 10);
+
+            var translated = box.Translate(offset);
+
+            Assert.Equal(new Vector2(5, 10), translated.Min);
+            Assert.Equal(new Vector2(15, 20), translated.Max);
+        }
+
+        #endregion
+
+        #region ContainsPoint Tests (Delegation Spot Check)
+
+        [Fact]
+        public void ContainsPoint_Inside()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 10));
+            var point = new Vector2(5, 5);
+
+            var result = box.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_OnBoundary()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 10));
+            var point = new Vector2(10, 5);
+
+            var result = box.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_Outside()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 10));
+            var point = new Vector2(15, 5);
+
+            var result = box.Contains(point);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        #endregion
+
+        #region Deconstruct Test
+
+        [Fact]
+        public void Deconstruct()
+        {
+            var box = new BoundingBox2D(new Vector2(1, 2), new Vector2(10, 20));
+
+            var (min, max) = box;
+
+            Assert.Equal(new Vector2(1, 2), min);
+            Assert.Equal(new Vector2(10, 20), max);
+        }
+
+        #endregion
+    }
+}

+ 316 - 0
tests/MonoGame.Extended.Tests/BoundingCapsule2DTest.cs

@@ -0,0 +1,316 @@
+// MonoGame - Copyright (C) MonoGame Foundation, Inc
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+    public sealed class BoundingCapsule2DTest
+    {
+        #region Constructor Tests
+
+        [Fact]
+        public void Constructor()
+        {
+            var pointA = new Vector2(0, 0);
+            var pointB = new Vector2(10, 0);
+            var radius = 5.0f;
+
+            var capsule = new BoundingCapsule2D(pointA, pointB, radius);
+
+            Assert.Equal(pointA, capsule.PointA);
+            Assert.Equal(pointB, capsule.PointB);
+            Assert.Equal(radius, capsule.Radius);
+        }
+
+        #endregion
+
+        #region Computed Property Tests
+
+        [Fact]
+        public void Center_ReturnsMidpoint()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 5.0f);
+
+            var center = capsule.Center;
+
+            Assert.Equal(new Vector2(5, 0), center);
+        }
+
+        [Fact]
+        public void Length_ReturnsDistance()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(3, 4), 5.0f);
+
+            float length = capsule.Length;
+
+            Assert.Equal(5.0f, length, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void LengthSquared_ReturnsSquaredDistance()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(3, 4), 5.0f);
+
+            float lengthSquared = capsule.LengthSquared;
+
+            Assert.Equal(25.0f, lengthSquared, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Direction_ReturnsUnitVector()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(3, 4), 5.0f);
+
+            var direction = capsule.Direction;
+
+            Assert.Equal(1.0f, direction.Length(), Collision2D.Epsilon);
+            Assert.Equal(0.6f, direction.X, Collision2D.Epsilon);
+            Assert.Equal(0.8f, direction.Y, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Direction_DegenerateCapsule()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(5, 5), new Vector2(5, 5), 3.0f);
+
+            var direction = capsule.Direction;
+
+            Assert.Equal(Vector2.Zero, direction);
+        }
+
+        [Fact]
+        public void Area_CalculatesCorrectly()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 5.0f);
+
+            float area = capsule.Area;
+
+            float expected = 100.0f + MathF.PI * 25.0f;
+            Assert.Equal(expected, area, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region Factory Method Tests
+
+        [Fact]
+        public void CreateFromCenterAndDirection()
+        {
+            var center = new Vector2(5, 5);
+            var direction = new Vector2(1, 0);
+            var length = 10.0f;
+            var radius = 3.0f;
+
+            var capsule = BoundingCapsule2D.CreateFromCenterAndDirection(center, direction, length, radius);
+
+            Assert.Equal(new Vector2(0, 5), capsule.PointA);
+            Assert.Equal(new Vector2(10, 5), capsule.PointB);
+            Assert.Equal(radius, capsule.Radius);
+        }
+
+        [Fact]
+        public void CreateFromCenterAndDirection_UnnormalizedDirection()
+        {
+            var center = new Vector2(5, 5);
+            var direction = new Vector2(3, 4);
+            var length = 10.0f;
+            var radius = 3.0f;
+
+            var capsule = BoundingCapsule2D.CreateFromCenterAndDirection(center, direction, length, radius);
+
+            float distance = Vector2.Distance(capsule.PointA, capsule.PointB);
+            Assert.Equal(10.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void CreateFromSegment()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+            var radius = 3.0f;
+
+            var capsule = BoundingCapsule2D.CreateFromSegment(segment, radius);
+
+            Assert.Equal(segment.Start, capsule.PointA);
+            Assert.Equal(segment.End, capsule.PointB);
+            Assert.Equal(radius, capsule.Radius);
+        }
+
+        [Fact]
+        public void CreateMerged_NonOverlapping()
+        {
+            var capsule1 = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(5, 0), 2.0f);
+            var capsule2 = new BoundingCapsule2D(new Vector2(20, 0), new Vector2(25, 0), 2.0f);
+
+            var merged = BoundingCapsule2D.CreateMerged(capsule1, capsule2);
+
+            Assert.Equal(ContainmentType.Contains, merged.Contains(capsule1));
+            Assert.Equal(ContainmentType.Contains, merged.Contains(capsule2));
+        }
+
+        [Fact]
+        public void CreateMerged_OneContainsOther()
+        {
+            var capsule1 = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(20, 0), 5.0f);
+            var capsule2 = new BoundingCapsule2D(new Vector2(8, 0), new Vector2(12, 0), 2.0f);
+
+            var merged = BoundingCapsule2D.CreateMerged(capsule1, capsule2);
+
+            Assert.Equal(capsule1.PointA.X, merged.PointA.X, Collision2D.Epsilon);
+            Assert.Equal(capsule1.PointA.Y, merged.PointA.Y, Collision2D.Epsilon);
+            Assert.Equal(capsule1.PointB.X, merged.PointB.X, Collision2D.Epsilon);
+            Assert.Equal(capsule1.PointB.Y, merged.PointB.Y, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void CreateMerged_PartiallyOverlapping()
+        {
+            var capsule1 = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 3.0f);
+            var capsule2 = new BoundingCapsule2D(new Vector2(8, 0), new Vector2(18, 0), 3.0f);
+
+            var merged = BoundingCapsule2D.CreateMerged(capsule1, capsule2);
+
+            Assert.Equal(ContainmentType.Contains, merged.Contains(capsule1));
+            Assert.Equal(ContainmentType.Contains, merged.Contains(capsule2));
+        }
+
+        #endregion
+
+        #region Transform Tests
+
+        [Fact]
+        public void Transform_Translation()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 3.0f);
+            var matrix = Matrix.CreateTranslation(5, 10, 0);
+
+            var transformed = capsule.Transform(matrix);
+
+            Assert.Equal(new Vector2(5, 10), transformed.PointA);
+            Assert.Equal(new Vector2(15, 10), transformed.PointB);
+            Assert.Equal(3.0f, transformed.Radius, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Transform_UniformScale()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 3.0f);
+            var matrix = Matrix.CreateScale(2.0f);
+
+            var transformed = capsule.Transform(matrix);
+
+            Assert.Equal(new Vector2(0, 0), transformed.PointA);
+            Assert.Equal(new Vector2(20, 0), transformed.PointB);
+            Assert.Equal(6.0f, transformed.Radius, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Transform_NonUniformScale()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 3.0f);
+            var matrix = Matrix.CreateScale(2.0f, 3.0f, 1.0f);
+
+            var transformed = capsule.Transform(matrix);
+
+            Assert.Equal(new Vector2(0, 0), transformed.PointA);
+            Assert.Equal(new Vector2(20, 0), transformed.PointB);
+            Assert.True(transformed.Radius >= 6.0f);
+        }
+
+        [Fact]
+        public void Transform_Rotation()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 3.0f);
+            var matrix = Matrix.CreateRotationZ(MathHelper.PiOver2);
+
+            var transformed = capsule.Transform(matrix);
+
+            Assert.Equal(0, transformed.PointA.X, Collision2D.Epsilon);
+            Assert.Equal(0, transformed.PointA.Y, Collision2D.Epsilon);
+            Assert.Equal(0, transformed.PointB.X, Collision2D.Epsilon);
+            Assert.Equal(10, transformed.PointB.Y, Collision2D.Epsilon);
+            Assert.Equal(3.0f, transformed.Radius, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Translate_OffsetsPosition()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 3.0f);
+            var offset = new Vector2(5, 10);
+
+            var translated = capsule.Translate(offset);
+
+            Assert.Equal(new Vector2(5, 10), translated.PointA);
+            Assert.Equal(new Vector2(15, 10), translated.PointB);
+            Assert.Equal(3.0f, translated.Radius, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region ContainsPoint Tests (Delegation Spot Check)
+
+        [Fact]
+        public void ContainsPoint_Inside()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 5.0f);
+            var point = new Vector2(5, 2);
+
+            var result = capsule.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_OnBoundary()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 5.0f);
+            var point = new Vector2(5, 5);
+
+            var result = capsule.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_Outside()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 5.0f);
+            var point = new Vector2(5, 10);
+
+            var result = capsule.Contains(point);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_AtEndCap()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 5.0f);
+            var point = new Vector2(-3, 4);
+
+            var result = capsule.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        #endregion
+
+        #region Deconstruct Test
+
+        [Fact]
+        public void Deconstruct()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 0), 5.0f);
+
+            var (pointA, pointB, radius) = capsule;
+
+            Assert.Equal(new Vector2(0, 0), pointA);
+            Assert.Equal(new Vector2(10, 0), pointB);
+            Assert.Equal(5.0f, radius);
+        }
+
+        #endregion
+    }
+}

+ 326 - 0
tests/MonoGame.Extended.Tests/BoundingCircleTest.cs

@@ -0,0 +1,326 @@
+// MonoGame - Copyright (C) MonoGame Foundation, Inc
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+    public sealed class BoundingCircleTest
+    {
+        #region Constructor Tests
+
+        [Fact]
+        public void Constructor()
+        {
+            var center = new Vector2(5, 10);
+            var radius = 15.0f;
+
+            var circle = new BoundingCircle2D(center, radius);
+
+            Assert.Equal(center, circle.Center);
+            Assert.Equal(radius, circle.Radius);
+        }
+
+        #endregion
+
+        #region Computed Property Tests
+
+        [Fact]
+        public void RadiusSquared_ReturnsSquaredValue()
+        {
+            var circle = new BoundingCircle2D(new Vector2(0, 0), 5.0f);
+
+            float radiusSquared = circle.RadiusSquared;
+
+            Assert.Equal(25.0f, radiusSquared, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Diameter_ReturnsTwiceRadius()
+        {
+            var circle = new BoundingCircle2D(new Vector2(0, 0), 5.0f);
+
+            float diameter = circle.Diameter;
+
+            Assert.Equal(10.0f, diameter, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Area_ReturnsPiTimesRadiusSquared()
+        {
+            var circle = new BoundingCircle2D(new Vector2(0, 0), 5.0f);
+
+            float area = circle.Area;
+
+            Assert.Equal(MathF.PI * 25.0f, area, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region Factory Method Tests
+
+        [Fact]
+        public void CreateFromPoints_SinglePoint()
+        {
+            var points = new[] { new Vector2(5, 5) };
+
+            var circle = BoundingCircle2D.CreateFromPoints(points);
+
+            Assert.Equal(new Vector2(5, 5), circle.Center);
+            Assert.Equal(0.0f, circle.Radius, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void CreateFromPoints_TwoPoints()
+        {
+            var points = new[] { new Vector2(0, 0), new Vector2(10, 0) };
+
+            var circle = BoundingCircle2D.CreateFromPoints(points);
+
+            Assert.Equal(new Vector2(5, 0), circle.Center);
+            Assert.Equal(5.0f, circle.Radius, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void CreateFromPoints_MultiplePoints()
+        {
+            var points = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(0, 10),
+                new Vector2(10, 10)
+            };
+
+            var circle = BoundingCircle2D.CreateFromPoints(points);
+
+            foreach (var point in points)
+            {
+                float distance = Vector2.Distance(circle.Center, point);
+                Assert.True(distance <= circle.Radius + Collision2D.Epsilon);
+            }
+        }
+
+        [Fact]
+        public void CreateFromPoints_ThrowsWhenNull()
+        {
+            Assert.Throws<ArgumentNullException>(() => BoundingCircle2D.CreateFromPoints(null));
+        }
+
+        [Fact]
+        public void CreateFromPoints_ThrowsWhenEmpty()
+        {
+            Assert.Throws<ArgumentException>(() => BoundingCircle2D.CreateFromPoints(new Vector2[0]));
+        }
+
+        [Fact]
+        public void CreateFromBoundingBox2D()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 10));
+
+            var circle = BoundingCircle2D.CreateFromBoundingBox2D(box);
+
+            Assert.True(circle.Contains(new Vector2(0, 0)) != ContainmentType.Disjoint);
+            Assert.True(circle.Contains(new Vector2(10, 10)) != ContainmentType.Disjoint);
+        }
+
+        [Fact]
+        public void CreateFromBoundingCapsule2D_HorizontalCapsule()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 5), new Vector2(10, 5), 3.0f);
+
+            var circle = BoundingCircle2D.CreateFromBoundingCapsule2D(capsule);
+
+            Assert.True(circle.Contains(new Vector2(-3, 5)) != ContainmentType.Disjoint);
+            Assert.True(circle.Contains(new Vector2(13, 5)) != ContainmentType.Disjoint);
+        }
+
+        [Fact]
+        public void CreateFromBoundingCapsule2D_VerticalCapsule()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(5, 0), new Vector2(5, 10), 3.0f);
+
+            var circle = BoundingCircle2D.CreateFromBoundingCapsule2D(capsule);
+
+            Assert.True(circle.Contains(new Vector2(5, -3)) != ContainmentType.Disjoint);
+            Assert.True(circle.Contains(new Vector2(5, 13)) != ContainmentType.Disjoint);
+        }
+
+        [Fact]
+        public void CreateFromBoundingCapsule2D_DegenerateCapsule()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(5, 5), new Vector2(5, 5), 3.0f);
+
+            var circle = BoundingCircle2D.CreateFromBoundingCapsule2D(capsule);
+
+            Assert.Equal(new Vector2(5, 5), circle.Center);
+            Assert.Equal(3.0f, circle.Radius, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void CreateFromBoundingCapsule2D_ContainsOriginal()
+        {
+            var capsule = new BoundingCapsule2D(new Vector2(0, 0), new Vector2(10, 10), 2.0f);
+
+            var circle = BoundingCircle2D.CreateFromBoundingCapsule2D(capsule);
+
+            Assert.Equal(ContainmentType.Contains, circle.Contains(capsule));
+        }
+
+        [Fact]
+        public void CreateMerged_NonOverlapping()
+        {
+            var circle1 = new BoundingCircle2D(new Vector2(0, 0), 5.0f);
+            var circle2 = new BoundingCircle2D(new Vector2(20, 0), 5.0f);
+
+            var merged = BoundingCircle2D.CreateMerged(circle1, circle2);
+
+            Assert.Equal(ContainmentType.Contains, merged.Contains(circle1));
+            Assert.Equal(ContainmentType.Contains, merged.Contains(circle2));
+        }
+
+        [Fact]
+        public void CreateMerged_OneContainsOther()
+        {
+            var circle1 = new BoundingCircle2D(new Vector2(0, 0), 10.0f);
+            var circle2 = new BoundingCircle2D(new Vector2(2, 2), 3.0f);
+
+            var merged = BoundingCircle2D.CreateMerged(circle1, circle2);
+
+            Assert.Equal(circle1.Center, merged.Center);
+            Assert.Equal(circle1.Radius, merged.Radius, 0.1f);
+        }
+
+        [Fact]
+        public void CreateMerged_PartiallyOverlapping()
+        {
+            var circle1 = new BoundingCircle2D(new Vector2(0, 0), 5.0f);
+            var circle2 = new BoundingCircle2D(new Vector2(8, 0), 5.0f);
+
+            var merged = BoundingCircle2D.CreateMerged(circle1, circle2);
+
+            Assert.Equal(ContainmentType.Contains, merged.Contains(circle1));
+            Assert.Equal(ContainmentType.Contains, merged.Contains(circle2));
+        }
+
+        #endregion
+
+        #region Transform Tests
+
+        [Fact]
+        public void Transform_Translation()
+        {
+            var circle = new BoundingCircle2D(new Vector2(0, 0), 5.0f);
+            var matrix = Matrix.CreateTranslation(10, 20, 0);
+
+            var transformed = circle.Transform(matrix);
+
+            Assert.Equal(new Vector2(10, 20), transformed.Center);
+            Assert.Equal(5.0f, transformed.Radius, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Transform_UniformScale()
+        {
+            var circle = new BoundingCircle2D(new Vector2(0, 0), 5.0f);
+            var matrix = Matrix.CreateScale(2.0f);
+
+            var transformed = circle.Transform(matrix);
+
+            Assert.Equal(new Vector2(0, 0), transformed.Center);
+            Assert.Equal(10.0f, transformed.Radius, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Transform_NonUniformScale()
+        {
+            var circle = new BoundingCircle2D(new Vector2(0, 0), 5.0f);
+            var matrix = Matrix.CreateScale(2.0f, 3.0f, 1.0f);
+
+            var transformed = circle.Transform(matrix);
+
+            Assert.True(transformed.Radius >= 10.0f);
+        }
+
+        [Fact]
+        public void Transform_Rotation()
+        {
+            var circle = new BoundingCircle2D(new Vector2(5, 0), 3.0f);
+            var matrix = Matrix.CreateRotationZ(MathHelper.PiOver2);
+
+            var transformed = circle.Transform(matrix);
+
+            Assert.Equal(0, transformed.Center.X, Collision2D.Epsilon);
+            Assert.Equal(5, transformed.Center.Y, Collision2D.Epsilon);
+            Assert.Equal(3.0f, transformed.Radius, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Translate_OffsetsPosition()
+        {
+            var circle = new BoundingCircle2D(new Vector2(5, 5), 3.0f);
+            var offset = new Vector2(10, 15);
+
+            var translated = circle.Translate(offset);
+
+            Assert.Equal(new Vector2(15, 20), translated.Center);
+            Assert.Equal(3.0f, translated.Radius, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region ContainsPoint Tests (Delegation Spot Check)
+
+        [Fact]
+        public void ContainsPoint_Inside()
+        {
+            var circle = new BoundingCircle2D(new Vector2(0, 0), 10.0f);
+            var point = new Vector2(5, 0);
+
+            var result = circle.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_OnBoundary()
+        {
+            var circle = new BoundingCircle2D(new Vector2(0, 0), 10.0f);
+            var point = new Vector2(10, 0);
+
+            var result = circle.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_Outside()
+        {
+            var circle = new BoundingCircle2D(new Vector2(0, 0), 10.0f);
+            var point = new Vector2(15, 0);
+
+            var result = circle.Contains(point);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        #endregion
+
+        #region Deconstruct Test
+
+        [Fact]
+        public void Deconstruct()
+        {
+            var circle = new BoundingCircle2D(new Vector2(5, 10), 15.0f);
+
+            var (center, radius) = circle;
+
+            Assert.Equal(new Vector2(5, 10), center);
+            Assert.Equal(15.0f, radius);
+        }
+
+        #endregion
+    }
+}

+ 544 - 0
tests/MonoGame.Extended.Tests/BoundingPolygon2DTest.cs

@@ -0,0 +1,544 @@
+// MonoGame - Copyright (C) MonoGame Foundation, Inc
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+    public sealed class BoundingPolygon2DTest
+    {
+        #region Constructor Tests
+
+        [Fact]
+        public void Constructor_Triangle()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(5, 10)
+            };
+
+            var polygon = new BoundingPolygon2D(vertices);
+
+            Assert.Equal(3, polygon.VertexCount);
+        }
+
+        [Fact]
+        public void Constructor_Square()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+
+            var polygon = new BoundingPolygon2D(vertices);
+
+            Assert.Equal(4, polygon.VertexCount);
+        }
+
+        [Fact]
+        public void Constructor_ThrowsWhenNull()
+        {
+            Assert.Throws<ArgumentNullException>(() => new BoundingPolygon2D(null));
+        }
+
+        [Fact]
+        public void Constructor_ThrowsWhenTooFewVertices()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0)
+            };
+
+            Assert.Throws<ArgumentException>(() => new BoundingPolygon2D(vertices));
+        }
+
+        [Fact]
+        public void Constructor_WithNormals()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(-0.707f, 0.707f)
+            };
+
+            var polygon = new BoundingPolygon2D(vertices, normals);
+
+            Assert.Equal(3, polygon.VertexCount);
+        }
+
+        [Fact]
+        public void Constructor_WithNormals_ThrowsWhenNullVertices()
+        {
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1)
+            };
+
+            Assert.Throws<ArgumentNullException>(() => new BoundingPolygon2D(null, normals));
+        }
+
+        [Fact]
+        public void Constructor_WithNormals_ThrowsWhenNullNormals()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10)
+            };
+
+            Assert.Throws<ArgumentNullException>(() => new BoundingPolygon2D(vertices, null));
+        }
+
+        [Fact]
+        public void Constructor_WithNormals_ThrowsWhenLengthMismatch()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0)
+            };
+
+            Assert.Throws<ArgumentException>(() => new BoundingPolygon2D(vertices, normals));
+        }
+
+        #endregion
+
+        #region Computed Property Tests
+
+        [Fact]
+        public void VertexCount_ReturnsCorrectCount()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+
+            var polygon = new BoundingPolygon2D(vertices);
+
+            Assert.Equal(4, polygon.VertexCount);
+        }
+
+        [Fact]
+        public void Centroid_TriangleReturnsCorrectValue()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(5, 10)
+            };
+
+            var polygon = new BoundingPolygon2D(vertices);
+
+            var centroid = polygon.Centroid;
+
+            Assert.Equal(5, centroid.X, Collision2D.Epsilon);
+            Assert.Equal(10.0f / 3.0f, centroid.Y, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Centroid_SquareReturnsCenter()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+
+            var polygon = new BoundingPolygon2D(vertices);
+
+            var centroid = polygon.Centroid;
+
+            Assert.Equal(5, centroid.X, Collision2D.Epsilon);
+            Assert.Equal(5, centroid.Y, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Area_TriangleReturnsCorrectValue()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(5, 10)
+            };
+
+            var polygon = new BoundingPolygon2D(vertices);
+
+            float area = polygon.Area;
+
+            Assert.Equal(50.0f, area, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Area_SquareReturnsCorrectValue()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+
+            var polygon = new BoundingPolygon2D(vertices);
+
+            float area = polygon.Area;
+
+            Assert.Equal(100.0f, area, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region Factory Method Tests
+
+        [Fact]
+        public void CreateFromVertices()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10)
+            };
+
+            var polygon = BoundingPolygon2D.CreateFromVertices(vertices);
+
+            Assert.Equal(3, polygon.VertexCount);
+        }
+
+        [Fact]
+        public void CreateRegular_Triangle()
+        {
+            var center = new Vector2(5, 5);
+            var radius = 10.0f;
+            var sides = 3;
+
+            var polygon = BoundingPolygon2D.CreateRegular(center, radius, sides);
+
+            Assert.Equal(3, polygon.VertexCount);
+
+            for (int i = 0; i < 3; i++)
+            {
+                float distance = Vector2.Distance(center, polygon.Vertices[i]);
+                Assert.Equal(radius, distance, Collision2D.Epsilon);
+            }
+        }
+
+        [Fact]
+        public void CreateRegular_Square()
+        {
+            var center = new Vector2(0, 0);
+            var radius = 10.0f;
+            var sides = 4;
+
+            var polygon = BoundingPolygon2D.CreateRegular(center, radius, sides);
+
+            Assert.Equal(4, polygon.VertexCount);
+        }
+
+        [Fact]
+        public void CreateRegular_Hexagon()
+        {
+            var center = new Vector2(0, 0);
+            var radius = 10.0f;
+            var sides = 6;
+
+            var polygon = BoundingPolygon2D.CreateRegular(center, radius, sides);
+
+            Assert.Equal(6, polygon.VertexCount);
+        }
+
+        [Fact]
+        public void CreateRegular_WithRotation()
+        {
+            var center = new Vector2(0, 0);
+            var radius = 10.0f;
+            var sides = 4;
+            var rotation = MathHelper.PiOver4;
+
+            var polygon = BoundingPolygon2D.CreateRegular(center, radius, sides, rotation);
+
+            Assert.Equal(4, polygon.VertexCount);
+
+            float angle = MathHelper.PiOver4;
+            float expectedX = radius * MathF.Cos(angle);
+            float expectedY = radius * MathF.Sin(angle);
+
+            Assert.Equal(expectedX, polygon.Vertices[0].X, Collision2D.Epsilon);
+            Assert.Equal(expectedY, polygon.Vertices[0].Y, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void CreateRegular_ThrowsWhenTooFewSides()
+        {
+            var center = new Vector2(0, 0);
+            var radius = 10.0f;
+            var sides = 2;
+
+            Assert.Throws<ArgumentException>(() => BoundingPolygon2D.CreateRegular(center, radius, sides));
+        }
+
+        [Fact]
+        public void CreateFromBoundingBox2D()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 10));
+
+            var polygon = BoundingPolygon2D.CreateFromBoundingBox2D(box);
+
+            Assert.Equal(4, polygon.VertexCount);
+
+            Assert.Contains(new Vector2(0, 0), polygon.Vertices);
+            Assert.Contains(new Vector2(10, 0), polygon.Vertices);
+            Assert.Contains(new Vector2(10, 10), polygon.Vertices);
+            Assert.Contains(new Vector2(0, 10), polygon.Vertices);
+        }
+
+        [Fact]
+        public void CreateMerged_NonOverlapping()
+        {
+            var vertices1 = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(5, 0),
+                new Vector2(5, 5),
+                new Vector2(0, 5)
+            };
+            var polygon1 = new BoundingPolygon2D(vertices1);
+
+            var vertices2 = new[]
+            {
+                new Vector2(10, 10),
+                new Vector2(15, 10),
+                new Vector2(15, 15),
+                new Vector2(10, 15)
+            };
+            var polygon2 = new BoundingPolygon2D(vertices2);
+
+            var merged = BoundingPolygon2D.CreateMerged(polygon1, polygon2);
+
+            Assert.Equal(ContainmentType.Contains, merged.Contains(polygon1));
+            Assert.Equal(ContainmentType.Contains, merged.Contains(polygon2));
+        }
+
+        [Fact]
+        public void CreateMerged_Overlapping()
+        {
+            var vertices1 = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var polygon1 = new BoundingPolygon2D(vertices1);
+
+            var vertices2 = new[]
+            {
+                new Vector2(5, 5),
+                new Vector2(15, 5),
+                new Vector2(15, 15),
+                new Vector2(5, 15)
+            };
+            var polygon2 = new BoundingPolygon2D(vertices2);
+
+            var merged = BoundingPolygon2D.CreateMerged(polygon1, polygon2);
+
+            Assert.Equal(ContainmentType.Contains, merged.Contains(polygon1));
+            Assert.Equal(ContainmentType.Contains, merged.Contains(polygon2));
+        }
+
+        #endregion
+
+        #region Transform Tests
+
+        [Fact]
+        public void Transform_Translation()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10)
+            };
+            var polygon = new BoundingPolygon2D(vertices);
+            var matrix = Matrix.CreateTranslation(5, 10, 0);
+
+            var transformed = polygon.Transform(matrix);
+
+            Assert.Equal(new Vector2(5, 10), transformed.Vertices[0]);
+            Assert.Equal(new Vector2(15, 10), transformed.Vertices[1]);
+            Assert.Equal(new Vector2(15, 20), transformed.Vertices[2]);
+        }
+
+        [Fact]
+        public void Transform_UniformScale()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10)
+            };
+            var polygon = new BoundingPolygon2D(vertices);
+            var matrix = Matrix.CreateScale(2.0f);
+
+            var transformed = polygon.Transform(matrix);
+
+            Assert.Equal(new Vector2(0, 0), transformed.Vertices[0]);
+            Assert.Equal(new Vector2(20, 0), transformed.Vertices[1]);
+            Assert.Equal(new Vector2(20, 20), transformed.Vertices[2]);
+        }
+
+        [Fact]
+        public void Transform_Rotation()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(0, 10)
+            };
+            var polygon = new BoundingPolygon2D(vertices);
+            var matrix = Matrix.CreateRotationZ(MathHelper.PiOver2);
+
+            var transformed = polygon.Transform(matrix);
+
+            Assert.Equal(0, transformed.Vertices[0].X, Collision2D.Epsilon);
+            Assert.Equal(0, transformed.Vertices[0].Y, Collision2D.Epsilon);
+            Assert.Equal(0, transformed.Vertices[1].X, Collision2D.Epsilon);
+            Assert.Equal(10, transformed.Vertices[1].Y, Collision2D.Epsilon);
+            Assert.Equal(-10, transformed.Vertices[2].X, Collision2D.Epsilon);
+            Assert.Equal(0, transformed.Vertices[2].Y, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Translate_OffsetsPosition()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10)
+            };
+            var polygon = new BoundingPolygon2D(vertices);
+            var offset = new Vector2(5, 10);
+
+            var translated = polygon.Translate(offset);
+
+            Assert.Equal(new Vector2(5, 10), translated.Vertices[0]);
+            Assert.Equal(new Vector2(15, 10), translated.Vertices[1]);
+            Assert.Equal(new Vector2(15, 20), translated.Vertices[2]);
+        }
+
+        #endregion
+
+        #region ContainsPoint Tests (Delegation Spot Check)
+
+        [Fact]
+        public void ContainsPoint_Inside()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var polygon = new BoundingPolygon2D(vertices);
+            var point = new Vector2(5, 5);
+
+            var result = polygon.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_OnBoundary()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var polygon = new BoundingPolygon2D(vertices);
+            var point = new Vector2(10, 5);
+
+            var result = polygon.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_Outside()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var polygon = new BoundingPolygon2D(vertices);
+            var point = new Vector2(15, 5);
+
+            var result = polygon.Contains(point);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        #endregion
+
+        #region Deconstruct Test
+
+        [Fact]
+        public void Deconstruct()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10)
+            };
+            var polygon = new BoundingPolygon2D(vertices);
+
+            var (v, n) = polygon;
+
+            Assert.Equal(vertices, v);
+            Assert.NotNull(n);
+        }
+
+        #endregion
+    }
+}

+ 3517 - 0
tests/MonoGame.Extended.Tests/Collision2DTest.cs

@@ -0,0 +1,3517 @@
+// MonoGame - Copyright (C) MonoGame Foundation, Inc
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+    public sealed class Collision2DTest
+    {
+        #region Helper Methods
+
+        #region IsValidPolygon Tests
+
+        [Fact]
+        public void IsValidPolygon_ValidPolygon_ReturnsTrue()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1)
+            };
+
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                Vector2.Normalize(new Vector2(1, 1)),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IsValidPolygon(vertices, normals);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IsValidPolygon_NullVertices_ReturnsFalse()
+        {
+            var normals = new[] { Vector2.UnitX, Vector2.UnitY, -Vector2.UnitX };
+
+            bool result = Collision2D.IsValidPolygon(null, normals);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void IsValidPolygon_NullNormals_ReturnsFalse()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1)
+            };
+
+            bool result = Collision2D.IsValidPolygon(vertices, null);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void IsValidPolygon_MismatchedArrayLengths_ReturnsFalse()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1)
+            };
+            var normals = new[]
+            {
+                Vector2.UnitX,
+                Vector2.UnitY
+            };
+
+            bool result = Collision2D.IsValidPolygon(vertices, normals);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void IsValidPolygon_LessThanThreeVertices_ReturnsFalse()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(1, 0)
+            };
+            var normals = new[]
+            {
+                Vector2.UnitX,
+                Vector2.UnitY
+            };
+
+            bool result = Collision2D.IsValidPolygon(vertices, normals);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region ClipInterval Tests (ref overload)
+
+        [Fact]
+        public void ClipInterval_OverlappingIntervals_ReturnsTrue()
+        {
+            float tMin = 0.0f;
+            float tMax = 10.0f;
+            float clipMin = 3.0f;
+            float clipMax = 8.0f;
+
+            bool result = Collision2D.ClipInterval(ref tMin, ref tMax, clipMin, clipMax);
+
+            Assert.True(result);
+            Assert.Equal(3.0f, tMin);
+            Assert.Equal(8.0f, tMax);
+        }
+
+        [Fact]
+        public void ClipInterval_NonOverlappingIntervals_ReturnsFalse()
+        {
+            float tMin = 0.0f;
+            float tMax = 5.0f;
+            float clipMin = 10.0f;
+            float clipMax = 15.0f;
+
+            bool result = Collision2D.ClipInterval(ref tMin, ref tMax, clipMin, clipMax);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void ClipInterval_TouchingAtBoundary_ReturnsTrue()
+        {
+            float tMin = 0.0f;
+            float tMax = 5.0f;
+            float clipMin = 5.0f;
+            float clipMax = 10.0f;
+
+            bool result = Collision2D.ClipInterval(ref tMin, ref tMax, clipMin, clipMax);
+
+            Assert.True(result);
+            Assert.Equal(5.0f, tMin);
+            Assert.Equal(5.0f, tMax);
+        }
+
+        [Fact]
+        public void ClipInterval_ClippingWiderThanOriginal_KeepsOriginalBounds()
+        {
+            float tMin = 3.0f;
+            float tMax = 7.0f;
+            float clipMin = 0.0f;
+            float clipMax = 10.0f;
+
+            bool result = Collision2D.ClipInterval(ref tMin, ref tMax, clipMin, clipMax);
+
+            Assert.True(result);
+            Assert.Equal(3.0f, tMin);
+            Assert.Equal(7.0f, tMax);
+        }
+
+        [Fact]
+        public void ClipInterval_NegativeIntervals_WorksCorrectly()
+        {
+            float tMin = -10.0f;
+            float tMax = -5.0f;
+            float clipMin = -8.0f;
+            float clipMax = -3.0f;
+
+            bool result = Collision2D.ClipInterval(ref tMin, ref tMax, clipMin, clipMax);
+
+            Assert.True(result);
+            Assert.Equal(-8.0f, tMin);
+            Assert.Equal(-5.0f, tMax);
+        }
+
+        #endregion
+
+        #region ClipInterval Tests (out overload)
+
+        [Fact]
+        public void ClipIntervalOut_OverlappingIntervals_ReturnsClippedValues()
+        {
+            float tEnter = 0.0f;
+            float tExit = 10.0f;
+            float tLower = 3.0f;
+            float tUpper = 8.0f;
+
+            bool result = Collision2D.ClipInterval(tEnter, tExit, tLower, tUpper, out float clippedEnter, out float clippedExit);
+
+            Assert.True(result);
+            Assert.Equal(3.0f, clippedEnter);
+            Assert.Equal(8.0f, clippedExit);
+        }
+
+        [Fact]
+        public void ClipIntervalOut_NonOverlappingIntervals_ReturnsFalse()
+        {
+            float tEnter = 0.0f;
+            float tExit = 5.0f;
+            float tLower = 10.0f;
+            float tUpper = 15.0f;
+
+            bool result = Collision2D.ClipInterval(tEnter, tExit, tLower, tUpper, out float clippedEnter, out float clippedExit);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void ClipIntervalOut_TouchingAtBoundary_ReturnsTrue()
+        {
+            float tEnter = 0.0f;
+            float tExit = 5.0f;
+            float tLower = 5.0f;
+            float tUpper = 10.0f;
+
+            bool result = Collision2D.ClipInterval(tEnter, tExit, tLower, tUpper, out float clippedEnter, out float clippedExit);
+
+            Assert.True(result);
+            Assert.Equal(5.0f, clippedEnter);
+            Assert.Equal(5.0f, clippedExit);
+        }
+
+        [Fact]
+        public void ClipIntervalOut_ClippingWiderThanOriginal_ReturnsOriginal()
+        {
+            float tEnter = 3.0f;
+            float tExit = 7.0f;
+            float tLower = 0.0f;
+            float tUpper = 10.0f;
+
+            bool result = Collision2D.ClipInterval(tEnter, tExit, tLower, tUpper, out float clippedEnter, out float clippedExit);
+
+            Assert.True(result);
+            Assert.Equal(3.0f, clippedEnter);
+            Assert.Equal(7.0f, clippedExit);
+        }
+
+        #endregion
+
+        #region IntervalsOverlap Tests
+
+        [Fact]
+        public void IntervalsOverlap_Overlapping_ReturnsTrue()
+        {
+            float minA = 0.0f;
+            float maxA = 10.0f;
+            float minB = 5.0f;
+            float maxB = 15.0f;
+
+            bool result = Collision2D.IntervalsOverlap(minA, maxA, minB, maxB);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntervalsOverlap_Separated_ReturnsFalse()
+        {
+            float minA = 0.0f;
+            float maxA = 5.0f;
+            float minB = 10.0f;
+            float maxB = 15.0f;
+
+            bool result = Collision2D.IntervalsOverlap(minA, maxA, minB, maxB);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void IntervalsOverlap_TouchingAtPoint_ReturnsTrue()
+        {
+            float minA = 0.0f;
+            float maxA = 5.0f;
+            float minB = 5.0f;
+            float maxB = 10.0f;
+
+            bool result = Collision2D.IntervalsOverlap(minA, maxA, minB, maxB);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntervalsOverlap_NegativeIntervals_WorksCorrectly()
+        {
+            float minA = -10.0f;
+            float maxA = -5.0f;
+            float minB = -8.0f;
+            float maxB = -3.0f;
+
+            bool result = Collision2D.IntervalsOverlap(minA, maxA, minB, maxB);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntervalsOverlap_NearlyTouching_ReturnsFalse()
+        {
+            float minA = 0.0f;
+            float maxA = 5.0f;
+            float minB = 5.0f + Collision2D.Epsilon * 2.0f;
+            float maxB = 10.0f;
+
+            bool result = Collision2D.IntervalsOverlap(minA, maxA, minB, maxB);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Containment - AABB Methods
+
+        #region ContainsAabbPoint Tests
+
+        [Fact]
+        public void ContainsAabbPoint_PointInside_ReturnsContains()
+        {
+            var point = new Vector2(5, 5);
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+
+            var result = Collision2D.ContainsAabbPoint(point, min, max);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsAabbPoint_PointOutside_ReturnsDisjoint()
+        {
+            var point = new Vector2(15, 5);
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+
+            var result = Collision2D.ContainsAabbPoint(point, min, max);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsAabbPoint_PointOnBoundary_ReturnsContains()
+        {
+            var point = new Vector2(10, 5);
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+
+            var result = Collision2D.ContainsAabbPoint(point, min, max);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        #endregion
+
+        #region ContainsAabbAabb Tests
+
+        [Fact]
+        public void ContainsAabbAabb_CompletelyContains_ReturnsContains()
+        {
+            var aMin = new Vector2(0, 0);
+            var aMax = new Vector2(10, 10);
+            var bMin = new Vector2(2, 2);
+            var bMax = new Vector2(8, 8);
+
+            var result = Collision2D.ContainsAabbAabb(aMin, aMax, bMin, bMax);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsAabbAabb_NoOverlap_ReturnsDisjoint()
+        {
+            var aMin = new Vector2(0, 0);
+            var aMax = new Vector2(10, 10);
+            var bMin = new Vector2(15, 15);
+            var bMax = new Vector2(20, 20);
+
+            var result = Collision2D.ContainsAabbAabb(aMin, aMax, bMin, bMax);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsAabbAabb_PartialOverlap_ReturnsIntersects()
+        {
+            var aMin = new Vector2(0, 0);
+            var aMax = new Vector2(10, 10);
+            var bMin = new Vector2(5, 5);
+            var bMax = new Vector2(15, 15);
+
+            var result = Collision2D.ContainsAabbAabb(aMin, aMax, bMin, bMax);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsAabbCircle Tests
+
+        [Fact]
+        public void ContainsAabbCircle_CircleCompletelyInside_ReturnsContains()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var circleCenter = new Vector2(5, 5);
+            var circleRadius = 2.0f;
+
+            var result = Collision2D.ContainsAabbCircle(boxMin, boxMax, circleCenter, circleRadius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsAabbCircle_CircleCompletelyOutside_ReturnsDisjoint()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var circleCenter = new Vector2(20, 20);
+            var circleRadius = 3.0f;
+
+            var result = Collision2D.ContainsAabbCircle(boxMin, boxMax, circleCenter, circleRadius);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsAabbCircle_CirclePartiallyInside_ReturnsIntersects()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var circleCenter = new Vector2(5, 5);
+            var circleRadius = 7.0f;
+
+            var result = Collision2D.ContainsAabbCircle(boxMin, boxMax, circleCenter, circleRadius);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsAabbObb Tests
+
+        [Fact]
+        public void ContainsAabbObb_ObbCompletelyInside_ReturnsContains()
+        {
+            var aabbMin = new Vector2(0, 0);
+            var aabbMax = new Vector2(10, 10);
+            var obbCenter = new Vector2(5, 5);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(2, 2);
+
+            var result = Collision2D.ContainsAabbObb(aabbMin, aabbMax, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsAabbObb_ObbCompletelyOutside_ReturnsDisjoint()
+        {
+            var aabbMin = new Vector2(0, 0);
+            var aabbMax = new Vector2(10, 10);
+            var obbCenter = new Vector2(20, 20);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(2, 2);
+
+            var result = Collision2D.ContainsAabbObb(aabbMin, aabbMax, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsAabbObb_ObbPartiallyInside_ReturnsIntersects()
+        {
+            var aabbMin = new Vector2(0, 0);
+            var aabbMax = new Vector2(10, 10);
+            var obbCenter = new Vector2(8, 8);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(5, 5);
+
+            var result = Collision2D.ContainsAabbObb(aabbMin, aabbMax, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsAabbCapsule Tests
+
+        [Fact]
+        public void ContainsAabbCapsule_CapsuleCompletelyInside_ReturnsContains()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var capsuleA = new Vector2(3, 5);
+            var capsuleB = new Vector2(7, 5);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsAabbCapsule(boxMin, boxMax, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsAabbCapsule_CapsuleCompletelyOutside_ReturnsDisjoint()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var capsuleA = new Vector2(15, 15);
+            var capsuleB = new Vector2(20, 20);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsAabbCapsule(boxMin, boxMax, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsAabbCapsule_CapsulePartiallyInside_ReturnsIntersects()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var capsuleA = new Vector2(5, 5);
+            var capsuleB = new Vector2(15, 5);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsAabbCapsule(boxMin, boxMax, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsAabbConvexPolygon Tests
+
+        [Fact]
+        public void ContainsAabbConvexPolygon_PolygonCompletelyInside_ReturnsContains()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var vertices = new[]
+            {
+                new Vector2(3, 3),
+                new Vector2(7, 3),
+                new Vector2(7, 7),
+                new Vector2(3, 7)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsAabbConvexPolygon(boxMin, boxMax, vertices, normals);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsAabbConvexPolygon_PolygonCompletelyOutside_ReturnsDisjoint()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var vertices = new[]
+            {
+                new Vector2(15, 15),
+                new Vector2(20, 15),
+                new Vector2(20, 20),
+                new Vector2(15, 20)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsAabbConvexPolygon(boxMin, boxMax, vertices, normals);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsAabbConvexPolygon_PolygonPartiallyInside_ReturnsIntersects()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var vertices = new[]
+            {
+                new Vector2(5, 5),
+                new Vector2(15, 5),
+                new Vector2(15, 15),
+                new Vector2(5, 15)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsAabbConvexPolygon(boxMin, boxMax, vertices, normals);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Containment - Circle Methods
+
+        #region ContainsCirclePoint Tests
+
+        [Fact]
+        public void ContainsCirclePoint_PointInside_ReturnsContains()
+        {
+            var point = new Vector2(3, 0);
+            var center = new Vector2(0, 0);
+            var radius = 5.0f;
+
+            var result = Collision2D.ContainsCirclePoint(point, center, radius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCirclePoint_PointOutside_ReturnsDisjoint()
+        {
+            var point = new Vector2(10, 0);
+            var center = new Vector2(0, 0);
+            var radius = 5.0f;
+
+            var result = Collision2D.ContainsCirclePoint(point, center, radius);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCirclePoint_PointOnCircumference_ReturnsContains()
+        {
+            var point = new Vector2(5, 0);
+            var center = new Vector2(0, 0);
+            var radius = 5.0f;
+
+            var result = Collision2D.ContainsCirclePoint(point, center, radius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        #endregion
+
+        #region ContainsCircleAabb Tests
+
+        [Fact]
+        public void ContainsCircleAabb_AabbCompletelyInside_ReturnsContains()
+        {
+            var circleCenter = new Vector2(10, 10);
+            var circleRadius = 10.0f;
+            var aabbMin = new Vector2(8, 8);
+            var aabbMax = new Vector2(12, 12);
+
+            var result = Collision2D.ContainsCircleAabb(circleCenter, circleRadius, aabbMin, aabbMax);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCircleAabb_AabbCompletelyOutside_ReturnsDisjoint()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var aabbMin = new Vector2(20, 20);
+            var aabbMax = new Vector2(25, 25);
+
+            var result = Collision2D.ContainsCircleAabb(circleCenter, circleRadius, aabbMin, aabbMax);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCircleAabb_AabbPartiallyInside_ReturnsIntersects()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var aabbMin = new Vector2(3, 3);
+            var aabbMax = new Vector2(8, 8);
+
+            var result = Collision2D.ContainsCircleAabb(circleCenter, circleRadius, aabbMin, aabbMax);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsCircleCircle Tests
+
+        [Fact]
+        public void ContainsCircleCircle_CompletelyContains_ReturnsContains()
+        {
+            var aCenter = new Vector2(0, 0);
+            var aRadius = 10.0f;
+            var bCenter = new Vector2(2, 0);
+            var bRadius = 3.0f;
+
+            var result = Collision2D.ContainsCircleCircle(aCenter, aRadius, bCenter, bRadius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCircleCircle_Separated_ReturnsDisjoint()
+        {
+            var aCenter = new Vector2(0, 0);
+            var aRadius = 5.0f;
+            var bCenter = new Vector2(20, 0);
+            var bRadius = 3.0f;
+
+            var result = Collision2D.ContainsCircleCircle(aCenter, aRadius, bCenter, bRadius);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCircleCircle_PartialOverlap_ReturnsIntersects()
+        {
+            var aCenter = new Vector2(0, 0);
+            var aRadius = 5.0f;
+            var bCenter = new Vector2(7, 0);
+            var bRadius = 4.0f;
+
+            var result = Collision2D.ContainsCircleCircle(aCenter, aRadius, bCenter, bRadius);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsCircleObb Tests
+
+        [Fact]
+        public void ContainsCircleObb_ObbCompletelyInside_ReturnsContains()
+        {
+            var circleCenter = new Vector2(10, 10);
+            var circleRadius = 10.0f;
+            var obbCenter = new Vector2(10, 10);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(2, 2);
+
+            var result = Collision2D.ContainsCircleObb(circleCenter, circleRadius, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCircleObb_ObbCompletelyOutside_ReturnsDisjoint()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var obbCenter = new Vector2(20, 20);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(2, 2);
+
+            var result = Collision2D.ContainsCircleObb(circleCenter, circleRadius, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCircleObb_ObbPartiallyInside_ReturnsIntersects()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var obbCenter = new Vector2(4, 4);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(3, 3);
+
+            var result = Collision2D.ContainsCircleObb(circleCenter, circleRadius, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsCircleCapsule Tests
+
+        [Fact]
+        public void ContainsCircleCapsule_CapsuleCompletelyInside_ReturnsContains()
+        {
+            var circleCenter = new Vector2(10, 10);
+            var circleRadius = 10.0f;
+            var capsuleA = new Vector2(8, 10);
+            var capsuleB = new Vector2(12, 10);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsCircleCapsule(circleCenter, circleRadius, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCircleCapsule_CapsuleCompletelyOutside_ReturnsDisjoint()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var capsuleA = new Vector2(20, 20);
+            var capsuleB = new Vector2(25, 25);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsCircleCapsule(circleCenter, circleRadius, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCircleCapsule_CapsulePartiallyInside_ReturnsIntersects()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var capsuleA = new Vector2(3, 0);
+            var capsuleB = new Vector2(10, 0);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsCircleCapsule(circleCenter, circleRadius, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsCircleConvexPolygon Tests
+
+        [Fact]
+        public void ContainsCircleConvexPolygon_PolygonCompletelyInside_ReturnsContains()
+        {
+            var circleCenter = new Vector2(10, 10);
+            var circleRadius = 10.0f;
+            var vertices = new[]
+            {
+                new Vector2(9, 9),
+                new Vector2(11, 9),
+                new Vector2(11, 11),
+                new Vector2(9, 11)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsCircleConvexPolygon(circleCenter, circleRadius, vertices, normals);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCircleConvexPolygon_PolygonCompletelyOutside_ReturnsDisjoint()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var vertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(25, 20),
+                new Vector2(25, 25),
+                new Vector2(20, 25)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsCircleConvexPolygon(circleCenter, circleRadius, vertices, normals);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCircleConvexPolygon_PolygonPartiallyInside_ReturnsIntersects()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var vertices = new[]
+            {
+                new Vector2(3, 3),
+                new Vector2(8, 3),
+                new Vector2(8, 8),
+                new Vector2(3, 8)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsCircleConvexPolygon(circleCenter, circleRadius, vertices, normals);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Containment - OBB Methods
+
+        #region ContainsObbPoint Tests
+
+        [Fact]
+        public void ContainsObbPoint_PointInside_ReturnsContains()
+        {
+            var point = new Vector2(5, 5);
+            var center = new Vector2(5, 5);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(3, 3);
+
+            var result = Collision2D.ContainsObbPoint(point, center, axisX, axisY, halfExtents);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsObbPoint_PointOutside_ReturnsDisjoint()
+        {
+            var point = new Vector2(15, 15);
+            var center = new Vector2(5, 5);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(3, 3);
+
+            var result = Collision2D.ContainsObbPoint(point, center, axisX, axisY, halfExtents);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsObbPoint_PointOnEdge_ReturnsContains()
+        {
+            var point = new Vector2(8, 5);
+            var center = new Vector2(5, 5);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(3, 3);
+
+            var result = Collision2D.ContainsObbPoint(point, center, axisX, axisY, halfExtents);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        #endregion
+
+        #region ContainsObbObb Tests
+
+        [Fact]
+        public void ContainsObbObb_CompletelyContains_ReturnsContains()
+        {
+            var aCenter = new Vector2(5, 5);
+            var aAxisX = Vector2.UnitX;
+            var aAxisY = Vector2.UnitY;
+            var aHalf = new Vector2(5, 5);
+            var bCenter = new Vector2(5, 5);
+            var bAxisX = Vector2.UnitX;
+            var bAxisY = Vector2.UnitY;
+            var bHalf = new Vector2(2, 2);
+
+            var result = Collision2D.ContainsObbObb(aCenter, aAxisX, aAxisY, aHalf, bCenter, bAxisX, bAxisY, bHalf);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsObbObb_Separated_ReturnsDisjoint()
+        {
+            var aCenter = new Vector2(0, 0);
+            var aAxisX = Vector2.UnitX;
+            var aAxisY = Vector2.UnitY;
+            var aHalf = new Vector2(2, 2);
+            var bCenter = new Vector2(20, 20);
+            var bAxisX = Vector2.UnitX;
+            var bAxisY = Vector2.UnitY;
+            var bHalf = new Vector2(2, 2);
+
+            var result = Collision2D.ContainsObbObb(aCenter, aAxisX, aAxisY, aHalf, bCenter, bAxisX, bAxisY, bHalf);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsObbObb_PartialOverlap_ReturnsIntersects()
+        {
+            var aCenter = new Vector2(5, 5);
+            var aAxisX = Vector2.UnitX;
+            var aAxisY = Vector2.UnitY;
+            var aHalf = new Vector2(5, 5);
+            var bCenter = new Vector2(8, 8);
+            var bAxisX = Vector2.UnitX;
+            var bAxisY = Vector2.UnitY;
+            var bHalf = new Vector2(5, 5);
+
+            var result = Collision2D.ContainsObbObb(aCenter, aAxisX, aAxisY, aHalf, bCenter, bAxisX, bAxisY, bHalf);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsObbCapsule Tests
+
+        [Fact]
+        public void ContainsObbCapsule_CapsuleCompletelyInside_ReturnsContains()
+        {
+            var boxCenter = new Vector2(10, 10);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(5, 5);
+            var capsuleA = new Vector2(8, 10);
+            var capsuleB = new Vector2(12, 10);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsObbCapsule(boxCenter, axisX, axisY, halfExtents, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsObbCapsule_CapsuleCompletelyOutside_ReturnsDisjoint()
+        {
+            var boxCenter = new Vector2(0, 0);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(5, 5);
+            var capsuleA = new Vector2(20, 20);
+            var capsuleB = new Vector2(25, 25);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsObbCapsule(boxCenter, axisX, axisY, halfExtents, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsObbCapsule_CapsulePartiallyInside_ReturnsIntersects()
+        {
+            var boxCenter = new Vector2(5, 5);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(5, 5);
+            var capsuleA = new Vector2(5, 5);
+            var capsuleB = new Vector2(15, 5);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsObbCapsule(boxCenter, axisX, axisY, halfExtents, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsObbConvexPolygon Tests
+
+        [Fact]
+        public void ContainsObbConvexPolygon_PolygonCompletelyInside_ReturnsContains()
+        {
+            var boxCenter = new Vector2(10, 10);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(5, 5);
+            var vertices = new[]
+            {
+                new Vector2(8, 8),
+                new Vector2(12, 8),
+                new Vector2(12, 12),
+                new Vector2(8, 12)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsObbConvexPolygon(boxCenter, axisX, axisY, halfExtents, vertices, normals);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsObbConvexPolygon_PolygonCompletelyOutside_ReturnsDisjoint()
+        {
+            var boxCenter = new Vector2(0, 0);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(5, 5);
+            var vertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(25, 20),
+                new Vector2(25, 25),
+                new Vector2(20, 25)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsObbConvexPolygon(boxCenter, axisX, axisY, halfExtents, vertices, normals);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsObbConvexPolygon_PolygonPartiallyInside_ReturnsIntersects()
+        {
+            var boxCenter = new Vector2(5, 5);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(5, 5);
+            var vertices = new[]
+            {
+                new Vector2(5, 5),
+                new Vector2(15, 5),
+                new Vector2(15, 15),
+                new Vector2(5, 15)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsObbConvexPolygon(boxCenter, axisX, axisY, halfExtents, vertices, normals);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Containment - Capsule Methods
+
+        #region ContainsCapsulePoint Tests
+
+        [Fact]
+        public void ContainsCapsulePoint_PointInside_ReturnsContains()
+        {
+            var point = new Vector2(5, 1);
+            var a = new Vector2(0, 0);
+            var b = new Vector2(10, 0);
+            var radius = 2.0f;
+
+            var result = Collision2D.ContainsCapsulePoint(point, a, b, radius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCapsulePoint_PointOutside_ReturnsDisjoint()
+        {
+            var point = new Vector2(5, 10);
+            var a = new Vector2(0, 0);
+            var b = new Vector2(10, 0);
+            var radius = 2.0f;
+
+            var result = Collision2D.ContainsCapsulePoint(point, a, b, radius);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCapsulePoint_PointOnBoundary_ReturnsContains()
+        {
+            var point = new Vector2(5, 2);
+            var a = new Vector2(0, 0);
+            var b = new Vector2(10, 0);
+            var radius = 2.0f;
+
+            var result = Collision2D.ContainsCapsulePoint(point, a, b, radius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        #endregion
+
+        #region ContainsCapsuleObb Tests
+
+        [Fact]
+        public void ContainsCapsuleObb_ObbCompletelyInside_ReturnsContains()
+        {
+            var capsuleA = new Vector2(0, 5);
+            var capsuleB = new Vector2(20, 5);
+            var capsuleRadius = 5.0f;
+            var obbCenter = new Vector2(10, 5);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalf = new Vector2(2, 2);
+
+            var result = Collision2D.ContainsCapsuleObb(capsuleA, capsuleB, capsuleRadius, obbCenter, obbAxisX, obbAxisY, obbHalf);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCapsuleObb_ObbCompletelyOutside_ReturnsDisjoint()
+        {
+            var capsuleA = new Vector2(0, 0);
+            var capsuleB = new Vector2(10, 0);
+            var capsuleRadius = 2.0f;
+            var obbCenter = new Vector2(20, 20);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalf = new Vector2(2, 2);
+
+            var result = Collision2D.ContainsCapsuleObb(capsuleA, capsuleB, capsuleRadius, obbCenter, obbAxisX, obbAxisY, obbHalf);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCapsuleObb_ObbPartiallyInside_ReturnsIntersects()
+        {
+            var capsuleA = new Vector2(0, 5);
+            var capsuleB = new Vector2(10, 5);
+            var capsuleRadius = 3.0f;
+            var obbCenter = new Vector2(8, 5);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalf = new Vector2(5, 5);
+
+            var result = Collision2D.ContainsCapsuleObb(capsuleA, capsuleB, capsuleRadius, obbCenter, obbAxisX, obbAxisY, obbHalf);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsCapsuleCapsule Tests
+
+        [Fact]
+        public void ContainsCapsuleCapsule_CompletelyContains_ReturnsContains()
+        {
+            var a0 = new Vector2(0, 0);
+            var a1 = new Vector2(20, 0);
+            var aRadius = 5.0f;
+            var b0 = new Vector2(8, 0);
+            var b1 = new Vector2(12, 0);
+            var bRadius = 2.0f;
+
+            var result = Collision2D.ContainsCapsuleCapsule(a0, a1, aRadius, b0, b1, bRadius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCapsuleCapsule_Separated_ReturnsDisjoint()
+        {
+            var a0 = new Vector2(0, 0);
+            var a1 = new Vector2(10, 0);
+            var aRadius = 2.0f;
+            var b0 = new Vector2(20, 20);
+            var b1 = new Vector2(30, 20);
+            var bRadius = 2.0f;
+
+            var result = Collision2D.ContainsCapsuleCapsule(a0, a1, aRadius, b0, b1, bRadius);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCapsuleCapsule_PartialOverlap_ReturnsIntersects()
+        {
+            var a0 = new Vector2(0, 0);
+            var a1 = new Vector2(10, 0);
+            var aRadius = 3.0f;
+            var b0 = new Vector2(8, 0);
+            var b1 = new Vector2(15, 0);
+            var bRadius = 2.0f;
+
+            var result = Collision2D.ContainsCapsuleCapsule(a0, a1, aRadius, b0, b1, bRadius);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsCapsuleConvexPolygon Tests
+
+        [Fact]
+        public void ContainsCapsuleConvexPolygon_PolygonCompletelyInside_ReturnsContains()
+        {
+            var capsuleA = new Vector2(0, 10);
+            var capsuleB = new Vector2(20, 10);
+            var capsuleRadius = 8.0f;
+            var vertices = new[]
+            {
+                new Vector2(8, 8),
+                new Vector2(12, 8),
+                new Vector2(12, 12),
+                new Vector2(8, 12)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsCapsuleConvexPolygon(capsuleA, capsuleB, capsuleRadius, vertices, normals);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsCapsuleConvexPolygon_PolygonCompletelyOutside_ReturnsDisjoint()
+        {
+            var capsuleA = new Vector2(0, 0);
+            var capsuleB = new Vector2(10, 0);
+            var capsuleRadius = 2.0f;
+            var vertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(25, 20),
+                new Vector2(25, 25),
+                new Vector2(20, 25)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsCapsuleConvexPolygon(capsuleA, capsuleB, capsuleRadius, vertices, normals);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsCapsuleConvexPolygon_PolygonPartiallyInside_ReturnsIntersects()
+        {
+            var capsuleA = new Vector2(0, 5);
+            var capsuleB = new Vector2(10, 5);
+            var capsuleRadius = 3.0f;
+            var vertices = new[]
+            {
+                new Vector2(5, 5),
+                new Vector2(15, 5),
+                new Vector2(15, 15),
+                new Vector2(5, 15)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsCapsuleConvexPolygon(capsuleA, capsuleB, capsuleRadius, vertices, normals);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Containment - ConvexPolygon Methods
+
+        #region ContainsConvexPolygonPoint Tests
+
+        [Fact]
+        public void ContainsConvexPolygonPoint_PointInside_ReturnsContains()
+        {
+            var point = new Vector2(5, 5);
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsConvexPolygonPoint(point, vertices, normals);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsConvexPolygonPoint_PointOutside_ReturnsDisjoint()
+        {
+            var point = new Vector2(15, 15);
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsConvexPolygonPoint(point, vertices, normals);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsConvexPolygonPoint_PointOnEdge_ReturnsContains()
+        {
+            var point = new Vector2(10, 5);
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsConvexPolygonPoint(point, vertices, normals);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        #endregion
+
+        #region ContainsConvexPolygonCapsule Tests
+
+        [Fact]
+        public void ContainsConvexPolygonCapsule_CapsuleCompletelyInside_ReturnsContains()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(20, 0),
+                new Vector2(20, 20),
+                new Vector2(0, 20)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var capsuleA = new Vector2(8, 10);
+            var capsuleB = new Vector2(12, 10);
+            var capsuleRadius = 2.0f;
+
+            var result = Collision2D.ContainsConvexPolygonCapsule(vertices, normals, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsConvexPolygonCapsule_CapsuleCompletelyOutside_ReturnsDisjoint()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var capsuleA = new Vector2(20, 20);
+            var capsuleB = new Vector2(25, 25);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsConvexPolygonCapsule(vertices, normals, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsConvexPolygonCapsule_CapsulePartiallyInside_ReturnsIntersects()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var capsuleA = new Vector2(5, 5);
+            var capsuleB = new Vector2(15, 5);
+            var capsuleRadius = 1.0f;
+
+            var result = Collision2D.ContainsConvexPolygonCapsule(vertices, normals, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsConvexPolygonObb Tests
+
+        [Fact]
+        public void ContainsConvexPolygonObb_ObbCompletelyInside_ReturnsContains()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(20, 0),
+                new Vector2(20, 20),
+                new Vector2(0, 20)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var obbCenter = new Vector2(10, 10);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalf = new Vector2(3, 3);
+
+            var result = Collision2D.ContainsConvexPolygonObb(vertices, normals, obbCenter, obbAxisX, obbAxisY, obbHalf);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsConvexPolygonObb_ObbCompletelyOutside_ReturnsDisjoint()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var obbCenter = new Vector2(20, 20);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalf = new Vector2(2, 2);
+
+            var result = Collision2D.ContainsConvexPolygonObb(vertices, normals, obbCenter, obbAxisX, obbAxisY, obbHalf);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsConvexPolygonObb_ObbPartiallyInside_ReturnsIntersects()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var obbCenter = new Vector2(8, 8);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalf = new Vector2(5, 5);
+
+            var result = Collision2D.ContainsConvexPolygonObb(vertices, normals, obbCenter, obbAxisX, obbAxisY, obbHalf);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #region ContainsConvexPolygonConvexPolygon Tests
+
+        [Fact]
+        public void ContainsConvexPolygonConvexPolygon_CompletelyContains_ReturnsContains()
+        {
+            var aVertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(20, 0),
+                new Vector2(20, 20),
+                new Vector2(0, 20)
+            };
+            var aNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var bVertices = new[]
+            {
+                new Vector2(5, 5),
+                new Vector2(15, 5),
+                new Vector2(15, 15),
+                new Vector2(5, 15)
+            };
+            var bNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsConvexPolygonConvexPolygon(aVertices, aNormals, bVertices, bNormals);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsConvexPolygonConvexPolygon_Separated_ReturnsDisjoint()
+        {
+            var aVertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var aNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var bVertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(30, 20),
+                new Vector2(30, 30),
+                new Vector2(20, 30)
+            };
+            var bNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsConvexPolygonConvexPolygon(aVertices, aNormals, bVertices, bNormals);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsConvexPolygonConvexPolygon_PartialOverlap_ReturnsIntersects()
+        {
+            var aVertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var aNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var bVertices = new[]
+            {
+                new Vector2(5, 5),
+                new Vector2(15, 5),
+                new Vector2(15, 15),
+                new Vector2(5, 15)
+            };
+            var bNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            var result = Collision2D.ContainsConvexPolygonConvexPolygon(aVertices, aNormals, bVertices, bNormals);
+
+            Assert.Equal(ContainmentType.Intersects, result);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Projection Methods
+
+        #region ProjectOntoAxis Tests
+
+        [Fact]
+        public void ProjectOntoAxis_AlignedWithAxis_ReturnsCorrectInterval()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var axis = Vector2.UnitX;
+
+            Collision2D.ProjectOntoAxis(vertices, axis, out float min, out float max);
+
+            Assert.Equal(0.0f, min, Collision2D.Epsilon);
+            Assert.Equal(10.0f, max, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ProjectOntoAxis_PerpendicularToAxis_ReturnsNarrowInterval()
+        {
+            var vertices = new[]
+            {
+                new Vector2(5, 0),
+                new Vector2(5, 10)
+            };
+            var axis = Vector2.UnitX;
+
+            Collision2D.ProjectOntoAxis(vertices, axis, out float min, out float max);
+
+            Assert.Equal(5.0f, min, Collision2D.Epsilon);
+            Assert.Equal(5.0f, max, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ProjectOntoAxis_DiagonalAxis_ReturnsCorrectInterval()
+        {
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var axis = Vector2.Normalize(new Vector2(1, 1));
+
+            Collision2D.ProjectOntoAxis(vertices, axis, out float min, out float max);
+
+            float expected = 10.0f / MathF.Sqrt(2);
+            Assert.Equal(0.0f, min, Collision2D.Epsilon);
+            Assert.Equal(expected * 2, max, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region ProjectAabbOntoAxis Tests
+
+        [Fact]
+        public void ProjectAabbOntoAxis_AlignedAxis_ReturnsExtents()
+        {
+            var center = new Vector2(5, 5);
+            var halfExtents = new Vector2(5, 5);
+            var axis = Vector2.UnitX;
+
+            Collision2D.ProjectAabbOntoAxis(center, halfExtents, axis, out float min, out float max);
+
+            Assert.Equal(0.0f, min, Collision2D.Epsilon);
+            Assert.Equal(10.0f, max, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ProjectAabbOntoAxis_DiagonalAxis_ReturnsCorrectInterval()
+        {
+            var center = new Vector2(5, 5);
+            var halfExtents = new Vector2(5, 5);
+            var axis = Vector2.Normalize(new Vector2(1, 1));
+
+            Collision2D.ProjectAabbOntoAxis(center, halfExtents, axis, out float min, out float max);
+
+            float centerProj = 5.0f * MathF.Sqrt(2);
+            float radius = 5.0f * MathF.Sqrt(2);
+
+            Assert.Equal(centerProj - radius, min, Collision2D.Epsilon);
+            Assert.Equal(centerProj + radius, max, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region ProjectObbOntoAxis Tests
+
+        [Fact]
+        public void ProjectObbOntoAxis_AxisAlignedBox_MatchesAabbResult()
+        {
+            var center = new Vector2(5, 5);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(5, 5);
+            var projAxis = Vector2.UnitX;
+
+            Collision2D.ProjectObbOntoAxis(center, axisX, axisY, halfExtents, projAxis, out float min, out float max);
+
+            Assert.Equal(0.0f, min, Collision2D.Epsilon);
+            Assert.Equal(10.0f, max, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ProjectObbOntoAxis_RotatedBox_ReturnsCorrectInterval()
+        {
+            var center = new Vector2(0, 0);
+            var axisX = Vector2.Normalize(new Vector2(1, 1));
+            var axisY = Vector2.Normalize(new Vector2(-1, 1));
+            var halfExtents = new Vector2(5, 5);
+            var projAxis = Vector2.UnitX;
+
+            Collision2D.ProjectObbOntoAxis(center, axisX, axisY, halfExtents, projAxis, out float min, out float max);
+
+            Assert.Equal(min, -max, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Distance Calculations
+
+        #region DistanceSquaredPointSegment Tests
+
+        [Fact]
+        public void DistanceSquaredPointSegment_PointOnSegment_ReturnsZero()
+        {
+            var point = new Vector2(5, 0);
+            var a = new Vector2(0, 0);
+            var b = new Vector2(10, 0);
+
+            float distance = Collision2D.DistanceSquaredPointSegment(point, a, b, out float t, out Vector2 closestPoint);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+            Assert.Equal(0.5f, t, Collision2D.Epsilon);
+            Assert.Equal(point, closestPoint);
+        }
+
+        [Fact]
+        public void DistanceSquaredPointSegment_ClosestAtEndpoint_ClampsParameter()
+        {
+            var point = new Vector2(15, 5);
+            var a = new Vector2(0, 0);
+            var b = new Vector2(10, 0);
+
+            float distance = Collision2D.DistanceSquaredPointSegment(point, a, b, out float t, out Vector2 closestPoint);
+
+            Assert.Equal(50.0f, distance, Collision2D.Epsilon);
+            Assert.Equal(1.0f, t);
+            Assert.Equal(b, closestPoint);
+        }
+
+        [Fact]
+        public void DistanceSquaredPointSegment_PerpendicularDistance_ReturnsCorrectValue()
+        {
+            var point = new Vector2(5, 3);
+            var a = new Vector2(0, 0);
+            var b = new Vector2(10, 0);
+
+            float distance = Collision2D.DistanceSquaredPointSegment(point, a, b, out float t, out Vector2 closestPoint);
+
+            Assert.Equal(9.0f, distance, Collision2D.Epsilon);
+            Assert.Equal(0.5f, t, Collision2D.Epsilon);
+            Assert.Equal(new Vector2(5, 0), closestPoint);
+        }
+
+        [Fact]
+        public void DistanceSquaredPointSegment_DegenerateSegment_TreatsAsPoint()
+        {
+            var point = new Vector2(5, 5);
+            var a = new Vector2(0, 0);
+            var b = new Vector2(0, 0);
+
+            float distance = Collision2D.DistanceSquaredPointSegment(point, a, b, out float t, out Vector2 closestPoint);
+
+            Assert.Equal(50.0f, distance, Collision2D.Epsilon);
+            Assert.Equal(0.0f, t);
+            Assert.Equal(a, closestPoint);
+        }
+
+        #endregion
+
+        #region DistanceSquaredSegmentSegment Tests
+
+        [Fact]
+        public void DistanceSquaredSegmentSegment_IntersectingSegments_ReturnsZero()
+        {
+            var p1 = new Vector2(0, 5);
+            var q1 = new Vector2(10, 5);
+            var p2 = new Vector2(5, 0);
+            var q2 = new Vector2(5, 10);
+
+            float distance = Collision2D.DistanceSquaredSegmentSegment(p1, q1, p2, q2, out float s, out float t, out Vector2 c1, out Vector2 c2);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+            Assert.Equal(new Vector2(5, 5), c1);
+            Assert.Equal(new Vector2(5, 5), c2);
+        }
+
+        [Fact]
+        public void DistanceSquaredSegmentSegment_ParallelSegments_ReturnsCorrectDistance()
+        {
+            var p1 = new Vector2(0, 0);
+            var q1 = new Vector2(10, 0);
+            var p2 = new Vector2(0, 5);
+            var q2 = new Vector2(10, 5);
+
+            float distance = Collision2D.DistanceSquaredSegmentSegment(p1, q1, p2, q2, out float s, out float t, out Vector2 c1, out Vector2 c2);
+
+            Assert.Equal(25.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredSegmentSegment_SkewSegments_ReturnsCorrectDistance()
+        {
+            var p1 = new Vector2(0, 0);
+            var q1 = new Vector2(5, 0);
+            var p2 = new Vector2(0, 2);
+            var q2 = new Vector2(0, 5);
+
+            float distance = Collision2D.DistanceSquaredSegmentSegment(p1, q1, p2, q2, out float s, out float t, out Vector2 c1, out Vector2 c2);
+
+            Assert.Equal(4.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredSegmentSegment_DegenerateSegments_HandlesCorrectly()
+        {
+            var p1 = new Vector2(0, 0);
+            var q1 = new Vector2(0, 0);
+            var p2 = new Vector2(3, 4);
+            var q2 = new Vector2(3, 4);
+
+            float distance = Collision2D.DistanceSquaredSegmentSegment(p1, q1, p2, q2, out float s, out float t, out Vector2 c1, out Vector2 c2);
+
+            Assert.Equal(25.0f, distance, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region DistanceSquaredPointAabb Tests
+
+        [Fact]
+        public void DistanceSquaredPointAabb_PointInside_ReturnsZero()
+        {
+            var point = new Vector2(5, 5);
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+
+            float distance = Collision2D.DistanceSquaredPointAabb(point, min, max);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredPointAabb_ClosestToCorner_ReturnsCorrectDistance()
+        {
+            var point = new Vector2(15, 15);
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+
+            float distance = Collision2D.DistanceSquaredPointAabb(point, min, max);
+
+            Assert.Equal(50.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredPointAabb_ClosestToEdge_ReturnsCorrectDistance()
+        {
+            var point = new Vector2(5, 15);
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+
+            float distance = Collision2D.DistanceSquaredPointAabb(point, min, max);
+
+            Assert.Equal(25.0f, distance, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region DistanceSquaredPointObb Tests
+
+        [Fact]
+        public void DistanceSquaredPointObb_PointInside_ReturnsZero()
+        {
+            var point = new Vector2(5, 5);
+            var center = new Vector2(5, 5);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(3, 3);
+
+            float distance = Collision2D.DistanceSquaredPointObb(point, center, axisX, axisY, halfExtents);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredPointObb_PointOutside_ReturnsCorrectDistance()
+        {
+            var point = new Vector2(10, 5);
+            var center = new Vector2(5, 5);
+            var axisX = Vector2.UnitX;
+            var axisY = Vector2.UnitY;
+            var halfExtents = new Vector2(2, 2);
+
+            float distance = Collision2D.DistanceSquaredPointObb(point, center, axisX, axisY, halfExtents);
+
+            Assert.Equal(9.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredPointObb_RotatedBox_ReturnsCorrectDistance()
+        {
+            var point = new Vector2(10, 0);
+            var center = new Vector2(0, 0);
+            var axisX = Vector2.Normalize(new Vector2(1, 1));
+            var axisY = Vector2.Normalize(new Vector2(-1, 1));
+            var halfExtents = new Vector2(2, 2);
+
+            float distance = Collision2D.DistanceSquaredPointObb(point, center, axisX, axisY, halfExtents);
+
+            Assert.True(distance > 0.0f);
+        }
+
+        #endregion
+
+        #region DistanceSquaredPointConvexPolygon Tests
+
+        [Fact]
+        public void DistanceSquaredPointConvexPolygon_PointInside_ReturnsZero()
+        {
+            var point = new Vector2(5, 5);
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            float distance = Collision2D.DistanceSquaredPointConvexPolygon(point, vertices, normals);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredPointConvexPolygon_ClosestToEdge_ReturnsCorrectDistance()
+        {
+            var point = new Vector2(5, -3);
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            float distance = Collision2D.DistanceSquaredPointConvexPolygon(point, vertices, normals);
+
+            Assert.Equal(9.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredPointConvexPolygon_ClosestToVertex_ReturnsCorrectDistance()
+        {
+            var point = new Vector2(-3, -3);
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            float distance = Collision2D.DistanceSquaredPointConvexPolygon(point, vertices, normals);
+
+            Assert.Equal(18.0f, distance, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region DistanceSquaredSegmentAabb Tests
+
+        [Fact]
+        public void DistanceSquaredSegmentAabb_SegmentInsideBox_ReturnsZero()
+        {
+            var a = new Vector2(3, 3);
+            var b = new Vector2(7, 7);
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+
+            float distance = Collision2D.DistanceSquaredSegmentAabb(a, b, min, max);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredSegmentAabb_SegmentIntersectsBox_ReturnsZero()
+        {
+            var a = new Vector2(-5, 5);
+            var b = new Vector2(15, 5);
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+
+            float distance = Collision2D.DistanceSquaredSegmentAabb(a, b, min, max);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+        }
+
+
+        [Fact]
+        public void DistanceSquaredSegmentAabb_SegmentOutsideBox_ReturnsCorrectDistance()
+        {
+            var a = new Vector2(15, 5);
+            var b = new Vector2(20, 5);
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+
+            float distance = Collision2D.DistanceSquaredSegmentAabb(a, b, min, max);
+
+            Assert.Equal(25.0f, distance, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region DistanceSquaredSegmentConvexPolygon Tests
+
+        [Fact]
+        public void DistanceSquaredSegmentConvexPolygon_SegmentInside_ReturnsZero()
+        {
+            var a = new Vector2(3, 3);
+            var b = new Vector2(7, 7);
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            float distance = Collision2D.DistanceSquaredSegmentConvexPolygon(a, b, vertices, normals);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredSegmentConvexPolygon_SegmentIntersects_ReturnsZero()
+        {
+            var a = new Vector2(-5, 5);
+            var b = new Vector2(15, 5);
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            float distance = Collision2D.DistanceSquaredSegmentConvexPolygon(a, b, vertices, normals);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredSegmentConvexPolygon_SegmentOutside_ReturnsCorrectDistance()
+        {
+            var a = new Vector2(15, 5);
+            var b = new Vector2(20, 5);
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            float distance = Collision2D.DistanceSquaredSegmentConvexPolygon(a, b, vertices, normals);
+
+            Assert.Equal(25.0f, distance, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region ClosestPointRaySegment Tests
+
+        [Fact]
+        public void ClosestPointRaySegment_RayIntersectsSegment_ReturnsZeroDistance()
+        {
+            var rayOrigin = new Vector2(0, 5);
+            var rayDirection = Vector2.UnitX;
+            var segA = new Vector2(5, 0);
+            var segB = new Vector2(5, 10);
+
+            Collision2D.ClosestPointRaySegment(rayOrigin, rayDirection, segA, segB, out float sRay, out float tSeg, out float distanceSquared);
+
+            Assert.Equal(0.0f, distanceSquared, Collision2D.Epsilon);
+            Assert.Equal(5.0f, sRay, Collision2D.Epsilon);
+            Assert.Equal(0.5f, tSeg, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ClosestPointRaySegment_RayParallelToSegment_ReturnsCorrectDistance()
+        {
+            var rayOrigin = new Vector2(0, 0);
+            var rayDirection = Vector2.UnitX;
+            var segA = new Vector2(0, 5);
+            var segB = new Vector2(10, 5);
+
+            Collision2D.ClosestPointRaySegment(rayOrigin, rayDirection, segA, segB, out float sRay, out float tSeg, out float distanceSquared);
+
+            Assert.Equal(25.0f, distanceSquared, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ClosestPointRaySegment_RayMissesSegment_ReturnsCorrectDistance()
+        {
+            var rayOrigin = new Vector2(0, 0);
+            var rayDirection = Vector2.UnitX;
+            var segA = new Vector2(5, 5);
+            var segB = new Vector2(10, 5);
+
+            Collision2D.ClosestPointRaySegment(rayOrigin, rayDirection, segA, segB, out float sRay, out float tSeg, out float distanceSquared);
+
+            Assert.True(distanceSquared > 0.0f);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Parametric Solvers
+
+        #region SolveParametricIntersection2D Tests
+
+        [Fact]
+        public void SolveParametricIntersection2D_PerpendicularLines_FindsIntersection()
+        {
+            var origin1 = new Vector2(0, 5);
+            var direction1 = Vector2.UnitX;
+            var origin2 = new Vector2(5, 0);
+            var direction2 = Vector2.UnitY;
+
+            bool result = Collision2D.SolveParametricIntersection2D(origin1, direction1, origin2, direction2, out float t1, out float t2);
+
+            Assert.True(result);
+            Assert.Equal(5.0f, t1, Collision2D.Epsilon);
+            Assert.Equal(5.0f, t2, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void SolveParametricIntersection2D_ParallelLines_ReturnsFalse()
+        {
+            var origin1 = new Vector2(0, 0);
+            var direction1 = Vector2.UnitX;
+            var origin2 = new Vector2(0, 5);
+            var direction2 = Vector2.UnitX;
+
+            bool result = Collision2D.SolveParametricIntersection2D(origin1, direction1, origin2, direction2, out float t1, out float t2);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void SolveParametricIntersection2D_CollinearLines_ReturnsFalse()
+        {
+            var origin1 = new Vector2(0, 0);
+            var direction1 = Vector2.UnitX;
+            var origin2 = new Vector2(5, 0);
+            var direction2 = Vector2.UnitX;
+
+            bool result = Collision2D.SolveParametricIntersection2D(origin1, direction1, origin2, direction2, out float t1, out float t2);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void SolveParametricIntersection2D_LinesAtAnAngle_FindsIntersection()
+        {
+            var origin1 = new Vector2(0, 0);
+            var direction1 = new Vector2(1, 0);
+            var origin2 = new Vector2(0, 0);
+            var direction2 = new Vector2(0, 1);
+
+            bool result = Collision2D.SolveParametricIntersection2D(origin1, direction1, origin2, direction2, out float t1, out float t2);
+
+            Assert.True(result);
+            Assert.Equal(0.0f, t1, Collision2D.Epsilon);
+            Assert.Equal(0.0f, t2, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region SolveParametricIntersectionWithImplicitLine Tests
+
+        [Fact]
+        public void SolveParametricIntersectionWithImplicitLine_Intersecting_FindsParameter()
+        {
+            var lineNormal = Vector2.UnitY;
+            var lineDistance = 5.0f;
+            var origin = new Vector2(0, 0);
+            var direction = Vector2.UnitY;
+
+            bool result = Collision2D.SolveParametricIntersectionWithImplicitLine(lineNormal, lineDistance, origin, direction, out float t);
+
+            Assert.True(result);
+            Assert.Equal(5.0f, t, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void SolveParametricIntersectionWithImplicitLine_Parallel_ReturnsFalse()
+        {
+            var lineNormal = Vector2.UnitY;
+            var lineDistance = 5.0f;
+            var origin = new Vector2(0, 0);
+            var direction = Vector2.UnitX;
+
+            bool result = Collision2D.SolveParametricIntersectionWithImplicitLine(lineNormal, lineDistance, origin, direction, out float t);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void SolveParametricIntersectionWithImplicitLine_Perpendicular_FindsParameter()
+        {
+            var lineNormal = Vector2.UnitX;
+            var lineDistance = 10.0f;
+            var origin = new Vector2(0, 0);
+            var direction = Vector2.UnitX;
+
+            bool result = Collision2D.SolveParametricIntersectionWithImplicitLine(lineNormal, lineDistance, origin, direction, out float t);
+
+            Assert.True(result);
+            Assert.Equal(10.0f, t, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Clipping Methods
+
+        #region ClipLineToAabb Tests
+
+        [Fact]
+        public void ClipLineToAabb_LineCompletelyInside_ReturnsFullInterval()
+        {
+            var origin = new Vector2(5, 5);
+            var direction = Vector2.UnitX;
+            var min = new Vector2(0, 0);
+            var max = new Vector2(20, 20);
+            float tLower = 0.0f;
+            float tUpper = 10.0f;
+
+            bool result = Collision2D.ClipLineToAabb(origin, direction, min, max, tLower, tUpper, out float tEnter, out float tExit);
+
+            Assert.True(result);
+            Assert.Equal(0.0f, tEnter, Collision2D.Epsilon);
+            Assert.Equal(10.0f, tExit, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ClipLineToAabb_LinePartiallyInside_ReturnsClippedInterval()
+        {
+            var origin = new Vector2(-5, 5);
+            var direction = Vector2.UnitX;
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+            float tLower = 0.0f;
+            float tUpper = 20.0f;
+
+            bool result = Collision2D.ClipLineToAabb(origin, direction, min, max, tLower, tUpper, out float tEnter, out float tExit);
+
+            Assert.True(result);
+            Assert.Equal(5.0f, tEnter, Collision2D.Epsilon);
+            Assert.Equal(15.0f, tExit, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ClipLineToAabb_LineMissesBox_ReturnsFalse()
+        {
+            var origin = new Vector2(0, 20);
+            var direction = Vector2.UnitX;
+            var min = new Vector2(0, 0);
+            var max = new Vector2(10, 10);
+            float tLower = 0.0f;
+            float tUpper = 10.0f;
+
+            bool result = Collision2D.ClipLineToAabb(origin, direction, min, max, tLower, tUpper, out float tEnter, out float tExit);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void ClipLineToAabb_LineThroughCorner_ReturnsInterval()
+        {
+            var origin = new Vector2(0, 0);
+            var direction = Vector2.Normalize(new Vector2(1, 1));
+            var min = new Vector2(5, 5);
+            var max = new Vector2(15, 15);
+            float tLower = 0.0f;
+            float tUpper = 50.0f;
+
+            bool result = Collision2D.ClipLineToAabb(origin, direction, min, max, tLower, tUpper, out float tEnter, out float tExit);
+
+            Assert.True(result);
+        }
+
+        #endregion
+
+        #region ClipLineToConvexPolygon Tests
+
+        [Fact]
+        public void ClipLineToConvexPolygon_LineCompletelyInside_ReturnsFullInterval()
+        {
+            var origin = new Vector2(5, 5);
+            var direction = Vector2.UnitX;
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(20, 0),
+                new Vector2(20, 20),
+                new Vector2(0, 20)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            float tLower = 0.0f;
+            float tUpper = 10.0f;
+
+            bool result = Collision2D.ClipLineToConvexPolygon(origin, direction, vertices, normals, tLower, tUpper, out float tEnter, out float tExit);
+
+            Assert.True(result);
+            Assert.Equal(0.0f, tEnter, Collision2D.Epsilon);
+            Assert.Equal(10.0f, tExit, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ClipLineToConvexPolygon_LinePartiallyInside_ReturnsClippedInterval()
+        {
+            var origin = new Vector2(-5, 5);
+            var direction = Vector2.UnitX;
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            float tLower = 0.0f;
+            float tUpper = 20.0f;
+
+            bool result = Collision2D.ClipLineToConvexPolygon(origin, direction, vertices, normals, tLower, tUpper, out float tEnter, out float tExit);
+
+            Assert.True(result);
+            Assert.Equal(5.0f, tEnter, Collision2D.Epsilon);
+            Assert.Equal(15.0f, tExit, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ClipLineToConvexPolygon_LineMissesPolygon_ReturnsFalse()
+        {
+            var origin = new Vector2(0, 20);
+            var direction = Vector2.UnitX;
+            var vertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            float tLower = 0.0f;
+            float tUpper = 10.0f;
+
+            bool result = Collision2D.ClipLineToConvexPolygon(origin, direction, vertices, normals, tLower, tUpper, out float tEnter, out float tExit);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Ray Interval Methods
+
+        #region RayCircleIntersectionInterval Tests
+
+        [Fact]
+        public void RayCircleIntersectionInterval_TwoIntersections_ReturnsBothParameters()
+        {
+            var origin = new Vector2(-10, 0);
+            var direction = Vector2.UnitX;
+            var center = new Vector2(0, 0);
+            var radius = 5.0f;
+
+            bool result = Collision2D.RayCircleIntersectionInterval(origin, direction, center, radius, out float tMin, out float tMax);
+
+            Assert.True(result);
+            Assert.Equal(5.0f, tMin, Collision2D.Epsilon);
+            Assert.Equal(15.0f, tMax, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void RayCircleIntersectionInterval_Tangent_ReturnsSinglePoint()
+        {
+            var origin = new Vector2(-10, 5);
+            var direction = Vector2.UnitX;
+            var center = new Vector2(0, 0);
+            var radius = 5.0f;
+
+            bool result = Collision2D.RayCircleIntersectionInterval(origin, direction, center, radius, out float tMin, out float tMax);
+
+            Assert.True(result);
+            Assert.Equal(tMin, tMax, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void RayCircleIntersectionInterval_Miss_ReturnsFalse()
+        {
+            var origin = new Vector2(-10, 10);
+            var direction = Vector2.UnitX;
+            var center = new Vector2(0, 0);
+            var radius = 5.0f;
+
+            bool result = Collision2D.RayCircleIntersectionInterval(origin, direction, center, radius, out float tMin, out float tMax);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void RayCircleIntersectionInterval_OriginInside_ReturnsIntervalFromZero()
+        {
+            var origin = new Vector2(0, 0);
+            var direction = Vector2.UnitX;
+            var center = new Vector2(0, 0);
+            var radius = 5.0f;
+
+            bool result = Collision2D.RayCircleIntersectionInterval(origin, direction, center, radius, out float tMin, out float tMax);
+
+            Assert.True(result);
+            Assert.Equal(0.0f, tMin, Collision2D.Epsilon);
+            Assert.Equal(5.0f, tMax, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region RayCapsuleIntersectionInterval Tests
+
+        [Fact]
+        public void RayCapsuleIntersectionInterval_HitsCylinder_ReturnsInterval()
+        {
+            var rayOrigin = new Vector2(-10, 0);
+            var rayDirection = Vector2.UnitX;
+            var segA = new Vector2(0, -5);
+            var segB = new Vector2(0, 5);
+            var radius = 3.0f;
+
+            bool result = Collision2D.RayCapsuleIntersectionInterval(rayOrigin, rayDirection, segA, segB, radius, out float tMin, out float tMax);
+
+            Assert.True(result);
+            Assert.True(tMax > tMin);
+        }
+
+        [Fact]
+        public void RayCapsuleIntersectionInterval_HitsEndCap_ReturnsInterval()
+        {
+            var rayOrigin = new Vector2(-10, 10);
+            var rayDirection = Vector2.UnitX;
+            var segA = new Vector2(0, 10);
+            var segB = new Vector2(10, 10);
+            var radius = 3.0f;
+
+            bool result = Collision2D.RayCapsuleIntersectionInterval(rayOrigin, rayDirection, segA, segB, radius, out float tMin, out float tMax);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void RayCapsuleIntersectionInterval_Miss_ReturnsFalse()
+        {
+            var rayOrigin = new Vector2(-10, 20);
+            var rayDirection = Vector2.UnitX;
+            var segA = new Vector2(0, 0);
+            var segB = new Vector2(10, 0);
+            var radius = 3.0f;
+
+            bool result = Collision2D.RayCapsuleIntersectionInterval(rayOrigin, rayDirection, segA, segB, radius, out float tMin, out float tMax);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void RayCapsuleIntersectionInterval_OriginInside_ReturnsIntervalFromZero()
+        {
+            var rayOrigin = new Vector2(5, 5);
+            var rayDirection = Vector2.UnitX;
+            var segA = new Vector2(0, 5);
+            var segB = new Vector2(10, 5);
+            var radius = 3.0f;
+
+            bool result = Collision2D.RayCapsuleIntersectionInterval(rayOrigin, rayDirection, segA, segB, radius, out float tMin, out float tMax);
+
+            Assert.True(result);
+            Assert.Equal(0.0f, tMin, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void RayCapsuleIntersectionInterval_DegenerateCapsule_BehavesLikeCircle()
+        {
+            var rayOrigin = new Vector2(-10, 0);
+            var rayDirection = Vector2.UnitX;
+            var segA = new Vector2(0, 0);
+            var segB = new Vector2(0, 0);
+            var radius = 5.0f;
+
+            bool result = Collision2D.RayCapsuleIntersectionInterval(rayOrigin, rayDirection, segA, segB, radius, out float tMin, out float tMax);
+
+            Assert.True(result);
+            Assert.Equal(5.0f, tMin, Collision2D.Epsilon);
+            Assert.Equal(15.0f, tMax, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Overlap Methods
+
+        #region OverlapOnAxis Tests
+
+        [Fact]
+        public void OverlapOnAxis_TwoPolygonsOverlapping_ReturnsTrue()
+        {
+            var aVerts = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var bVerts = new[]
+            {
+                new Vector2(5, 5),
+                new Vector2(15, 5),
+                new Vector2(15, 15),
+                new Vector2(5, 15)
+            };
+            var axis = Vector2.UnitX;
+
+            bool result = Collision2D.OverlapOnAxis(aVerts, bVerts, axis);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void OverlapOnAxis_TwoPolygonsSeparated_ReturnsFalse()
+        {
+            var aVerts = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var bVerts = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(30, 20),
+                new Vector2(30, 30),
+                new Vector2(20, 30)
+            };
+            var axis = Vector2.UnitX;
+
+            bool result = Collision2D.OverlapOnAxis(aVerts, bVerts, axis);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void OverlapOnAxis_PolygonsTouching_ReturnsTrue()
+        {
+            var aVerts = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var bVerts = new[]
+            {
+                new Vector2(10, 0),
+                new Vector2(20, 0),
+                new Vector2(20, 10),
+                new Vector2(10, 10)
+            };
+            var axis = Vector2.UnitX;
+
+            bool result = Collision2D.OverlapOnAxis(aVerts, bVerts, axis);
+
+            Assert.True(result);
+        }
+
+        #endregion
+
+        #region OverlapOnAxisAabbPolygon Tests
+
+        [Fact]
+        public void OverlapOnAxisAabbPolygon_Overlapping_ReturnsTrue()
+        {
+            var aabbCenter = new Vector2(5, 5);
+            var aabbHalfExtents = new Vector2(5, 5);
+            var polygonVertices = new[]
+            {
+                new Vector2(8, 8),
+                new Vector2(15, 8),
+                new Vector2(15, 15),
+                new Vector2(8, 15)
+            };
+            var axis = Vector2.UnitX;
+
+            bool result = Collision2D.OverlapOnAxis(aabbCenter, aabbHalfExtents, polygonVertices, axis);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void OverlapOnAxisAabbPolygon_Separated_ReturnsFalse()
+        {
+            var aabbCenter = new Vector2(5, 5);
+            var aabbHalfExtents = new Vector2(5, 5);
+            var polygonVertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(30, 20),
+                new Vector2(30, 30),
+                new Vector2(20, 30)
+            };
+            var axis = Vector2.UnitX;
+
+            bool result = Collision2D.OverlapOnAxis(aabbCenter, aabbHalfExtents, polygonVertices, axis);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #endregion
+
+        #region Intersection Methods
+
+        #region IntersectsAabbAabb Tests
+
+        [Fact]
+        public void IntersectsAabbAabb_Overlapping_ReturnsTrue()
+        {
+            var aMin = new Vector2(0, 0);
+            var aMax = new Vector2(10, 10);
+            var bMin = new Vector2(5, 5);
+            var bMax = new Vector2(15, 15);
+
+            bool result = Collision2D.IntersectsAabbAabb(aMin, aMax, bMin, bMax);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsAabbAabb_Separated_ReturnsFalse()
+        {
+            var aMin = new Vector2(0, 0);
+            var aMax = new Vector2(10, 10);
+            var bMin = new Vector2(20, 20);
+            var bMax = new Vector2(30, 30);
+
+            bool result = Collision2D.IntersectsAabbAabb(aMin, aMax, bMin, bMax);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsAabbCapsule Tests
+
+        [Fact]
+        public void IntersectsAabbCapsule_Overlapping_ReturnsTrue()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var capsuleA = new Vector2(5, 5);
+            var capsuleB = new Vector2(15, 5);
+            var capsuleRadius = 2.0f;
+
+            bool result = Collision2D.IntersectsAabbCapsule(boxMin, boxMax, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsAabbCapsule_Separated_ReturnsFalse()
+        {
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+            var capsuleA = new Vector2(20, 20);
+            var capsuleB = new Vector2(30, 30);
+            var capsuleRadius = 2.0f;
+
+            bool result = Collision2D.IntersectsAabbCapsule(boxMin, boxMax, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsAabbConvexPolygon Tests
+
+        [Fact]
+        public void IntersectsAabbConvexPolygon_Overlapping_ReturnsTrue()
+        {
+            var aabbCenter = new Vector2(5, 5);
+            var aabbHalfExtents = new Vector2(5, 5);
+            var vertices = new[]
+            {
+                new Vector2(8, 8),
+                new Vector2(15, 8),
+                new Vector2(15, 15),
+                new Vector2(8, 15)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsAabbConvexPolygon(aabbCenter, aabbHalfExtents, vertices, normals);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsAabbConvexPolygon_Separated_ReturnsFalse()
+        {
+            var aabbCenter = new Vector2(5, 5);
+            var aabbHalfExtents = new Vector2(5, 5);
+            var vertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(30, 20),
+                new Vector2(30, 30),
+                new Vector2(20, 30)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsAabbConvexPolygon(aabbCenter, aabbHalfExtents, vertices, normals);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsAabbObb Tests
+
+        [Fact]
+        public void IntersectsAabbObb_Overlapping_ReturnsTrue()
+        {
+            var aabbCenter = new Vector2(5, 5);
+            var aabbHalfExtents = new Vector2(5, 5);
+            var obbCenter = new Vector2(8, 8);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(3, 3);
+
+            bool result = Collision2D.IntersectsAabbObb(aabbCenter, aabbHalfExtents, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsAabbObb_Separated_ReturnsFalse()
+        {
+            var aabbCenter = new Vector2(5, 5);
+            var aabbHalfExtents = new Vector2(5, 5);
+            var obbCenter = new Vector2(20, 20);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(3, 3);
+
+            bool result = Collision2D.IntersectsAabbObb(aabbCenter, aabbHalfExtents, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsCircleCircle Tests
+
+        [Fact]
+        public void IntersectsCircleCircle_Overlapping_ReturnsTrue()
+        {
+            var aCenter = new Vector2(0, 0);
+            var aRadius = 5.0f;
+            var bCenter = new Vector2(8, 0);
+            var bRadius = 5.0f;
+
+            bool result = Collision2D.IntersectsCircleCircle(aCenter, aRadius, bCenter, bRadius);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsCircleCircle_Separated_ReturnsFalse()
+        {
+            var aCenter = new Vector2(0, 0);
+            var aRadius = 5.0f;
+            var bCenter = new Vector2(20, 0);
+            var bRadius = 5.0f;
+
+            bool result = Collision2D.IntersectsCircleCircle(aCenter, aRadius, bCenter, bRadius);
+
+            Assert.False(result);
+        }
+
+        [Fact]
+        public void IntersectsCircleCircle_Touching_ReturnsTrue()
+        {
+            var aCenter = new Vector2(0, 0);
+            var aRadius = 5.0f;
+            var bCenter = new Vector2(10, 0);
+            var bRadius = 5.0f;
+
+            bool result = Collision2D.IntersectsCircleCircle(aCenter, aRadius, bCenter, bRadius);
+
+            Assert.True(result);
+        }
+
+        #endregion
+
+        #region IntersectsCircleAabb Tests
+
+        [Fact]
+        public void IntersectsCircleAabb_Overlapping_ReturnsTrue()
+        {
+            var circleCenter = new Vector2(5, 5);
+            var circleRadius = 5.0f;
+            var boxMin = new Vector2(0, 0);
+            var boxMax = new Vector2(10, 10);
+
+            bool result = Collision2D.IntersectsCircleAabb(circleCenter, circleRadius, boxMin, boxMax);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsCircleAabb_Separated_ReturnsFalse()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var boxMin = new Vector2(20, 20);
+            var boxMax = new Vector2(30, 30);
+
+            bool result = Collision2D.IntersectsCircleAabb(circleCenter, circleRadius, boxMin, boxMax);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsCircleObb Tests
+
+        [Fact]
+        public void IntersectsCircleObb_Overlapping_ReturnsTrue()
+        {
+            var circleCenter = new Vector2(5, 5);
+            var circleRadius = 5.0f;
+            var obbCenter = new Vector2(8, 8);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(3, 3);
+
+            bool result = Collision2D.IntersectsCircleObb(circleCenter, circleRadius, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsCircleObb_Separated_ReturnsFalse()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var obbCenter = new Vector2(20, 20);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(3, 3);
+
+            bool result = Collision2D.IntersectsCircleObb(circleCenter, circleRadius, obbCenter, obbAxisX, obbAxisY, obbHalfExtents);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsCircleConvexPolygon Tests
+
+        [Fact]
+        public void IntersectsCircleConvexPolygon_Overlapping_ReturnsTrue()
+        {
+            var circleCenter = new Vector2(5, 5);
+            var circleRadius = 5.0f;
+            var vertices = new[]
+            {
+                new Vector2(8, 8),
+                new Vector2(15, 8),
+                new Vector2(15, 15),
+                new Vector2(8, 15)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsCircleConvexPolygon(circleCenter, circleRadius, vertices, normals);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsCircleConvexPolygon_Separated_ReturnsFalse()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var vertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(30, 20),
+                new Vector2(30, 30),
+                new Vector2(20, 30)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsCircleConvexPolygon(circleCenter, circleRadius, vertices, normals);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsCircleCapsule Tests
+
+        [Fact]
+        public void IntersectsCircleCapsule_Overlapping_ReturnsTrue()
+        {
+            var circleCenter = new Vector2(5, 5);
+            var circleRadius = 5.0f;
+            var capsuleA = new Vector2(8, 8);
+            var capsuleB = new Vector2(15, 8);
+            var capsuleRadius = 2.0f;
+
+            bool result = Collision2D.IntersectsCircleCapsule(circleCenter, circleRadius, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsCircleCapsule_Separated_ReturnsFalse()
+        {
+            var circleCenter = new Vector2(0, 0);
+            var circleRadius = 5.0f;
+            var capsuleA = new Vector2(20, 20);
+            var capsuleB = new Vector2(30, 30);
+            var capsuleRadius = 2.0f;
+
+            bool result = Collision2D.IntersectsCircleCapsule(circleCenter, circleRadius, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsObbObb Tests
+
+        [Fact]
+        public void IntersectsObbObb_Overlapping_ReturnsTrue()
+        {
+            var aCenter = new Vector2(5, 5);
+            var aAxisX = Vector2.UnitX;
+            var aAxisY = Vector2.UnitY;
+            var aHalf = new Vector2(5, 5);
+            var bCenter = new Vector2(8, 8);
+            var bAxisX = Vector2.UnitX;
+            var bAxisY = Vector2.UnitY;
+            var bHalf = new Vector2(3, 3);
+
+            bool result = Collision2D.IntersectsObbObb(aCenter, aAxisX, aAxisY, aHalf, bCenter, bAxisX, bAxisY, bHalf);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsObbObb_Separated_ReturnsFalse()
+        {
+            var aCenter = new Vector2(0, 0);
+            var aAxisX = Vector2.UnitX;
+            var aAxisY = Vector2.UnitY;
+            var aHalf = new Vector2(5, 5);
+            var bCenter = new Vector2(20, 20);
+            var bAxisX = Vector2.UnitX;
+            var bAxisY = Vector2.UnitY;
+            var bHalf = new Vector2(3, 3);
+
+            bool result = Collision2D.IntersectsObbObb(aCenter, aAxisX, aAxisY, aHalf, bCenter, bAxisX, bAxisY, bHalf);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsObbCapsule Tests
+
+        [Fact]
+        public void IntersectsObbCapsule_Overlapping_ReturnsTrue()
+        {
+            var obbCenter = new Vector2(5, 5);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(5, 5);
+            var capsuleA = new Vector2(8, 8);
+            var capsuleB = new Vector2(15, 8);
+            var capsuleRadius = 2.0f;
+
+            bool result = Collision2D.IntersectsObbCapsule(obbCenter, obbAxisX, obbAxisY, obbHalfExtents, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsObbCapsule_Separated_ReturnsFalse()
+        {
+            var obbCenter = new Vector2(0, 0);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(5, 5);
+            var capsuleA = new Vector2(20, 20);
+            var capsuleB = new Vector2(30, 30);
+            var capsuleRadius = 2.0f;
+
+            bool result = Collision2D.IntersectsObbCapsule(obbCenter, obbAxisX, obbAxisY, obbHalfExtents, capsuleA, capsuleB, capsuleRadius);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsObbConvexPolygon Tests
+
+        [Fact]
+        public void IntersectsObbConvexPolygon_Overlapping_ReturnsTrue()
+        {
+            var obbCenter = new Vector2(5, 5);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(5, 5);
+            var vertices = new[]
+            {
+                new Vector2(8, 8),
+                new Vector2(15, 8),
+                new Vector2(15, 15),
+                new Vector2(8, 15)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsObbConvexPolygon(obbCenter, obbAxisX, obbAxisY, obbHalfExtents, vertices, normals);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsObbConvexPolygon_Separated_ReturnsFalse()
+        {
+            var obbCenter = new Vector2(0, 0);
+            var obbAxisX = Vector2.UnitX;
+            var obbAxisY = Vector2.UnitY;
+            var obbHalfExtents = new Vector2(5, 5);
+            var vertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(30, 20),
+                new Vector2(30, 30),
+                new Vector2(20, 30)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsObbConvexPolygon(obbCenter, obbAxisX, obbAxisY, obbHalfExtents, vertices, normals);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsCapsuleCapsule Tests
+
+        [Fact]
+        public void IntersectsCapsuleCapsule_Overlapping_ReturnsTrue()
+        {
+            var a0 = new Vector2(0, 0);
+            var a1 = new Vector2(10, 0);
+            var aRadius = 3.0f;
+            var b0 = new Vector2(8, 0);
+            var b1 = new Vector2(15, 0);
+            var bRadius = 3.0f;
+
+            bool result = Collision2D.IntersectsCapsuleCapsule(a0, a1, aRadius, b0, b1, bRadius);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsCapsuleCapsule_Separated_ReturnsFalse()
+        {
+            var a0 = new Vector2(0, 0);
+            var a1 = new Vector2(10, 0);
+            var aRadius = 2.0f;
+            var b0 = new Vector2(20, 20);
+            var b1 = new Vector2(30, 30);
+            var bRadius = 2.0f;
+
+            bool result = Collision2D.IntersectsCapsuleCapsule(a0, a1, aRadius, b0, b1, bRadius);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsCapsuleConvexPolygon Tests
+
+        [Fact]
+        public void IntersectsCapsuleConvexPolygon_Overlapping_ReturnsTrue()
+        {
+            var capsuleA = new Vector2(5, 5);
+            var capsuleB = new Vector2(15, 5);
+            var capsuleRadius = 3.0f;
+            var vertices = new[]
+            {
+                new Vector2(8, 8),
+                new Vector2(15, 8),
+                new Vector2(15, 15),
+                new Vector2(8, 15)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsCapsuleConvexPolygon(capsuleA, capsuleB, capsuleRadius, vertices, normals);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsCapsuleConvexPolygon_Separated_ReturnsFalse()
+        {
+            var capsuleA = new Vector2(0, 0);
+            var capsuleB = new Vector2(10, 0);
+            var capsuleRadius = 2.0f;
+            var vertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(30, 20),
+                new Vector2(30, 30),
+                new Vector2(20, 30)
+            };
+            var normals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsCapsuleConvexPolygon(capsuleA, capsuleB, capsuleRadius, vertices, normals);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #region IntersectsConvexPolygonConvexPolygon Tests
+
+        [Fact]
+        public void IntersectsConvexPolygonConvexPolygon_Overlapping_ReturnsTrue()
+        {
+            var aVertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var aNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var bVertices = new[]
+            {
+                new Vector2(5, 5),
+                new Vector2(15, 5),
+                new Vector2(15, 15),
+                new Vector2(5, 15)
+            };
+            var bNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsConvexPolygonConvexPolygon(aVertices, aNormals, bVertices, bNormals);
+
+            Assert.True(result);
+        }
+
+        [Fact]
+        public void IntersectsConvexPolygonConvexPolygon_Separated_ReturnsFalse()
+        {
+            var aVertices = new[]
+            {
+                new Vector2(0, 0),
+                new Vector2(10, 0),
+                new Vector2(10, 10),
+                new Vector2(0, 10)
+            };
+            var aNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+            var bVertices = new[]
+            {
+                new Vector2(20, 20),
+                new Vector2(30, 20),
+                new Vector2(30, 30),
+                new Vector2(20, 30)
+            };
+            var bNormals = new[]
+            {
+                new Vector2(0, -1),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0)
+            };
+
+            bool result = Collision2D.IntersectsConvexPolygonConvexPolygon(aVertices, aNormals, bVertices, bNormals);
+
+            Assert.False(result);
+        }
+
+        #endregion
+
+        #endregion
+    }
+}

+ 175 - 0
tests/MonoGame.Extended.Tests/LIne2DTest.cs

@@ -0,0 +1,175 @@
+// MonoGame - Copyright (C) MonoGame Foundation, Inc
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+    public sealed class Line2DTest
+    {
+        #region Constructor Tests
+
+        [Fact]
+        public void Constructor()
+        {
+            var normal = new Vector2(0, 1);
+            var distance = 5.0f;
+
+            var line = new Line2D(normal, distance);
+
+            Assert.Equal(normal, line.Normal);
+            Assert.Equal(distance, line.Distance);
+        }
+
+        #endregion
+
+        #region Factory Method Tests
+
+        [Fact]
+        public void CreateFromPointAndNormal()
+        {
+            var point = new Vector2(0, 5);
+            var normal = new Vector2(0, 1);
+
+            var line = Line2D.CreateFromPointAndNormal(point, normal);
+
+            Assert.Equal(new Vector2(0, 1), line.Normal);
+            Assert.Equal(5.0f, line.Distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void CreateFromTwoPoints()
+        {
+            var p1 = new Vector2(0, 0);
+            var p2 = new Vector2(10, 0);
+
+            var line = Line2D.CreateFromTwoPoints(p1, p2);
+
+            Assert.Equal(0, MathF.Abs(line.Normal.X), Collision2D.Epsilon);
+            Assert.Equal(1, MathF.Abs(line.Normal.Y), Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void CreateFromTwoPoints_ThrowsWhenPointsTooClose()
+        {
+            var p1 = new Vector2(0, 0);
+            var p2 = new Vector2(1e-7f, 0);
+
+            Assert.Throws<ArgumentException>(() => Line2D.CreateFromTwoPoints(p1, p2));
+        }
+
+        [Fact]
+        public void CreateFromPointAndDirection()
+        {
+            var point = new Vector2(5, 0);
+            var direction = new Vector2(1, 0);
+
+            var line = Line2D.CreateFromPointAndDirection(point, direction);
+
+            Assert.Equal(0, MathF.Abs(line.Normal.X), Collision2D.Epsilon);
+            Assert.Equal(1, MathF.Abs(line.Normal.Y), Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region Distance and Projection Tests (Delegation Spot Checks)
+
+        [Fact]
+        public void DistanceToPoint_PointOnLine()
+        {
+            var line = new Line2D(new Vector2(0, 1), 5);
+            var point = new Vector2(0, 5);
+
+            float distance = line.DistanceToPoint(point);
+
+            Assert.Equal(0.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceToPoint_PointOffLine()
+        {
+            var line = new Line2D(new Vector2(0, 1), 5);
+            var point = new Vector2(0, 8);
+
+            float distance = line.DistanceToPoint(point);
+
+            Assert.Equal(3.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ClosestPoint_ReturnsProjection()
+        {
+            var line = new Line2D(new Vector2(0, 1), 5);
+            var point = new Vector2(3, 8);
+
+            var closest = line.ClosestPoint(point, out float distanceAlongLine);
+
+            Assert.Equal(new Vector2(3, 5), closest);
+        }
+
+        #endregion
+
+        #region Normalize Tests
+
+        [Fact]
+        public void Normalize_Static_CreatesUnitNormal()
+        {
+            var line = new Line2D(new Vector2(3, 4), 10);
+
+            var normalized = Line2D.Normalize(line);
+
+            Assert.Equal(1.0f, normalized.Normal.Length(), Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Normalize_StaticRef_CreatesUnitNormal()
+        {
+            var line = new Line2D(new Vector2(3, 4), 10);
+
+            Line2D.Normalize(ref line, out var normalized);
+
+            Assert.Equal(1.0f, normalized.Normal.Length(), Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Normalize_Instance_ModifiesInPlace()
+        {
+            var line = new Line2D(new Vector2(3, 4), 10);
+
+            line.Normalize();
+
+            Assert.Equal(1.0f, line.Normal.Length(), Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Normalize_AlreadyNormalized_RemainsUnchanged()
+        {
+            var line = new Line2D(new Vector2(0, 1), 5);
+            var original = line;
+
+            line.Normalize();
+
+            Assert.Equal(original.Normal, line.Normal);
+            Assert.Equal(original.Distance, line.Distance);
+        }
+
+        #endregion
+
+        #region Deconstruct Test
+
+        [Fact]
+        public void Deconstruct()
+        {
+            var line = new Line2D(new Vector2(0, 1), 5);
+
+            var (normal, distance) = line;
+
+            Assert.Equal(new Vector2(0, 1), normal);
+            Assert.Equal(5.0f, distance);
+        }
+
+        #endregion
+    }
+}

+ 272 - 0
tests/MonoGame.Extended.Tests/LineSegment2DTest.cs

@@ -0,0 +1,272 @@
+// MonoGame - Copyright (C) MonoGame Foundation, Inc
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+    public sealed class LineSegment2DTest
+    {
+        #region Constructor Tests
+
+        [Fact]
+        public void Constructor()
+        {
+            var start = new Vector2(1, 2);
+            var end = new Vector2(3, 4);
+
+            var segment = new LineSegment2D(start, end);
+
+            Assert.Equal(start, segment.Start);
+            Assert.Equal(end, segment.End);
+        }
+
+        #endregion
+
+        #region Computed Property Tests
+
+        [Fact]
+        public void Direction_ReturnsVector()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(3, 4));
+
+            var direction = segment.Direction;
+
+            Assert.Equal(new Vector2(3, 4), direction);
+        }
+
+        [Fact]
+        public void Midpoint_ReturnsCenterPoint()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 10));
+
+            var midpoint = segment.Midpoint;
+
+            Assert.Equal(new Vector2(5, 5), midpoint);
+        }
+
+        [Fact]
+        public void Length_Horizontal()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+
+            float length = segment.Length;
+
+            Assert.Equal(10.0f, length, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Length_Diagonal()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(3, 4));
+
+            float length = segment.Length;
+
+            Assert.Equal(5.0f, length, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void LengthSquared_AvoidsSqrt()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(3, 4));
+
+            float lengthSquared = segment.LengthSquared;
+
+            Assert.Equal(25.0f, lengthSquared, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region GetPoint Tests (Type-Specific Utility)
+
+        [Fact]
+        public void GetPoint_AtStart()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+
+            var point = segment.GetPoint(0.0f);
+
+            Assert.Equal(new Vector2(0, 0), point);
+        }
+
+        [Fact]
+        public void GetPoint_AtMidpoint()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+
+            var point = segment.GetPoint(0.5f);
+
+            Assert.Equal(new Vector2(5, 0), point);
+        }
+
+        [Fact]
+        public void GetPoint_AtEnd()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+
+            var point = segment.GetPoint(1.0f);
+
+            Assert.Equal(new Vector2(10, 0), point);
+        }
+
+        [Fact]
+        public void GetPoint_BeyondEnd()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+
+            var point = segment.GetPoint(1.5f);
+
+            Assert.Equal(new Vector2(15, 0), point);
+        }
+
+        #endregion
+
+        #region GetBounds Tests (Type-Specific Method)
+
+        [Fact]
+        public void GetBounds_HorizontalSegment()
+        {
+            var segment = new LineSegment2D(new Vector2(2, 5), new Vector2(8, 5));
+
+            var bounds = segment.GetBounds();
+
+            Assert.Equal(new Vector2(2, 5), bounds.Min);
+            Assert.Equal(new Vector2(8, 5), bounds.Max);
+        }
+
+        [Fact]
+        public void GetBounds_VerticalSegment()
+        {
+            var segment = new LineSegment2D(new Vector2(5, 2), new Vector2(5, 8));
+
+            var bounds = segment.GetBounds();
+
+            Assert.Equal(new Vector2(5, 2), bounds.Min);
+            Assert.Equal(new Vector2(5, 8), bounds.Max);
+        }
+
+        [Fact]
+        public void GetBounds_DiagonalSegment()
+        {
+            var segment = new LineSegment2D(new Vector2(1, 2), new Vector2(9, 7));
+
+            var bounds = segment.GetBounds();
+
+            Assert.Equal(new Vector2(1, 2), bounds.Min);
+            Assert.Equal(new Vector2(9, 7), bounds.Max);
+        }
+
+        [Fact]
+        public void GetBounds_ReversedEndpoints()
+        {
+            var segment = new LineSegment2D(new Vector2(9, 7), new Vector2(1, 2));
+
+            var bounds = segment.GetBounds();
+
+            Assert.Equal(new Vector2(1, 2), bounds.Min);
+            Assert.Equal(new Vector2(9, 7), bounds.Max);
+        }
+
+        [Fact]
+        public void GetBounds_DegenerateSegment()
+        {
+            var segment = new LineSegment2D(new Vector2(5, 5), new Vector2(5, 5));
+
+            var bounds = segment.GetBounds();
+
+            Assert.Equal(new Vector2(5, 5), bounds.Min);
+            Assert.Equal(new Vector2(5, 5), bounds.Max);
+        }
+
+        #endregion
+
+        #region Distance and Projection Tests (Delegation Spot Checks)
+
+        [Fact]
+        public void ClosestPoint_OnSegment()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+            var point = new Vector2(5, 3);
+
+            var closest = segment.ClosestPoint(point, out float distanceAlongSegment);
+
+            Assert.Equal(new Vector2(5, 0), closest);
+            Assert.Equal(0.5f, distanceAlongSegment, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ClosestPoint_BeforeStart()
+        {
+            var segment = new LineSegment2D(new Vector2(10, 0), new Vector2(20, 0));
+            var point = new Vector2(5, 3);
+
+            var closest = segment.ClosestPoint(point, out float distanceAlongSegment);
+
+            Assert.Equal(new Vector2(10, 0), closest);
+            Assert.Equal(0.0f, distanceAlongSegment, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void ClosestPoint_AfterEnd()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+            var point = new Vector2(15, 3);
+
+            var closest = segment.ClosestPoint(point, out float distanceAlongSegment);
+
+            Assert.Equal(new Vector2(10, 0), closest);
+        }
+
+        [Fact]
+        public void DistanceToPoint_PerpendicularDistance()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+            var point = new Vector2(5, 3);
+
+            float distance = segment.DistanceToPoint(point);
+
+            Assert.Equal(3.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredToPoint_AvoidsSqrt()
+        {
+            var segment = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+            var point = new Vector2(5, 3);
+
+            float distanceSquared = segment.DistanceSquaredToPoint(point);
+
+            Assert.Equal(9.0f, distanceSquared, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceToSegment_Parallel()
+        {
+            var segment1 = new LineSegment2D(new Vector2(0, 0), new Vector2(10, 0));
+            var segment2 = new LineSegment2D(new Vector2(0, 5), new Vector2(10, 5));
+
+            float distance = segment1.DistanceToSegment(segment2);
+
+            Assert.Equal(5.0f, distance, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region Deconstruct Test
+
+        [Fact]
+        public void Deconstruct()
+        {
+            var segment = new LineSegment2D(new Vector2(1, 2), new Vector2(3, 4));
+
+            var (start, end) = segment;
+
+            Assert.Equal(new Vector2(1, 2), start);
+            Assert.Equal(new Vector2(3, 4), end);
+        }
+
+        #endregion
+    }
+}

+ 512 - 0
tests/MonoGame.Extended.Tests/OrientedBoundingBox2DTest.cs

@@ -0,0 +1,512 @@
+// MonoGame - Copyright (C) MonoGame Foundation, Inc
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+    public sealed class OrientedBoundingBox2DTest
+    {
+        #region Constructor Tests
+
+        [Fact]
+        public void Constructor()
+        {
+            var center = new Vector2(5, 10);
+            var axisX = new Vector2(1, 0);
+            var axisY = new Vector2(0, 1);
+            var halfExtents = new Vector2(3, 4);
+
+            var obb = new OrientedBoundingBox2D(center, axisX, axisY, halfExtents);
+
+            Assert.Equal(center, obb.Center);
+            Assert.Equal(axisX, obb.AxisX);
+            Assert.Equal(axisY, obb.AxisY);
+            Assert.Equal(halfExtents, obb.HalfExtents);
+        }
+
+        #endregion
+
+        #region Computed Property Tests
+
+        [Fact]
+        public void Width_ReturnsTwiceHalfExtentX()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+
+            float width = obb.Width;
+
+            Assert.Equal(10.0f, width, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Height_ReturnsTwiceHalfExtentY()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+
+            float height = obb.Height;
+
+            Assert.Equal(6.0f, height, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Rotation_ReturnsZeroForAlignedBox()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+
+            float rotation = obb.Rotation;
+
+            Assert.Equal(0.0f, rotation, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Rotation_Returns90DegreesForRotatedBox()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(0, 1),
+                new Vector2(-1, 0),
+                new Vector2(5, 3)
+            );
+
+            float rotation = obb.Rotation;
+
+            Assert.Equal(MathHelper.PiOver2, rotation, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Area_CalculatesCorrectly()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+
+            float area = obb.Area;
+
+            Assert.Equal(60.0f, area, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region Factory Method Tests
+
+        [Fact]
+        public void CreateFromRotation_ZeroRotation()
+        {
+            var center = new Vector2(5, 5);
+            var halfExtents = new Vector2(3, 2);
+            var rotation = 0.0f;
+
+            var obb = OrientedBoundingBox2D.CreateFromRotation(center, rotation, halfExtents);
+
+            Assert.Equal(center, obb.Center);
+            Assert.Equal(1, obb.AxisX.X, Collision2D.Epsilon);
+            Assert.Equal(0, obb.AxisX.Y, Collision2D.Epsilon);
+            Assert.Equal(0, obb.AxisY.X, Collision2D.Epsilon);
+            Assert.Equal(1, obb.AxisY.Y, Collision2D.Epsilon);
+            Assert.Equal(halfExtents, obb.HalfExtents);
+        }
+
+        [Fact]
+        public void CreateFromRotation_90Degrees()
+        {
+            var center = new Vector2(5, 5);
+            var halfExtents = new Vector2(3, 2);
+            var rotation = MathHelper.PiOver2;
+
+            var obb = OrientedBoundingBox2D.CreateFromRotation(center, rotation, halfExtents);
+
+            Assert.Equal(center, obb.Center);
+            Assert.Equal(0, obb.AxisX.X, Collision2D.Epsilon);
+            Assert.Equal(1, obb.AxisX.Y, Collision2D.Epsilon);
+            Assert.Equal(-1, obb.AxisY.X, Collision2D.Epsilon);
+            Assert.Equal(0, obb.AxisY.Y, Collision2D.Epsilon);
+            Assert.Equal(halfExtents, obb.HalfExtents);
+        }
+
+        [Fact]
+        public void CreateFromRotation_45Degrees()
+        {
+            var center = new Vector2(5, 5);
+            var halfExtents = new Vector2(3, 2);
+            var rotation = MathHelper.PiOver4;
+
+            var obb = OrientedBoundingBox2D.CreateFromRotation(center, rotation, halfExtents);
+
+            Assert.Equal(center, obb.Center);
+            Assert.Equal(halfExtents, obb.HalfExtents);
+
+            float cos45 = MathF.Cos(MathHelper.PiOver4);
+            float sin45 = MathF.Sin(MathHelper.PiOver4);
+            Assert.Equal(cos45, obb.AxisX.X, Collision2D.Epsilon);
+            Assert.Equal(sin45, obb.AxisX.Y, Collision2D.Epsilon);
+            Assert.Equal(-sin45, obb.AxisY.X, Collision2D.Epsilon);
+            Assert.Equal(cos45, obb.AxisY.Y, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void CreateFromBoundingBox2D()
+        {
+            var box = new BoundingBox2D(new Vector2(0, 0), new Vector2(10, 20));
+
+            var obb = OrientedBoundingBox2D.CreateFromBoundingBox2D(box);
+
+            Assert.Equal(new Vector2(5, 10), obb.Center);
+            Assert.Equal(new Vector2(1, 0), obb.AxisX);
+            Assert.Equal(new Vector2(0, 1), obb.AxisY);
+            Assert.Equal(new Vector2(5, 10), obb.HalfExtents);
+        }
+
+        [Fact]
+        public void CreateMerged_NonOverlapping()
+        {
+            var obb1 = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(2, 2)
+            );
+            var obb2 = new OrientedBoundingBox2D(
+                new Vector2(10, 10),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(2, 2)
+            );
+
+            var merged = OrientedBoundingBox2D.CreateMerged(obb1, obb2);
+
+            Assert.Equal(ContainmentType.Contains, merged.Contains(obb1));
+            Assert.Equal(ContainmentType.Contains, merged.Contains(obb2));
+        }
+
+        [Fact]
+        public void CreateMerged_Overlapping()
+        {
+            var obb1 = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 5)
+            );
+            var obb2 = new OrientedBoundingBox2D(
+                new Vector2(5, 5),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(3, 3)
+            );
+
+            var merged = OrientedBoundingBox2D.CreateMerged(obb1, obb2);
+
+            Assert.Equal(ContainmentType.Contains, merged.Contains(obb1));
+            Assert.Equal(ContainmentType.Contains, merged.Contains(obb2));
+        }
+
+        [Fact]
+        public void CreateMerged_DifferentRotations()
+        {
+            var obb1 = OrientedBoundingBox2D.CreateFromRotation(new Vector2(0, 0), 0, new Vector2(3, 2));
+            var obb2 = OrientedBoundingBox2D.CreateFromRotation(new Vector2(5, 5), MathHelper.PiOver4, new Vector2(3, 2));
+
+            var merged = OrientedBoundingBox2D.CreateMerged(obb1, obb2);
+
+            Assert.Equal(ContainmentType.Contains, merged.Contains(obb1));
+            Assert.Equal(ContainmentType.Contains, merged.Contains(obb2));
+        }
+
+        #endregion
+
+        #region GetCorners Tests (Type-Specific Method)
+
+        [Fact]
+        public void GetCorners_ReturnsArrayOfFour()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+
+            var corners = obb.GetCorners();
+
+            Assert.Equal(4, corners.Length);
+        }
+
+        [Fact]
+        public void GetCorners_AlignedBox()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+
+            var corners = obb.GetCorners();
+
+            Assert.Contains(new Vector2(-5, -3), corners);
+            Assert.Contains(new Vector2(5, -3), corners);
+            Assert.Contains(new Vector2(5, 3), corners);
+            Assert.Contains(new Vector2(-5, 3), corners);
+        }
+
+        [Fact]
+        public void GetCorners_FillArray()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+            var corners = new Vector2[4];
+
+            obb.GetCorners(corners);
+
+            Assert.Contains(new Vector2(-5, -3), corners);
+            Assert.Contains(new Vector2(5, -3), corners);
+            Assert.Contains(new Vector2(5, 3), corners);
+            Assert.Contains(new Vector2(-5, 3), corners);
+        }
+
+        [Fact]
+        public void GetCorners_ThrowsWhenArrayNull()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+
+            Assert.Throws<ArgumentNullException>(() => obb.GetCorners(null));
+        }
+
+        [Fact]
+        public void GetCorners_ThrowsWhenArrayTooSmall()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+            var corners = new Vector2[3];
+
+            Assert.Throws<ArgumentException>(() => obb.GetCorners(corners));
+        }
+
+        #endregion
+
+        #region Transform Tests
+
+        [Fact]
+        public void Transform_Translation()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+            var matrix = Matrix.CreateTranslation(10, 20, 0);
+
+            var transformed = obb.Transform(matrix);
+
+            Assert.Equal(new Vector2(10, 20), transformed.Center);
+            Assert.Equal(obb.AxisX, transformed.AxisX);
+            Assert.Equal(obb.AxisY, transformed.AxisY);
+            Assert.Equal(obb.HalfExtents, transformed.HalfExtents);
+        }
+
+        [Fact]
+        public void Transform_UniformScale()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+            var matrix = Matrix.CreateScale(2.0f);
+
+            var transformed = obb.Transform(matrix);
+
+            Assert.Equal(new Vector2(0, 0), transformed.Center);
+            Assert.Equal(new Vector2(10, 6), transformed.HalfExtents);
+        }
+
+        [Fact]
+        public void Transform_NonUniformScale()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+            var matrix = Matrix.CreateScale(2.0f, 3.0f, 1.0f);
+
+            var transformed = obb.Transform(matrix);
+
+            Assert.Equal(0, transformed.Center.X, Collision2D.Epsilon);
+            Assert.Equal(0, transformed.Center.Y, Collision2D.Epsilon);
+            Assert.Equal(10, transformed.HalfExtents.X, Collision2D.Epsilon);
+            Assert.Equal(9, transformed.HalfExtents.Y, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void Transform_Rotation()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(5, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(3, 2)
+            );
+            var matrix = Matrix.CreateRotationZ(MathHelper.PiOver2);
+
+            var transformed = obb.Transform(matrix);
+
+            Assert.Equal(0, transformed.Center.X, Collision2D.Epsilon);
+            Assert.Equal(5, transformed.Center.Y, Collision2D.Epsilon);
+
+            Assert.Equal(0, transformed.AxisX.X, Collision2D.Epsilon);
+            Assert.Equal(1, transformed.AxisX.Y, Collision2D.Epsilon);
+            Assert.Equal(-1, transformed.AxisY.X, Collision2D.Epsilon);
+            Assert.Equal(0, transformed.AxisY.Y, Collision2D.Epsilon);
+
+            Assert.Equal(new Vector2(3, 2), transformed.HalfExtents);
+        }
+
+        [Fact]
+        public void Translate_OffsetsPosition()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(5, 5),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(3, 2)
+            );
+            var offset = new Vector2(10, 15);
+
+            var translated = obb.Translate(offset);
+
+            Assert.Equal(new Vector2(15, 20), translated.Center);
+            Assert.Equal(obb.AxisX, translated.AxisX);
+            Assert.Equal(obb.AxisY, translated.AxisY);
+            Assert.Equal(obb.HalfExtents, translated.HalfExtents);
+        }
+
+        #endregion
+
+        #region ContainsPoint Tests (Delegation Spot Check)
+
+        [Fact]
+        public void ContainsPoint_Inside()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+            var point = new Vector2(2, 1);
+
+            var result = obb.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_OnBoundary()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+            var point = new Vector2(5, 0);
+
+            var result = obb.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_Outside()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(0, 0),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(5, 3)
+            );
+            var point = new Vector2(10, 0);
+
+            var result = obb.Contains(point);
+
+            Assert.Equal(ContainmentType.Disjoint, result);
+        }
+
+        [Fact]
+        public void ContainsPoint_RotatedBox()
+        {
+            var obb = OrientedBoundingBox2D.CreateFromRotation(
+                new Vector2(0, 0),
+                MathHelper.PiOver4,
+                new Vector2(5, 3)
+            );
+            var point = new Vector2(0, 0);
+
+            var result = obb.Contains(point);
+
+            Assert.Equal(ContainmentType.Contains, result);
+        }
+
+        #endregion
+
+        #region Deconstruct Test
+
+        [Fact]
+        public void Deconstruct()
+        {
+            var obb = new OrientedBoundingBox2D(
+                new Vector2(5, 10),
+                new Vector2(1, 0),
+                new Vector2(0, 1),
+                new Vector2(3, 4)
+            );
+
+            var (center, axisX, axisY, halfExtents) = obb;
+
+            Assert.Equal(new Vector2(5, 10), center);
+            Assert.Equal(new Vector2(1, 0), axisX);
+            Assert.Equal(new Vector2(0, 1), axisY);
+            Assert.Equal(new Vector2(3, 4), halfExtents);
+        }
+
+        #endregion
+    }
+}

+ 176 - 0
tests/MonoGame.Extended.Tests/Ray2DTest.cs

@@ -0,0 +1,176 @@
+// MonoGame - Copyright (C) MonoGame Foundation, Inc
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE.txt', which is part of this source code package.
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tests
+{
+    public sealed class Ray2DTest
+    {
+        #region Constructor Tests
+
+        [Fact]
+        public void Constructor()
+        {
+            var origin = new Vector2(1, 2);
+            var direction = new Vector2(3, 4);
+
+            var ray = new Ray2D(origin, direction);
+
+            Assert.Equal(origin, ray.Origin);
+            Assert.Equal(direction, ray.Direction);
+        }
+
+        #endregion
+
+        #region Factory Method Tests
+
+        [Fact]
+        public void CreateFromPoints()
+        {
+            var start = new Vector2(0, 0);
+            var through = new Vector2(10, 0);
+
+            var ray = Ray2D.CreateFromPoints(start, through);
+
+            Assert.Equal(start, ray.Origin);
+            Assert.Equal(new Vector2(1, 0), ray.Direction);
+        }
+
+        [Fact]
+        public void CreateFromPoints_ThrowsWhenPointsTooClose()
+        {
+            var start = new Vector2(0, 0);
+            var through = new Vector2(1e-7f, 0);
+
+            Assert.Throws<ArgumentException>(() => Ray2D.CreateFromPoints(start, through));
+        }
+
+        #endregion
+
+        #region GetPoint Tests (Type-Specific Utility)
+
+        [Fact]
+        public void GetPoint_AtOrigin()
+        {
+            var ray = new Ray2D(new Vector2(5, 5), new Vector2(1, 0));
+
+            var point = ray.GetPoint(0);
+
+            Assert.Equal(new Vector2(5, 5), point);
+        }
+
+        [Fact]
+        public void GetPoint_ForwardDirection()
+        {
+            var ray = new Ray2D(new Vector2(0, 0), new Vector2(1, 0));
+
+            var point = ray.GetPoint(10);
+
+            Assert.Equal(new Vector2(10, 0), point);
+        }
+
+        [Fact]
+        public void GetPoint_NegativeDistance()
+        {
+            var ray = new Ray2D(new Vector2(10, 0), new Vector2(1, 0));
+
+            var point = ray.GetPoint(-5);
+
+            Assert.Equal(new Vector2(5, 0), point);
+        }
+
+        #endregion
+
+        #region Distance and Projection Tests (Delegation Spot Checks)
+
+        [Fact]
+        public void ClosestPoint_PointAhead()
+        {
+            var ray = new Ray2D(new Vector2(0, 0), new Vector2(1, 0));
+            var point = new Vector2(5, 3);
+
+            var closest = ray.ClosestPoint(point, out float distanceAlongRay);
+
+            Assert.Equal(new Vector2(5, 0), closest);
+        }
+
+        [Fact]
+        public void ClosestPoint_PointBehind()
+        {
+            var ray = new Ray2D(new Vector2(10, 0), new Vector2(1, 0));
+            var point = new Vector2(5, 3);
+
+            var closest = ray.ClosestPoint(point, out float distanceAlongRay);
+
+            Assert.Equal(new Vector2(10, 0), closest);
+        }
+
+        [Fact]
+        public void DistanceToPoint_PerpendicularDistance()
+        {
+            var ray = new Ray2D(new Vector2(0, 0), new Vector2(1, 0));
+            var point = new Vector2(5, 3);
+
+            float distance = ray.DistanceToPoint(point);
+
+            Assert.Equal(3.0f, distance, Collision2D.Epsilon);
+        }
+
+        [Fact]
+        public void DistanceSquaredToPoint_AvoidsSqrt()
+        {
+            var ray = new Ray2D(new Vector2(0, 0), new Vector2(1, 0));
+            var point = new Vector2(5, 3);
+
+            float distanceSquared = ray.DistanceSquaredToPoint(point);
+
+            Assert.Equal(9.0f, distanceSquared, Collision2D.Epsilon);
+        }
+
+        #endregion
+
+        #region Normalize Tests
+
+        [Fact]
+        public void Normalize_Static_CreatesUnitDirection()
+        {
+            var ray = new Ray2D(new Vector2(0, 0), new Vector2(3, 4));
+
+            var normalized = Ray2D.Normalize(ray);
+
+            Assert.Equal(1.0f, normalized.Direction.Length(), Collision2D.Epsilon);
+            Assert.Equal(ray.Origin, normalized.Origin);
+        }
+
+        [Fact]
+        public void Normalize_Instance_ModifiesInPlace()
+        {
+            var ray = new Ray2D(new Vector2(5, 5), new Vector2(3, 4));
+
+            ray.Normalize();
+
+            Assert.Equal(1.0f, ray.Direction.Length(), Collision2D.Epsilon);
+            Assert.Equal(new Vector2(5, 5), ray.Origin);
+        }
+
+        #endregion
+
+        #region Deconstruct Test
+
+        [Fact]
+        public void Deconstruct()
+        {
+            var ray = new Ray2D(new Vector2(1, 2), new Vector2(3, 4));
+
+            var (origin, direction) = ray;
+
+            Assert.Equal(new Vector2(1, 2), origin);
+            Assert.Equal(new Vector2(3, 4), direction);
+        }
+
+        #endregion
+    }
+}