Skip to content

Commit

Permalink
Refactor Polar and Spherical coordinates
Browse files Browse the repository at this point in the history
  • Loading branch information
aalmada committed Oct 18, 2023
1 parent 599277a commit 2e12984
Show file tree
Hide file tree
Showing 13 changed files with 761 additions and 42 deletions.
1 change: 1 addition & 0 deletions src/NetFabric.Numerics.Angle/Utils.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

namespace NetFabric.Numerics
Expand Down
30 changes: 30 additions & 0 deletions src/NetFabric.Numerics.UnitTests/Cartesian2/PointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,34 @@ public void CoordinateSystem_Should_Succeed()
result.Coordinates[0].Should().Be(new Coordinate("X", typeof(double)));
result.Coordinates[1].Should().Be(new Coordinate("Y", typeof(double)));
}


static readonly double sqr2 = double.Sqrt(2.0);

public static TheoryData<Point<double>, Polar.Point<Degrees, double, double>> ToPolarData => new()
{
{ new(0.0, 0.0), new(0.0, new(0.0)) },
{ new(1.0, 0.0), new(1.0, new(0.0)) },
{ new(1.0, 1.0), new(sqr2, new(45.0)) },
{ new(0.0, 1.0), new(1.0, new(90.0)) },
{ new(-1.0, 1.0), new(sqr2, new(135.0)) },
{ new(-1.0, 0.0), new(1.0, new(180.0)) },
{ new(-1.0, -1.0), new(sqr2, new(225.0)) },
{ new(0.0, -1.0), new(1.0, new(270.0)) },
{ new(1.0, -1.0), new(sqr2, new(315.0)) },
};

[Theory]
[MemberData(nameof(ToPolarData))]
public void ToPolar_Should_Succeed(Point<double> point, Polar.Point<Degrees, double, double> expected)
{
// arrange

// act
var result = Polar.Point.Reduce(Polar.Point.ToDegrees(Point.ToPolar(point)));

// assert
result.Azimuth.Value.Should().BeApproximately(expected.Azimuth.Value, 0.0001);
result.Radius.Should().BeApproximately(expected.Radius, 0.0001);
}
}
65 changes: 65 additions & 0 deletions src/NetFabric.Numerics.UnitTests/Polar/PointTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
namespace NetFabric.Numerics.Polar.UnitTests;

public class PointTests
{
[Fact]
public void CoordinateSystem_Should_Succeed()
{
// arrange

// act
var result = Point<Degrees, double, double>.Zero.CoordinateSystem;

// assert
result.Coordinates[0].Should().Be(new Coordinate("Radius", typeof(double)));
result.Coordinates[1].Should().Be(new Coordinate("Azimuth", typeof(Angle<Degrees, double>)));
}

const double radius = 2.0;
static readonly double radiusCos45 = radius * Angle.Cos(Angle.ToRadians(new Angle<Degrees, double>(45.0)));

public static TheoryData<Point<Degrees, double, double>, Cartesian2.Point<double>> ToCartesianData => new()
{
{ new(0.0, new(0.0)), new(0.0, 0.0) },

{ new(radius, new(0.0)), new(radius, 0.0) },
{ new(radius, new(45.0)), new(radiusCos45, radiusCos45) },
{ new(radius, new(90.0)), new(0.0, radius) },
{ new(radius, new(135.0)), new(-radiusCos45, radiusCos45) },
{ new(radius, new(180.0)), new(-radius, 0.0) },
{ new(radius, new(225.0)), new(-radiusCos45, -radiusCos45) },
{ new(radius, new(270.0)), new(0.0, -radius) },
{ new(radius, new(315.0)), new(radiusCos45, -radiusCos45) },
{ new(radius, new(360.0)), new(radius, 0.0) },
};

[Theory]
[MemberData(nameof(ToCartesianData))]
public void ToCartesian_Should_Succeed(Point<Degrees, double, double> point, Cartesian2.Point<double> expected)
{
// arrange

// act
var result = Point.ToCartesian(Point.ToRadians(point));

// assert
result.Should().BeOfType<Cartesian2.Point<double>>();
result.X.Should().BeApproximately(expected.X, 0.0001);
result.Y.Should().BeApproximately(expected.Y, 0.0001);
}

[Theory]
[MemberData(nameof(ToCartesianData))]
public void ToCartesian_With_Conversion_Should_Succeed(Point<Degrees, double, double> point, Cartesian2.Point<double> expected)
{
// arrange

// act
var result = Point.ToCartesian<double, double, float>(Point.ToRadians(point));

// assert
result.Should().BeOfType<Cartesian2.Point<float>>();
((double)result.X).Should().BeApproximately(expected.X, 0.0001);
((double)result.Y).Should().BeApproximately(expected.Y, 0.0001);
}
}
62 changes: 62 additions & 0 deletions src/NetFabric.Numerics.UnitTests/Spherical/PointTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers;

namespace NetFabric.Numerics.Spherical.UnitTests;

public class PointTests
{
[Fact]
public void CoordinateSystem_Should_Succeed()
{
// arrange

// act
var result = Point<Degrees, double, double>.Zero.CoordinateSystem;

// assert
result.Coordinates[0].Should().Be(new Coordinate("Radius", typeof(double)));
result.Coordinates[1].Should().Be(new Coordinate("Azimuth", typeof(Angle<Degrees, double>)));
result.Coordinates[2].Should().Be(new Coordinate("Zenith", typeof(Angle<Degrees, double>)));
}


public static TheoryData<Point<Degrees, double, double>, Cartesian3.Point<double>> ToCartesianData => new()
{
{ new(0.0, new(0.0), new(0.0)), new(0.0, 0.0, 0.0) },

{ new(2.0, new(0.0), new(0.0)), new(0.0, 0.0, 2.0) },
{ new(2.0, new(45.0), new(30.0)), new(0.7071067811865475, 0.7071067811865475, 1.7320508075688774) },
{ new(3.5, new(90.0), new(60.0)), new(1.856006667768587E-16, 3.031088913245535, 1.7500000000000004) },
};

[Theory]
[MemberData(nameof(ToCartesianData))]
public void ToCartesian_Should_Succeed(Point<Degrees, double, double> point, Cartesian3.Point<double> expected)
{
// arrange

// act
var result = Point.ToCartesian(Point.ToRadians(point));

// assert
result.Should().BeOfType<Cartesian3.Point<double>>();
result.X.Should().BeApproximately(expected.X, 0.0001);
result.Y.Should().BeApproximately(expected.Y, 0.0001);
result.Z.Should().BeApproximately(expected.Z, 0.0001);
}

[Theory]
[MemberData(nameof(ToCartesianData))]
public void ToCartesian_With_Conversion_Should_Succeed(Point<Degrees, double, double> point, Cartesian3.Point<double> expected)
{
// arrange

// act
var result = Point.ToCartesian<double, double, float>(Point.ToRadians(point));

// assert
result.Should().BeOfType<Cartesian3.Point<float>>();
((double)result.X).Should().BeApproximately(expected.X, 0.0001);
((double)result.Y).Should().BeApproximately(expected.Y, 0.0001);
((double)result.Z).Should().BeApproximately(expected.Z, 0.0001);
}
}
4 changes: 2 additions & 2 deletions src/NetFabric.Numerics/Cartesian2/Point.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,8 @@ public static Polar.Point<Radians, T, T> ToPolar<T>(in Point<T> point)
where T : struct, IFloatingPointIeee754<T>, IMinMaxValue<T>
{
var azimuth = Angle.Atan2(point.Y, point.X);
var radius = T.Sqrt(Utils.Pow2(point.X) + Utils.Pow2(point.Y));
var radius = Utils.Magnitude(point.X, point.Y);

return new(azimuth, radius);
return new(radius, azimuth);
}
}
4 changes: 2 additions & 2 deletions src/NetFabric.Numerics/Cartesian3/Point.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,9 @@ public static Spherical.Point<Radians, T, T> ToSpherical<T>(Point<T> point)
where T : struct, IFloatingPointIeee754<T>, IMinMaxValue<T>
{
var azimuth = Angle.Atan2(point.Y, point.X);
var radius = T.Sqrt(Utils.Pow2(point.X) + Utils.Pow2(point.Y) + Utils.Pow2(point.Z));
var radius = Utils.Magnitude(point.X, point.Y, point.Z);
var zenith = Angle.Acos(point.Z / radius);

return new(azimuth, zenith, radius);
return new(radius, azimuth, zenith);
}
}
2 changes: 1 addition & 1 deletion src/NetFabric.Numerics/Polar/CoordinateSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public readonly record struct CoordinateSystem<TAngleUnits, TAngle, TRadius>
{
static readonly IReadOnlyList<Coordinate> coordinates
= new[] {
new Coordinate("Azimuth", typeof(Angle<TAngleUnits, TAngle>)),
new Coordinate("Radius", typeof(TRadius)),
new Coordinate("Azimuth", typeof(Angle<TAngleUnits, TAngle>)),
};

public IReadOnlyList<Coordinate> Coordinates
Expand Down
105 changes: 89 additions & 16 deletions src/NetFabric.Numerics/Polar/Point.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,33 @@ namespace NetFabric.Numerics.Polar;
/// <typeparam name="TRadius">The type of the radius coordinate.</typeparam>
/// <param name="Azimuth">The azimuth coordinate.</param>
/// <param name="Radius">The radius coordinate.</param>
[System.Diagnostics.DebuggerDisplay("Azimuth = {Azimuth}, Radius = {Radius}")]
[System.Diagnostics.DebuggerDisplay("Radius = {Radius}, Azimuth = {Azimuth}")]
[SkipLocalsInit]
public readonly record struct Point<TAngleUnits, TAngle, TRadius>(Angle<TAngleUnits, TAngle> Azimuth, TRadius Radius)
public readonly record struct Point<TAngleUnits, TAngle, TRadius>(TRadius Radius, Angle<TAngleUnits, TAngle> Azimuth)
: IPoint<Point<TAngleUnits, TAngle, TRadius>>
where TAngleUnits : struct, IAngleUnits<TAngleUnits>
where TAngle : struct, IFloatingPoint<TAngle>, IMinMaxValue<TAngle>
where TRadius : struct, IFloatingPoint<TRadius>, IMinMaxValue<TRadius>
{
#region constants

public static readonly Point<TAngleUnits, TAngle, TRadius> Zero = new(Angle<TAngleUnits, TAngle>.Zero, TRadius.Zero);
/// <summary>
/// Represents the zero value. This field is read-only.
/// </summary>
public static readonly PointReduced<TAngleUnits, TAngle, TRadius> Zero = new(TRadius.Zero, Angle<TAngleUnits, TAngle>.Zero);

static Point<TAngleUnits, TAngle, TRadius> INumericBase<Point<TAngleUnits, TAngle, TRadius>>.Zero
=> Zero;

/// <summary>
/// Represents the minimum value. This field is read-only.
/// </summary>
public static readonly Point<TAngleUnits, TAngle, TRadius> MinValue = new(Angle<TAngleUnits, TAngle>.MinValue, TRadius.MinValue);
public static readonly Point<TAngleUnits, TAngle, TRadius> MinValue = new(TRadius.MinValue, Angle<TAngleUnits, TAngle>.MinValue);

/// <summary>
/// Represents the maximum value. This field is read-only.
/// </summary>
public static readonly Point<TAngleUnits, TAngle, TRadius> MaxValue = new(Angle<TAngleUnits, TAngle>.MaxValue, TRadius.MaxValue);
public static readonly Point<TAngleUnits, TAngle, TRadius> MaxValue = new(TRadius.MaxValue, Angle<TAngleUnits, TAngle>.MaxValue);

static Point<TAngleUnits, TAngle, TRadius> IMinMaxValue<Point<TAngleUnits, TAngle, TRadius>>.MinValue
=> MinValue;
Expand Down Expand Up @@ -62,9 +65,9 @@ public static Point<TAngleUnits, TAngle, TRadius> CreateChecked<TAngleOther, TRa
where TAngleOther : struct, IFloatingPoint<TAngleOther>, IMinMaxValue<TAngleOther>
where TRadiusOther : struct, IFloatingPoint<TRadiusOther>, IMinMaxValue<TRadiusOther>
=> new(
Angle<TAngleUnits, TAngle>.CreateChecked(point.Azimuth),
TRadius.CreateChecked(point.Radius)
);
,
Angle<TAngleUnits, TAngle>.CreateChecked(point.Azimuth));

/// <summary>
/// Creates an instance of the current type from a value,
Expand All @@ -80,9 +83,9 @@ public static Point<TAngleUnits, TAngle, TRadius> CreateSaturating<TAngleOther,
where TAngleOther : struct, IFloatingPoint<TAngleOther>, IMinMaxValue<TAngleOther>
where TRadiusOther : struct, IFloatingPoint<TRadiusOther>, IMinMaxValue<TRadiusOther>
=> new(
Angle<TAngleUnits, TAngle>.CreateSaturating(point.Azimuth),
TRadius.CreateSaturating(point.Radius)
);
,
Angle<TAngleUnits, TAngle>.CreateSaturating(point.Azimuth));

/// <summary>
/// Creates an instance of the current type from a value,
Expand All @@ -98,31 +101,101 @@ public static Point<TAngleUnits, TAngle, TRadius> CreateTruncating<TAngleOther,
where TAngleOther : struct, IFloatingPoint<TAngleOther>, IMinMaxValue<TAngleOther>
where TRadiusOther : struct, IFloatingPoint<TRadiusOther>, IMinMaxValue<TRadiusOther>
=> new(
Angle<TAngleUnits, TAngle>.CreateTruncating(point.Azimuth),
TRadius.CreateTruncating(point.Radius)
);
,
Angle<TAngleUnits, TAngle>.CreateTruncating(point.Azimuth));

object IPoint<Point<TAngleUnits, TAngle, TRadius>>.this[int index]
=> index switch
{
0 => Azimuth,
1 => Radius,
0 => Radius,
1 => Azimuth,
_ => Throw.ArgumentOutOfRangeException<object>(nameof(index), index, "index out of range")
};
}

/// <summary>
/// Provides static methods for point operations.
/// </summary>
public static class Point
public static partial class Point
{
public static Point<Degrees, T, T> ToDegrees<T>(Point<Degrees, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> point;

public static Point<Radians, T, T> ToRadians<T>(Point<Degrees, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToRadians(point.Azimuth));

public static Point<Gradians, T, T> ToGradians<T>(Point<Degrees, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToGradians(point.Azimuth));

public static Point<Revolutions, T, T> ToRevolutions<T>(Point<Degrees, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToRevolutions(point.Azimuth));

public static Point<Degrees, T, T> ToDegrees<T>(Point<Radians, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToDegrees(point.Azimuth));

public static Point<Radians, T, T> ToRadians<T>(Point<Radians, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> point;

public static Point<Gradians, T, T> ToGradians<T>(Point<Radians, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToGradians(point.Azimuth));

public static Point<Revolutions, T, T> ToRevolutions<T>(Point<Radians, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToRevolutions(point.Azimuth));

public static Point<Degrees, T, T> ToDegrees<T>(Point<Gradians, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToDegrees(point.Azimuth));

public static Point<Radians, T, T> ToRadians<T>(Point<Gradians, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToRadians(point.Azimuth));

public static Point<Gradians, T, T> ToGradians<T>(Point<Gradians, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> point;

public static Point<Revolutions, T, T> ToRevolutions<T>(Point<Gradians, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToRevolutions(point.Azimuth));

public static Point<Degrees, T, T> ToDegrees<T>(Point<Revolutions, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToDegrees(point.Azimuth));

public static Point<Radians, T, T> ToRadians<T>(Point<Revolutions, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToRadians(point.Azimuth));

public static Point<Gradians, T, T> ToGradians<T>(Point<Revolutions, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> new(point.Radius, Angle.ToGradians(point.Azimuth));

public static Point<Revolutions, T, T> ToRevolutions<T>(Point<Revolutions, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>
=> point;

public static PointReduced<TAngleUnits, TAngle, TRadius> Reduce<TAngleUnits, TAngle, TRadius>(Point<TAngleUnits, TAngle, TRadius> point)
where TAngleUnits : struct, IAngleUnits<TAngleUnits>
where TAngle : struct, IFloatingPoint<TAngle>, IMinMaxValue<TAngle>
where TRadius : struct, IFloatingPoint<TRadius>, IMinMaxValue<TRadius>
=> new(point.Radius, Angle.Reduce(point.Azimuth));

/// <summary>
/// Converts a point in polar coordinates to cartesian 2D coordinates.
/// </summary>
/// <typeparam name="T">The type of the coordinates of the point.</typeparam>
/// <param name="point">The point in polar coordinates to convert.</param>
/// <returns>The cartesian 2D coordinates representing the point.</returns>
public static Cartesian2.Point<T> ConvertToCartesian<T>(Point<Radians, T, T> point)
public static Cartesian2.Point<T> ToCartesian<T>(Point<Radians, T, T> point)
where T : struct, IFloatingPoint<T>, IMinMaxValue<T>, ITrigonometricFunctions<T>
{
var x = point.Radius * Angle.Cos(point.Azimuth);
Expand All @@ -139,7 +212,7 @@ public static Cartesian2.Point<T> ConvertToCartesian<T>(Point<Radians, T, T> poi
/// <typeparam name="T">The type used by the resulting cartesian point coordinates.</typeparam>
/// <param name="point">The point in polar coordinates to convert.</param>
/// <returns>The cartesian 2D coordinates representing the point.</returns>
public static Cartesian2.Point<T> ConvertToCartesian<TAngle, TRadius, T>(Point<Radians, TAngle, TRadius> point)
public static Cartesian2.Point<T> ToCartesian<TAngle, TRadius, T>(Point<Radians, TAngle, TRadius> point)
where TAngle : struct, IFloatingPoint<TAngle>, IMinMaxValue<TAngle>, ITrigonometricFunctions<TAngle>
where TRadius : struct, IFloatingPoint<TRadius>, IMinMaxValue<TRadius>
where T : struct, INumber<T>, IMinMaxValue<T>
Expand Down
Loading

0 comments on commit 2e12984

Please sign in to comment.