From 5e835e10f17e41e8cb23166e8c9958d948d11efd Mon Sep 17 00:00:00 2001 From: Oliver Booth Date: Wed, 1 Jun 2022 15:36:18 +0100 Subject: [PATCH] Add Ellipse struct --- X10D.Tests/src/Drawing/EllipseFTests.cs | 128 +++++++++++++++++ X10D.Tests/src/Drawing/EllipseTests.cs | 92 +++++++++++++ X10D/src/Drawing/Ellipse.cs | 138 +++++++++++++++++++ X10D/src/Drawing/EllipseF.cs | 174 ++++++++++++++++++++++++ 4 files changed, 532 insertions(+) create mode 100644 X10D.Tests/src/Drawing/EllipseFTests.cs create mode 100644 X10D.Tests/src/Drawing/EllipseTests.cs create mode 100644 X10D/src/Drawing/Ellipse.cs create mode 100644 X10D/src/Drawing/EllipseF.cs diff --git a/X10D.Tests/src/Drawing/EllipseFTests.cs b/X10D.Tests/src/Drawing/EllipseFTests.cs new file mode 100644 index 0000000..78a7514 --- /dev/null +++ b/X10D.Tests/src/Drawing/EllipseFTests.cs @@ -0,0 +1,128 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class EllipseFTests +{ + [TestMethod] + public void Area_ShouldBePiRadiusRadius_GivenUnitEllipse() + { + var unitEllipse = EllipseF.Unit; + Assert.AreEqual(MathF.PI, unitEllipse.Area, 1e-6f); + } + + [TestMethod] + public void ApproximateCircumference_ShouldBe2PiRadius_GivenUnitEllipse() + { + var unitEllipse = EllipseF.Unit; + Assert.AreEqual(2 * MathF.PI, unitEllipse.ApproximateCircumference, 1e-6f); + } + + [TestMethod] + public void Equals_ShouldBeTrue_GivenTwoUnitEllipses() + { + var unitEllipse1 = EllipseF.Unit; + var unitEllipse2 = EllipseF.Unit; + Assert.AreEqual(unitEllipse1, unitEllipse2); + Assert.IsTrue(unitEllipse1 == unitEllipse2); + Assert.IsFalse(unitEllipse1 != unitEllipse2); + } + + [TestMethod] + public void Equals_ShouldBeFalse_GivenDifferentEllipses() + { + Assert.AreNotEqual(EllipseF.Unit, EllipseF.Empty); + Assert.IsFalse(EllipseF.Unit == EllipseF.Empty); + Assert.IsTrue(EllipseF.Unit != EllipseF.Empty); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenEmptyEllipse() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = EllipseF.Empty.GetHashCode(); + Assert.AreEqual(hashCode, EllipseF.Empty.GetHashCode()); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenUnitEllipse() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = EllipseF.Unit.GetHashCode(); + Assert.AreEqual(hashCode, EllipseF.Unit.GetHashCode()); + } + + [TestMethod] + public void HorizontalRadius_ShouldBe0_GivenEmptyEllipse() + { + Assert.AreEqual(0, EllipseF.Empty.HorizontalRadius); + } + + [TestMethod] + public void HorizontalRadius_ShouldBe1_GivenUnitEllipse() + { + Assert.AreEqual(1, EllipseF.Unit.HorizontalRadius); + } + + [TestMethod] + public void op_Explicit_ShouldReturnEquivalentEllipse_GivenEllipse() + { + EllipseF unitEllipse = EllipseF.Unit; + Ellipse converted = (Ellipse)unitEllipse; + + Assert.AreEqual(unitEllipse, converted); + Assert.AreEqual(unitEllipse.HorizontalRadius, converted.HorizontalRadius); + Assert.AreEqual(unitEllipse.VerticalRadius, converted.VerticalRadius); + Assert.AreEqual(unitEllipse.Center, converted.Center); + } + + [TestMethod] + public void op_Implicit_ShouldReturnEquivalentEllipse_GivenCircle() + { + Circle unitCircle = Circle.Unit; + EllipseF converted = unitCircle; + + Assert.AreEqual(unitCircle, converted); + Assert.AreEqual(unitCircle.Radius, converted.HorizontalRadius); + Assert.AreEqual(unitCircle.Radius, converted.VerticalRadius); + Assert.AreEqual(unitCircle.Center, converted.Center); + } + + [TestMethod] + public void op_Implicit_ShouldReturnEquivalentEllipse_GivenCircleF() + { + CircleF unitCircle = CircleF.Unit; + EllipseF converted = unitCircle; + + Assert.AreEqual(unitCircle, converted); + Assert.AreEqual(unitCircle.Radius, converted.HorizontalRadius); + Assert.AreEqual(unitCircle.Radius, converted.VerticalRadius); + Assert.AreEqual(unitCircle.Center, converted.Center); + } + + [TestMethod] + public void op_Implicit_ShouldReturnEquivalentEllipse_GivenEllipse() + { + Ellipse unitEllipse = Ellipse.Unit; + EllipseF converted = unitEllipse; + + Assert.AreEqual(unitEllipse, converted); + Assert.AreEqual(unitEllipse.HorizontalRadius, converted.HorizontalRadius); + Assert.AreEqual(unitEllipse.VerticalRadius, converted.VerticalRadius); + Assert.AreEqual(unitEllipse.Center, converted.Center); + } + + [TestMethod] + public void VerticalRadius_ShouldBe0_GivenEmptyEllipse() + { + Assert.AreEqual(0, EllipseF.Empty.VerticalRadius); + } + + [TestMethod] + public void VerticalRadius_ShouldBe1_GivenUnitEllipse() + { + Assert.AreEqual(1, EllipseF.Unit.VerticalRadius); + } +} diff --git a/X10D.Tests/src/Drawing/EllipseTests.cs b/X10D.Tests/src/Drawing/EllipseTests.cs new file mode 100644 index 0000000..0c62cdc --- /dev/null +++ b/X10D.Tests/src/Drawing/EllipseTests.cs @@ -0,0 +1,92 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class EllipseTests +{ + [TestMethod] + public void Area_ShouldBePiRadiusRadius_GivenUnitEllipse() + { + var unitEllipse = Ellipse.Unit; + Assert.AreEqual(MathF.PI, unitEllipse.Area, 1e-6f); + } + + [TestMethod] + public void ApproximateCircumference_ShouldBe2PiRadius_GivenUnitEllipse() + { + var unitEllipse = Ellipse.Unit; + Assert.AreEqual(2 * MathF.PI, unitEllipse.ApproximateCircumference, 1e-6f); + } + + [TestMethod] + public void Equals_ShouldBeTrue_GivenTwoUnitEllipses() + { + var unitEllipse1 = Ellipse.Unit; + var unitEllipse2 = Ellipse.Unit; + Assert.AreEqual(unitEllipse1, unitEllipse2); + Assert.IsTrue(unitEllipse1 == unitEllipse2); + Assert.IsFalse(unitEllipse1 != unitEllipse2); + } + + [TestMethod] + public void Equals_ShouldBeFalse_GivenDifferentEllipses() + { + Assert.AreNotEqual(Ellipse.Unit, Ellipse.Empty); + Assert.IsFalse(Ellipse.Unit == Ellipse.Empty); + Assert.IsTrue(Ellipse.Unit != Ellipse.Empty); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenEmptyEllipse() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = Ellipse.Empty.GetHashCode(); + Assert.AreEqual(hashCode, Ellipse.Empty.GetHashCode()); + } + + [TestMethod] + public void GetHashCode_ShouldBeCorrect_GivenUnitEllipse() + { + // this test is pretty pointless, it exists only for code coverage purposes + int hashCode = Ellipse.Unit.GetHashCode(); + Assert.AreEqual(hashCode, Ellipse.Unit.GetHashCode()); + } + + [TestMethod] + public void HorizontalRadius_ShouldBe0_GivenEmptyEllipse() + { + Assert.AreEqual(0, Ellipse.Empty.HorizontalRadius); + } + + [TestMethod] + public void HorizontalRadius_ShouldBe1_GivenUnitEllipse() + { + Assert.AreEqual(1, Ellipse.Unit.HorizontalRadius); + } + + [TestMethod] + public void op_Implicit_ShouldReturnEquivalentEllipse_GivenCircle() + { + Circle unitCircle = Circle.Unit; + Ellipse converted = unitCircle; + + Assert.AreEqual(unitCircle, converted); + Assert.AreEqual(unitCircle.Radius, converted.HorizontalRadius); + Assert.AreEqual(unitCircle.Radius, converted.VerticalRadius); + Assert.AreEqual(unitCircle.Center, converted.Center); + } + + [TestMethod] + public void VerticalRadius_ShouldBe0_GivenEmptyEllipse() + { + Assert.AreEqual(0, Ellipse.Empty.VerticalRadius); + } + + [TestMethod] + public void VerticalRadius_ShouldBe1_GivenUnitEllipse() + { + Assert.AreEqual(1, Ellipse.Unit.VerticalRadius); + } +} diff --git a/X10D/src/Drawing/Ellipse.cs b/X10D/src/Drawing/Ellipse.cs new file mode 100644 index 0000000..950265a --- /dev/null +++ b/X10D/src/Drawing/Ellipse.cs @@ -0,0 +1,138 @@ +using System.Drawing; + +namespace X10D.Drawing; + +/// +/// Represents an ellipse that is composed of a 32-bit signed integer center point and radius. +/// +public readonly struct Ellipse : IEquatable +{ + /// + /// The empty ellipse. That is, an ellipse whose center point is (0, 0) and whose two radii are 0. + /// + public static readonly Ellipse Empty = new(); + + /// + /// The unit ellipse. That is, an ellipse whose center point is (0, 0) and whose two radii are 1. + /// + public static readonly Ellipse Unit = new(Point.Empty, 1, 1); + + /// + /// Initializes a new instance of the struct. + /// + /// The center point of the ellipse. + /// The horizontal radius of the ellipse. + /// The vertical radius of the ellipse. + public Ellipse(Point center, int horizontalRadius, int verticalRadius) + { + Center = center; + HorizontalRadius = horizontalRadius; + VerticalRadius = verticalRadius; + } + + public static implicit operator Ellipse(Circle circle) + { + return new Ellipse(circle.Center, circle.Radius, circle.Radius); + } + + /// + /// Gets the area of the ellipse. + /// + /// The area of the ellipse, calculated as πab. + public float Area + { + get => MathF.PI * HorizontalRadius * VerticalRadius; + } + + /// + /// Gets the center point of the ellipse. + /// + /// The center point. + public Point Center { get; } + + /// + /// Gets the approximate circumference of the ellipse. + /// + /// + /// The approximate circumference of the ellipse, calculated as + /// π(a+b)(3([(a-b)²]/(a+b)²(sqrt(-3(((a-b)²)/(a+b)²)+4+10))+1). + /// + public float ApproximateCircumference + { + get + { + float aMinusB = HorizontalRadius - VerticalRadius; + float aPlusB = HorizontalRadius + VerticalRadius; + + float aMinusB2 = aMinusB * aMinusB; + float aPlusB2 = aPlusB * aPlusB; + + return MathF.PI * (aPlusB * (3 * (aMinusB2 / (aPlusB2 * MathF.Sqrt(-3 * (aMinusB2 / aPlusB2) + 4 + 10))) + 1)); + } + } + + /// + /// Gets the horizontal radius of the ellipse. + /// + /// The horizontal radius. + public int HorizontalRadius { get; } + + /// + /// Gets the vertical radius of the ellipse. + /// + /// The vertical radius. + public int VerticalRadius { 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 ==(Ellipse left, Ellipse 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 !=(Ellipse left, Ellipse right) + { + return !left.Equals(right); + } + + /// + public override bool Equals(object? obj) + { + return obj is Ellipse ellipse && Equals(ellipse); + } + + /// + /// 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(Ellipse other) + { + return HorizontalRadius.Equals(other.HorizontalRadius) && VerticalRadius.Equals(other.VerticalRadius); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(HorizontalRadius, VerticalRadius); + } +} diff --git a/X10D/src/Drawing/EllipseF.cs b/X10D/src/Drawing/EllipseF.cs new file mode 100644 index 0000000..c07cd2d --- /dev/null +++ b/X10D/src/Drawing/EllipseF.cs @@ -0,0 +1,174 @@ +using System.Drawing; + +namespace X10D.Drawing; + +/// +/// Represents an ellipse that is composed of a single-precision floating-point center point and radius. +/// +public readonly struct EllipseF : IEquatable +{ + /// + /// The empty ellipse. That is, an ellipse whose center point is (0, 0) and whose two radii are 0. + /// + public static readonly EllipseF Empty = new(); + + /// + /// The unit ellipse. That is, an ellipse whose center point is (0, 0) and whose two radii are 1. + /// + public static readonly EllipseF Unit = new(PointF.Empty, 1.0f, 1.0f); + + /// + /// Initializes a new instance of the struct. + /// + /// The center point of the ellipse. + /// The horizontal radius of the ellipse. + /// The vertical radius of the ellipse. + public EllipseF(PointF center, float horizontalRadius, float verticalRadius) + { + Center = center; + HorizontalRadius = horizontalRadius; + VerticalRadius = verticalRadius; + } + + /// + /// Gets the area of the ellipse. + /// + /// The area of the ellipse, calculated as πab. + public float Area + { + get => MathF.PI * HorizontalRadius * VerticalRadius; + } + + /// + /// Gets the center point of the ellipse. + /// + /// The center point. + public PointF Center { get; } + + /// + /// Gets the approximate circumference of the ellipse. + /// + /// + /// The approximate circumference of the ellipse, calculated as + /// π(a+b)(3([(a-b)²]/(a+b)²(sqrt(-3(((a-b)²)/(a+b)²)+4+10))+1). + /// + public float ApproximateCircumference + { + get + { + float aMinusB = HorizontalRadius - VerticalRadius; + float aPlusB = HorizontalRadius + VerticalRadius; + + float aMinusB2 = aMinusB * aMinusB; + float aPlusB2 = aPlusB * aPlusB; + + return MathF.PI * (aPlusB * (3 * (aMinusB2 / (aPlusB2 * MathF.Sqrt(-3 * (aMinusB2 / aPlusB2) + 4 + 10))) + 1)); + } + } + + /// + /// Gets the horizontal radius of the ellipse. + /// + /// The horizontal radius. + public float HorizontalRadius { get; } + + /// + /// Gets the vertical radius of the ellipse. + /// + /// The vertical radius. + public float VerticalRadius { 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 ==(EllipseF left, EllipseF 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 !=(EllipseF left, EllipseF right) + { + return !left.Equals(right); + } + + /// + /// Implicitly converts a to an . + /// + /// The circle to convert. + /// The converted ellipse. + public static implicit operator EllipseF(Circle circle) + { + return new EllipseF(circle.Center, circle.Radius, circle.Radius); + } + + /// + /// Implicitly converts a to an . + /// + /// The circle to convert. + /// The converted ellipse. + public static implicit operator EllipseF(CircleF circle) + { + return new EllipseF(circle.Center, circle.Radius, circle.Radius); + } + + /// + /// Implicitly converts an to an . + /// + /// The ellipse to convert. + /// The converted ellipse. + public static implicit operator EllipseF(Ellipse ellipse) + { + return new EllipseF(ellipse.Center, ellipse.HorizontalRadius, ellipse.VerticalRadius); + } + + /// + /// Explicitly converts an to an . + /// + /// The ellipse to convert. + /// The converted ellipse. + public static explicit operator Ellipse(EllipseF ellipse) + { + PointF center = ellipse.Center; + return new Ellipse(new Point((int)center.X, (int)center.Y), (int)ellipse.HorizontalRadius, (int)ellipse.VerticalRadius); + } + + /// + public override bool Equals(object? obj) + { + return obj is EllipseF ellipse && Equals(ellipse); + } + + /// + /// 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(EllipseF other) + { + return HorizontalRadius.Equals(other.HorizontalRadius) && VerticalRadius.Equals(other.VerticalRadius); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(HorizontalRadius, VerticalRadius); + } +}