Add Ellipse struct

This commit is contained in:
Oliver Booth 2022-06-01 15:36:18 +01:00
parent 2f74ef5f50
commit 5e835e10f1
No known key found for this signature in database
GPG Key ID: 32A00B35503AF634
4 changed files with 532 additions and 0 deletions

View File

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

View File

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

138
X10D/src/Drawing/Ellipse.cs Normal file
View File

@ -0,0 +1,138 @@
using System.Drawing;
namespace X10D.Drawing;
/// <summary>
/// Represents an ellipse that is composed of a 32-bit signed integer center point and radius.
/// </summary>
public readonly struct Ellipse : IEquatable<Ellipse>
{
/// <summary>
/// The empty ellipse. That is, an ellipse whose center point is (0, 0) and whose two radii are 0.
/// </summary>
public static readonly Ellipse Empty = new();
/// <summary>
/// The unit ellipse. That is, an ellipse whose center point is (0, 0) and whose two radii are 1.
/// </summary>
public static readonly Ellipse Unit = new(Point.Empty, 1, 1);
/// <summary>
/// Initializes a new instance of the <see cref="Ellipse" /> struct.
/// </summary>
/// <param name="center">The center point of the ellipse.</param>
/// <param name="horizontalRadius">The horizontal radius of the ellipse.</param>
/// <param name="verticalRadius">The vertical radius of the ellipse.</param>
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);
}
/// <summary>
/// Gets the area of the ellipse.
/// </summary>
/// <value>The area of the ellipse, calculated as <c>πab</c>.</value>
public float Area
{
get => MathF.PI * HorizontalRadius * VerticalRadius;
}
/// <summary>
/// Gets the center point of the ellipse.
/// </summary>
/// <value>The center point.</value>
public Point Center { get; }
/// <summary>
/// Gets the approximate circumference of the ellipse.
/// </summary>
/// <value>
/// The approximate circumference of the ellipse, calculated as
/// <c>π(a+b)(3([(a-b)²]/(a+b)²(sqrt(-3(((a-b)²)/(a+b)²)+4+10))+1)</c>.
/// </value>
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));
}
}
/// <summary>
/// Gets the horizontal radius of the ellipse.
/// </summary>
/// <value>The horizontal radius.</value>
public int HorizontalRadius { get; }
/// <summary>
/// Gets the vertical radius of the ellipse.
/// </summary>
/// <value>The vertical radius.</value>
public int VerticalRadius { get; }
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Ellipse" /> 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 ==(Ellipse left, Ellipse right)
{
return left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Ellipse" /> 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 !=(Ellipse left, Ellipse right)
{
return !left.Equals(right);
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is Ellipse ellipse && Equals(ellipse);
}
/// <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(Ellipse other)
{
return HorizontalRadius.Equals(other.HorizontalRadius) && VerticalRadius.Equals(other.VerticalRadius);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(HorizontalRadius, VerticalRadius);
}
}

View File

@ -0,0 +1,174 @@
using System.Drawing;
namespace X10D.Drawing;
/// <summary>
/// Represents an ellipse that is composed of a single-precision floating-point center point and radius.
/// </summary>
public readonly struct EllipseF : IEquatable<EllipseF>
{
/// <summary>
/// The empty ellipse. That is, an ellipse whose center point is (0, 0) and whose two radii are 0.
/// </summary>
public static readonly EllipseF Empty = new();
/// <summary>
/// The unit ellipse. That is, an ellipse whose center point is (0, 0) and whose two radii are 1.
/// </summary>
public static readonly EllipseF Unit = new(PointF.Empty, 1.0f, 1.0f);
/// <summary>
/// Initializes a new instance of the <see cref="EllipseF" /> struct.
/// </summary>
/// <param name="center">The center point of the ellipse.</param>
/// <param name="horizontalRadius">The horizontal radius of the ellipse.</param>
/// <param name="verticalRadius">The vertical radius of the ellipse.</param>
public EllipseF(PointF center, float horizontalRadius, float verticalRadius)
{
Center = center;
HorizontalRadius = horizontalRadius;
VerticalRadius = verticalRadius;
}
/// <summary>
/// Gets the area of the ellipse.
/// </summary>
/// <value>The area of the ellipse, calculated as <c>πab</c>.</value>
public float Area
{
get => MathF.PI * HorizontalRadius * VerticalRadius;
}
/// <summary>
/// Gets the center point of the ellipse.
/// </summary>
/// <value>The center point.</value>
public PointF Center { get; }
/// <summary>
/// Gets the approximate circumference of the ellipse.
/// </summary>
/// <value>
/// The approximate circumference of the ellipse, calculated as
/// <c>π(a+b)(3([(a-b)²]/(a+b)²(sqrt(-3(((a-b)²)/(a+b)²)+4+10))+1)</c>.
/// </value>
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));
}
}
/// <summary>
/// Gets the horizontal radius of the ellipse.
/// </summary>
/// <value>The horizontal radius.</value>
public float HorizontalRadius { get; }
/// <summary>
/// Gets the vertical radius of the ellipse.
/// </summary>
/// <value>The vertical radius.</value>
public float VerticalRadius { get; }
/// <summary>
/// Returns a value indicating whether two instances of <see cref="EllipseF" /> 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 ==(EllipseF left, EllipseF right)
{
return left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="EllipseF" /> 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 !=(EllipseF left, EllipseF right)
{
return !left.Equals(right);
}
/// <summary>
/// Implicitly converts a <see cref="Circle" /> to an <see cref="EllipseF" />.
/// </summary>
/// <param name="circle">The circle to convert.</param>
/// <returns>The converted ellipse.</returns>
public static implicit operator EllipseF(Circle circle)
{
return new EllipseF(circle.Center, circle.Radius, circle.Radius);
}
/// <summary>
/// Implicitly converts a <see cref="CircleF" /> to an <see cref="EllipseF" />.
/// </summary>
/// <param name="circle">The circle to convert.</param>
/// <returns>The converted ellipse.</returns>
public static implicit operator EllipseF(CircleF circle)
{
return new EllipseF(circle.Center, circle.Radius, circle.Radius);
}
/// <summary>
/// Implicitly converts an <see cref="Ellipse" /> to an <see cref="EllipseF" />.
/// </summary>
/// <param name="ellipse">The ellipse to convert.</param>
/// <returns>The converted ellipse.</returns>
public static implicit operator EllipseF(Ellipse ellipse)
{
return new EllipseF(ellipse.Center, ellipse.HorizontalRadius, ellipse.VerticalRadius);
}
/// <summary>
/// Explicitly converts an <see cref="EllipseF" /> to an <see cref="Ellipse" />.
/// </summary>
/// <param name="ellipse">The ellipse to convert.</param>
/// <returns>The converted ellipse.</returns>
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);
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is EllipseF ellipse && Equals(ellipse);
}
/// <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(EllipseF other)
{
return HorizontalRadius.Equals(other.HorizontalRadius) && VerticalRadius.Equals(other.VerticalRadius);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(HorizontalRadius, VerticalRadius);
}
}