Add primitive 2D structs to complement System.Drawing types

This commit is contained in:
Oliver Booth 2022-06-01 13:46:22 +01:00
parent aca68cce72
commit e9b0ed08d4
No known key found for this signature in database
GPG Key ID: 32A00B35503AF634
13 changed files with 2114 additions and 0 deletions

View File

@ -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()`

View File

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

View File

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

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

272
X10D/src/Drawing/Circle.cs Normal file
View File

@ -0,0 +1,272 @@
using System.Drawing;
namespace X10D.Drawing;
/// <summary>
/// Represents a circle that is composed of a 32-bit signed integer center point and radius.
/// </summary>
public readonly struct Circle : IEquatable<Circle>, IComparable<Circle>, IComparable
{
/// <summary>
/// The empty circle. That is, a circle whose center point is (0, 0) and whose radius is 0.
/// </summary>
public static readonly Circle Empty = new();
/// <summary>
/// The unit circle. That is, a circle whose center point is (0, 0) and whose radius is 1.
/// </summary>
public static readonly Circle Unit = new(Point.Empty, 1);
/// <summary>
/// Initializes a new instance of the <see cref="Circle" /> struct.
/// </summary>
/// <param name="center">The center point of the circle.</param>
/// <param name="radius">The radius of the circle.</param>
public Circle(Point center, int radius)
{
Center = center;
Radius = radius;
}
/// <summary>
/// Gets the area of the circle.
/// </summary>
/// <value>The area of the circle, calculated as <c>πr²</c>.</value>
public float Area
{
get => MathF.PI * Radius * Radius;
}
/// <summary>
/// Gets the center point of the circle.
/// </summary>
/// <value>The center point.</value>
public Point Center { get; }
/// <summary>
/// Gets the circumference of the circle.
/// </summary>
/// <value>The circumference of the circle, calculated as <c>2πr</c>.</value>
public float Circumference
{
get => 2 * MathF.PI * Radius;
}
/// <summary>
/// Gets the diameter of the circle.
/// </summary>
/// <value>The diameter. This is always twice the <see cref="Radius" />.</value>
public int Diameter
{
get => Radius * 2;
}
/// <summary>
/// Gets the radius of the circle.
/// </summary>
/// <value>The radius.</value>
public int Radius { get; }
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Circle" /> are equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator ==(Circle left, Circle right)
{
return left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Circle" /> are not equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered not equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator !=(Circle left, Circle right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether the radius of one circle is less than that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Radius" /> of <paramref name="left" /> is less than that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator <(Circle left, Circle right)
{
return left.CompareTo(right) < 0;
}
/// <summary>
/// Returns a value indicating whether the radius of one circle is greater than to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Radius" /> of <paramref name="left" /> is greater than that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator >(Circle left, Circle right)
{
return left.CompareTo(right) > 0;
}
/// <summary>
/// Returns a value indicating whether the radius of one circle is less than or equal to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Radius" /> of <paramref name="left" /> is less than or equal to that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator <=(Circle left, Circle right)
{
return left.CompareTo(right) <= 0;
}
/// <summary>
/// Returns a value indicating whether the radius of one circle is greater than or equal to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Radius" /> of <paramref name="left" /> is greater than or equal to that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator >=(Circle left, Circle right)
{
return left.CompareTo(right) >= 0;
}
/// <summary>
/// Compares this instance to another <see cref="Circle" />.
/// </summary>
/// <param name="obj">The other object.</param>
/// <returns>
/// A signed number indicating the relative values of this instance and <paramref name="obj" />.
///
/// <list type="table">
/// <listheader>
/// <term>Return value</term>
/// <description>Meaning</description>
/// </listheader>
///
/// <item>
/// <term>Less than zero</term>
/// <description>
/// The <see cref="Radius" /> of this instance is less than that of <paramref name="obj" />.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance is equal to <paramref name="obj" />, or the <see cref="Radius" /> of both this instance
/// and <paramref name="obj" /> are not a number (<see cref="float.NaN" />),
/// <see cref="float.PositiveInfinity" />, or <see cref="float.NegativeInfinity" />.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// The <see cref="Radius" /> of this instance is greater than that of <paramref name="obj" />, or
/// <paramref name="obj" /> is <see langword="null" />.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <remarks>Comparison only takes into consideration the <see cref="Radius" />.</remarks>
/// <exception cref="ArgumentException"><paramref name="obj" /> is not an instance of <see cref="Circle" />.</exception>
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);
}
/// <summary>
/// Compares this instance to another <see cref="Circle" />.
/// </summary>
/// <param name="other">The other circle.</param>
/// <returns>
/// A signed number indicating the relative values of this instance and <paramref name="other" />.
///
/// <list type="table">
/// <listheader>
/// <term>Return value</term>
/// <description>Meaning</description>
/// </listheader>
///
/// <item>
/// <term>Less than zero</term>
/// <description>
/// The <see cref="Radius" /> of this instance is less than that of <paramref name="other" />.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance is equal to <paramref name="other" />, or the <see cref="Radius" /> of both this instance
/// and <paramref name="other" /> are not a number (<see cref="float.NaN" />),
/// <see cref="float.PositiveInfinity" />, or <see cref="float.NegativeInfinity" />.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// The <see cref="Radius" /> of this instance is greater than that of <paramref name="other" />.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <remarks>Comparison only takes into consideration the <see cref="Radius" />.</remarks>
public int CompareTo(Circle other)
{
return Radius.CompareTo(other.Radius);
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is Circle circle && Equals(circle);
}
/// <summary>
/// Returns a value indicating whether this instance and another instance are equal.
/// </summary>
/// <param name="other">The instance with which to compare.</param>
/// <returns>
/// <see langword="true" /> if this instance and <paramref name="other" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
public bool Equals(Circle other)
{
return Center.Equals(other.Center) && Radius.Equals(other.Radius);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(Center, Radius);
}
}

284
X10D/src/Drawing/CircleF.cs Normal file
View File

@ -0,0 +1,284 @@
using System.Drawing;
using System.Numerics;
using X10D.Numerics;
namespace X10D.Drawing;
/// <summary>
/// Represents a circle that is composed of a single-precision floating-point center point and radius.
/// </summary>
public readonly struct CircleF : IEquatable<CircleF>, IComparable<CircleF>, IComparable
{
/// <summary>
/// The empty circle. That is, a circle whose center point is (0, 0) and whose radius is 0.
/// </summary>
public static readonly CircleF Empty = new();
/// <summary>
/// The unit circle. That is, a circle whose center point is (0, 0) and whose radius is 1.
/// </summary>
public static readonly CircleF Unit = new(Vector2.Zero, 1.0f);
/// <summary>
/// Initializes a new instance of the <see cref="CircleF" /> struct.
/// </summary>
/// <param name="center">The center point of the circle.</param>
/// <param name="radius">The radius of the circle.</param>
public CircleF(Vector2 center, float radius)
: this(center.ToPointF(), radius)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CircleF" /> struct.
/// </summary>
/// <param name="center">The center point of the circle.</param>
/// <param name="radius">The radius of the circle.</param>
public CircleF(PointF center, float radius)
{
Center = center;
Radius = radius;
}
/// <summary>
/// Gets the area of the circle.
/// </summary>
/// <value>The area of the circle, calculated as <c>πr²</c>.</value>
public float Area
{
get => MathF.PI * Radius * Radius;
}
/// <summary>
/// Gets the center point of the circle.
/// </summary>
/// <value>The center point.</value>
public PointF Center { get; }
/// <summary>
/// Gets the circumference of the circle.
/// </summary>
/// <value>The circumference of the circle, calculated as <c>2πr</c>.</value>
public float Circumference
{
get => 2 * MathF.PI * Radius;
}
/// <summary>
/// Gets the diameter of the circle.
/// </summary>
/// <value>The diameter. This is always twice the <see cref="Radius" />.</value>
public float Diameter
{
get => Radius * 2;
}
/// <summary>
/// Gets the radius of the circle.
/// </summary>
/// <value>The radius.</value>
public float Radius { get; }
/// <summary>
/// Returns a value indicating whether two instances of <see cref="CircleF" /> are equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator ==(CircleF left, CircleF right)
{
return left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="CircleF" /> are not equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered not equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator !=(CircleF left, CircleF right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether the radius of one circle is less than that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Radius" /> of <paramref name="left" /> is less than that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator <(CircleF left, CircleF right)
{
return left.CompareTo(right) < 0;
}
/// <summary>
/// Returns a value indicating whether the radius of one circle is greater than to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Radius" /> of <paramref name="left" /> is greater than that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator >(CircleF left, CircleF right)
{
return left.CompareTo(right) > 0;
}
/// <summary>
/// Returns a value indicating whether the radius of one circle is less than or equal to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Radius" /> of <paramref name="left" /> is less than or equal to that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator <=(CircleF left, CircleF right)
{
return left.CompareTo(right) <= 0;
}
/// <summary>
/// Returns a value indicating whether the radius of one circle is greater than or equal to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Radius" /> of <paramref name="left" /> is greater than or equal to that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator >=(CircleF left, CircleF right)
{
return left.CompareTo(right) >= 0;
}
/// <summary>
/// Compares this instance to another <see cref="Circle" />.
/// </summary>
/// <param name="obj">The other object.</param>
/// <returns>
/// A signed number indicating the relative values of this instance and <paramref name="obj" />.
///
/// <list type="table">
/// <listheader>
/// <term>Return value</term>
/// <description>Meaning</description>
/// </listheader>
///
/// <item>
/// <term>Less than zero</term>
/// <description>
/// The <see cref="Radius" /> of this instance is less than that of <paramref name="obj" />.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance is equal to <paramref name="obj" />, or the <see cref="Radius" /> of both this instance
/// and <paramref name="obj" /> are not a number (<see cref="float.NaN" />),
/// <see cref="float.PositiveInfinity" />, or <see cref="float.NegativeInfinity" />.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// The <see cref="Radius" /> of this instance is greater than that of <paramref name="obj" />, or
/// <paramref name="obj" /> is <see langword="null" />.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <remarks>Comparison only takes into consideration the <see cref="Radius" />.</remarks>
/// <exception cref="ArgumentException"><paramref name="obj" /> is not an instance of <see cref="CircleF" />.</exception>
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);
}
/// <summary>
/// Compares this instance to another <see cref="CircleF" />.
/// </summary>
/// <param name="other">The other circle.</param>
/// <returns>
/// A signed number indicating the relative values of this instance and <paramref name="other" />.
///
/// <list type="table">
/// <listheader>
/// <term>Return value</term>
/// <description>Meaning</description>
/// </listheader>
///
/// <item>
/// <term>Less than zero</term>
/// <description>
/// The <see cref="Radius" /> of this instance is less than that of <paramref name="other" />.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance is equal to <paramref name="other" />, or the <see cref="Radius" /> of both this instance
/// and <paramref name="other" /> are not a number (<see cref="float.NaN" />),
/// <see cref="float.PositiveInfinity" />, or <see cref="float.NegativeInfinity" />.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// The <see cref="Radius" /> of this instance is greater than that of <paramref name="other" />.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <remarks>Comparison only takes into consideration the <see cref="Radius" />.</remarks>
public int CompareTo(CircleF other)
{
return Radius.CompareTo(other.Radius);
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is CircleF circle && Equals(circle);
}
/// <summary>
/// Returns a value indicating whether this instance and another instance are equal.
/// </summary>
/// <param name="other">The instance with which to compare.</param>
/// <returns>
/// <see langword="true" /> if this instance and <paramref name="other" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
public bool Equals(CircleF other)
{
return Center.Equals(other.Center) && Radius.Equals(other.Radius);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(Center, Radius);
}
}

276
X10D/src/Drawing/Line.cs Normal file
View File

@ -0,0 +1,276 @@
using System.Drawing;
namespace X10D.Drawing;
/// <summary>
/// Represents a line that is composed of 32-bit signed integer X and Y coordinates.
/// </summary>
public readonly struct Line : IEquatable<Line>, IComparable<Line>, IComparable
{
/// <summary>
/// The empty line. That is, a line whose start and end points are at (0, 0).
/// </summary>
public static readonly Line Empty = new();
/// <summary>
/// The line whose start point is at (0, 0) and end point is at (1, 1).
/// </summary>
public static readonly Line One = new(Point.Empty, new Point(1, 1));
/// <summary>
/// The line whose start point is at (0, 0) and end point is at (1, 0).
/// </summary>
public static readonly Line UnitX = new(Point.Empty, new Point(1, 0));
/// <summary>
/// The line whose start point is at (0, 0) and end point is at (0, 1).
/// </summary>
public static readonly Line UnitY = new(Point.Empty, new Point(0, 1));
/// <summary>
/// Initializes a new instance of the <see cref="Line" /> struct by taking the start and end points.
/// </summary>
/// <param name="start">The start point.</param>
/// <param name="end">The end point.</param>
public Line(Point start, Point end)
{
End = end;
Start = start;
}
/// <summary>
/// Gets the end point of the line.
/// </summary>
/// <value>The end point.</value>
public Point End { get; }
/// <summary>
/// Gets the length of this line.
/// </summary>
/// <value>The length.</value>
public float Length
{
get => MathF.Sqrt(LengthSquared);
}
/// <summary>
/// Gets the length of this line, squared.
/// </summary>
/// <value>The squared length.</value>
public float LengthSquared
{
get => MathF.Pow(End.X - Start.X, 2.0f) + MathF.Pow(End.Y - Start.Y, 2.0f);
}
/// <summary>
/// Gets the start point of the line.
/// </summary>
/// <value>The start point.</value>
public Point Start { get; }
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Line" /> are not equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered not equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator ==(Line left, Line right)
{
return left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Line" /> are not equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered not equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator !=(Line left, Line right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether the length of one line is less than that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Length" /> of <paramref name="left" /> is less than that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator <(Line left, Line right)
{
return left.CompareTo(right) < 0;
}
/// <summary>
/// Returns a value indicating whether the length of one line is greater than that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Length" /> of <paramref name="left" /> is greater than that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator >(Line left, Line right)
{
return left.CompareTo(right) > 0;
}
/// <summary>
/// Returns a value indicating whether the length of one line is less than or equal to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Length" /> of <paramref name="left" /> is less than or equal to that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator <=(Line left, Line right)
{
return left.CompareTo(right) <= 0;
}
/// <summary>
/// Returns a value indicating whether the length of one line is greater than or equal to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Length" /> of <paramref name="left" /> is greater than or equal to that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator >=(Line left, Line right)
{
return left.CompareTo(right) >= 0;
}
/// <summary>
/// Compares this instance to another object.
/// </summary>
/// <param name="obj">The object with with which to compare</param>
/// <returns>
/// A signed number indicating the relative values of this instance and <paramref name="obj"/>.
///
/// <list type="table">
/// <listheader>
/// <term>Return value</term>
/// <description>Meaning</description>
/// </listheader>
///
/// <item>
/// <term>Less than zero</term>
/// <description>
/// The <see cref="Length" /> of this instance is less than that of <paramref name="obj" />.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance is equal to <paramref name="obj" />, or the <see cref="Length" /> of both this instance
/// and <paramref name="obj" /> are not a number (<see cref="float.NaN" />),
/// <see cref="float.PositiveInfinity" />, or <see cref="float.NegativeInfinity" />.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// The <see cref="Length" /> of this instance is greater than that of <paramref name="obj" />.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <remarks>
/// Comparison internally measures the <see cref="LengthSquared" /> property to avoid calls to <see cref="MathF.Sqrt" />.
/// <exception cref="ArgumentException"><paramref name="obj" /> is not an instance of <see cref="Line" />.</exception>
/// </remarks>
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);
}
/// <summary>
/// Compares this instance to another <see cref="Line" />.
/// </summary>
/// <param name="other"></param>
/// <returns>
/// A signed number indicating the relative values of this instance and <paramref name="other" />.
///
/// <list type="table">
/// <listheader>
/// <term>Return value</term>
/// <description>Meaning</description>
/// </listheader>
///
/// <item>
/// <term>Less than zero</term>
/// <description>
/// The <see cref="Length" /> of this instance is less than that of <paramref name="other" />.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance is equal to <paramref name="other" />, or the <see cref="Length" /> of both this instance
/// and <paramref name="other" /> are not a number (<see cref="float.NaN" />),
/// <see cref="float.PositiveInfinity" />, or <see cref="float.NegativeInfinity" />.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// The <see cref="Length" /> of this instance is greater than that of <paramref name="other" />.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <remarks>
/// Comparison internally measures the <see cref="LengthSquared" /> property to avoid calls to <see cref="MathF.Sqrt" />.
/// </remarks>
public int CompareTo(Line other)
{
return LengthSquared.CompareTo(other.LengthSquared);
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is Line other && Equals(other);
}
/// <summary>
/// Returns a value indicating whether this instance and another instance are equal.
/// </summary>
/// <param name="other">The instance with which to compare.</param>
/// <returns>
/// <see langword="true" /> if this instance and <paramref name="other" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
public bool Equals(Line other)
{
return End.Equals(other.End) && Start.Equals(other.Start);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(End, Start);
}
}

310
X10D/src/Drawing/LineF.cs Normal file
View File

@ -0,0 +1,310 @@
using System.Drawing;
using System.Numerics;
using X10D.Numerics;
namespace X10D.Drawing;
/// <summary>
/// Represents a line that is composed of single-precision floating-point X and Y coordinates.
/// </summary>
public readonly struct LineF : IEquatable<LineF>, IComparable<LineF>, IComparable
{
/// <summary>
/// The empty line. That is, a line whose start and end points are at (0, 0).
/// </summary>
public static readonly LineF Empty = new();
/// <summary>
/// The line whose start point is at (0, 0) and end point is at (1, 1).
/// </summary>
public static readonly LineF One = new(Vector2.Zero, new Vector2(1, 1));
/// <summary>
/// The line whose start point is at (0, 0) and end point is at (1, 0).
/// </summary>
public static readonly LineF UnitX = new(Vector2.Zero, new Vector2(1, 0));
/// <summary>
/// The line whose start point is at (0, 0) and end point is at (0, 1).
/// </summary>
public static readonly LineF UnitY = new(Vector2.Zero, new Vector2(0, 1));
/// <summary>
/// Initializes a new instance of the <see cref="LineF" /> struct by taking the start and end points.
/// </summary>
/// <param name="start">The start point.</param>
/// <param name="end">The end point.</param>
public LineF(Vector2 start, Vector2 end)
: this(start.ToPointF(), end.ToPointF())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="LineF" /> struct by taking the start and end points.
/// </summary>
/// <param name="start">The start point.</param>
/// <param name="end">The end point.</param>
public LineF(PointF start, PointF end)
{
End = end;
Start = start;
}
/// <summary>
/// Gets the end point of the line.
/// </summary>
/// <value>The end point.</value>
public PointF End { get; }
/// <summary>
/// Gets the length of this line.
/// </summary>
/// <value>The length.</value>
public float Length
{
get => MathF.Sqrt(LengthSquared);
}
/// <summary>
/// Gets the length of this line, squared.
/// </summary>
/// <value>The squared length.</value>
public float LengthSquared
{
get => MathF.Pow(End.X - Start.X, 2.0f) + MathF.Pow(End.Y - Start.Y, 2.0f);
}
/// <summary>
/// Gets the start point of the line.
/// </summary>
/// <value>The start point.</value>
public PointF Start { get; }
/// <summary>
/// Returns a value indicating whether two instances of <see cref="LineF" /> are not equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered not equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator ==(LineF left, LineF right)
{
return left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="LineF" /> are not equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered not equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator !=(LineF left, LineF right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether the length of one line is less than that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Length" /> of <paramref name="left" /> is less than that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator <(LineF left, LineF right)
{
return left.CompareTo(right) < 0;
}
/// <summary>
/// Returns a value indicating whether the length of one line is greater than that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Length" /> of <paramref name="left" /> is greater than that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator >(LineF left, LineF right)
{
return left.CompareTo(right) > 0;
}
/// <summary>
/// Returns a value indicating whether the length of one line is less than or equal to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Length" /> of <paramref name="left" /> is less than or equal to that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator <=(LineF left, LineF right)
{
return left.CompareTo(right) <= 0;
}
/// <summary>
/// Returns a value indicating whether the length of one line is greater than or equal to that of another.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if the <see cref="Length" /> of <paramref name="left" /> is greater than or equal to that of
/// <paramref name="right" />; otherwise, <see langword="false" />.
/// </returns>
public static bool operator >=(LineF left, LineF right)
{
return left.CompareTo(right) >= 0;
}
/// <summary>
/// Explicitly converts a <see cref="Line" /> to a <see cref="LineF" />.
/// </summary>
/// <param name="line">The line to convert.</param>
/// <returns>The converted line.</returns>
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));
}
/// <summary>
/// Implicitly converts a <see cref="Line" /> to a <see cref="LineF" />.
/// </summary>
/// <param name="line">The line to convert.</param>
/// <returns>The line polygon.</returns>
public static implicit operator LineF(Line line)
{
return new LineF(line.Start, line.End);
}
/// <summary>
/// Compares this instance to another object.
/// </summary>
/// <param name="obj">The object with with which to compare</param>
/// <returns>
/// A signed number indicating the relative values of this instance and <paramref name="obj"/>.
///
/// <list type="table">
/// <listheader>
/// <term>Return value</term>
/// <description>Meaning</description>
/// </listheader>
///
/// <item>
/// <term>Less than zero</term>
/// <description>
/// The <see cref="Length" /> of this instance is less than that of <paramref name="obj" />.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance is equal to <paramref name="obj" />, or the <see cref="Length" /> of both this instance
/// and <paramref name="obj" /> are not a number (<see cref="float.NaN" />),
/// <see cref="float.PositiveInfinity" />, or <see cref="float.NegativeInfinity" />.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// The <see cref="Length" /> of this instance is greater than that of <paramref name="obj" />.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <remarks>
/// Comparison internally measures the <see cref="LengthSquared" /> property to avoid calls to <see cref="MathF.Sqrt" />.
/// <exception cref="ArgumentException"><paramref name="obj" /> is not an instance of <see cref="Line" />.</exception>
/// </remarks>
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);
}
/// <summary>
/// Compares this instance to another <see cref="Line" />.
/// </summary>
/// <param name="other"></param>
/// <returns>
/// A signed number indicating the relative values of this instance and <paramref name="other" />.
///
/// <list type="table">
/// <listheader>
/// <term>Return value</term>
/// <description>Meaning</description>
/// </listheader>
///
/// <item>
/// <term>Less than zero</term>
/// <description>
/// The <see cref="Length" /> of this instance is less than that of <paramref name="other" />.
/// </description>
/// </item>
/// <item>
/// <term>Zero</term>
/// <description>
/// This instance is equal to <paramref name="other" />, or the <see cref="Length" /> of both this instance
/// and <paramref name="other" /> are not a number (<see cref="float.NaN" />),
/// <see cref="float.PositiveInfinity" />, or <see cref="float.NegativeInfinity" />.
/// </description>
/// </item>
/// <item>
/// <term>Greater than zero</term>
/// <description>
/// The <see cref="Length" /> of this instance is greater than that of <paramref name="other" />.
/// </description>
/// </item>
/// </list>
/// </returns>
/// <remarks>
/// Comparison internally measures the <see cref="LengthSquared" /> property to avoid calls to <see cref="MathF.Sqrt" />.
/// </remarks>
public int CompareTo(LineF other)
{
return LengthSquared.CompareTo(other.LengthSquared);
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is LineF other && Equals(other);
}
/// <summary>
/// Returns a value indicating whether this instance and another instance are equal.
/// </summary>
/// <param name="other">The instance with which to compare.</param>
/// <returns>
/// <see langword="true" /> if this instance and <paramref name="other" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
public bool Equals(LineF other)
{
return End.Equals(other.End) && Start.Equals(other.Start);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(End, Start);
}
}

197
X10D/src/Drawing/Polygon.cs Normal file
View File

@ -0,0 +1,197 @@
using System.Drawing;
namespace X10D.Drawing;
/// <summary>
/// Represents a 2D polygon composed of 32-bit signed integer points.
/// </summary>
public struct Polygon : IEquatable<Polygon>
{
/// <summary>
/// The empty polygon. That is, a polygon with no points.
/// </summary>
public static readonly Polygon Empty = new(ArraySegment<Point>.Empty);
private Point[]? _points;
/// <summary>
/// Initializes a new instance of the <see cref="Polygon" /> struct by copying the specified polygon.
/// </summary>
public Polygon(Polygon polygon)
: this(polygon._points ?? ArraySegment<Point>.Empty)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Polygon" /> struct by constructing it from the specified points.
/// </summary>
/// <param name="points">An enumerable collection of points from which the polygon should be constructed.</param>
public Polygon(IEnumerable<Point> points)
{
_points = points.ToArray();
}
/// <summary>
/// Returns a value indicating whether this polygon is convex.
/// </summary>
/// <value><see langword="true" /> if this polygon is convex; otherwise, <see langword="false" />.</value>
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;
}
}
/// <summary>
/// Gets the number of points in this polygon.
/// </summary>
/// <value>An <see cref="int" /> value, representing the number of points in this polygon.</value>
public int PointCount
{
get => _points?.Length ?? 0;
}
/// <summary>
/// Gets a read-only view of the points in this polygon.
/// </summary>
/// <value>A <see cref="IReadOnlyList{T}" /> of <see cref="Point" /> values, representing the points of this polygon.</value>
public IReadOnlyList<Point> Points
{
get => _points?.ToArray() ?? ArraySegment<Point>.Empty;
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Polygon" /> are equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator ==(Polygon left, Polygon right)
{
return left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Polygon" /> are not equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered not equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator !=(Polygon left, Polygon right)
{
return !left.Equals(right);
}
/// <summary>
/// Adds a point to this polygon.
/// </summary>
/// <param name="point">The point to add.</param>
public void AddPoint(Point point)
{
_points ??= Array.Empty<Point>();
Span<Point> span = stackalloc Point[_points.Length + 1];
_points.CopyTo(span);
span[^1] = point;
_points = span.ToArray();
}
/// <summary>
/// Adds a collection of points to this polygon.
/// </summary>
/// <param name="points">An enumerable collection of points to add.</param>
/// <exception cref="ArgumentNullException"><paramref name="points" /> is <see langword="null" />.</exception>
public void AddPoints(IEnumerable<Point> 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);
}
}
/// <summary>
/// Clears all points from this polygon.
/// </summary>
public void ClearPoints()
{
_points = Array.Empty<Point>();
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is Polygon polygon && Equals(polygon);
}
/// <summary>
/// Returns a value indicating whether this instance and another instance are equal.
/// </summary>
/// <param name="other">The instance with which to compare.</param>
/// <returns>
/// <see langword="true" /> if this instance and <paramref name="other" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
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)
};
}
/// <inheritdoc />
public override int GetHashCode()
{
Point[] points = _points ?? Array.Empty<Point>();
return points.Aggregate(0, HashCode.Combine);
}
}

View File

@ -0,0 +1,291 @@
using System.Drawing;
using System.Numerics;
using X10D.Numerics;
namespace X10D.Drawing;
/// <summary>
/// Represents a 2D polygon composed of single-precision floating-point points.
/// </summary>
public struct PolygonF
{
/// <summary>
/// The empty polygon. That is, a polygon with no points.
/// </summary>
public static readonly PolygonF Empty = new(ArraySegment<PointF>.Empty);
private PointF[]? _points;
/// <summary>
/// Initializes a new instance of the <see cref="PolygonF" /> struct by copying the specified polygon.
/// </summary>
public PolygonF(PolygonF polygon)
: this(polygon._points ?? Array.Empty<PointF>())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PolygonF" /> struct by constructing it from the specified points.
/// </summary>
/// <param name="points">An enumerable collection of points from which the polygon should be constructed.</param>
public PolygonF(IEnumerable<Vector2> 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
}
/// <summary>
/// Initializes a new instance of the <see cref="PolygonF" /> struct by constructing it from the specified points.
/// </summary>
/// <param name="points">An enumerable collection of points from which the polygon should be constructed.</param>
/// <exception cref="ArgumentNullException"><paramref name="points" /> is <see langword="null" />.</exception>
public PolygonF(IEnumerable<PointF> points)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(points);
#else
if (points is null)
{
throw new ArgumentNullException(nameof(points));
}
#endif
_points = points.ToArray();
}
/// <summary>
/// Returns a value indicating whether this polygon is convex.
/// </summary>
/// <value><see langword="true" /> if this polygon is convex; otherwise, <see langword="false" />.</value>
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;
}
}
/// <summary>
/// Gets the number of points in this polygon.
/// </summary>
/// <value>An <see cref="int" /> value, representing the number of points in this polygon.</value>
public int PointCount
{
get => _points?.Length ?? 0;
}
/// <summary>
/// Gets a read-only view of the points in this polygon.
/// </summary>
/// <value>A <see cref="IReadOnlyList{T}" /> of <see cref="PointF" /> values, representing the points of this polygon.</value>
public IReadOnlyList<PointF> Points
{
get => _points?.ToArray() ?? ArraySegment<PointF>.Empty;
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="PolygonF" /> are equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator ==(PolygonF left, PolygonF right)
{
return left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="PolygonF" /> are not equal.
/// </summary>
/// <param name="left">The first instance.</param>
/// <param name="right">The second instance.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are considered not equal; otherwise,
/// <see langword="false" />.
/// </returns>
public static bool operator !=(PolygonF left, PolygonF right)
{
return !left.Equals(right);
}
/// <summary>
/// Explicitly converts a <see cref="Polygon" /> to a <see cref="PolygonF" />.
/// </summary>
/// <param name="polygon">The polygon to convert.</param>
/// <returns>The converted polygon.</returns>
public static explicit operator Polygon(PolygonF polygon)
{
var points = new List<Point>();
foreach (PointF point in polygon.Points)
{
points.Add(new Point((int)point.X, (int)point.Y));
}
return new Polygon(points);
}
/// <summary>
/// Implicitly converts a <see cref="Polygon" /> to a <see cref="PolygonF" />.
/// </summary>
/// <param name="polygon">The polygon to convert.</param>
/// <returns>The converted polygon.</returns>
public static implicit operator PolygonF(Polygon polygon)
{
var points = new List<PointF>();
foreach (Point point in polygon.Points)
{
points.Add(point);
}
return new PolygonF(points);
}
/// <summary>
/// Adds a point to this polygon.
/// </summary>
/// <param name="point">The point to add.</param>
public void AddPoint(PointF point)
{
_points ??= Array.Empty<PointF>();
Span<PointF> span = stackalloc PointF[_points.Length + 1];
_points.CopyTo(span);
span[^1] = point;
_points = span.ToArray();
}
/// <summary>
/// Adds a point to this polygon.
/// </summary>
/// <param name="point">The point to add.</param>
public void AddPoint(Vector2 point)
{
AddPoint(point.ToPointF());
}
/// <summary>
/// Adds a collection of points to this polygon.
/// </summary>
/// <param name="points">An enumerable collection of points to add.</param>
/// <exception cref="ArgumentNullException"><paramref name="points" /> is <see langword="null" />.</exception>
public void AddPoints(IEnumerable<PointF> 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);
}
}
/// <summary>
/// Adds a collection of points to this polygon.
/// </summary>
/// <param name="points">An enumerable collection of points to add.</param>
/// <exception cref="ArgumentNullException"><paramref name="points" /> is <see langword="null" />.</exception>
public void AddPoints(IEnumerable<Vector2> 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);
}
}
/// <summary>
/// Clears all points from this polygon.
/// </summary>
public void ClearPoints()
{
_points = Array.Empty<PointF>();
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is PolygonF polygon && Equals(polygon);
}
/// <summary>
/// Returns a value indicating whether this instance and another instance are equal.
/// </summary>
/// <param name="other">The instance with which to compare.</param>
/// <returns>
/// <see langword="true" /> if this instance and <paramref name="other" /> are considered equal; otherwise,
/// <see langword="false" />.
/// </returns>
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)
};
}
/// <inheritdoc />
public override int GetHashCode()
{
PointF[] points = _points ?? Array.Empty<PointF>();
return points.Aggregate(0, HashCode.Combine);
}
}