diff --git a/CSMath.Tests/Geometry/Arc2DTests.cs b/CSMath.Tests/Geometry/Arc2DTests.cs new file mode 100644 index 0000000..5536be4 --- /dev/null +++ b/CSMath.Tests/Geometry/Arc2DTests.cs @@ -0,0 +1,550 @@ +using CSMath.Geometry; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace CSMath.Tests.Geometry; + +public class Arc2DTests +{ + private const double Tolerance = 1e-10; + + [Fact] + public void Arc2D_DefaultConstructor_CreatesZeroArc() + { + // Arrange & Act + Arc2D arc = new Arc2D(); + + // Assert + Assert.Equal(0, arc.StartAngle); + Assert.Equal(0, arc.EndAngle); + Assert.Equal(0, arc.Radius); + Assert.Equal(XY.Zero, arc.Center); + } + + [Fact] + public void Arc2D_Properties_CanBeSetAndRetrieved() + { + // Arrange + Arc2D arc = new Arc2D + { + StartAngle = MathHelper.DegToRad(45), + EndAngle = MathHelper.DegToRad(135), + Radius = 10.0, + Center = new XY(5, 5) + }; + + // Assert + Assert.Equal(MathHelper.DegToRad(45), arc.StartAngle, Tolerance); + Assert.Equal(MathHelper.DegToRad(135), arc.EndAngle, Tolerance); + Assert.Equal(10.0, arc.Radius, Tolerance); + Assert.Equal(new XY(5, 5), arc.Center); + } + + [Fact] + public void FindIntersections_ArcWithOffsetCenter_ReturnsCorrectPoints() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = new XY(10, 10), + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.TwoPI + }; + + // Horizontal line through the center + Line2D line = new Line2D(new XY(0, 10), new XY(20, 0)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + Assert.Equal(2, intersections.Count); + Assert.Contains(intersections, p => Math.Abs(p.X - 15) < Tolerance && Math.Abs(p.Y - 10) < Tolerance); + Assert.Contains(intersections, p => Math.Abs(p.X - 5) < Tolerance && Math.Abs(p.Y - 10) < Tolerance); + } + + [Fact] + public void FindIntersections_DiagonalLineThroughArc_ReturnsTwoPoints() + { + // Arrange - Full circle + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.TwoPI + }; + + // Diagonal line passing through center (45 degree angle) + Line2D line = new Line2D(new XY(-10, -10), new XY(20, 20)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + Assert.Equal(2, intersections.Count); + + // Points should be at 45° and 225° + double expectedCoord = 5 / Math.Sqrt(2); + Assert.Contains(intersections, p => + Math.Abs(p.X - expectedCoord) < Tolerance && + Math.Abs(p.Y - expectedCoord) < Tolerance); + Assert.Contains(intersections, p => + Math.Abs(p.X + expectedCoord) < Tolerance && + Math.Abs(p.Y + expectedCoord) < Tolerance); + } + + [Fact] + public void FindIntersections_FullCircle_LinePassesThrough_ReturnsTwoPoints() + { + // Arrange - Full circle (0° to 360°) + Arc2D arc = new Arc2D + { + Center = new XY(5, 5), + Radius = 3.0, + StartAngle = 0, + EndAngle = MathHelper.TwoPI + }; + + // Line passing through circle + Line2D line = new Line2D(new XY(5, 0), new XY(0, 10)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + Assert.Equal(2, intersections.Count); + // Verify both points are at the correct distance from center + foreach (var point in intersections) + { + double distance = Math.Sqrt(Math.Pow(point.X - 5, 2) + Math.Pow(point.Y - 5, 2)); + Assert.True(Math.Abs(distance - 3.0) < Tolerance); + } + } + + [Fact] + public void FindIntersections_LargeRadius_ReturnsCorrectPoints() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 1000.0, + StartAngle = 0, + EndAngle = MathHelper.TwoPI + }; + + // Horizontal line through center + Line2D line = new Line2D(new XY(-2000, 0), new XY(4000, 0)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + Assert.Equal(2, intersections.Count); + Assert.Contains(intersections, p => Math.Abs(p.X - 1000) < Tolerance && Math.Abs(p.Y) < Tolerance); + Assert.Contains(intersections, p => Math.Abs(p.X + 1000) < Tolerance && Math.Abs(p.Y) < Tolerance); + } + + [Fact] + public void FindIntersections_LineIntersectsArcAtOnePoint_ReturnsOnePoint() + { + // Arrange - Arc from 45° to 135° (upper half, centered) + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = MathHelper.DegToRad(45), + EndAngle = MathHelper.DegToRad(135) + }; + + // Vertical line through center at x = 0 + Line2D line = new Line2D(new XY(0, -10), new XY(0, 20)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + // Should intersect at 90° (0, 5) + Assert.Single(intersections); + Assert.True(Math.Abs(intersections[0].X) < Tolerance); + Assert.True(Math.Abs(intersections[0].Y - 5) < Tolerance); + } + + [Fact] + public void FindIntersections_LineIntersectsCircleButNotArc_ReturnsEmpty() + { + // Arrange - Arc from 0° to 90° (first quadrant only) + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.HalfPI + }; + + // Horizontal line through center (would intersect at 180° and 0°) + // But 180° is outside the arc range + Line2D line = new Line2D(new XY(-10, 0), new XY(20, 0)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + // Should only find the point at 0° (5, 0) + Assert.Single(intersections); + Assert.True(Math.Abs(intersections[0].X - 5) < Tolerance); + Assert.True(Math.Abs(intersections[0].Y) < Tolerance); + } + + [Fact] + public void FindIntersections_LineMissesArc_ReturnsEmpty() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.TwoPI + }; + + // Horizontal line far above the arc + Line2D line = new Line2D(new XY(-10, 10), new XY(20, 0)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance); + + // Assert + Assert.Empty(intersections); + } + + [Fact] + public void FindIntersections_LinePassesThroughArc_ReturnsTwoPoints() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = 0, + EndAngle = Math.PI // Half circle (0° to 180°) + }; + + // Horizontal line passing through center + Line2D line = new Line2D(new XY(-10, 0), new XY(20, 0)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + Assert.Equal(2, intersections.Count); + Assert.Contains(intersections, p => Math.Abs(p.X - 5) < Tolerance && Math.Abs(p.Y) < Tolerance); + Assert.Contains(intersections, p => Math.Abs(p.X + 5) < Tolerance && Math.Abs(p.Y) < Tolerance); + } + + [Fact] + public void FindIntersections_LineTangentToArc_ReturnsOnePoint() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.TwoPI // Full circle + }; + + // Horizontal line tangent at the top + Line2D line = new Line2D(new XY(-10, 5), new XY(20, 0)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + Assert.Single(intersections); + Assert.True(Math.Abs(intersections[0].X) < Tolerance); + Assert.True(Math.Abs(intersections[0].Y - 5) < Tolerance); + } + + [Fact] + public void FindIntersections_SmallArc_LineIntersectsOnce_ReturnsOnePoint() + { + // Arrange - Small arc from 80° to 100° + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 10.0, + StartAngle = MathHelper.DegToRad(80), + EndAngle = MathHelper.DegToRad(100) + }; + + // Vertical line at x = 0 (intersects at 90°) + Line2D line = new Line2D(new XY(0, 0), new XY(0, 20)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + Assert.Single(intersections); + Assert.True(Math.Abs(intersections[0].X) < Tolerance); + Assert.True(Math.Abs(intersections[0].Y - 10) < Tolerance); + } + + [Fact] + public void FindIntersections_VerticalLine_ReturnsCorrectPoints() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.TwoPI + }; + + // Vertical line at x = 3 + Line2D line = new Line2D(new XY(3, -10), new XY(0, 20)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + Assert.Equal(2, intersections.Count); + + // Calculate expected Y coordinates: sqrt(r^2 - x^2) = sqrt(25 - 9) = 4 + double expectedY = 4.0; + Assert.Contains(intersections, p => Math.Abs(p.X - 3) < Tolerance && Math.Abs(p.Y - expectedY) < Tolerance); + Assert.Contains(intersections, p => Math.Abs(p.X - 3) < Tolerance && Math.Abs(p.Y + expectedY) < Tolerance); + } + + [Fact] + public void FindIntersections_VerySmallRadius_ReturnsCorrectPoints() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 0.001, + StartAngle = 0, + EndAngle = MathHelper.TwoPI + }; + + // Horizontal line through center + Line2D line = new Line2D(new XY(-1, 0), new XY(2, 0)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + Assert.Equal(2, intersections.Count); + Assert.Contains(intersections, p => Math.Abs(p.X - 0.001) < Tolerance && Math.Abs(p.Y) < Tolerance); + Assert.Contains(intersections, p => Math.Abs(p.X + 0.001) < Tolerance && Math.Abs(p.Y) < Tolerance); + } + + [Fact] + public void FindIntersections_WrapAroundArc_ReturnsCorrectPoints() + { + // Arrange - Arc from 315° to 45° (wraps around 0°) + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = MathHelper.DegToRad(315), + EndAngle = MathHelper.DegToRad(45) + }; + + // Horizontal line through center + Line2D line = new Line2D(new XY(-10, 0), new XY(20, 0)); + + // Act + var intersections = arc.FindIntersections(line, Tolerance).ToList(); + + // Assert + // Should only intersect at 0° (5, 0), not at 180° (-5, 0) + Assert.Single(intersections); + Assert.True(Math.Abs(intersections[0].X - 5) < Tolerance); + Assert.True(Math.Abs(intersections[0].Y) < Tolerance); + } + + [Fact] + public void FindIntersections_ZeroLengthLine_ReturnsEmpty() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.TwoPI + }; + + // Zero-length line (point) + Line2D line = new Line2D(new XY(5, 0), XY.Zero); + + // Act + var intersections = arc.FindIntersections(line, Tolerance); + + // Assert + Assert.Empty(intersections); + } + + [Fact] + public void InAngularRange_CenterOffsetArc_PointInRange_ReturnsTrue() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = new XY(10, 10), + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.HalfPI + }; + + // Point at 45 degrees from center + double angle = MathHelper.DegToRad(45); + XY point = new XY(10 + 5 * Math.Cos(angle), 10 + 5 * Math.Sin(angle)); + + // Act + bool result = arc.InAngularRange(point); + + // Assert + Assert.True(result); + } + + [Fact] + public void InAngularRange_PointAtEndAngle_ReturnsTrue() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = MathHelper.DegToRad(30), + EndAngle = MathHelper.DegToRad(120) + }; + + double endAngle = MathHelper.DegToRad(120); + XY point = new XY(5 * Math.Cos(endAngle), 5 * Math.Sin(endAngle)); + + // Act + bool result = arc.InAngularRange(point); + + // Assert + Assert.True(result); + } + + [Fact] + public void InAngularRange_PointAtStartAngle_ReturnsTrue() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = MathHelper.DegToRad(30), + EndAngle = MathHelper.DegToRad(120) + }; + + double startAngle = MathHelper.DegToRad(30); + XY point = new XY(5 * Math.Cos(startAngle), 5 * Math.Sin(startAngle)); + + // Act + bool result = arc.InAngularRange(point); + + // Assert + Assert.True(result); + } + + [Fact] + public void InAngularRange_PointOutsideRange_ReturnsFalse() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.HalfPI // 90 degrees + }; + + // Point at 180 degrees + XY point = new XY(-5, 0); + + // Act + bool result = arc.InAngularRange(point); + + // Assert + Assert.False(result); + } + + [Fact] + public void InAngularRange_PointWithinRange_ReturnsTrue() + { + // Arrange + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = 0, + EndAngle = MathHelper.HalfPI // 90 degrees + }; + + // Point at 45 degrees + XY point = new XY(5 * Math.Cos(MathHelper.DegToRad(45)), 5 * Math.Sin(MathHelper.DegToRad(45))); + + // Act + bool result = arc.InAngularRange(point); + + // Assert + Assert.True(result); + } + + [Fact] + public void InAngularRange_WrapAroundArc_PointInRange_ReturnsTrue() + { + // Arrange - Arc from 315° to 45° (wraps around 0°) + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = MathHelper.DegToRad(315), + EndAngle = MathHelper.DegToRad(45) + }; + + // Point at 10 degrees (should be in range) + double angle = MathHelper.DegToRad(10); + XY point = new XY(5 * Math.Cos(angle), 5 * Math.Sin(angle)); + + // Act + bool result = arc.InAngularRange(point); + + // Assert + Assert.True(result); + } + + [Fact] + public void InAngularRange_WrapAroundArc_PointOutsideRange_ReturnsFalse() + { + // Arrange - Arc from 315° to 45° (wraps around 0°) + Arc2D arc = new Arc2D + { + Center = XY.Zero, + Radius = 5.0, + StartAngle = MathHelper.DegToRad(315), + EndAngle = MathHelper.DegToRad(45) + }; + + // Point at 180 degrees (should be outside range) + XY point = new XY(-5, 0); + + // Act + bool result = arc.InAngularRange(point); + + // Assert + Assert.False(result); + } +} \ No newline at end of file diff --git a/CSMath.Tests/Geometry/Circle2DTests.cs b/CSMath.Tests/Geometry/Circle2DTests.cs new file mode 100644 index 0000000..31e01ef --- /dev/null +++ b/CSMath.Tests/Geometry/Circle2DTests.cs @@ -0,0 +1,218 @@ +using CSMath.Geometry; +using System.Linq; +using Xunit; + +namespace CSMath.Tests.Geometry; + +public class Circle2DTests +{ + [Fact] + public void Constructor_WithCenterAndRadius_SetsPropertiesCorrectly() + { + // Arrange + XY center = new XY(5.0, 10.0); + double radius = 7.5; + + // Act + Circle2D circle = new Circle2D(center, radius); + + // Assert + Assert.Equal(center, circle.Center); + Assert.Equal(radius, circle.Radius); + } + + [Fact] + public void Constructor_WithCoordinatesAndRadius_SetsPropertiesCorrectly() + { + // Arrange + double centerX = 3.0; + double centerY = 4.0; + double radius = 5.0; + + // Act + Circle2D circle = new Circle2D(centerX, centerY, radius); + + // Assert + Assert.Equal(centerX, circle.Center.X); + Assert.Equal(centerY, circle.Center.Y); + Assert.Equal(radius, circle.Radius); + } + + [Theory] + [InlineData(0.0, 0.0, 1.0)] + [InlineData(5.0, 5.0, 10.0)] + [InlineData(-3.0, 4.0, 2.5)] + public void Constructor_WithVariousValues_CreatesCorrectCircle(double x, double y, double radius) + { + // Act + Circle2D circle1 = new Circle2D(x, y, radius); + Circle2D circle2 = new Circle2D(new XY(x, y), radius); + + // Assert + Assert.Equal(x, circle1.Center.X); + Assert.Equal(y, circle1.Center.Y); + Assert.Equal(radius, circle1.Radius); + Assert.Equal(circle1.Center, circle2.Center); + Assert.Equal(circle1.Radius, circle2.Radius); + } + + [Fact] + public void FindIntersections_WithDiagonalLine_ReturnsTwoIntersections() + { + // Arrange + Circle2D circle = new Circle2D(0.0, 0.0, 5.0); + Line2D line = new Line2D(new XY(-10.0, -10.0), new XY(1.0, 1.0)); // Diagonal line y=x + + // Act + var intersections = circle.FindIntersections(line).ToArray(); + + // Assert + Assert.Equal(2, intersections.Length); + + // Both points should be on the circle (distance from center = radius) + double distance1 = (intersections[0] - circle.Center).GetLength(); + double distance2 = (intersections[1] - circle.Center).GetLength(); + Assert.True(MathHelper.IsEqual(circle.Radius, distance1, 1e-10)); + Assert.True(MathHelper.IsEqual(circle.Radius, distance2, 1e-10)); + + // Both points should satisfy y=x + Assert.True(MathHelper.IsEqual(intersections[0].X, intersections[0].Y, 1e-10)); + Assert.True(MathHelper.IsEqual(intersections[1].X, intersections[1].Y, 1e-10)); + } + + [Fact] + public void FindIntersections_WithLargeCircle_ReturnsPreciseIntersections() + { + // Arrange + Circle2D circle = new Circle2D(0.0, 0.0, 1000.0); + Line2D line = new Line2D(new XY(-2000.0, 0.0), new XY(1.0, 0.0)); // Horizontal line through center + + // Act + var intersections = circle.FindIntersections(line).ToArray(); + + // Assert + Assert.Equal(2, intersections.Length); + + // Verify distances are equal to radius + double distance1 = intersections[0].GetLength(); + double distance2 = intersections[1].GetLength(); + Assert.True(MathHelper.IsEqual(circle.Radius, distance1, 1e-8)); + Assert.True(MathHelper.IsEqual(circle.Radius, distance2, 1e-8)); + } + + [Fact] + public void FindIntersections_WithLineNegativeDirection_ReturnsTwoIntersections() + { + // Arrange + Circle2D circle = new Circle2D(0.0, 0.0, 5.0); + Line2D line = new Line2D(new XY(0.0, 10.0), new XY(0.0, -1.0)); // Vertical line with negative direction + + // Act + var intersections = circle.FindIntersections(line).ToArray(); + + // Assert + Assert.Equal(2, intersections.Length); + + // Both points should be at x=0 + Assert.True(MathHelper.IsZero(intersections[0].X, 1e-10)); + Assert.True(MathHelper.IsZero(intersections[1].X, 1e-10)); + + // Both points should be on the circle + double distance1 = intersections[0].GetLength(); + double distance2 = intersections[1].GetLength(); + Assert.True(MathHelper.IsEqual(circle.Radius, distance1, 1e-10)); + Assert.True(MathHelper.IsEqual(circle.Radius, distance2, 1e-10)); + } + + [Fact] + public void FindIntersections_WithLineThroughCenter_ReturnsTwoIntersections() + { + // Arrange + Circle2D circle = new Circle2D(0.0, 0.0, 5.0); + Line2D line = new Line2D(new XY(-10.0, 0.0), new XY(1.0, 0.0)); // Horizontal line through center + + // Act + var intersections = circle.FindIntersections(line).ToArray(); + + // Assert + Assert.Equal(2, intersections.Length); + Assert.True(MathHelper.IsEqual(5.0, intersections[0].X, 1e-10) || MathHelper.IsEqual(-5.0, intersections[0].X, 1e-10)); + Assert.True(MathHelper.IsZero(intersections[0].Y, 1e-10)); + Assert.True(MathHelper.IsEqual(5.0, intersections[1].X, 1e-10) || MathHelper.IsEqual(-5.0, intersections[1].X, 1e-10)); + Assert.True(MathHelper.IsZero(intersections[1].Y, 1e-10)); + } + + [Fact] + public void FindIntersections_WithNonIntersectingLine_ReturnsEmpty() + { + // Arrange + Circle2D circle = new Circle2D(0.0, 0.0, 5.0); + Line2D line = new Line2D(new XY(10.0, 0.0), new XY(0.0, 1.0)); // Vertical line at x=10, outside circle + + // Act + var intersections = circle.FindIntersections(line); + + // Assert + Assert.Empty(intersections); + } + + [Fact] + public void FindIntersections_WithOffsetCircle_ReturnsTwoIntersections() + { + // Arrange + Circle2D circle = new Circle2D(3.0, 4.0, 5.0); + Line2D line = new Line2D(new XY(3.0, -10.0), new XY(0.0, 1.0)); // Vertical line through center x=3 + + // Act + var intersections = circle.FindIntersections(line).ToArray(); + + // Assert + Assert.Equal(2, intersections.Length); + + // Both points should be on the vertical line x=3 + Assert.True(MathHelper.IsEqual(3.0, intersections[0].X, 1e-10)); + Assert.True(MathHelper.IsEqual(3.0, intersections[1].X, 1e-10)); + + // Both points should be on the circle + double distance1 = (intersections[0] - circle.Center).GetLength(); + double distance2 = (intersections[1] - circle.Center).GetLength(); + Assert.True(MathHelper.IsEqual(circle.Radius, distance1, 1e-10)); + Assert.True(MathHelper.IsEqual(circle.Radius, distance2, 1e-10)); + } + + [Fact] + public void FindIntersections_WithSmallCircle_ReturnsPreciseIntersections() + { + // Arrange + Circle2D circle = new Circle2D(0.0, 0.0, 0.1); + Line2D line = new Line2D(new XY(-1.0, 0.0), new XY(1.0, 0.0)); // Horizontal line through center + + // Act + var intersections = circle.FindIntersections(line).ToArray(); + + // Assert + Assert.Equal(2, intersections.Length); + + // Verify distances are equal to radius + double distance1 = intersections[0].GetLength(); + double distance2 = intersections[1].GetLength(); + Assert.True(MathHelper.IsEqual(circle.Radius, distance1, 1e-10)); + Assert.True(MathHelper.IsEqual(circle.Radius, distance2, 1e-10)); + } + + [Fact] + public void FindIntersections_WithTangentLine_ReturnsOneIntersection() + { + // Arrange + Circle2D circle = new Circle2D(0.0, 0.0, 5.0); + Line2D line = new Line2D(new XY(5.0, -10.0), new XY(0.0, 1.0)); // Vertical tangent line at x=5 + + // Act + var intersections = circle.FindIntersections(line).ToArray(); + + // Assert + Assert.Single(intersections); + Assert.True(MathHelper.IsEqual(5.0, intersections[0].X, 1e-10)); + Assert.True(MathHelper.IsZero(intersections[0].Y, 1e-10)); + } +} \ No newline at end of file diff --git a/CSMath.Tests/Geometry/Segment2DTests.cs b/CSMath.Tests/Geometry/Segment2DTests.cs new file mode 100644 index 0000000..7233348 --- /dev/null +++ b/CSMath.Tests/Geometry/Segment2DTests.cs @@ -0,0 +1,258 @@ +using CSMath.Geometry; +using Xunit; + +namespace CSMath.Tests.Geometry; + +public class Segment2DTests +{ + [Fact] + public void Constructor_WithValidPoints_CreatesSegment() + { + var origin = new XY(0, 0); + var end = new XY(1, 1); + + var segment = new Segment2D(origin, end); + + Assert.Equal(origin, segment.Origin); + Assert.Equal(end, segment.End); + } + + [Fact] + public void Constructor_WithZeroLengthSegment_CreatesSegment() + { + var point = new XY(5, 5); + + var segment = new Segment2D(point, point); + + Assert.Equal(point, segment.Origin); + Assert.Equal(point, segment.End); + } + + [Fact] + public void Direction_ForZeroLengthSegment_ReturnsZeroVector() + { + var point = new XY(5, 5); + var segment = new Segment2D(point, point); + + var direction = segment.Direction; + + Assert.Equal(XY.Zero, direction); + } + + [Fact] + public void Direction_ReturnsVectorFromOriginToEnd() + { + var origin = new XY(1, 2); + var end = new XY(4, 6); + var segment = new Segment2D(origin, end); + + var direction = segment.Direction; + + Assert.Equal(new XY(3, 4), direction); + } + + [Fact] + public void Direction_UpdatesWhenPropertiesChange() + { + var segment = new Segment2D(new XY(0, 0), new XY(1, 1)); + + segment.End = new XY(2, 2); + + Assert.Equal(new XY(2, 2), segment.Direction); + } + + [Fact] + public void Direction_WithNegativeCoordinates_ReturnsCorrectVector() + { + var origin = new XY(-1, -2); + var end = new XY(-4, -6); + var segment = new Segment2D(origin, end); + + var direction = segment.Direction; + + Assert.Equal(new XY(-3, -4), direction); + } + + [Fact] + public void End_CanBeModified() + { + var segment = new Segment2D(new XY(0, 0), new XY(1, 1)); + var newEnd = new XY(3, 3); + + segment.End = newEnd; + + Assert.Equal(newEnd, segment.End); + } + + [Fact] + public void Equals_WithDifferentEnd_ReturnsFalse() + { + var segment1 = new Segment2D(new XY(0, 0), new XY(1, 1)); + var segment2 = new Segment2D(new XY(0, 0), new XY(2, 2)); + + Assert.False(segment1.Equals(segment2)); + } + + [Fact] + public void Equals_WithDifferentOrigin_ReturnsFalse() + { + var segment1 = new Segment2D(new XY(0, 0), new XY(1, 1)); + var segment2 = new Segment2D(new XY(1, 0), new XY(1, 1)); + + Assert.False(segment1.Equals(segment2)); + } + + [Fact] + public void Equals_WithSameOriginAndEnd_ReturnsTrue() + { + var segment1 = new Segment2D(new XY(0, 0), new XY(1, 1)); + var segment2 = new Segment2D(new XY(0, 0), new XY(1, 1)); + + Assert.True(segment1.Equals(segment2)); + } + + [Fact] + public void FindIntersection_WithCollinearSegments_ReturnsNaN() + { + // Two collinear segments on the same line + var segment1 = new Segment2D(new XY(0, 0), new XY(2, 0)); + var segment2 = new Segment2D(new XY(1, 0), new XY(3, 0)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.True(intersection.IsNaN()); + } + + [Fact] + public void FindIntersection_WithDiagonalSegments_ReturnsIntersectionPoint() + { + // Segment from (0, 0) to (2, 2) + var segment1 = new Segment2D(new XY(0, 0), new XY(2, 2)); + // Segment from (0, 2) to (2, 0) + var segment2 = new Segment2D(new XY(0, 2), new XY(2, 0)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.Equal(new XY(1, 1), intersection); + } + + [Fact] + public void FindIntersection_WithIntersectingSegment_ReturnsIntersectionPoint() + { + // Horizontal segment from (0, 1) to (2, 1) + var segment1 = new Segment2D(new XY(0, 1), new XY(2, 1)); + // Vertical segment from (1, 0) to (1, 2) + var segment2 = new Segment2D(new XY(1, 0), new XY(1, 2)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.Equal(new XY(1, 1), intersection); + } + + [Fact] + public void FindIntersection_WithIntersectionAtEndpoint_ReturnsIntersectionPoint() + { + // Segment from (0, 0) to (2, 0) + var segment1 = new Segment2D(new XY(0, 0), new XY(2, 0)); + // Segment from (2, -1) to (2, 1) + var segment2 = new Segment2D(new XY(2, -1), new XY(2, 1)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.Equal(new XY(2, 0), intersection); + } + + [Fact] + public void FindIntersection_WithIntersectionJustOutsideBounds_ReturnsNaN() + { + // Segment from (0, 0) to (1, 0) + var segment1 = new Segment2D(new XY(0, 0), new XY(1, 0)); + // Segment from (2, -1) to (2, 1) + var segment2 = new Segment2D(new XY(2, -1), new XY(2, 1)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.True(intersection.IsNaN()); + } + + [Fact] + public void FindIntersection_WithIntersectionOutsideSegmentBounds_ReturnsNaN() + { + // Segment from (0, 1) to (1, 1) + var segment1 = new Segment2D(new XY(0, 1), new XY(1, 1)); + // Vertical segment from (2, 0) to (2, 2) + var segment2 = new Segment2D(new XY(2, 0), new XY(2, 2)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.True(intersection.IsNaN()); + } + + [Fact] + public void FindIntersection_WithParallelSegments_ReturnsNaN() + { + // Two parallel horizontal segments + var segment1 = new Segment2D(new XY(0, 1), new XY(2, 1)); + var segment2 = new Segment2D(new XY(0, 2), new XY(2, 2)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.True(intersection.IsNaN()); + } + + [Fact] + public void GetHashCode_DifferentSegments_MayHaveDifferentHashCode() + { + var segment1 = new Segment2D(new XY(0, 0), new XY(1, 1)); + var segment2 = new Segment2D(new XY(2, 1), new XY(2, 2)); + + // Not guaranteed to be different, but highly likely + Assert.NotEqual(segment1.GetHashCode(), segment2.GetHashCode()); + } + + [Fact] + public void GetHashCode_EqualSegments_HaveSameHashCode() + { + var segment1 = new Segment2D(new XY(0, 0), new XY(1, 1)); + var segment2 = new Segment2D(new XY(0, 0), new XY(1, 1)); + + Assert.Equal(segment1.GetHashCode(), segment2.GetHashCode()); + } + + [Fact] + public void ObjectEquals_WithDifferentType_ReturnsFalse() + { + var segment = new Segment2D(new XY(0, 0), new XY(1, 1)); + object other = "not a segment"; + + Assert.False(segment.Equals(other)); + } + + [Fact] + public void ObjectEquals_WithNull_ReturnsFalse() + { + var segment = new Segment2D(new XY(0, 0), new XY(1, 1)); + + Assert.False(segment.Equals(null)); + } + + [Fact] + public void ObjectEquals_WithSameSegment_ReturnsTrue() + { + var segment1 = new Segment2D(new XY(0, 0), new XY(1, 1)); + object segment2 = new Segment2D(new XY(0, 0), new XY(1, 1)); + + Assert.True(segment1.Equals(segment2)); + } + + [Fact] + public void Origin_CanBeModified() + { + var segment = new Segment2D(new XY(0, 0), new XY(1, 1)); + var newOrigin = new XY(2, 2); + + segment.Origin = newOrigin; + + Assert.Equal(newOrigin, segment.Origin); + } +} \ No newline at end of file diff --git a/CSMath.Tests/Geometry/Segment3DTests.cs b/CSMath.Tests/Geometry/Segment3DTests.cs new file mode 100644 index 0000000..15dd16d --- /dev/null +++ b/CSMath.Tests/Geometry/Segment3DTests.cs @@ -0,0 +1,313 @@ +using CSMath.Geometry; +using Xunit; + +namespace CSMath.Tests.Geometry; + +public class Segment3DTests +{ + [Fact] + public void Constructor_WithValidPoints_CreatesSegment() + { + var origin = new XYZ(0, 0, 0); + var end = new XYZ(1, 1, 1); + + var segment = new Segment3D(origin, end); + + Assert.Equal(origin, segment.Origin); + Assert.Equal(end, segment.End); + } + + [Fact] + public void Constructor_WithZeroLengthSegment_CreatesSegment() + { + var point = new XYZ(5, 5, 5); + + var segment = new Segment3D(point, point); + + Assert.Equal(point, segment.Origin); + Assert.Equal(point, segment.End); + } + + [Fact] + public void Direction_ForZeroLengthSegment_ReturnsZeroVector() + { + var point = new XYZ(5, 5, 5); + var segment = new Segment3D(point, point); + + var direction = segment.Direction; + + Assert.Equal(XYZ.Zero, direction); + } + + [Fact] + public void Direction_ReturnsVectorFromOriginToEnd() + { + var origin = new XYZ(1, 2, 3); + var end = new XYZ(4, 6, 9); + var segment = new Segment3D(origin, end); + + var direction = segment.Direction; + + Assert.Equal(new XYZ(3, 4, 6), direction); + } + + [Fact] + public void Direction_UpdatesWhenOriginChanges() + { + var segment = new Segment3D(new XYZ(0, 0, 0), new XYZ(2, 2, 2)); + + segment.Origin = new XYZ(1, 1, 1); + + Assert.Equal(new XYZ(1, 1, 1), segment.Direction); + } + + [Fact] + public void Direction_UpdatesWhenPropertiesChange() + { + var segment = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + + segment.End = new XYZ(2, 2, 2); + + Assert.Equal(new XYZ(2, 2, 2), segment.Direction); + } + + [Fact] + public void Direction_WithMixedCoordinates_ReturnsCorrectVector() + { + var origin = new XYZ(-1, 2, -3); + var end = new XYZ(4, -6, 9); + var segment = new Segment3D(origin, end); + + var direction = segment.Direction; + + Assert.Equal(new XYZ(5, -8, 12), direction); + } + + [Fact] + public void Direction_WithNegativeCoordinates_ReturnsCorrectVector() + { + var origin = new XYZ(-1, -2, -3); + var end = new XYZ(-4, -6, -9); + var segment = new Segment3D(origin, end); + + var direction = segment.Direction; + + Assert.Equal(new XYZ(-3, -4, -6), direction); + } + + [Fact] + public void End_CanBeModified() + { + var segment = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + var newEnd = new XYZ(3, 3, 3); + + segment.End = newEnd; + + Assert.Equal(newEnd, segment.End); + } + + [Fact] + public void Equals_WithDifferentEnd_ReturnsFalse() + { + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + var segment2 = new Segment3D(new XYZ(0, 0, 0), new XYZ(2, 2, 2)); + + Assert.False(segment1.Equals(segment2)); + } + + [Fact] + public void Equals_WithDifferentOrigin_ReturnsFalse() + { + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + var segment2 = new Segment3D(new XYZ(1, 0, 0), new XYZ(1, 1, 1)); + + Assert.False(segment1.Equals(segment2)); + } + + [Fact] + public void Equals_WithDifferentZ_ReturnsFalse() + { + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + var segment2 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 2)); + + Assert.False(segment1.Equals(segment2)); + } + + [Fact] + public void Equals_WithSameOriginAndEnd_ReturnsTrue() + { + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + var segment2 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + + Assert.True(segment1.Equals(segment2)); + } + + [Fact] + public void FindIntersection_WithCollinearSegments_ReturnsNaN() + { + // Two collinear segments on the same line + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(2, 0, 0)); + var segment2 = new Segment3D(new XYZ(1, 0, 0), new XYZ(3, 0, 0)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.True(intersection.IsNaN()); + } + + [Fact] + public void FindIntersection_WithDiagonalSegments_ReturnsIntersectionPoint() + { + // Segment from (0, 0, 0) to (2, 2, 0) + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(2, 2, 0)); + // Segment from (0, 2, 0) to (2, 0, 0) + var segment2 = new Segment3D(new XYZ(0, 2, 0), new XYZ(2, 0, 0)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.Equal(new XYZ(1, 1, 0), intersection); + } + + [Fact] + public void FindIntersection_WithIntersectingSegmentsIn3D_ReturnsIntersectionPoint() + { + // Segment from (0, 0, 0) to (2, 2, 2) + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(2, 2, 2)); + // Segment from (0, 2, 0) to (2, 0, 2) + var segment2 = new Segment3D(new XYZ(0, 2, 0), new XYZ(2, 0, 2)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.Equal(new XYZ(1, 1, 1), intersection); + } + + [Fact] + public void FindIntersection_WithIntersectingSegmentsInXYPlane_ReturnsIntersectionPoint() + { + // Horizontal segment from (0, 1, 0) to (2, 1, 0) + var segment1 = new Segment3D(new XYZ(0, 1, 0), new XYZ(2, 1, 0)); + // Vertical segment from (1, 0, 0) to (1, 2, 0) + var segment2 = new Segment3D(new XYZ(1, 0, 0), new XYZ(1, 2, 0)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.Equal(new XYZ(1, 1, 0), intersection); + } + + [Fact] + public void FindIntersection_WithIntersectionAtEndpoint_ReturnsIntersectionPoint() + { + // Segment from (0, 0, 0) to (2, 0, 0) + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(2, 0, 0)); + // Segment from (2, -1, 0) to (2, 1, 0) + var segment2 = new Segment3D(new XYZ(2, -1, 0), new XYZ(2, 1, 0)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.Equal(new XYZ(2, 0, 0), intersection); + } + + [Fact] + public void FindIntersection_WithIntersectionJustOutsideBounds_ReturnsNaN() + { + // Segment from (0, 0, 0) to (1, 0, 0) + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 0, 0)); + // Segment from (2, -1, 0) to (2, 1, 0) + var segment2 = new Segment3D(new XYZ(2, -1, 0), new XYZ(2, 1, 0)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.True(intersection.IsNaN()); + } + + [Fact] + public void FindIntersection_WithIntersectionOutsideSegmentBounds_ReturnsNaN() + { + // Segment from (0, 1, 0) to (1, 1, 0) + var segment1 = new Segment3D(new XYZ(0, 1, 0), new XYZ(1, 1, 0)); + // Vertical segment from (2, 0, 0) to (2, 2, 0) + var segment2 = new Segment3D(new XYZ(2, 0, 0), new XYZ(2, 2, 0)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.True(intersection.IsNaN()); + } + + [Fact] + public void FindIntersection_WithParallelSegments_ReturnsNaN() + { + // Two parallel horizontal segments + var segment1 = new Segment3D(new XYZ(0, 1, 0), new XYZ(2, 1, 0)); + var segment2 = new Segment3D(new XYZ(0, 2, 0), new XYZ(2, 2, 0)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.True(intersection.IsNaN()); + } + + [Fact] + public void FindIntersection_WithSegmentsInDifferentPlanes_MayReturnNaN() + { + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(2, 2, 0)); + var segment2 = new Segment3D(new XYZ(3, 3, 3), new XYZ(1, 1, 3)); + + var intersection = segment1.FindIntersection(segment2); + + Assert.True(intersection.IsNaN()); + } + + [Fact] + public void GetHashCode_DifferentSegments_MayHaveDifferentHashCode() + { + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + var segment2 = new Segment3D(new XYZ(1, 1, 1), new XYZ(2, 2, 2)); + + // Not guaranteed to be different, but highly likely + Assert.NotEqual(segment1.GetHashCode(), segment2.GetHashCode()); + } + + [Fact] + public void GetHashCode_EqualSegments_HaveSameHashCode() + { + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + var segment2 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + + Assert.Equal(segment1.GetHashCode(), segment2.GetHashCode()); + } + + [Fact] + public void ObjectEquals_WithDifferentType_ReturnsFalse() + { + var segment = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + object other = "not a segment"; + + Assert.False(segment.Equals(other)); + } + + [Fact] + public void ObjectEquals_WithNull_ReturnsFalse() + { + var segment = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + + Assert.False(segment.Equals(null)); + } + + [Fact] + public void ObjectEquals_WithSameSegment_ReturnsTrue() + { + var segment1 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + object segment2 = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + + Assert.True(segment1.Equals(segment2)); + } + + [Fact] + public void Origin_CanBeModified() + { + var segment = new Segment3D(new XYZ(0, 0, 0), new XYZ(1, 1, 1)); + var newOrigin = new XYZ(2, 2, 2); + + segment.Origin = newOrigin; + + Assert.Equal(newOrigin, segment.Origin); + } +} \ No newline at end of file diff --git a/CSMath/BoundingBox.cs b/CSMath/BoundingBox.cs index f39353f..681a6d7 100644 --- a/CSMath/BoundingBox.cs +++ b/CSMath/BoundingBox.cs @@ -8,28 +8,6 @@ namespace CSMath; /// public struct BoundingBox { - /// - /// Instance of a null bounding box. - /// - public static readonly BoundingBox Null = new BoundingBox(BoundingBoxExtent.Null); - - /// - /// Instance of an infinite bounding box. - /// - public static readonly BoundingBox Infinite = new BoundingBox(BoundingBoxExtent.Infinite); - - public BoundingBoxExtent Extent { get; } - - /// - /// Get the min corner of the bounding box. - /// - public XYZ Min { get; set; } - - /// - /// Get the max corner of the bounding box. - /// - public XYZ Max { get; set; } - /// /// Center of the box. /// @@ -41,6 +19,17 @@ public XYZ Center } } + public BoundingBoxExtent Extent { get; } + + [Obsolete("Use LengthY instead.")] + public double Height + { + get + { + return Math.Abs(this.Max.Y - this.Min.Y); + } + } + /// /// Gets the length of the bounding box along the X-axis. /// @@ -74,6 +63,16 @@ public double LengthZ } } + /// + /// Get the max corner of the bounding box. + /// + public XYZ Max { get; set; } + + /// + /// Get the min corner of the bounding box. + /// + public XYZ Min { get; set; } + [Obsolete("Use LengthX instead.")] public double Width { @@ -83,34 +82,15 @@ public double Width } } - [Obsolete("Use LengthY instead.")] - public double Height - { - get - { - return Math.Abs(this.Max.Y - this.Min.Y); - } - } + /// + /// Instance of an infinite bounding box. + /// + public static readonly BoundingBox Infinite = new BoundingBox(BoundingBoxExtent.Infinite); - private BoundingBox(BoundingBoxExtent extent) - { - this.Extent = extent; - switch (extent) - { - case BoundingBoxExtent.Null: - this.Max = new XYZ(double.NaN); - this.Min = new XYZ(double.NaN); - break; - case BoundingBoxExtent.Infinite: - this.Min = new XYZ(double.NegativeInfinity); - this.Max = new XYZ(double.PositiveInfinity); - break; - case BoundingBoxExtent.Finite: - case BoundingBoxExtent.Point: - default: - break; - } - } + /// + /// Instance of a null bounding box. + /// + public static readonly BoundingBox Null = new BoundingBox(BoundingBoxExtent.Null); /// /// Initializes a new instance of the BoundingBox class that represents a single point. @@ -162,48 +142,58 @@ public BoundingBox(double minX, double minY, double minZ, double maxX, double ma this = new BoundingBox(new XYZ(minX, minY, minZ), new XYZ(maxX, maxY, maxZ)); } + private BoundingBox(BoundingBoxExtent extent) + { + this.Extent = extent; + switch (extent) + { + case BoundingBoxExtent.Null: + this.Max = new XYZ(double.NaN); + this.Min = new XYZ(double.NaN); + break; + case BoundingBoxExtent.Infinite: + this.Min = new XYZ(double.NegativeInfinity); + this.Max = new XYZ(double.PositiveInfinity); + break; + case BoundingBoxExtent.Finite: + case BoundingBoxExtent.Point: + default: + break; + } + } + /// - /// Returns a new bounding box translated by the specified vector. + /// Create a bounding box from a collection of points. /// - /// The vector by which to translate the bounding box. Each component is added to the corresponding coordinates of the - /// bounding box's minimum and maximum points. - /// A new BoundingBox instance that is the result of translating the current bounding box by the specified vector. - public BoundingBox Move(XYZ xyz) + /// + /// + public static BoundingBox FromPoints(IEnumerable points) { - return new BoundingBox(this.Min + xyz, this.Max + xyz); + BoundingBox boundingBox = Null; + + foreach (var point in points) + { + boundingBox = boundingBox.Merge(new BoundingBox(point)); + } + + return boundingBox; } /// - /// Merge 2 boxes into the common one. + /// Merge Multiple boxes into the common one. /// - /// + /// /// The merged box. - public BoundingBox Merge(BoundingBox box) + public static BoundingBox Merge(IEnumerable boxes) { - if (this.Extent == BoundingBoxExtent.Infinite - || box.Extent == BoundingBoxExtent.Infinite) - { - return BoundingBox.Infinite; - } - else if (this.Extent == BoundingBoxExtent.Null) - { - return box; - } - else if (box.Extent == BoundingBoxExtent.Null) + BoundingBox b = BoundingBox.Null; + + foreach (var box in boxes) { - return this; + b = b.Merge(box); } - var min = new XYZ( - System.Math.Min(this.Min.X, box.Min.X), - System.Math.Min(this.Min.Y, box.Min.Y), - System.Math.Min(this.Min.Z, box.Min.Z)); - var max = new XYZ( - System.Math.Max(this.Max.X, box.Max.X), - System.Math.Max(this.Max.Y, box.Max.Y), - System.Math.Max(this.Max.Z, box.Max.Z)); - - return new BoundingBox(min, max); + return b; } /// @@ -253,36 +243,46 @@ public bool IsIn(XYZ point) } /// - /// Merge Multiple boxes into the common one. + /// Merge 2 boxes into the common one. /// - /// + /// /// The merged box. - public static BoundingBox Merge(IEnumerable boxes) + public BoundingBox Merge(BoundingBox box) { - BoundingBox b = BoundingBox.Null; - - foreach (var box in boxes) + if (this.Extent == BoundingBoxExtent.Infinite + || box.Extent == BoundingBoxExtent.Infinite) { - b = b.Merge(box); + return BoundingBox.Infinite; + } + else if (this.Extent == BoundingBoxExtent.Null) + { + return box; + } + else if (box.Extent == BoundingBoxExtent.Null) + { + return this; } - return b; + var min = new XYZ( + System.Math.Min(this.Min.X, box.Min.X), + System.Math.Min(this.Min.Y, box.Min.Y), + System.Math.Min(this.Min.Z, box.Min.Z)); + var max = new XYZ( + System.Math.Max(this.Max.X, box.Max.X), + System.Math.Max(this.Max.Y, box.Max.Y), + System.Math.Max(this.Max.Z, box.Max.Z)); + + return new BoundingBox(min, max); } /// - /// Create a bounding box from a collection of points. + /// Returns a new bounding box translated by the specified vector. /// - /// - /// - public static BoundingBox FromPoints(IEnumerable points) + /// The vector by which to translate the bounding box. Each component is added to the corresponding coordinates of the + /// bounding box's minimum and maximum points. + /// A new BoundingBox instance that is the result of translating the current bounding box by the specified vector. + public BoundingBox Move(XYZ xyz) { - BoundingBox boundingBox = Null; - - foreach (var point in points) - { - boundingBox = boundingBox.Merge(new BoundingBox(point)); - } - - return boundingBox; + return new BoundingBox(this.Min + xyz, this.Max + xyz); } } \ No newline at end of file diff --git a/CSMath/BoundingBoxExtent.cs b/CSMath/BoundingBoxExtent.cs index c1efed3..7d3d259 100644 --- a/CSMath/BoundingBoxExtent.cs +++ b/CSMath/BoundingBoxExtent.cs @@ -3,7 +3,10 @@ public enum BoundingBoxExtent { Null, + Finite, + Infinite, + Point -} +} \ No newline at end of file diff --git a/CSMath/CSMath.projitems b/CSMath/CSMath.projitems index 6b9faa1..b437265 100644 --- a/CSMath/CSMath.projitems +++ b/CSMath/CSMath.projitems @@ -11,11 +11,15 @@ + + + + diff --git a/CSMath/Geometry/Arc2D.cs b/CSMath/Geometry/Arc2D.cs new file mode 100644 index 0000000..f25923b --- /dev/null +++ b/CSMath/Geometry/Arc2D.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CSMath.Geometry; + +/// +/// Represents a 2D circular arc defined by a center point, radius, and angular range. +/// +public struct Arc2D +{ + /// + /// Gets or sets the center point of the arc. + /// + public XY Center { get; set; } + + /// + /// Gets or sets the ending angle of the arc in radians. + /// + public double EndAngle { get; set; } + + /// + /// Gets or sets the radius of the arc. + /// + public double Radius { get; set; } + + /// + /// Gets or sets the starting angle of the arc in radians. + /// + public double StartAngle { get; set; } + + public Arc2D(XY center, double radius, double startAngle, double endAngle) + { + this.Center = center; + this.Radius = radius; + this.StartAngle = startAngle; + this.EndAngle = endAngle; + } + + /// + /// Calculates the intersection points between this arc and a line segment. + /// + /// The line segment to test for intersection. + /// The precision value for the calculation. + /// An enumerable collection of intersection points. Returns an empty collection if no intersections exist. + public IEnumerable FindIntersections(Line2D line, double precision = MathHelper.Epsilon) + { + double lengthSquared = line.Direction.GetLengthSquared(); + XY originOffset = line.Origin - this.Center; + XY offsetEnd = originOffset + line.Direction; + double crossProduct = originOffset.X * offsetEnd.Y - offsetEnd.X * originOffset.Y; + double discriminant = this.Radius * this.Radius * lengthSquared - crossProduct * crossProduct; + + if (discriminant < 0) + { + return Enumerable.Empty(); + } + + double invLengthSquared = 1.0 / lengthSquared; + double baseX = crossProduct * line.Direction.Y; + double baseY = -crossProduct * line.Direction.X; + + if (discriminant <= 0) + { + XY tangentPoint = new XY(baseX * invLengthSquared, baseY * invLengthSquared) + this.Center; + if (this.InAngularRange(tangentPoint)) + { + return new[] { tangentPoint }; + } + + return Enumerable.Empty(); + } + + double sqrtDiscriminant = System.Math.Sqrt(discriminant); + double sign = line.Direction.Y < 0.0 ? -1.0 : 1.0; + double offsetX = sign * line.Direction.X * sqrtDiscriminant; + double offsetY = System.Math.Abs(line.Direction.Y) * sqrtDiscriminant; + + List intersections = new List(2); + XY firstPoint = new XY((baseX + offsetX) * invLengthSquared, (baseY + offsetY) * invLengthSquared) + this.Center; + if (this.InAngularRange(firstPoint)) + { + intersections.Add(firstPoint); + } + + XY secondPoint = new XY((baseX - offsetX) * invLengthSquared, (baseY - offsetY) * invLengthSquared) + this.Center; + if (this.InAngularRange(secondPoint)) + { + intersections.Add(secondPoint); + } + + return intersections; + } + + /// + /// Determines whether a point lies within the angular range of this arc. + /// + /// The point to test. + /// true if the point's angle from the center falls within the arc's angular range; otherwise, false. + public bool InAngularRange(XY point) + { + XY dir = point - this.Center; + double angle = System.Math.Atan2(dir.Y, dir.X); + return MathHelper.IsAngleInRange(angle, this.StartAngle, this.EndAngle); + } +} \ No newline at end of file diff --git a/CSMath/Geometry/Circle2D.cs b/CSMath/Geometry/Circle2D.cs new file mode 100644 index 0000000..2e272e7 --- /dev/null +++ b/CSMath/Geometry/Circle2D.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; + +namespace CSMath.Geometry; + +/// +/// Represents a circle in 2D space defined by a center point and radius. +/// +public struct Circle2D +{ + /// + /// Gets or sets the center point of the circle. + /// + public XY Center { get; set; } + + /// + /// Gets or sets the radius of the circle. + /// + public double Radius { get; set; } + + /// + /// Initializes a new instance of the struct with a center point and radius. + /// + /// The center point of the circle. + /// The radius of the circle. + public Circle2D(XY center, double radius) + { + this.Center = center; + this.Radius = radius; + } + + /// + /// Initializes a new instance of the struct with center coordinates and radius. + /// + /// The X coordinate of the center point. + /// The Y coordinate of the center point. + /// The radius of the circle. + public Circle2D(double centerX, double centerY, double radius) + { + this.Center = new XY(centerX, centerY); + this.Radius = radius; + } + + /// + /// Finds the intersection points between this circle and a line. + /// + /// The line to test for intersection with this circle. + /// + /// An enumerable collection of intersection points. Returns:
+ /// - Empty enumerable if the line does not intersect the circle.
+ /// - Single point if the line is tangent to the circle.
+ /// - Two points if the line intersects the circle at two locations.
+ ///
+ public IEnumerable FindIntersections(Line2D line) + { + double lengthSquared = line.Direction.GetLengthSquared(); + XY relativeOrigin = line.Origin - this.Center; + double determinant = XY.Cross(relativeOrigin, line.Direction); + double discriminant = this.Radius * this.Radius * lengthSquared - determinant * determinant; + + if (MathHelper.IsZero(discriminant)) + { + double x = determinant * line.Direction.Y / lengthSquared; + double y = -determinant * line.Direction.X / lengthSquared; + + return new[] + { + new XY(x, y) + this.Center + }; + } + + if (discriminant < 0.0) + { + return Enumerable.Empty(); + } + + double sqrtDiscriminant = System.Math.Sqrt(discriminant); + double sign = line.Direction.Y < 0.0 ? -1.0 : 1.0; + + double baseX = determinant * line.Direction.Y; + double baseY = -determinant * line.Direction.X; + double offsetX = sign * line.Direction.X * sqrtDiscriminant; + double offsetY = System.Math.Abs(line.Direction.Y) * sqrtDiscriminant; + + XY intersection1 = new XY( + (baseX + offsetX) / lengthSquared, + (baseY + offsetY) / lengthSquared) + this.Center; + + XY intersection2 = new XY( + (baseX - offsetX) / lengthSquared, + (baseY - offsetY) / lengthSquared) + this.Center; + + return new[] + { + intersection1, + intersection2 + }; + } +} \ No newline at end of file diff --git a/CSMath/Geometry/ICurve.cs b/CSMath/Geometry/ICurve.cs index c7f97a3..a54e687 100644 --- a/CSMath/Geometry/ICurve.cs +++ b/CSMath/Geometry/ICurve.cs @@ -17,17 +17,17 @@ public interface ICurve /// double RadiusRatio { get; } - /// - /// Converts the curve in a list of vertexes. - /// - /// Number of vertexes generated. - /// A list vertexes that represents the curve expressed in object coordinate system. - List PolygonalVertexes(int precision); - /// /// Calculate the local point on the curve for a given angle relative to the center. /// /// Angle in radians. /// A local point on the curve for the given angle relative to the center. XYZ PolarCoordinateRelativeToCenter(double angle); + + /// + /// Converts the curve in a list of vertexes. + /// + /// Number of vertexes generated. + /// A list vertexes that represents the curve expressed in object coordinate system. + List PolygonalVertexes(int precision); } \ No newline at end of file diff --git a/CSMath/Geometry/ILine.cs b/CSMath/Geometry/ILine.cs index 8380bc0..879bb14 100644 --- a/CSMath/Geometry/ILine.cs +++ b/CSMath/Geometry/ILine.cs @@ -4,14 +4,14 @@ public interface ILine where T : IVector { /// - /// Origin point that the line intersects with + /// Direction fo the line /// - public T Origin { get; set; } + public T Direction { get; } /// - /// Direction fo the line + /// Origin point that the line intersects with /// - public T Direction { get; set; } + public T Origin { get; } /// /// Find the intersection between 2 lines. diff --git a/CSMath/Geometry/Line2D.cs b/CSMath/Geometry/Line2D.cs index 0f92f7e..156ded9 100644 --- a/CSMath/Geometry/Line2D.cs +++ b/CSMath/Geometry/Line2D.cs @@ -2,17 +2,26 @@ namespace CSMath.Geometry; +/// +/// Represents a 2D line defined by an origin point and a direction vector. +/// public struct Line2D : ILine, IEquatable { /// public XY Direction { get; set; } + /// + /// Gets the y-intercept of the line, calculated from the origin and slope. + /// public double Offset { get { return Origin.Y - this.Slope * Origin.X; } } /// public XY Origin { get; set; } - public double Slope { get { return (this.Direction.Y - this.Direction.Y) / (this.Direction.X - this.Direction.X); } } + /// + /// Gets the slope of the line based on its direction vector. + /// + public double Slope { get { return this.Direction.Y / this.Direction.X; } } public Line2D(XY origin, XY direction) { @@ -20,12 +29,39 @@ public Line2D(XY origin, XY direction) this.Direction = direction; } + /// + /// Creates a line from 2 points, the first point is the origin and the second point is used to calculate the direction. + /// + /// The first point, which will be the origin of the line. + /// The second point, used to calculate the direction of the line. + /// A new instance of the class. + public static Line2D FromPoints(XY pt1, XY pt2) + { + return new Line2D(pt1, pt2 - pt1); + } + + /// + /// Creates a line from a 2D segment, using the segment's origin and direction. + /// + /// The 2D segment used to create the line. + /// A new instance of the class. + public static Line2D FromSegment2D(Segment2D segment) + { + return new Line2D(segment.Origin, segment.Direction); + } + /// public bool Equals(Line2D other) { return this.IsPointOnLine(other.Origin) && other.Direction == this.Direction; } + /// + public override bool Equals(object obj) + { + return obj is Line2D && Equals((Line2D)obj); + } + /// public XY FindIntersection(ILine line) { @@ -39,4 +75,20 @@ public XY FindIntersection(ILine line) double s = (v.X * line.Direction.Y - v.Y * line.Direction.X) / cross; return this.Origin + s * this.Direction; } + + /// + public override int GetHashCode() + { + return this.Origin.GetHashCode() ^ this.Direction.GetHashCode(); + } + + /// + /// Determines if a given point lies on the line. + /// + /// The parameter value along the line. + /// The point on the line corresponding to the given parameter value. + public XY PointInLine(double lambda) + { + return this.Origin + lambda * this.Direction; + } } \ No newline at end of file diff --git a/CSMath/Geometry/Line3D.cs b/CSMath/Geometry/Line3D.cs index 33aa62d..aef0e24 100644 --- a/CSMath/Geometry/Line3D.cs +++ b/CSMath/Geometry/Line3D.cs @@ -2,14 +2,13 @@ namespace CSMath.Geometry; -// Eq: pt = origin + a * Direction public struct Line3D : ILine, IEquatable { /// - public XYZ Origin { get; set; } + public XYZ Direction { get; set; } /// - public XYZ Direction { get; set; } + public XYZ Origin { get; set; } /// /// Initialize a new instance of the class. @@ -29,6 +28,33 @@ public Line3D(XYZ origin, XYZ direction) this.Direction = direction.Normalize(); } + /// + /// Creates a line from 2 points, the first point is the origin and the second point is used to calculate the direction. + /// + /// The first point, which will be the origin of the line. + /// The second point, used to calculate the direction of the line. + /// A new instance of the class. + public static Line3D FromPoints(XYZ pt1, XYZ pt2) + { + return new Line3D(pt1, pt2 - pt1); + } + + /// + /// Creates a line from a segment, the origin of the line will be the origin of the segment and the direction will be the direction of the segment. + /// + /// The 3D segment used to create the line. + /// A new instance of the class. + public static Line3D FromSegment3D(Segment3D segment) + { + return new Line3D(segment.Origin, segment.Direction); + } + + /// + public bool Equals(Line3D other) + { + return this.IsPointOnLine(other.Origin) && other.Direction == this.Direction; + } + /// public XYZ FindIntersection(ILine line) { @@ -59,10 +85,4 @@ public XYZ FindIntersection(ILine line) return XYZ.NaN; } } - - /// - public bool Equals(Line3D other) - { - return this.IsPointOnLine(other.Origin) && other.Direction == this.Direction; - } } \ No newline at end of file diff --git a/CSMath/Geometry/LineExtensions.cs b/CSMath/Geometry/LineExtensions.cs index 938feaf..56cdcaf 100644 --- a/CSMath/Geometry/LineExtensions.cs +++ b/CSMath/Geometry/LineExtensions.cs @@ -4,6 +4,14 @@ namespace CSMath.Geometry; public static class LineExtensions { + /// + /// Creates a line from 2 points, the first point is the origin and the second point is used to calculate the direction. + /// + /// The type of the line to create. + /// The type of the vector used for the points. + /// The first point, which will be the origin of the line. + /// The second point, used to calculate the direction of the line. + /// A new instance of the specified line type. public static T CreateFromPoints(R pt1, R pt2) where T : ILine where R : IVector, new() @@ -14,9 +22,9 @@ public static T CreateFromPoints(R pt1, R pt2) /// /// Determines whether the specified point is on the line, or not. /// - /// - /// - /// + /// The line to check against. + /// The point to check. + /// True if the point is on the line; otherwise, false. public static bool IsPointOnLine(this ILine line, T point) where T : IVector { @@ -38,14 +46,16 @@ public static bool IsPointOnLine(this ILine line, T point) /// /// Tries to find the intersection between 2 lines. /// - /// - /// - /// - /// - /// + /// The type of the first line. + /// The type of the second line. + /// The type of the vector used for the lines. + /// The first line. + /// The second line. + /// The intersection point, if found. /// True if the intersection is found. - public static bool TryFindIntersection(this T line1, T line2, out R intersection) + public static bool TryFindIntersection(this T line1, S line2, out R intersection) where T : ILine + where S : ILine where R : IVector { intersection = line1.FindIntersection(line2); diff --git a/CSMath/Geometry/Segment2D.cs b/CSMath/Geometry/Segment2D.cs new file mode 100644 index 0000000..12bcdbd --- /dev/null +++ b/CSMath/Geometry/Segment2D.cs @@ -0,0 +1,68 @@ +using System; + +namespace CSMath.Geometry; + +public struct Segment2D : ILine, IEquatable +{ + /// + public XY Direction { get { return this.End - this.Origin; } } + + /// + /// Gets or sets the end point of the segment in the XY plane. + /// + public XY End { get; set; } + + /// + public XY Origin { get; set; } + + public Segment2D(XY origin, XY end) + { + this.Origin = origin; + this.End = end; + } + + /// + public bool Equals(Segment2D other) + { + return this.Origin.Equals(other.Origin) && this.End.Equals(other.End); + } + + /// + public override bool Equals(object obj) + { + return obj is Segment2D && Equals((Segment2D)obj); + } + + /// + /// Calculates the intersection point of the current line segment and the specified line. + /// + /// The line to intersect with. + /// The intersection point as an XY object, or XY.NaN if there is no intersection within the bounds of the current + /// segment. + public XY FindIntersection(ILine line) + { + var curr = new Line2D(this.Origin, this.Direction); + var intersection = curr.FindIntersection(line); + + if (intersection.IsNaN()) + { + return XY.NaN; + } + + if (intersection.X < Math.Min(this.Origin.X, this.End.X) + || intersection.X > Math.Max(this.Origin.X, this.End.X) + || intersection.Y < Math.Min(this.Origin.Y, this.End.Y) + || intersection.Y > Math.Max(this.Origin.Y, this.End.Y)) + { + return XY.NaN; + } + + return intersection; + } + + /// + public override int GetHashCode() + { + return this.Origin.GetHashCode() ^ this.End.GetHashCode(); + } +} \ No newline at end of file diff --git a/CSMath/Geometry/Segment3D.cs b/CSMath/Geometry/Segment3D.cs new file mode 100644 index 0000000..4ee843b --- /dev/null +++ b/CSMath/Geometry/Segment3D.cs @@ -0,0 +1,68 @@ +using System; + +namespace CSMath.Geometry; + +public struct Segment3D : ILine, IEquatable +{ + /// + public XYZ Direction { get { return this.End - this.Origin; } } + + /// + /// Gets or sets the end point of the segment in 3D space. + /// + public XYZ End { get; set; } + + /// + public XYZ Origin { get; set; } + + public Segment3D(XYZ origin, XYZ end) + { + this.Origin = origin; + this.End = end; + } + + /// + public bool Equals(Segment3D other) + { + return this.Origin.Equals(other.Origin) && this.End.Equals(other.End); + } + + /// + public override bool Equals(object obj) + { + return obj is Segment3D && Equals((Segment3D)obj); + } + + /// + /// Calculates the intersection point of the current line segment and the specified line. + /// + /// The line to intersect with. + /// The intersection point as an XYZ object, or XYZ.NaN if there is no intersection within the bounds of the current + /// segment. + public XYZ FindIntersection(ILine line) + { + var curr = new Line3D(this.Origin, this.Direction); + var intersection = curr.FindIntersection(line); + + if (intersection.IsNaN()) + { + return XYZ.NaN; + } + + if (intersection.X < Math.Min(this.Origin.X, this.End.X) + || intersection.X > Math.Max(this.Origin.X, this.End.X) + || intersection.Y < Math.Min(this.Origin.Y, this.End.Y) + || intersection.Y > Math.Max(this.Origin.Y, this.End.Y)) + { + return XYZ.NaN; + } + + return intersection; + } + + /// + public override int GetHashCode() + { + return this.Origin.GetHashCode() ^ this.End.GetHashCode(); + } +} \ No newline at end of file diff --git a/CSMath/IVector.cs b/CSMath/IVector.cs index 49cfc5e..59af5d0 100644 --- a/CSMath/IVector.cs +++ b/CSMath/IVector.cs @@ -15,4 +15,4 @@ public interface IVector /// The index. /// The value of the coordinate at the specified index. public double this[int index] { get; set; } -} +} \ No newline at end of file diff --git a/CSMath/MathHelper.cs b/CSMath/MathHelper.cs index c420705..d8ca7ef 100644 --- a/CSMath/MathHelper.cs +++ b/CSMath/MathHelper.cs @@ -20,7 +20,7 @@ public static class MathHelper /// /// Factor for converting degrees to radians. /// - public const double DegToRadFactor = (Math.PI / 180); + public const double DegToRadFactor = Math.PI / 180; /// /// Default tolerance @@ -50,7 +50,7 @@ public static class MathHelper /// /// Factor for converting radians to degrees. /// - public const double RadToDegFactor = (180 / Math.PI); + public const double RadToDegFactor = 180 / Math.PI; /// /// Factor for converting radians to gradians. @@ -108,6 +108,17 @@ public static double FixZero(double number) return FixZero(number, Epsilon); } + /// + /// Replaces the specified number with zero if it is within the defined threshold. + /// + /// The number to evaluate. + /// The threshold value for determining if the number is considered zero. + /// Zero if the number is within the threshold; otherwise, the original number. + public static double FixZero(double number, double threshold) + { + return IsZero(number, threshold) ? 0 : number; + } + /// /// Returns zero if the specified number is within the given threshold of zero; otherwise, returns the original number. /// @@ -117,12 +128,6 @@ public static double FixZero(double number) /// The value to evaluate for near-zero equivalence. /// The tolerance within which the number is considered to be zero. Must be non-negative. /// Zero if the absolute value of the number is less than or equal to the threshold; otherwise, the original number. - - public static double FixZero(double number, double threshold) - { - return IsZero(number, threshold) ? 0 : number; - } - /// /// Returns a copy of the specified vector with components that are effectively zero replaced by exact zeros. /// @@ -195,6 +200,35 @@ public static bool IsAlmostZero(double value) return false; } + /// + /// Determines whether a specified angle falls within a given angular range. + /// + /// + /// This method normalizes all angles to the range [0, 2π) before comparison. It correctly handles ranges that wrap + /// around the 0/2π boundary. For example, if the start angle is greater than the end angle, the range is considered + /// to span across 0 radians. + /// + /// The angle to test, in radians. + /// The start of the angular range, in radians. + /// The end of the angular range, in radians. + /// The precision value for the calculation. + /// + /// true if the angle falls within the specified range (inclusive); otherwise, false. + /// + public static bool IsAngleInRange(double angle, double start, double end, double precision = Epsilon) + { + angle = NormalizeAngleRadians(angle); + start = NormalizeAngleRadians(start); + end = NormalizeAngleRadians(end); + + if (start > end) + { + return (angle >= 0.0 && angle <= end + precision) || (angle >= start - precision && angle <= TwoPI); + } + + return angle >= start - precision && angle <= end + precision; + } + /// /// Checks if a number is equal to another. /// @@ -267,11 +301,26 @@ public static double NormalizeAngle(double angle) } /// - /// Convert a value from radian to degree + /// Normalizes the value of an angle in radians between 0-2π. + /// + /// Angle in radians. + /// The equivalent angle in the range 0-2π. + public static double NormalizeAngleRadians(double angle) + { + if (angle < 0.0 || angle > TwoPI) + { + angle -= TwoPI * Math.Floor(angle / TwoPI); + } + + return angle; + } + + /// + /// Convert a value from radian to degree. /// /// Value in radians /// Calculates the negative values in a 0-360 range. - /// The radian value + /// The degree value. public static double RadToDeg(double value, bool absolute = true) { var result = value * RadToDegFactor; @@ -282,7 +331,7 @@ public static double RadToDeg(double value, bool absolute = true) /// Convert a value from radian to gradian. /// /// Value in radians. - /// The radian value. + /// The gradian value. public static double RadToGrad(double value) { return value * RadToGradFactor; @@ -303,8 +352,8 @@ public static double RoundToNearest(double number, double roundTo) /// /// Returns the sine of specific angle in radians adjusting the value to 0 using as tolerance. /// - /// - /// + /// Angle in radians. + /// The sine of the angle. public static double Sin(double value) { double result = Math.Sin(value); diff --git a/CSMath/Matrix3.cs b/CSMath/Matrix3.cs index 37e808e..c2862d6 100644 --- a/CSMath/Matrix3.cs +++ b/CSMath/Matrix3.cs @@ -6,14 +6,6 @@ namespace CSMath; public partial struct Matrix3 { - /// - /// 4-dimensional zero matrix. - /// - public static readonly Matrix3 Zero = new Matrix3( - 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0, - 0.0, 0.0, 0.0); - /// /// 4-dimensional identity matrix. /// @@ -22,16 +14,24 @@ public partial struct Matrix3 0.0, 1.0, 0.0, 0.0, 0.0, 1.0); - #region Public Fields + /// + /// 4-dimensional zero matrix. + /// + public static readonly Matrix3 Zero = new Matrix3( + 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); /// /// Value at column 0, row 0 of the matrix. /// public double M00; + /// /// Value at column 0, row 1 of the matrix. /// public double M01; + /// /// Value at column 0, row 2 of the matrix. /// @@ -41,10 +41,12 @@ public partial struct Matrix3 /// Value at column 1, row 0 of the matrix. /// public double M10; + /// /// Value at column 1, row 1 of the matrix. /// public double M11; + /// /// Value at column 1, row 2 of the matrix. /// @@ -54,94 +56,17 @@ public partial struct Matrix3 /// Value at column 2, row 0 of the matrix. /// public double M20; + /// /// Value at column 2, row 1 of the matrix. /// public double M21; + /// /// Value at column 2, row 2 of the matrix. /// public double M22; - #endregion Public Fields - - public double this[int index] - { - get - { - switch (index) - { - case 0: - return this.M00; - case 1: - return this.M01; - case 2: - return this.M02; - case 3: - return this.M10; - case 4: - return this.M11; - case 5: - return this.M12; - case 6: - return this.M20; - case 7: - return this.M21; - case 8: - return this.M22; - default: - throw new IndexOutOfRangeException(); - } - } - set - { - switch (index) - { - case 0: - this.M00 = value; - break; - case 1: - this.M01 = value; - break; - case 2: - this.M02 = value; - break; - case 3: - this.M10 = value; - break; - case 4: - this.M11 = value; - break; - case 5: - this.M12 = value; - break; - case 6: - this.M20 = value; - break; - case 7: - this.M21 = value; - break; - case 8: - this.M22 = value; - break; - default: - throw new IndexOutOfRangeException(); - } - } - } - - public double this[int column, int row] - { - get - { - return this[(column * 3) + row]; - } - set - { - this[(column * 3) + row] = value; - } - } - public Matrix3( double m00, double m10, double m20, double m01, double m11, double m21, @@ -176,17 +101,6 @@ public Matrix3(Matrix4 matrix) this.M22 = matrix.M22; } - /// - /// Obtains the transpose matrix. - /// - /// Transpose matrix. - public Matrix3 Transpose() - { - return new Matrix3(this.M00, this.M10, this.M20, - this.M01, this.M11, this.M21, - this.M02, this.M12, this.M22); - } - /// /// Gets the rotation matrix from the normal vector (extrusion direction) of an entity. /// @@ -247,4 +161,92 @@ public override string ToString() s.Append(string.Format("|{0}{3} {1}{3} {2}|", this.M20, this.M21, this.M22, separator)); return s.ToString(); } + + /// + /// Obtains the transpose matrix. + /// + /// Transpose matrix. + public Matrix3 Transpose() + { + return new Matrix3(this.M00, this.M10, this.M20, + this.M01, this.M11, this.M21, + this.M02, this.M12, this.M22); + } + + public double this[int index] + { + get + { + switch (index) + { + case 0: + return this.M00; + case 1: + return this.M01; + case 2: + return this.M02; + case 3: + return this.M10; + case 4: + return this.M11; + case 5: + return this.M12; + case 6: + return this.M20; + case 7: + return this.M21; + case 8: + return this.M22; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + this.M00 = value; + break; + case 1: + this.M01 = value; + break; + case 2: + this.M02 = value; + break; + case 3: + this.M10 = value; + break; + case 4: + this.M11 = value; + break; + case 5: + this.M12 = value; + break; + case 6: + this.M20 = value; + break; + case 7: + this.M21 = value; + break; + case 8: + this.M22 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + + public double this[int column, int row] + { + get + { + return this[(column * 3) + row]; + } + set + { + this[(column * 3) + row] = value; + } + } } \ No newline at end of file diff --git a/CSMath/Matrix3.operators.cs b/CSMath/Matrix3.operators.cs index 525882b..a810f70 100644 --- a/CSMath/Matrix3.operators.cs +++ b/CSMath/Matrix3.operators.cs @@ -15,4 +15,4 @@ public partial struct Matrix3 a.M10 * b.M00 + a.M11 * b.M10 + a.M12 * b.M20, a.M10 * b.M01 + a.M11 * b.M11 + a.M12 * b.M21, a.M10 * b.M02 + a.M11 * b.M12 + a.M12 * b.M22, a.M20 * b.M00 + a.M21 * b.M10 + a.M22 * b.M20, a.M20 * b.M01 + a.M21 * b.M11 + a.M22 * b.M21, a.M20 * b.M02 + a.M21 * b.M12 + a.M22 * b.M22); } -} +} \ No newline at end of file diff --git a/CSMath/Matrix4.Operators.cs b/CSMath/Matrix4.Operators.cs index 8f4e2ca..1658f85 100644 --- a/CSMath/Matrix4.Operators.cs +++ b/CSMath/Matrix4.Operators.cs @@ -76,4 +76,4 @@ public static Matrix4 Multiply(Matrix4 a, Matrix4 b) matrix.M02 * v.X + matrix.M12 * v.Y + matrix.M22 * v.Z + matrix.M32 * v.M, matrix.M03 * v.X + matrix.M13 * v.Y + matrix.M23 * v.Z + matrix.M33 * v.M); } -} +} \ No newline at end of file diff --git a/CSMath/Matrix4.cs b/CSMath/Matrix4.cs index c832c3d..acb2797 100644 --- a/CSMath/Matrix4.cs +++ b/CSMath/Matrix4.cs @@ -31,8 +31,6 @@ public partial struct Matrix4 /// public static readonly Matrix4 Zero = new Matrix4(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); - #region Public Fields - /// /// Value at column 0, row 0 of the matrix. /// @@ -113,120 +111,6 @@ public partial struct Matrix4 ///
public double M33; - public double this[int index] - { - get - { - switch (index) - { - case 0: - return this.M00; - case 1: - return this.M01; - case 2: - return this.M02; - case 3: - return this.M03; - case 4: - return this.M10; - case 5: - return this.M11; - case 6: - return this.M12; - case 7: - return this.M13; - case 8: - return this.M20; - case 9: - return this.M21; - case 10: - return this.M22; - case 11: - return this.M23; - case 12: - return this.M30; - case 13: - return this.M31; - case 14: - return this.M32; - case 15: - return this.M33; - default: - throw new IndexOutOfRangeException(); - } - } - set - { - switch (index) - { - case 0: - this.M00 = value; - break; - case 1: - this.M01 = value; - break; - case 2: - this.M02 = value; - break; - case 3: - this.M03 = value; - break; - case 4: - this.M10 = value; - break; - case 5: - this.M11 = value; - break; - case 6: - this.M12 = value; - break; - case 7: - this.M13 = value; - break; - case 8: - this.M20 = value; - break; - case 9: - this.M21 = value; - break; - case 10: - this.M22 = value; - break; - case 11: - this.M23 = value; - break; - case 12: - this.M30 = value; - break; - case 13: - this.M31 = value; - break; - case 14: - this.M32 = value; - break; - case 15: - this.M33 = value; - break; - default: - throw new IndexOutOfRangeException(); - } - } - } - - public double this[int column, int row] - { - get - { - return this[(column * 4) + row]; - } - set - { - this[(column * 4) + row] = value; - } - } - - #endregion Public Fields - public Matrix4( double m00, double m10, double m20, double m30, double m01, double m11, double m21, double m31, @@ -883,4 +767,116 @@ public Matrix4 Transpose() return result; } + + public double this[int index] + { + get + { + switch (index) + { + case 0: + return this.M00; + case 1: + return this.M01; + case 2: + return this.M02; + case 3: + return this.M03; + case 4: + return this.M10; + case 5: + return this.M11; + case 6: + return this.M12; + case 7: + return this.M13; + case 8: + return this.M20; + case 9: + return this.M21; + case 10: + return this.M22; + case 11: + return this.M23; + case 12: + return this.M30; + case 13: + return this.M31; + case 14: + return this.M32; + case 15: + return this.M33; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + this.M00 = value; + break; + case 1: + this.M01 = value; + break; + case 2: + this.M02 = value; + break; + case 3: + this.M03 = value; + break; + case 4: + this.M10 = value; + break; + case 5: + this.M11 = value; + break; + case 6: + this.M12 = value; + break; + case 7: + this.M13 = value; + break; + case 8: + this.M20 = value; + break; + case 9: + this.M21 = value; + break; + case 10: + this.M22 = value; + break; + case 11: + this.M23 = value; + break; + case 12: + this.M30 = value; + break; + case 13: + this.M31 = value; + break; + case 14: + this.M32 = value; + break; + case 15: + this.M33 = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + + public double this[int column, int row] + { + get + { + return this[(column * 4) + row]; + } + set + { + this[(column * 4) + row] = value; + } + } } \ No newline at end of file diff --git a/CSMath/Quaternion.operators.cs b/CSMath/Quaternion.operators.cs index 8ed5995..bb36008 100644 --- a/CSMath/Quaternion.operators.cs +++ b/CSMath/Quaternion.operators.cs @@ -9,4 +9,4 @@ public partial struct Quaternion XYZ cr2 = XYZ.Cross(u, cr1); return v + (cr1 * (float)q.W + cr2) * 2f; } -} +} \ No newline at end of file diff --git a/CSMath/XY.operators.cs b/CSMath/XY.operators.cs index 2067ef9..c41fc3c 100644 --- a/CSMath/XY.operators.cs +++ b/CSMath/XY.operators.cs @@ -4,15 +4,9 @@ namespace CSMath; public partial struct XY : IVector, IEquatable { - /// - /// Adds two vectors together. - /// - /// The first source vector. - /// The second source vector. - /// The summed vector. - public static XY operator +(XY left, XY right) + public static explicit operator XY(XYZ xyz) { - return left.Add(right); + return new XY(xyz.X, xyz.Y); } /// @@ -26,6 +20,28 @@ public partial struct XY : IVector, IEquatable return left.Subtract(right); } + /// + /// Negates a given vector. + /// + /// The source vector. + /// The negated vector. + public static XY operator -(XY value) + { + return Zero.Subtract(value); + } + + /// + /// Returns a boolean indicating whether the two given vectors are not equal. + /// + /// The first vector to compare. + /// The second vector to compare. + /// True if the vectors are not equal; False if they are equal. + public static bool operator !=(XY left, XY right) + { + return (left.X != right.X || + left.Y != right.Y); + } + /// /// Multiplies two vectors together. /// @@ -99,13 +115,14 @@ public partial struct XY : IVector, IEquatable } /// - /// Negates a given vector. + /// Adds two vectors together. /// - /// The source vector. - /// The negated vector. - public static XY operator -(XY value) + /// The first source vector. + /// The second source vector. + /// The summed vector. + public static XY operator +(XY left, XY right) { - return Zero.Subtract(value); + return left.Add(right); } /// @@ -119,21 +136,4 @@ public partial struct XY : IVector, IEquatable return (left.X == right.X && left.Y == right.Y); } - - /// - /// Returns a boolean indicating whether the two given vectors are not equal. - /// - /// The first vector to compare. - /// The second vector to compare. - /// True if the vectors are not equal; False if they are equal. - public static bool operator !=(XY left, XY right) - { - return (left.X != right.X || - left.Y != right.Y); - } - - public static explicit operator XY(XYZ xyz) - { - return new XY(xyz.X, xyz.Y); - } -} +} \ No newline at end of file diff --git a/CSMath/XYZ.operators.cs b/CSMath/XYZ.operators.cs index cd2d0c0..0a5eaa0 100644 --- a/CSMath/XYZ.operators.cs +++ b/CSMath/XYZ.operators.cs @@ -4,15 +4,9 @@ namespace CSMath; public partial struct XYZ : IVector, IEquatable { - /// - /// Adds two vectors together. - /// - /// The first source vector. - /// The second source vector. - /// The summed vector. - public static XYZ operator +(XYZ left, XYZ right) + public static explicit operator XYZ(XY xy) { - return left.Add(right); + return new XYZ(xy.X, xy.Y, 0); } /// @@ -26,6 +20,29 @@ public partial struct XYZ : IVector, IEquatable return left.Subtract(right); } + /// + /// Negates a given vector. + /// + /// The source vector. + /// The negated vector. + public static XYZ operator -(XYZ value) + { + return Zero.Subtract(value); + } + + /// + /// Returns a boolean indicating whether the two given vectors are not equal. + /// + /// The first vector to compare. + /// The second vector to compare. + /// True if the vectors are not equal; False if they are equal. + public static bool operator !=(XYZ left, XYZ right) + { + return (left.X != right.X || + left.Y != right.Y || + left.Z != right.Z); + } + /// /// Multiplies two vectors together. /// @@ -101,13 +118,14 @@ public partial struct XYZ : IVector, IEquatable } /// - /// Negates a given vector. + /// Adds two vectors together. /// - /// The source vector. - /// The negated vector. - public static XYZ operator -(XYZ value) + /// The first source vector. + /// The second source vector. + /// The summed vector. + public static XYZ operator +(XYZ left, XYZ right) { - return Zero.Subtract(value); + return left.Add(right); } /// @@ -122,22 +140,4 @@ public partial struct XYZ : IVector, IEquatable left.Y == right.Y && left.Z == right.Z); } - - /// - /// Returns a boolean indicating whether the two given vectors are not equal. - /// - /// The first vector to compare. - /// The second vector to compare. - /// True if the vectors are not equal; False if they are equal. - public static bool operator !=(XYZ left, XYZ right) - { - return (left.X != right.X || - left.Y != right.Y || - left.Z != right.Z); - } - - public static explicit operator XYZ(XY xy) - { - return new XYZ(xy.X, xy.Y, 0); - } -} +} \ No newline at end of file diff --git a/CSMath/XYZM.cs b/CSMath/XYZM.cs index 7387882..8b95856 100644 --- a/CSMath/XYZM.cs +++ b/CSMath/XYZM.cs @@ -4,12 +4,13 @@ namespace CSMath; public partial struct XYZM : IVector, IEquatable { - public readonly static XYZM NaN = new XYZM(double.NaN); - public readonly static XYZM Zero = new XYZM(0, 0, 0, 0); - public readonly static XYZM AxisX = new XYZM(1, 0, 0, 0); - public readonly static XYZM AxisY = new XYZM(0, 1, 0, 0); - public readonly static XYZM AxisZ = new XYZM(0, 0, 1, 0); - public readonly static XYZM AxisM = new XYZM(0, 0, 0, 1); + /// + public uint Dimension { get { return 4; } } + + /// + /// Specifies the M-value of the vector component + /// + public double M { get; set; } /// /// Specifies the X-value of the vector component @@ -26,54 +27,17 @@ public partial struct XYZM : IVector, IEquatable /// public double Z { get; set; } - /// - /// Specifies the M-value of the vector component - /// - public double M { get; set; } + public static readonly XYZM AxisM = new XYZM(0, 0, 0, 1); - /// - public uint Dimension { get { return 4; } } + public static readonly XYZM AxisX = new XYZM(1, 0, 0, 0); - /// - public double this[int index] - { - get - { - switch (index) - { - case 0: - return X; - case 1: - return Y; - case 2: - return Z; - case 3: - return M; - default: - throw new IndexOutOfRangeException($"The index must be between 0 and {this.Dimension}."); - } - } - set - { - switch (index) - { - case 0: - X = value; - break; - case 1: - Y = value; - break; - case 2: - Z = value; - break; - case 3: - M = value; - break; - default: - throw new IndexOutOfRangeException($"The index must be between 0 and {this.Dimension}."); - } - } - } + public static readonly XYZM AxisY = new XYZM(0, 1, 0, 0); + + public static readonly XYZM AxisZ = new XYZM(0, 0, 1, 0); + + public static readonly XYZM NaN = new XYZM(double.NaN); + + public static readonly XYZM Zero = new XYZM(0, 0, 0, 0); /// /// Constructor with the coordinate components @@ -144,4 +108,45 @@ public string ToString(IFormatProvider? cultureInfo) { return $"{X.ToString(cultureInfo)},{Y.ToString(cultureInfo)},{Z.ToString(cultureInfo)},{M.ToString(cultureInfo)}"; } -} + + /// + public double this[int index] + { + get + { + switch (index) + { + case 0: + return X; + case 1: + return Y; + case 2: + return Z; + case 3: + return M; + default: + throw new IndexOutOfRangeException($"The index must be between 0 and {this.Dimension}."); + } + } + set + { + switch (index) + { + case 0: + X = value; + break; + case 1: + Y = value; + break; + case 2: + Z = value; + break; + case 3: + M = value; + break; + default: + throw new IndexOutOfRangeException($"The index must be between 0 and {this.Dimension}."); + } + } + } +} \ No newline at end of file diff --git a/CSMath/XYZM.operators.cs b/CSMath/XYZM.operators.cs index 27e1a68..344a337 100644 --- a/CSMath/XYZM.operators.cs +++ b/CSMath/XYZM.operators.cs @@ -5,25 +5,35 @@ namespace CSMath; public partial struct XYZM : IVector, IEquatable { /// - /// Adds two vectors together. + /// Subtracts the second vector from the first. /// /// The first source vector. /// The second source vector. - /// The summed vector. - public static XYZM operator +(XYZM left, XYZM right) + /// The difference vector. + public static XYZM operator -(XYZM left, XYZM right) { - return left.Add(right); + return left.Subtract(right); } /// - /// Subtracts the second vector from the first. + /// Negates a given vector. /// - /// The first source vector. - /// The second source vector. - /// The difference vector. - public static XYZM operator -(XYZM left, XYZM right) + /// The source vector. + /// The negated vector. + public static XYZM operator -(XYZM value) { - return left.Subtract(right); + return Zero.Subtract(value); + } + + /// + /// Returns a boolean indicating whether the two given vectors are not equal. + /// + /// The first vector to compare. + /// The second vector to compare. + /// True if the vectors are not equal; False if they are equal. + public static bool operator !=(XYZM left, XYZM right) + { + return !left.IsEqual(right); } /// @@ -82,13 +92,14 @@ public partial struct XYZM : IVector, IEquatable } /// - /// Negates a given vector. + /// Adds two vectors together. /// - /// The source vector. - /// The negated vector. - public static XYZM operator -(XYZM value) + /// The first source vector. + /// The second source vector. + /// The summed vector. + public static XYZM operator +(XYZM left, XYZM right) { - return Zero.Subtract(value); + return left.Add(right); } /// @@ -101,15 +112,4 @@ public partial struct XYZM : IVector, IEquatable { return left.IsEqual(right); } - - /// - /// Returns a boolean indicating whether the two given vectors are not equal. - /// - /// The first vector to compare. - /// The second vector to compare. - /// True if the vectors are not equal; False if they are equal. - public static bool operator !=(XYZM left, XYZM right) - { - return !left.IsEqual(right); - } -} +} \ No newline at end of file