diff --git a/CHANGELOG.md b/CHANGELOG.md index 0de03ed..35c452f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 3.2.0 ### Added - X10D: Added `MathUtility.InverseLerp(float, float, float)` and `MathUtility.InverseLerp(double, double, double)` +- X10D: Added `Circle`, `CircleF`, `Line`, `LineF`, `Polygon`, and `PolygonF`, to complement System.Drawing structs such as `Point` and `Rectangle` - X10D: Added `Point.ToSize()` - X10D: Added `Point.ToSizeF()` - X10D: Added `Point.ToVector2()` diff --git a/X10D.Tests/src/Drawing/CircleFTests.cs b/X10D.Tests/src/Drawing/CircleFTests.cs new file mode 100644 index 0000000..c789dc8 --- /dev/null +++ b/X10D.Tests/src/Drawing/CircleFTests.cs @@ -0,0 +1,93 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class CircleFTests +{ + [TestMethod] + public void Area_ShouldBePiRadiusRadius_GivenUnitCircle() + { + var unitCircle = CircleF.Unit; + Assert.AreEqual(MathF.PI * unitCircle.Radius * unitCircle.Radius, unitCircle.Area); + } + + [TestMethod] + public void Circumference_ShouldBe2PiRadius_GivenUnitCircle() + { + var unitCircle = CircleF.Unit; + Assert.AreEqual(2.0f * MathF.PI * unitCircle.Radius, unitCircle.Circumference, 1e-6f); + } + + [TestMethod] + public void CompareTo_ShouldBeNegativeOne_GivenUnitCircleAndEmpty() + { + Assert.AreEqual(-1, CircleF.Empty.CompareTo(CircleF.Unit)); + } + + [TestMethod] + public void CompareTo_ShouldBeOne_GivenUnitCircleAndEmpty() + { + Assert.AreEqual(1, CircleF.Unit.CompareTo(CircleF.Empty)); + } + + [TestMethod] + public void CompareTo_ShouldBeZero_GivenUnitCircle() + { + var unitCircle = CircleF.Unit; + Assert.AreEqual(0, unitCircle.CompareTo(unitCircle)); + } + + [TestMethod] + public void Diameter_ShouldBe2_GivenUnitCircle() + { + Assert.AreEqual(2.0f, CircleF.Unit.Diameter, 1e-6f); + } + + [TestMethod] + public void Equals_ShouldBeTrue_GivenTwoUnitCircles() + { + var unitCircle1 = CircleF.Unit; + var unitCircle2 = CircleF.Unit; + Assert.AreEqual(unitCircle1, unitCircle2); + Assert.IsTrue(unitCircle1 == unitCircle2); + Assert.IsFalse(unitCircle1 != unitCircle2); + } + + [TestMethod] + public void Equals_ShouldBeFalse_GivenDifferentCircles() + { + Assert.AreNotEqual(CircleF.Unit, CircleF.Empty); + Assert.IsFalse(CircleF.Unit == CircleF.Empty); + Assert.IsTrue(CircleF.Unit != CircleF.Empty); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenEmptyCircle() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = CircleF.Empty.GetHashCode(); + Assert.AreEqual(hashCode, CircleF.Empty.GetHashCode()); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenUnitCircle() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = CircleF.Unit.GetHashCode(); + Assert.AreEqual(hashCode, CircleF.Unit.GetHashCode()); + } + + [TestMethod] + public void Radius_ShouldBe0_GivenEmptyCircle() + { + Assert.AreEqual(0.0f, CircleF.Empty.Radius, 1e-6f); + } + + [TestMethod] + public void Radius_ShouldBe1_GivenUnitCircle() + { + Assert.AreEqual(1.0f, CircleF.Unit.Radius, 1e-6f); + } +} diff --git a/X10D.Tests/src/Drawing/CircleTests.cs b/X10D.Tests/src/Drawing/CircleTests.cs new file mode 100644 index 0000000..f25981f --- /dev/null +++ b/X10D.Tests/src/Drawing/CircleTests.cs @@ -0,0 +1,93 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class CircleTests +{ + [TestMethod] + public void Area_ShouldBePiRadiusRadius_GivenUnitCircle() + { + var unitCircle = Circle.Unit; + Assert.AreEqual(MathF.PI * unitCircle.Radius * unitCircle.Radius, unitCircle.Area); + } + + [TestMethod] + public void Circumference_ShouldBe2PiRadius_GivenUnitCircle() + { + var unitCircle = Circle.Unit; + Assert.AreEqual(2.0f * MathF.PI * unitCircle.Radius, unitCircle.Circumference, 1e-6f); + } + + [TestMethod] + public void CompareTo_ShouldBeNegativeOne_GivenUnitCircleAndEmpty() + { + Assert.AreEqual(-1, Circle.Empty.CompareTo(Circle.Unit)); + } + + [TestMethod] + public void CompareTo_ShouldBeOne_GivenUnitCircleAndEmpty() + { + Assert.AreEqual(1, Circle.Unit.CompareTo(Circle.Empty)); + } + + [TestMethod] + public void CompareTo_ShouldBeZero_GivenUnitCircle() + { + var unitCircle = Circle.Unit; + Assert.AreEqual(0, unitCircle.CompareTo(unitCircle)); + } + + [TestMethod] + public void Diameter_ShouldBe2_GivenUnitCircle() + { + Assert.AreEqual(2.0f, Circle.Unit.Diameter, 1e-6f); + } + + [TestMethod] + public void Equals_ShouldBeTrue_GivenTwoUnitCircles() + { + var unitCircle1 = Circle.Unit; + var unitCircle2 = Circle.Unit; + Assert.AreEqual(unitCircle1, unitCircle2); + Assert.IsTrue(unitCircle1 == unitCircle2); + Assert.IsFalse(unitCircle1 != unitCircle2); + } + + [TestMethod] + public void Equals_ShouldBeFalse_GivenDifferentCircles() + { + Assert.AreNotEqual(Circle.Unit, Circle.Empty); + Assert.IsFalse(Circle.Unit == Circle.Empty); + Assert.IsTrue(Circle.Unit != Circle.Empty); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenEmptyCircle() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = Circle.Empty.GetHashCode(); + Assert.AreEqual(hashCode, Circle.Empty.GetHashCode()); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenUnitCircle() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = Circle.Unit.GetHashCode(); + Assert.AreEqual(hashCode, Circle.Unit.GetHashCode()); + } + + [TestMethod] + public void Radius_ShouldBe0_GivenEmptyCircle() + { + Assert.AreEqual(0, Circle.Empty.Radius); + } + + [TestMethod] + public void Radius_ShouldBe1_GivenUnitCircle() + { + Assert.AreEqual(1, Circle.Unit.Radius); + } +} diff --git a/X10D.Tests/src/Drawing/LineFTests.cs b/X10D.Tests/src/Drawing/LineFTests.cs new file mode 100644 index 0000000..4891ad7 --- /dev/null +++ b/X10D.Tests/src/Drawing/LineFTests.cs @@ -0,0 +1,79 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class LineFTests +{ + [TestMethod] + public void CompareTo_ShouldBeNegativeOne_GivenEmptyAndOne() + { + Assert.AreEqual(-1, LineF.Empty.CompareTo(LineF.One)); + } + + [TestMethod] + public void CompareTo_ShouldBeOne_GivenOneAndEmpty() + { + Assert.AreEqual(1, LineF.One.CompareTo(LineF.Empty)); + } + + [TestMethod] + public void CompareTo_ShouldBeZero_GivenUnitLine() + { + var unitLineF = LineF.One; + Assert.AreEqual(0, unitLineF.CompareTo(unitLineF)); + } + + [TestMethod] + public void Length_ShouldBe0_GivenEmptyLine() + { + Assert.AreEqual(0.0f, LineF.Empty.Length); + } + + [TestMethod] + public void Length_ShouldBe1_GivenUnitXLine() + { + Assert.AreEqual(1.0f, LineF.UnitX.Length, 1e-6f); + } + + [TestMethod] + public void Length_ShouldBe1_GivenUnitYLine() + { + Assert.AreEqual(1.0f, LineF.UnitY.Length, 1e-6f); + } + + [TestMethod] + public void Equals_ShouldBeTrue_GivenTwoUnitLines() + { + var unitLineF1 = LineF.One; + var unitLineF2 = LineF.One; + Assert.AreEqual(unitLineF1, unitLineF2); + Assert.IsTrue(unitLineF1 == unitLineF2); + Assert.IsFalse(unitLineF1 != unitLineF2); + } + + [TestMethod] + public void Equals_ShouldBeFalse_GivenDifferentLines() + { + Assert.AreNotEqual(LineF.One, LineF.Empty); + Assert.IsFalse(LineF.One == LineF.Empty); + Assert.IsTrue(LineF.One != LineF.Empty); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenEmptyLine() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = LineF.Empty.GetHashCode(); + Assert.AreEqual(hashCode, LineF.Empty.GetHashCode()); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenUnitLine() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = LineF.One.GetHashCode(); + Assert.AreEqual(hashCode, LineF.One.GetHashCode()); + } +} diff --git a/X10D.Tests/src/Drawing/LineTests.cs b/X10D.Tests/src/Drawing/LineTests.cs new file mode 100644 index 0000000..19d7ba1 --- /dev/null +++ b/X10D.Tests/src/Drawing/LineTests.cs @@ -0,0 +1,79 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class LineTests +{ + [TestMethod] + public void CompareTo_ShouldBeNegativeOne_GivenEmptyAndOne() + { + Assert.AreEqual(-1, Line.Empty.CompareTo(Line.One)); + } + + [TestMethod] + public void CompareTo_ShouldBeOne_GivenOneAndEmpty() + { + Assert.AreEqual(1, Line.One.CompareTo(Line.Empty)); + } + + [TestMethod] + public void CompareTo_ShouldBeZero_GivenUnitLine() + { + var unitLine = Line.One; + Assert.AreEqual(0, unitLine.CompareTo(unitLine)); + } + + [TestMethod] + public void Length_ShouldBe0_GivenEmptyLine() + { + Assert.AreEqual(0.0f, Line.Empty.Length); + } + + [TestMethod] + public void Length_ShouldBe1_GivenUnitXLine() + { + Assert.AreEqual(1.0f, Line.UnitX.Length, 1e-6f); + } + + [TestMethod] + public void Length_ShouldBe1_GivenUnitYLine() + { + Assert.AreEqual(1.0f, Line.UnitY.Length, 1e-6f); + } + + [TestMethod] + public void Equals_ShouldBeTrue_GivenTwoUnitLines() + { + var unitLine1 = Line.One; + var unitLine2 = Line.One; + Assert.AreEqual(unitLine1, unitLine2); + Assert.IsTrue(unitLine1 == unitLine2); + Assert.IsFalse(unitLine1 != unitLine2); + } + + [TestMethod] + public void Equals_ShouldBeFalse_GivenDifferentLines() + { + Assert.AreNotEqual(Line.One, Line.Empty); + Assert.IsFalse(Line.One == Line.Empty); + Assert.IsTrue(Line.One != Line.Empty); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenEmptyLine() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = Line.Empty.GetHashCode(); + Assert.AreEqual(hashCode, Line.Empty.GetHashCode()); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenUnitLine() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = Line.One.GetHashCode(); + Assert.AreEqual(hashCode, Line.One.GetHashCode()); + } +} diff --git a/X10D.Tests/src/Drawing/PolygonFTests.cs b/X10D.Tests/src/Drawing/PolygonFTests.cs new file mode 100644 index 0000000..b12308d --- /dev/null +++ b/X10D.Tests/src/Drawing/PolygonFTests.cs @@ -0,0 +1,70 @@ +using System.Drawing; +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class PolygonFTests +{ + [TestMethod] + public void IsConvex_ShouldBeFalse_GivenEmptyPolygon() + { + Assert.IsFalse(PolygonF.Empty.IsConvex); + } + + [TestMethod] + public void IsConvex_ShouldBeTrue_GivenHexagon() + { + Assert.IsTrue(CreateHexagon().IsConvex); + } + + [TestMethod] + public void PointCount_ShouldBe1_GivenPolygonFWith1Point() + { + var polygon = new PolygonF(); + polygon.AddPoint(new Point(1, 1)); + + Assert.AreEqual(1, polygon.PointCount); + + // assert that the empty polygon was not modified + Assert.AreEqual(0, PolygonF.Empty.PointCount); + } + + [TestMethod] + public void PointCount_ShouldBe0_GivenEmptyPolygon() + { + Assert.AreEqual(0, PolygonF.Empty.PointCount); + } + + [TestMethod] + public void Equals_ShouldBeTrue_GivenTwoUnitCircles() + { + var emptyPolygonF1 = PolygonF.Empty; + var emptyPolygonF2 = PolygonF.Empty; + Assert.AreEqual(emptyPolygonF1, emptyPolygonF2); + Assert.IsTrue(emptyPolygonF1 == emptyPolygonF2); + Assert.IsFalse(emptyPolygonF1 != emptyPolygonF2); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenEmptyCircle() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = PolygonF.Empty.GetHashCode(); + Assert.AreEqual(hashCode, PolygonF.Empty.GetHashCode()); + } + + private static PolygonF CreateHexagon() + { + var hexagon = new PolygonF(); + hexagon.AddPoint(new Vector2(0, 0)); + hexagon.AddPoint(new Vector2(1, 0)); + hexagon.AddPoint(new Vector2(1, 1)); + hexagon.AddPoint(new Vector2(0, 1)); + hexagon.AddPoint(new Vector2(-1, 1)); + hexagon.AddPoint(new Vector2(-1, 0)); + return hexagon; + } +} diff --git a/X10D.Tests/src/Drawing/PolygonTests.cs b/X10D.Tests/src/Drawing/PolygonTests.cs new file mode 100644 index 0000000..d354732 --- /dev/null +++ b/X10D.Tests/src/Drawing/PolygonTests.cs @@ -0,0 +1,69 @@ +using System.Drawing; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class PolygonTests +{ + [TestMethod] + public void IsConvex_ShouldBeFalse_GivenEmptyPolygon() + { + Assert.IsFalse(Polygon.Empty.IsConvex); + } + + [TestMethod] + public void IsConvex_ShouldBeTrue_GivenHexagon() + { + Assert.IsTrue(CreateHexagon().IsConvex); + } + + [TestMethod] + public void PointCount_ShouldBe1_GivenPolygonWith1Point() + { + var polygon = new Polygon(); + polygon.AddPoint(new Point(1, 1)); + + Assert.AreEqual(1, polygon.PointCount); + + // assert that the empty polygon was not modified + Assert.AreEqual(0, Polygon.Empty.PointCount); + } + + [TestMethod] + public void PointCount_ShouldBe0_GivenEmptyPolygon() + { + Assert.AreEqual(0, Polygon.Empty.PointCount); + } + + [TestMethod] + public void Equals_ShouldBeTrue_GivenTwoUnitCircles() + { + var emptyPolygon1 = Polygon.Empty; + var emptyPolygon2 = Polygon.Empty; + Assert.AreEqual(emptyPolygon1, emptyPolygon2); + Assert.IsTrue(emptyPolygon1 == emptyPolygon2); + Assert.IsFalse(emptyPolygon1 != emptyPolygon2); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenEmptyCircle() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = Polygon.Empty.GetHashCode(); + Assert.AreEqual(hashCode, Polygon.Empty.GetHashCode()); + } + + private static Polygon CreateHexagon() + { + var hexagon = new Polygon(); + hexagon.AddPoint(new Point(0, 0)); + hexagon.AddPoint(new Point(1, 0)); + hexagon.AddPoint(new Point(1, 1)); + hexagon.AddPoint(new Point(0, 1)); + hexagon.AddPoint(new Point(-1, 1)); + hexagon.AddPoint(new Point(-1, 0)); + return hexagon; + } +} diff --git a/X10D/src/Drawing/Circle.cs b/X10D/src/Drawing/Circle.cs new file mode 100644 index 0000000..fb64d3b --- /dev/null +++ b/X10D/src/Drawing/Circle.cs @@ -0,0 +1,272 @@ +using System.Drawing; + +namespace X10D.Drawing; + +/// +/// Represents a circle that is composed of a 32-bit signed integer center point and radius. +/// +public readonly struct Circle : IEquatable, IComparable, IComparable +{ + /// + /// The empty circle. That is, a circle whose center point is (0, 0) and whose radius is 0. + /// + public static readonly Circle Empty = new(); + + /// + /// The unit circle. That is, a circle whose center point is (0, 0) and whose radius is 1. + /// + public static readonly Circle Unit = new(Point.Empty, 1); + + /// + /// Initializes a new instance of the struct. + /// + /// The center point of the circle. + /// The radius of the circle. + public Circle(Point center, int radius) + { + Center = center; + Radius = radius; + } + + /// + /// Gets the area of the circle. + /// + /// The area of the circle, calculated as πr². + public float Area + { + get => MathF.PI * Radius * Radius; + } + + /// + /// Gets the center point of the circle. + /// + /// The center point. + public Point Center { get; } + + /// + /// Gets the circumference of the circle. + /// + /// The circumference of the circle, calculated as 2πr. + public float Circumference + { + get => 2 * MathF.PI * Radius; + } + + /// + /// Gets the diameter of the circle. + /// + /// The diameter. This is always twice the . + public int Diameter + { + get => Radius * 2; + } + + /// + /// Gets the radius of the circle. + /// + /// The radius. + public int Radius { get; } + + /// + /// Returns a value indicating whether two instances of are equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered equal; otherwise, + /// . + /// + public static bool operator ==(Circle left, Circle right) + { + return left.Equals(right); + } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered not equal; otherwise, + /// . + /// + public static bool operator !=(Circle left, Circle right) + { + return !left.Equals(right); + } + + /// + /// Returns a value indicating whether the radius of one circle is less than that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is less than that of + /// ; otherwise, . + /// + public static bool operator <(Circle left, Circle right) + { + return left.CompareTo(right) < 0; + } + + /// + /// Returns a value indicating whether the radius of one circle is greater than to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is greater than that of + /// ; otherwise, . + /// + public static bool operator >(Circle left, Circle right) + { + return left.CompareTo(right) > 0; + } + + /// + /// Returns a value indicating whether the radius of one circle is less than or equal to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is less than or equal to that of + /// ; otherwise, . + /// + public static bool operator <=(Circle left, Circle right) + { + return left.CompareTo(right) <= 0; + } + + /// + /// Returns a value indicating whether the radius of one circle is greater than or equal to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is greater than or equal to that of + /// ; otherwise, . + /// + public static bool operator >=(Circle left, Circle right) + { + return left.CompareTo(right) >= 0; + } + + /// + /// Compares this instance to another . + /// + /// The other object. + /// + /// A signed number indicating the relative values of this instance and . + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// Less than zero + /// + /// The of this instance is less than that of . + /// + /// + /// + /// Zero + /// + /// This instance is equal to , or the of both this instance + /// and are not a number (), + /// , or . + /// + /// + /// + /// Greater than zero + /// + /// The of this instance is greater than that of , or + /// is . + /// + /// + /// + /// + /// Comparison only takes into consideration the . + /// is not an instance of . + public int CompareTo(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return 1; + } + + if (obj is not Circle other) + { + throw new ArgumentException($"Object must be of type {GetType()}"); + } + + return CompareTo(other); + } + + /// + /// Compares this instance to another . + /// + /// The other circle. + /// + /// A signed number indicating the relative values of this instance and . + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// Less than zero + /// + /// The of this instance is less than that of . + /// + /// + /// + /// Zero + /// + /// This instance is equal to , or the of both this instance + /// and are not a number (), + /// , or . + /// + /// + /// + /// Greater than zero + /// + /// The of this instance is greater than that of . + /// + /// + /// + /// + /// Comparison only takes into consideration the . + public int CompareTo(Circle other) + { + return Radius.CompareTo(other.Radius); + } + + /// + public override bool Equals(object? obj) + { + return obj is Circle circle && Equals(circle); + } + + /// + /// Returns a value indicating whether this instance and another instance are equal. + /// + /// The instance with which to compare. + /// + /// if this instance and are considered equal; otherwise, + /// . + /// + public bool Equals(Circle other) + { + return Center.Equals(other.Center) && Radius.Equals(other.Radius); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Center, Radius); + } +} diff --git a/X10D/src/Drawing/CircleF.cs b/X10D/src/Drawing/CircleF.cs new file mode 100644 index 0000000..ec5de6d --- /dev/null +++ b/X10D/src/Drawing/CircleF.cs @@ -0,0 +1,284 @@ +using System.Drawing; +using System.Numerics; +using X10D.Numerics; + +namespace X10D.Drawing; + +/// +/// Represents a circle that is composed of a single-precision floating-point center point and radius. +/// +public readonly struct CircleF : IEquatable, IComparable, IComparable +{ + /// + /// The empty circle. That is, a circle whose center point is (0, 0) and whose radius is 0. + /// + public static readonly CircleF Empty = new(); + + /// + /// The unit circle. That is, a circle whose center point is (0, 0) and whose radius is 1. + /// + public static readonly CircleF Unit = new(Vector2.Zero, 1.0f); + + /// + /// Initializes a new instance of the struct. + /// + /// The center point of the circle. + /// The radius of the circle. + public CircleF(Vector2 center, float radius) + : this(center.ToPointF(), radius) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The center point of the circle. + /// The radius of the circle. + public CircleF(PointF center, float radius) + { + Center = center; + Radius = radius; + } + + /// + /// Gets the area of the circle. + /// + /// The area of the circle, calculated as πr². + public float Area + { + get => MathF.PI * Radius * Radius; + } + + /// + /// Gets the center point of the circle. + /// + /// The center point. + public PointF Center { get; } + + /// + /// Gets the circumference of the circle. + /// + /// The circumference of the circle, calculated as 2πr. + public float Circumference + { + get => 2 * MathF.PI * Radius; + } + + /// + /// Gets the diameter of the circle. + /// + /// The diameter. This is always twice the . + public float Diameter + { + get => Radius * 2; + } + + /// + /// Gets the radius of the circle. + /// + /// The radius. + public float Radius { get; } + + /// + /// Returns a value indicating whether two instances of are equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered equal; otherwise, + /// . + /// + public static bool operator ==(CircleF left, CircleF right) + { + return left.Equals(right); + } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered not equal; otherwise, + /// . + /// + public static bool operator !=(CircleF left, CircleF right) + { + return !left.Equals(right); + } + + /// + /// Returns a value indicating whether the radius of one circle is less than that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is less than that of + /// ; otherwise, . + /// + public static bool operator <(CircleF left, CircleF right) + { + return left.CompareTo(right) < 0; + } + + /// + /// Returns a value indicating whether the radius of one circle is greater than to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is greater than that of + /// ; otherwise, . + /// + public static bool operator >(CircleF left, CircleF right) + { + return left.CompareTo(right) > 0; + } + + /// + /// Returns a value indicating whether the radius of one circle is less than or equal to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is less than or equal to that of + /// ; otherwise, . + /// + public static bool operator <=(CircleF left, CircleF right) + { + return left.CompareTo(right) <= 0; + } + + /// + /// Returns a value indicating whether the radius of one circle is greater than or equal to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is greater than or equal to that of + /// ; otherwise, . + /// + public static bool operator >=(CircleF left, CircleF right) + { + return left.CompareTo(right) >= 0; + } + + /// + /// Compares this instance to another . + /// + /// The other object. + /// + /// A signed number indicating the relative values of this instance and . + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// Less than zero + /// + /// The of this instance is less than that of . + /// + /// + /// + /// Zero + /// + /// This instance is equal to , or the of both this instance + /// and are not a number (), + /// , or . + /// + /// + /// + /// Greater than zero + /// + /// The of this instance is greater than that of , or + /// is . + /// + /// + /// + /// + /// Comparison only takes into consideration the . + /// is not an instance of . + public int CompareTo(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return 1; + } + + if (obj is not Circle other) + { + throw new ArgumentException($"Object must be of type {GetType()}"); + } + + return CompareTo(other); + } + + /// + /// Compares this instance to another . + /// + /// The other circle. + /// + /// A signed number indicating the relative values of this instance and . + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// Less than zero + /// + /// The of this instance is less than that of . + /// + /// + /// + /// Zero + /// + /// This instance is equal to , or the of both this instance + /// and are not a number (), + /// , or . + /// + /// + /// + /// Greater than zero + /// + /// The of this instance is greater than that of . + /// + /// + /// + /// + /// Comparison only takes into consideration the . + public int CompareTo(CircleF other) + { + return Radius.CompareTo(other.Radius); + } + + /// + public override bool Equals(object? obj) + { + return obj is CircleF circle && Equals(circle); + } + + /// + /// Returns a value indicating whether this instance and another instance are equal. + /// + /// The instance with which to compare. + /// + /// if this instance and are considered equal; otherwise, + /// . + /// + public bool Equals(CircleF other) + { + return Center.Equals(other.Center) && Radius.Equals(other.Radius); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Center, Radius); + } +} diff --git a/X10D/src/Drawing/Line.cs b/X10D/src/Drawing/Line.cs new file mode 100644 index 0000000..8b81fb9 --- /dev/null +++ b/X10D/src/Drawing/Line.cs @@ -0,0 +1,276 @@ +using System.Drawing; + +namespace X10D.Drawing; + +/// +/// Represents a line that is composed of 32-bit signed integer X and Y coordinates. +/// +public readonly struct Line : IEquatable, IComparable, IComparable +{ + /// + /// The empty line. That is, a line whose start and end points are at (0, 0). + /// + public static readonly Line Empty = new(); + + /// + /// The line whose start point is at (0, 0) and end point is at (1, 1). + /// + public static readonly Line One = new(Point.Empty, new Point(1, 1)); + + /// + /// The line whose start point is at (0, 0) and end point is at (1, 0). + /// + public static readonly Line UnitX = new(Point.Empty, new Point(1, 0)); + + /// + /// The line whose start point is at (0, 0) and end point is at (0, 1). + /// + public static readonly Line UnitY = new(Point.Empty, new Point(0, 1)); + + /// + /// Initializes a new instance of the struct by taking the start and end points. + /// + /// The start point. + /// The end point. + public Line(Point start, Point end) + { + End = end; + Start = start; + } + + /// + /// Gets the end point of the line. + /// + /// The end point. + public Point End { get; } + + /// + /// Gets the length of this line. + /// + /// The length. + public float Length + { + get => MathF.Sqrt(LengthSquared); + } + + /// + /// Gets the length of this line, squared. + /// + /// The squared length. + public float LengthSquared + { + get => MathF.Pow(End.X - Start.X, 2.0f) + MathF.Pow(End.Y - Start.Y, 2.0f); + } + + /// + /// Gets the start point of the line. + /// + /// The start point. + public Point Start { get; } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered not equal; otherwise, + /// . + /// + public static bool operator ==(Line left, Line right) + { + return left.Equals(right); + } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered not equal; otherwise, + /// . + /// + public static bool operator !=(Line left, Line right) + { + return !left.Equals(right); + } + + /// + /// Returns a value indicating whether the length of one line is less than that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is less than that of + /// ; otherwise, . + /// + public static bool operator <(Line left, Line right) + { + return left.CompareTo(right) < 0; + } + + /// + /// Returns a value indicating whether the length of one line is greater than that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is greater than that of + /// ; otherwise, . + /// + public static bool operator >(Line left, Line right) + { + return left.CompareTo(right) > 0; + } + + /// + /// Returns a value indicating whether the length of one line is less than or equal to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is less than or equal to that of + /// ; otherwise, . + /// + public static bool operator <=(Line left, Line right) + { + return left.CompareTo(right) <= 0; + } + + /// + /// Returns a value indicating whether the length of one line is greater than or equal to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is greater than or equal to that of + /// ; otherwise, . + /// + public static bool operator >=(Line left, Line right) + { + return left.CompareTo(right) >= 0; + } + + /// + /// Compares this instance to another object. + /// + /// The object with with which to compare + /// + /// A signed number indicating the relative values of this instance and . + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// Less than zero + /// + /// The of this instance is less than that of . + /// + /// + /// + /// Zero + /// + /// This instance is equal to , or the of both this instance + /// and are not a number (), + /// , or . + /// + /// + /// + /// Greater than zero + /// + /// The of this instance is greater than that of . + /// + /// + /// + /// + /// + /// Comparison internally measures the property to avoid calls to . + /// is not an instance of . + /// + public int CompareTo(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return 1; + } + + if (obj is not Line other) + { + throw new ArgumentException($"Object must be of type {GetType()}"); + } + + return CompareTo(other); + } + + /// + /// Compares this instance to another . + /// + /// + /// + /// A signed number indicating the relative values of this instance and . + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// Less than zero + /// + /// The of this instance is less than that of . + /// + /// + /// + /// Zero + /// + /// This instance is equal to , or the of both this instance + /// and are not a number (), + /// , or . + /// + /// + /// + /// Greater than zero + /// + /// The of this instance is greater than that of . + /// + /// + /// + /// + /// + /// Comparison internally measures the property to avoid calls to . + /// + public int CompareTo(Line other) + { + return LengthSquared.CompareTo(other.LengthSquared); + } + + /// + public override bool Equals(object? obj) + { + return obj is Line other && Equals(other); + } + + /// + /// Returns a value indicating whether this instance and another instance are equal. + /// + /// The instance with which to compare. + /// + /// if this instance and are considered equal; otherwise, + /// . + /// + public bool Equals(Line other) + { + return End.Equals(other.End) && Start.Equals(other.Start); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(End, Start); + } +} diff --git a/X10D/src/Drawing/LineF.cs b/X10D/src/Drawing/LineF.cs new file mode 100644 index 0000000..ba845e3 --- /dev/null +++ b/X10D/src/Drawing/LineF.cs @@ -0,0 +1,310 @@ +using System.Drawing; +using System.Numerics; +using X10D.Numerics; + +namespace X10D.Drawing; + +/// +/// Represents a line that is composed of single-precision floating-point X and Y coordinates. +/// +public readonly struct LineF : IEquatable, IComparable, IComparable +{ + /// + /// The empty line. That is, a line whose start and end points are at (0, 0). + /// + public static readonly LineF Empty = new(); + + /// + /// The line whose start point is at (0, 0) and end point is at (1, 1). + /// + public static readonly LineF One = new(Vector2.Zero, new Vector2(1, 1)); + + /// + /// The line whose start point is at (0, 0) and end point is at (1, 0). + /// + public static readonly LineF UnitX = new(Vector2.Zero, new Vector2(1, 0)); + + /// + /// The line whose start point is at (0, 0) and end point is at (0, 1). + /// + public static readonly LineF UnitY = new(Vector2.Zero, new Vector2(0, 1)); + + /// + /// Initializes a new instance of the struct by taking the start and end points. + /// + /// The start point. + /// The end point. + public LineF(Vector2 start, Vector2 end) + : this(start.ToPointF(), end.ToPointF()) + { + } + + /// + /// Initializes a new instance of the struct by taking the start and end points. + /// + /// The start point. + /// The end point. + public LineF(PointF start, PointF end) + { + End = end; + Start = start; + } + + /// + /// Gets the end point of the line. + /// + /// The end point. + public PointF End { get; } + + /// + /// Gets the length of this line. + /// + /// The length. + public float Length + { + get => MathF.Sqrt(LengthSquared); + } + + /// + /// Gets the length of this line, squared. + /// + /// The squared length. + public float LengthSquared + { + get => MathF.Pow(End.X - Start.X, 2.0f) + MathF.Pow(End.Y - Start.Y, 2.0f); + } + + /// + /// Gets the start point of the line. + /// + /// The start point. + public PointF Start { get; } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered not equal; otherwise, + /// . + /// + public static bool operator ==(LineF left, LineF right) + { + return left.Equals(right); + } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered not equal; otherwise, + /// . + /// + public static bool operator !=(LineF left, LineF right) + { + return !left.Equals(right); + } + + /// + /// Returns a value indicating whether the length of one line is less than that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is less than that of + /// ; otherwise, . + /// + public static bool operator <(LineF left, LineF right) + { + return left.CompareTo(right) < 0; + } + + /// + /// Returns a value indicating whether the length of one line is greater than that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is greater than that of + /// ; otherwise, . + /// + public static bool operator >(LineF left, LineF right) + { + return left.CompareTo(right) > 0; + } + + /// + /// Returns a value indicating whether the length of one line is less than or equal to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is less than or equal to that of + /// ; otherwise, . + /// + public static bool operator <=(LineF left, LineF right) + { + return left.CompareTo(right) <= 0; + } + + /// + /// Returns a value indicating whether the length of one line is greater than or equal to that of another. + /// + /// The first instance. + /// The second instance. + /// + /// if the of is greater than or equal to that of + /// ; otherwise, . + /// + public static bool operator >=(LineF left, LineF right) + { + return left.CompareTo(right) >= 0; + } + + /// + /// Explicitly converts a to a . + /// + /// The line to convert. + /// The converted line. + public static explicit operator Line(LineF line) + { + PointF start = line.Start; + PointF end = line.End; + return new Line(new Point((int)start.X, (int)start.Y), new Point((int)end.X, (int)end.Y)); + } + + /// + /// Implicitly converts a to a . + /// + /// The line to convert. + /// The line polygon. + public static implicit operator LineF(Line line) + { + return new LineF(line.Start, line.End); + } + + /// + /// Compares this instance to another object. + /// + /// The object with with which to compare + /// + /// A signed number indicating the relative values of this instance and . + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// Less than zero + /// + /// The of this instance is less than that of . + /// + /// + /// + /// Zero + /// + /// This instance is equal to , or the of both this instance + /// and are not a number (), + /// , or . + /// + /// + /// + /// Greater than zero + /// + /// The of this instance is greater than that of . + /// + /// + /// + /// + /// + /// Comparison internally measures the property to avoid calls to . + /// is not an instance of . + /// + public int CompareTo(object? obj) + { + if (ReferenceEquals(null, obj)) + { + return 1; + } + + if (obj is not LineF other) + { + throw new ArgumentException($"Object must be of type {GetType()}"); + } + + return CompareTo(other); + } + + /// + /// Compares this instance to another . + /// + /// + /// + /// A signed number indicating the relative values of this instance and . + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// Less than zero + /// + /// The of this instance is less than that of . + /// + /// + /// + /// Zero + /// + /// This instance is equal to , or the of both this instance + /// and are not a number (), + /// , or . + /// + /// + /// + /// Greater than zero + /// + /// The of this instance is greater than that of . + /// + /// + /// + /// + /// + /// Comparison internally measures the property to avoid calls to . + /// + public int CompareTo(LineF other) + { + return LengthSquared.CompareTo(other.LengthSquared); + } + + /// + public override bool Equals(object? obj) + { + return obj is LineF other && Equals(other); + } + + /// + /// Returns a value indicating whether this instance and another instance are equal. + /// + /// The instance with which to compare. + /// + /// if this instance and are considered equal; otherwise, + /// . + /// + public bool Equals(LineF other) + { + return End.Equals(other.End) && Start.Equals(other.Start); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(End, Start); + } +} diff --git a/X10D/src/Drawing/Polygon.cs b/X10D/src/Drawing/Polygon.cs new file mode 100644 index 0000000..b9e1b4e --- /dev/null +++ b/X10D/src/Drawing/Polygon.cs @@ -0,0 +1,197 @@ +using System.Drawing; + +namespace X10D.Drawing; + +/// +/// Represents a 2D polygon composed of 32-bit signed integer points. +/// +public struct Polygon : IEquatable +{ + /// + /// The empty polygon. That is, a polygon with no points. + /// + public static readonly Polygon Empty = new(ArraySegment.Empty); + + private Point[]? _points; + + /// + /// Initializes a new instance of the struct by copying the specified polygon. + /// + public Polygon(Polygon polygon) + : this(polygon._points ?? ArraySegment.Empty) + { + } + + /// + /// Initializes a new instance of the struct by constructing it from the specified points. + /// + /// An enumerable collection of points from which the polygon should be constructed. + public Polygon(IEnumerable points) + { + _points = points.ToArray(); + } + + /// + /// Returns a value indicating whether this polygon is convex. + /// + /// if this polygon is convex; otherwise, . + public bool IsConvex + { + get + { + if (_points is null || _points.Length < 3) + { + return false; + } + + var positive = false; + var negative = false; + Point p0 = _points[0]; + + for (var index = 1; index < _points.Length; index++) + { + Point p1 = _points[index]; + int d = (p1.X - p0.X) * (p1.Y + p0.Y); + + if (d > 0) + { + positive = true; + } + else if (d < 0) + { + negative = true; + } + + if (positive && negative) + { + return false; + } + + p0 = p1; + } + + return true; + } + } + + /// + /// Gets the number of points in this polygon. + /// + /// An value, representing the number of points in this polygon. + public int PointCount + { + get => _points?.Length ?? 0; + } + + /// + /// Gets a read-only view of the points in this polygon. + /// + /// A of values, representing the points of this polygon. + public IReadOnlyList Points + { + get => _points?.ToArray() ?? ArraySegment.Empty; + } + + /// + /// Returns a value indicating whether two instances of are equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered equal; otherwise, + /// . + /// + public static bool operator ==(Polygon left, Polygon right) + { + return left.Equals(right); + } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered not equal; otherwise, + /// . + /// + public static bool operator !=(Polygon left, Polygon right) + { + return !left.Equals(right); + } + + /// + /// Adds a point to this polygon. + /// + /// The point to add. + public void AddPoint(Point point) + { + _points ??= Array.Empty(); + Span span = stackalloc Point[_points.Length + 1]; + _points.CopyTo(span); + span[^1] = point; + _points = span.ToArray(); + } + + /// + /// Adds a collection of points to this polygon. + /// + /// An enumerable collection of points to add. + /// is . + public void AddPoints(IEnumerable points) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(points); +#else + if (points is null) + { + throw new ArgumentNullException(nameof(points)); + } +#endif + + foreach (Point point in points) + { + AddPoint(point); + } + } + + /// + /// Clears all points from this polygon. + /// + public void ClearPoints() + { + _points = Array.Empty(); + } + + /// + public override bool Equals(object? obj) + { + return obj is Polygon polygon && Equals(polygon); + } + + /// + /// Returns a value indicating whether this instance and another instance are equal. + /// + /// The instance with which to compare. + /// + /// if this instance and are considered equal; otherwise, + /// . + /// + public bool Equals(Polygon other) + { + return _points switch + { + null when other._points is null => true, + null => false, + not null when other._points is null => false, + _ => _points.SequenceEqual(other._points) + }; + } + + /// + public override int GetHashCode() + { + Point[] points = _points ?? Array.Empty(); + return points.Aggregate(0, HashCode.Combine); + } +} diff --git a/X10D/src/Drawing/PolygonF.cs b/X10D/src/Drawing/PolygonF.cs new file mode 100644 index 0000000..0de9510 --- /dev/null +++ b/X10D/src/Drawing/PolygonF.cs @@ -0,0 +1,291 @@ +using System.Drawing; +using System.Numerics; +using X10D.Numerics; + +namespace X10D.Drawing; + +/// +/// Represents a 2D polygon composed of single-precision floating-point points. +/// +public struct PolygonF +{ + /// + /// The empty polygon. That is, a polygon with no points. + /// + public static readonly PolygonF Empty = new(ArraySegment.Empty); + + private PointF[]? _points; + + /// + /// Initializes a new instance of the struct by copying the specified polygon. + /// + public PolygonF(PolygonF polygon) + : this(polygon._points ?? Array.Empty()) + { + } + + /// + /// Initializes a new instance of the struct by constructing it from the specified points. + /// + /// An enumerable collection of points from which the polygon should be constructed. + public PolygonF(IEnumerable points) + : this(points.Select(p => p.ToPointF())) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(points); +#else + if (points is null) + { + throw new ArgumentNullException(nameof(points)); + } +#endif + } + + /// + /// Initializes a new instance of the struct by constructing it from the specified points. + /// + /// An enumerable collection of points from which the polygon should be constructed. + /// is . + public PolygonF(IEnumerable points) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(points); +#else + if (points is null) + { + throw new ArgumentNullException(nameof(points)); + } +#endif + + _points = points.ToArray(); + } + + /// + /// Returns a value indicating whether this polygon is convex. + /// + /// if this polygon is convex; otherwise, . + public bool IsConvex + { + get + { + if (_points is null || _points.Length < 3) + { + return false; + } + + var positive = false; + var negative = false; + PointF p0 = _points[0]; + + for (var index = 1; index < _points.Length; index++) + { + PointF p1 = _points[index]; + float d = (p1.X - p0.X) * (p1.Y + p0.Y); + + if (d > 0) + { + positive = true; + } + else if (d < 0) + { + negative = true; + } + + if (positive && negative) + { + return false; + } + + p0 = p1; + } + + return true; + } + } + + /// + /// Gets the number of points in this polygon. + /// + /// An value, representing the number of points in this polygon. + public int PointCount + { + get => _points?.Length ?? 0; + } + + /// + /// Gets a read-only view of the points in this polygon. + /// + /// A of values, representing the points of this polygon. + public IReadOnlyList Points + { + get => _points?.ToArray() ?? ArraySegment.Empty; + } + + /// + /// Returns a value indicating whether two instances of are equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered equal; otherwise, + /// . + /// + public static bool operator ==(PolygonF left, PolygonF right) + { + return left.Equals(right); + } + + /// + /// Returns a value indicating whether two instances of are not equal. + /// + /// The first instance. + /// The second instance. + /// + /// if and are considered not equal; otherwise, + /// . + /// + public static bool operator !=(PolygonF left, PolygonF right) + { + return !left.Equals(right); + } + + /// + /// Explicitly converts a to a . + /// + /// The polygon to convert. + /// The converted polygon. + public static explicit operator Polygon(PolygonF polygon) + { + var points = new List(); + + foreach (PointF point in polygon.Points) + { + points.Add(new Point((int)point.X, (int)point.Y)); + } + + return new Polygon(points); + } + + /// + /// Implicitly converts a to a . + /// + /// The polygon to convert. + /// The converted polygon. + public static implicit operator PolygonF(Polygon polygon) + { + var points = new List(); + + foreach (Point point in polygon.Points) + { + points.Add(point); + } + + return new PolygonF(points); + } + + /// + /// Adds a point to this polygon. + /// + /// The point to add. + public void AddPoint(PointF point) + { + _points ??= Array.Empty(); + Span span = stackalloc PointF[_points.Length + 1]; + _points.CopyTo(span); + span[^1] = point; + _points = span.ToArray(); + } + + /// + /// Adds a point to this polygon. + /// + /// The point to add. + public void AddPoint(Vector2 point) + { + AddPoint(point.ToPointF()); + } + + /// + /// Adds a collection of points to this polygon. + /// + /// An enumerable collection of points to add. + /// is . + public void AddPoints(IEnumerable points) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(points); +#else + if (points is null) + { + throw new ArgumentNullException(nameof(points)); + } +#endif + + foreach (PointF point in points) + { + AddPoint(point); + } + } + + /// + /// Adds a collection of points to this polygon. + /// + /// An enumerable collection of points to add. + /// is . + public void AddPoints(IEnumerable points) + { +#if NET6_0_OR_GREATER + ArgumentNullException.ThrowIfNull(points); +#else + if (points is null) + { + throw new ArgumentNullException(nameof(points)); + } +#endif + + foreach (Vector2 point in points) + { + AddPoint(point); + } + } + + /// + /// Clears all points from this polygon. + /// + public void ClearPoints() + { + _points = Array.Empty(); + } + + /// + public override bool Equals(object? obj) + { + return obj is PolygonF polygon && Equals(polygon); + } + + /// + /// Returns a value indicating whether this instance and another instance are equal. + /// + /// The instance with which to compare. + /// + /// if this instance and are considered equal; otherwise, + /// . + /// + public bool Equals(PolygonF other) + { + return _points switch + { + null when other._points is null => true, + null => false, + not null when other._points is null => false, + _ => _points.SequenceEqual(other._points) + }; + } + + /// + public override int GetHashCode() + { + PointF[] points = _points ?? Array.Empty(); + return points.Aggregate(0, HashCode.Combine); + } +}