From 3449b9757e0e0ca0677e847e92d1085bfd5c323b Mon Sep 17 00:00:00 2001 From: Mirco Bianchini Date: Fri, 20 Aug 2021 15:49:11 +1000 Subject: [PATCH] Added pointAtLength and tangentAtLength for arc. --- src/GShark.Test.XUnit/Geometry/ArcTests.cs | 44 +++++++- src/GShark/Geometry/Arc.cs | 125 +++++++++++++++------ 2 files changed, 132 insertions(+), 37 deletions(-) diff --git a/src/GShark.Test.XUnit/Geometry/ArcTests.cs b/src/GShark.Test.XUnit/Geometry/ArcTests.cs index 6267f995..fa76200c 100644 --- a/src/GShark.Test.XUnit/Geometry/ArcTests.cs +++ b/src/GShark.Test.XUnit/Geometry/ArcTests.cs @@ -94,8 +94,9 @@ public void It_Returns_The_BoundingBox_Of_The_Arc() } [Theory] - [InlineData(1.2, new double[] { 70.334926, 18.808863, 2.762032 })] - [InlineData(2.5, new double[] { 87.505564, 8.962333, 5.203339 })] + [InlineData(0.15, new double[] { 69.334566, 27.421862, 0.510428 })] + [InlineData(0.5, new double[] { 82.012463, 9.246222, 5.174356 })] + [InlineData(0.72, new double[] { 96.621782, 12.93396, 4.08574 })] public void It_Returns_A_Point_On_The_Arc_At_The_Given_Parameter(double t, double[] pts) { // Arrange @@ -109,6 +110,40 @@ public void It_Returns_A_Point_On_The_Arc_At_The_Given_Parameter(double t, doubl pt.EpsilonEquals(expectedPt, 1e-6).Should().BeTrue(); } + [Theory] + [InlineData(10, new double[] { 69.463068, 28.087104, 0.334816 })] + [InlineData(17.5, new double[] { 69.624396, 20.896489, 2.220164 })] + [InlineData(22.5, new double[] { 71.568605, 16.456984, 3.368902 })] + public void It_Returns_A_Point_On_The_Arc_At_The_Given_Length(double length, double[] pts) + { + // Arrange + var expectedPt = new Point3(pts[0], pts[1], pts[2]); + Arc arc = _exampleArc3D; + + // Act + var pt = arc.PointAtLength(length); + + // Assert + pt.EpsilonEquals(expectedPt, 1e-6).Should().BeTrue(); + } + + [Theory] + [InlineData(10, new double[] { -0.20443, -0.946415, 0.250015 })] + [InlineData(17.5, new double[] { 0.246705, -0.937861, 0.244034 })] + [InlineData(22.5, new double[] { 0.525002, -0.824293, 0.21193 })] + public void It_Returns_A_Tangent_On_The_Arc_At_The_Given_Length(double length, double[] tangent) + { + // Arrange + var expectedTan = new Vector3(tangent[0], tangent[1], tangent[2]); + Arc arc = _exampleArc3D; + + // Act + var tan = arc.TangentAtLength(length); + + // Assert + tan.EpsilonEquals(expectedTan, 1e-6).Should().BeTrue(); + } + [Theory] [InlineData(new double[] { 82.248292, 15.836914, 3.443127 }, new double[] { 80.001066, 9.815219, 5.041724 })] [InlineData(new double[] { 85.591741, 24.79606, 1.064717 }, new double[] { 74.264416, 36.39316, -1.884313 })] @@ -144,8 +179,9 @@ public void It_Returns_A_Transformed_Arc() } [Theory] - [InlineData(0.0, new double[] { -0.726183, -0.663492, 0.180104 })] - [InlineData(1.2, new double[] { 0.377597, -0.896416, 0.232075 })] + [InlineData(0.15, new double[] { -0.162674, -0.954043, 0.251671 })] + [InlineData(0.5, new double[] { 0.976088, -0.212106, 0.047567 })] + [InlineData(0.72, new double[] { 0.742847, 0.646006, -0.175654 })] public void It_Returns_The_Tangent_At_The_Give_Parameter_T(double t, double[] pts) { // Arrange diff --git a/src/GShark/Geometry/Arc.cs b/src/GShark/Geometry/Arc.cs index 9c9ca909..704889d3 100644 --- a/src/GShark/Geometry/Arc.cs +++ b/src/GShark/Geometry/Arc.cs @@ -3,6 +3,7 @@ using GShark.Operation; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; namespace GShark.Geometry @@ -224,30 +225,81 @@ public static Arc ByStartEndDirection(Point3 ptStart, Point3 ptEnd, Vector3 dir) } /// - /// Returns the point at the parameter t on the arc. + /// Evaluates the point at the parameter t on the arc. /// /// A parameter between 0.0 to 1.0 or between the angle domain.> /// Point on the arc. public Point3 PointAt(double t) { - Vector3 xDir = Plane.XAxis * Math.Cos(t) * Radius; - Vector3 yDir = Plane.YAxis * Math.Sin(t) * Radius; + if (t > 1.0 || t < 0.0) + { + throw new ArgumentOutOfRangeException($"The value provided for parameter t, {t}, must be between 0.0 and 1.0."); + } + + double theta = Domain.T0 + (Domain.T1 - Domain.T0) * t; + Vector3 xDir = Plane.XAxis * Math.Cos(theta) * Radius; + Vector3 yDir = Plane.YAxis * Math.Sin(theta) * Radius; return Plane.Origin + xDir + yDir; } /// - /// Returns the tangent at the parameter t on the arc. + /// Evaluates the point at the specific length. /// - /// A parameter between 0.0 to 1.0 or between the angle domain. + /// The length where to evaluate the point. + /// The point at the length. + public Point3 PointAtLength(double length) + { + if (length < 0) + { + throw new Exception("Length factor cannot be less than zero."); + } + + if (length > Length) + { + throw new Exception("Length factor cannot be larger than the length of the curve."); + } + + double angleLength = GSharkMath.ToRadians((length * 360) / (Math.PI * 2 * Radius)); + + Vector3 xDir = Plane.XAxis * Math.Cos(angleLength) * Radius; + Vector3 yDir = Plane.YAxis * Math.Sin(angleLength) * Radius; + + return Plane.Origin + xDir + yDir; + } + + /// + /// Evaluates the tangent at the specific length. + /// + /// The length where to evaluate the tangent. + /// The unitize tangent at the length. + public Vector3 TangentAtLength(double length) + { + Point3 pt = PointAtLength(length); + (double u, double v) = Plane.ClosestParameters(pt); + double t = EvaluateParameter(u, v, true); + return TangentAt(t); + } + + /// + /// Calculates the tangent at the parameter t on the arc. + /// + /// A parameter between 0.0 to 1.0. /// Tangent vector at the t parameter. public Vector3 TangentAt(double t) { + if (t > 1.0 || t < 0.0) + { + throw new ArgumentOutOfRangeException($"The value provided, {t}, must be between 0.0 and 1.0."); + } + + double theta = Domain.T0 + (Domain.T1 - Domain.T0) * t; + double r1 = Radius; double r2 = Radius; - r1 *= -Math.Sin(t); - r2 *= Math.Cos(t); + r1 *= -Math.Sin(theta); + r2 *= Math.Cos(theta); Vector3 vector = Plane.XAxis * r1 + Plane.YAxis * r2; @@ -261,39 +313,15 @@ public Vector3 TangentAt(double t) /// The point on the arc that is close to the test point. public Point3 ClosestPoint(Point3 pt) { - double twoPi = 2.0 * Math.PI; - (double u, double v) = Plane.ClosestParameters(pt); if (Math.Abs(u) < GSharkMath.MinTolerance && Math.Abs(v) < GSharkMath.MinTolerance) { return PointAt(0.0); } - double t = Math.Atan2(v, u); - if (t < 0.0) - { - t += twoPi; - } - - t -= Domain.T0; - - while (t < 0.0) - { - t += twoPi; - } + double t = EvaluateParameter(u, v, true); - while (t >= twoPi) - { - t -= twoPi; - } - - double t1 = Domain.Length; - if (t > t1) - { - t = t > 0.5 * t1 + Math.PI ? 0.0 : t1; - } - - return PointAt(Domain.T0 + t); + return PointAt(t); } /// @@ -478,5 +506,36 @@ private static double AngularDiff(double theta1, double theta2) return dif; } + + private double EvaluateParameter(double u, double v, bool parametrize = false) + { + double twoPi = 2.0 * Math.PI; + + double t = Math.Atan2(v, u); + if (t < 0.0) + { + t += twoPi; + } + + t -= Domain.T0; + + while (t < 0.0) + { + t += twoPi; + } + + while (t >= twoPi) + { + t -= twoPi; + } + + double t1 = Domain.Length; + if (t > t1) + { + t = t > 0.5 * t1 + Math.PI ? 0.0 : t1; + } + + return (parametrize) ? (t - Domain.T0) / (Domain.T1 - Domain.T0) : t; + } } }