Add Line3D

This commit is contained in:
Oliver Booth 2022-06-01 16:47:03 +01:00
parent ac23d01554
commit ea56f2be48
No known key found for this signature in database
GPG Key ID: 32A00B35503AF634
7 changed files with 527 additions and 13 deletions

View File

@ -3,7 +3,7 @@
## 3.2.0 ## 3.2.0
### Added ### Added
- X10D: Added `MathUtility.InverseLerp(float, float, float)` and `MathUtility.InverseLerp(double, double, double)` - X10D: Added `MathUtility.InverseLerp(float, float, float)` and `MathUtility.InverseLerp(double, double, double)`
- X10D: Added `Circle`, `CircleF`, `Ellipse`, `EllipseF` `Line`, `LineF`, `Polygon`, and `PolygonF`, to complement System.Drawing structs such as `Point` and `Rectangle` - X10D: Added `Circle`, `CircleF`, `Ellipse`, `EllipseF` `Line`, `LineF`, `Line3D`, `Polygon`, and `PolygonF`, to complement System.Drawing structs such as `Point` and `Rectangle`
- X10D: Added `Point.ToSize()` - X10D: Added `Point.ToSize()`
- X10D: Added `Point.ToSizeF()` - X10D: Added `Point.ToSizeF()`
- X10D: Added `Point.ToVector2()` - X10D: Added `Point.ToVector2()`

View File

@ -0,0 +1,182 @@
using System.Drawing;
using System.Numerics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using X10D.Drawing;
namespace X10D.Tests.Drawing;
[TestClass]
public class Line3DTests
{
[TestMethod]
public void CompareTo_ShouldBeNegativeOne_GivenEmptyAndOne()
{
Assert.AreEqual(-1, Line3D.Empty.CompareTo(Line3D.One));
}
[TestMethod]
public void CompareTo_ShouldBeNegativeOne_GivenEmptyLineAndOneLineAsObject()
{
Assert.AreEqual(-1, Line3D.Empty.CompareTo((object)Line3D.One));
}
[TestMethod]
public void CompareTo_ShouldBeOne_GivenNull()
{
Assert.AreEqual(1, Line3D.One.CompareTo(null));
}
[TestMethod]
public void CompareTo_ShouldBeOne_GivenOneAndEmpty()
{
Assert.AreEqual(1, Line3D.One.CompareTo(Line3D.Empty));
}
[TestMethod]
public void CompareTo_ShouldBeZero_GivenUnitLine()
{
var unitLine3D = Line3D.One;
Assert.AreEqual(0, unitLine3D.CompareTo(unitLine3D));
}
[TestMethod]
public void CompareTo_ShouldThrowArgumentException_GivenInvalidType()
{
Assert.ThrowsException<ArgumentException>(() => Line3D.Empty.CompareTo(new object()));
}
[TestMethod]
public void Length_ShouldBe0_GivenEmptyLine()
{
Assert.AreEqual(0.0f, Line3D.Empty.Length);
}
[TestMethod]
public void Length_ShouldBe1_GivenUnitXLine()
{
Assert.AreEqual(1.0f, Line3D.UnitX.Length, 1e-6f);
}
[TestMethod]
public void Length_ShouldBe1_GivenUnitYLine()
{
Assert.AreEqual(1.0f, Line3D.UnitY.Length, 1e-6f);
}
[TestMethod]
public void Length_ShouldBe1_GivenUnitZLine()
{
Assert.AreEqual(1.0f, Line3D.UnitZ.Length, 1e-6f);
}
[TestMethod]
public void Equals_ShouldBeTrue_GivenTwoUnitLines()
{
Line3D first = Line3D.One;
Line3D second = Line3D.One;
Assert.AreEqual(first, second);
Assert.IsTrue(first == second);
Assert.IsFalse(first != second);
}
[TestMethod]
public void Equals_ShouldBeFalse_GivenDifferentLines()
{
Assert.AreNotEqual(Line3D.One, Line3D.Empty);
Assert.IsFalse(Line3D.One == Line3D.Empty);
Assert.IsTrue(Line3D.One != Line3D.Empty);
}
[TestMethod]
public void GetHashCode_ShouldBeCorrect_GivenEmptyLine()
{
// this test is pretty pointless, it exists only for code coverage purposes
int hashCode = Line3D.Empty.GetHashCode();
Assert.AreEqual(hashCode, Line3D.Empty.GetHashCode());
}
[TestMethod]
public void GetHashCode_ShouldBeCorrect_GivenUnitLine()
{
// this test is pretty pointless, it exists only for code coverage purposes
int hashCode = Line3D.One.GetHashCode();
Assert.AreEqual(hashCode, Line3D.One.GetHashCode());
}
[TestMethod]
public void op_Explicit_ShouldReturnEquivalentLine_GivenLine()
{
Line3D oneLine = new Line3D(Vector3.Zero, Vector3.UnitX + Vector3.UnitY);
Line converted = (Line)oneLine;
var expectedStart = new Point((int)oneLine.Start.X, (int)oneLine.Start.Y);
var expectedEnd = new Point((int)oneLine.End.X, (int)oneLine.End.Y);
Assert.AreEqual(oneLine.Length, converted.Length);
Assert.AreEqual(expectedStart, converted.Start);
Assert.AreEqual(expectedEnd, converted.End);
}
[TestMethod]
public void op_Explicit_ShouldReturnEquivalentLineF_GivenLine()
{
Line3D oneLine = new Line3D(Vector3.Zero, Vector3.UnitX + Vector3.UnitY);
LineF converted = (LineF)oneLine;
var expectedStart = new PointF(oneLine.Start.X, oneLine.Start.Y);
var expectedEnd = new PointF(oneLine.End.X, oneLine.End.Y);
Assert.AreEqual(oneLine.Length, converted.Length);
Assert.AreEqual(expectedStart, converted.Start);
Assert.AreEqual(expectedEnd, converted.End);
}
[TestMethod]
public void op_GreaterThan_True_GivenUnitAndEmptyCircle()
{
Assert.IsTrue(Line3D.One > Line3D.Empty);
Assert.IsTrue(Line3D.One >= Line3D.Empty);
Assert.IsFalse(Line3D.One < Line3D.Empty);
Assert.IsFalse(Line3D.One <= Line3D.Empty);
}
[TestMethod]
public void op_Implicit_ShouldReturnEquivalentLine_GivenLine()
{
Line oneLine = Line.One;
Line3D converted = oneLine;
var expectedStart = new Vector3(oneLine.Start.X, oneLine.Start.Y, 0.0f);
var expectedEnd = new Vector3(oneLine.End.X, oneLine.End.Y, 0.0f);
Assert.AreEqual(oneLine, converted);
Assert.AreEqual(oneLine.Length, converted.Length);
Assert.AreEqual(expectedStart, converted.Start);
Assert.AreEqual(expectedEnd, converted.End);
}
[TestMethod]
public void op_Implicit_ShouldReturnEquivalentLine_GivenLineF()
{
LineF oneLine = LineF.One;
Line3D converted = oneLine;
var expectedStart = new Vector3(oneLine.Start.X, oneLine.Start.Y, 0.0f);
var expectedEnd = new Vector3(oneLine.End.X, oneLine.End.Y, 0.0f);
Assert.AreEqual(oneLine, converted);
Assert.AreEqual(oneLine.Length, converted.Length);
Assert.AreEqual(expectedStart, converted.Start);
Assert.AreEqual(expectedEnd, converted.End);
}
[TestMethod]
public void op_LessThan_True_GivenEmptyAndUnitCircle()
{
Assert.IsTrue(Line3D.Empty < Line3D.One);
Assert.IsTrue(Line3D.Empty <= Line3D.One);
Assert.IsFalse(Line3D.Empty > Line3D.One);
Assert.IsFalse(Line3D.Empty >= Line3D.One);
}
}

View File

@ -64,11 +64,12 @@ public class LineFTests
[TestMethod] [TestMethod]
public void Equals_ShouldBeTrue_GivenTwoUnitLines() public void Equals_ShouldBeTrue_GivenTwoUnitLines()
{ {
var unitLineF1 = LineF.One; LineF first = LineF.One;
var unitLineF2 = LineF.One; LineF second = LineF.One;
Assert.AreEqual(unitLineF1, unitLineF2);
Assert.IsTrue(unitLineF1 == unitLineF2); Assert.AreEqual(first, second);
Assert.IsFalse(unitLineF1 != unitLineF2); Assert.IsTrue(first == second);
Assert.IsFalse(first != second);
} }
[TestMethod] [TestMethod]

View File

@ -46,11 +46,12 @@ public class LineTests
[TestMethod] [TestMethod]
public void Equals_ShouldBeTrue_GivenTwoUnitLines() public void Equals_ShouldBeTrue_GivenTwoUnitLines()
{ {
var unitLine1 = Line.One; Line first = Line.One;
var unitLine2 = Line.One; Line second = Line.One;
Assert.AreEqual(unitLine1, unitLine2);
Assert.IsTrue(unitLine1 == unitLine2); Assert.AreEqual(first, second);
Assert.IsFalse(unitLine1 != unitLine2); Assert.IsTrue(first == second);
Assert.IsFalse(first != second);
} }
[TestMethod] [TestMethod]

View File

@ -3,7 +3,7 @@
namespace X10D.Drawing; namespace X10D.Drawing;
/// <summary> /// <summary>
/// Represents a line that is composed of 32-bit signed integer X and Y coordinates. /// Represents a line in 2D space that is composed of 32-bit signed integer X and Y coordinates.
/// </summary> /// </summary>
public readonly struct Line : IEquatable<Line>, IComparable<Line>, IComparable public readonly struct Line : IEquatable<Line>, IComparable<Line>, IComparable
{ {

330
X10D/src/Drawing/Line3D.cs Normal file
View File

@ -0,0 +1,330 @@
using System.Drawing;
using System.Numerics;
namespace X10D.Drawing;
/// <summary>
/// Represents a line in 3D space that is composed of 32-bit signed integer X, Y and Z coordinates.
/// </summary>
public readonly struct Line3D : IEquatable<Line3D>, IComparable<Line3D>, IComparable
{
/// <summary>
/// The empty line. That is, a line whose start and end points are at (0, 0).
/// </summary>
public static readonly Line3D Empty = new();
/// <summary>
/// The line whose start point is at (0, 0, 0) and end point is at (1, 1, 1).
/// </summary>
public static readonly Line3D One = new(Vector3.Zero, Vector3.One);
/// <summary>
/// The line whose start point is at (0, 0, 0) and end point is at (1, 0, 0).
/// </summary>
public static readonly Line3D UnitX = new(Vector3.Zero, Vector3.UnitX);
/// <summary>
/// The line whose start point is at (0, 0, 0) and end point is at (0, 1, 0).
/// </summary>
public static readonly Line3D UnitY = new(Vector3.Zero, Vector3.UnitY);
/// <summary>
/// The line whose start point is at (0, 0, 0) and end point is at (0, 0, 1).
/// </summary>
public static readonly Line3D UnitZ = new(Vector3.Zero, Vector3.UnitZ);
/// <summary>
/// Initializes a new instance of the <see cref="Line3D" /> struct by taking the start and end points.
/// </summary>
/// <param name="start">The start point.</param>
/// <param name="end">The end point.</param>
public Line3D(Vector3 start, Vector3 end)
{
End = end;
Start = start;
}
/// <summary>
/// Gets the end point of the line.
/// </summary>
/// <value>The end point.</value>
public Vector3 End { get; }
/// <summary>
/// Gets the length of this line.
/// </summary>
/// <value>The length.</value>
public float Length
{
get => (End - Start).Length();
}
/// <summary>
/// Gets the length of this line, squared.
/// </summary>
/// <value>The squared length.</value>
public float LengthSquared
{
get => (End - Start).LengthSquared();
}
/// <summary>
/// Gets the start point of the line.
/// </summary>
/// <value>The start point.</value>
public Vector3 Start { get; }
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Line3D" /> 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 ==(Line3D left, Line3D right)
{
return left.Equals(right);
}
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Line3D" /> 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 !=(Line3D left, Line3D 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 <(Line3D left, Line3D 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 >(Line3D left, Line3D 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 <=(Line3D left, Line3D 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 >=(Line3D left, Line3D right)
{
return left.CompareTo(right) >= 0;
}
/// <summary>
/// Explicitly converts a <see cref="Line3D" /> to a <see cref="Line" />.
/// </summary>
/// <param name="line">The line to convert.</param>
/// <returns>The converted line.</returns>
public static explicit operator Line(Line3D line)
{
Vector3 start = line.Start;
Vector3 end = line.End;
return new Line(new Point((int)start.X, (int)start.Y), new Point((int)end.X, (int)end.Y));
}
/// <summary>
/// Explicitly converts a <see cref="Line3D" /> to a <see cref="LineF" />.
/// </summary>
/// <param name="line">The line to convert.</param>
/// <returns>The converted line.</returns>
public static explicit operator LineF(Line3D line)
{
Vector3 start = line.Start;
Vector3 end = line.End;
return new LineF(new PointF(start.X, start.Y), new PointF(end.X, 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 converted line.</returns>
public static implicit operator Line3D(Line line)
{
Point start = line.Start;
Point end = line.End;
return new Line3D(new Vector3(start.X, start.Y, 0), new Vector3(end.X, end.Y, 0));
}
/// <summary>
/// Implicitly converts a <see cref="LineF" /> to a <see cref="Line3D" />.
/// </summary>
/// <param name="line">The line to convert.</param>
/// <returns>The converted line.</returns>
public static implicit operator Line3D(LineF line)
{
PointF start = line.Start;
PointF end = line.End;
return new Line3D(new Vector3(start.X, start.Y, 0), new Vector3(end.X, end.Y, 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="Line3D" />.</exception>
/// </remarks>
public int CompareTo(object? obj)
{
if (ReferenceEquals(null, obj))
{
return 1;
}
if (obj is not Line3D other)
{
throw new ArgumentException($"Object must be of type {GetType()}");
}
return CompareTo(other);
}
/// <summary>
/// Compares this instance to another <see cref="Line3D" />.
/// </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(Line3D other)
{
return LengthSquared.CompareTo(other.LengthSquared);
}
/// <inheritdoc />
public override bool Equals(object? obj)
{
return obj is Line3D 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(Line3D other)
{
return End.Equals(other.End) && Start.Equals(other.Start);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(End, Start);
}
}

View File

@ -5,7 +5,7 @@ using X10D.Numerics;
namespace X10D.Drawing; namespace X10D.Drawing;
/// <summary> /// <summary>
/// Represents a line that is composed of single-precision floating-point X and Y coordinates. /// Represents a line in 2D space that is composed of single-precision floating-point X and Y coordinates.
/// </summary> /// </summary>
public readonly struct LineF : IEquatable<LineF>, IComparable<LineF>, IComparable public readonly struct LineF : IEquatable<LineF>, IComparable<LineF>, IComparable
{ {