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);
+ }
+}