diff --git a/src/GShark.Test.XUnit/DebugFiles/GHDebug_Curves.gh b/src/GShark.Test.XUnit/DebugFiles/GHDebug_Curves.gh index c82f15d8..98fe4fa6 100644 Binary files a/src/GShark.Test.XUnit/DebugFiles/GHDebug_Curves.gh and b/src/GShark.Test.XUnit/DebugFiles/GHDebug_Curves.gh differ diff --git a/src/GShark.Test.XUnit/Operation/DivideTests.cs b/src/GShark.Test.XUnit/Operation/DivideTests.cs index 62e69655..223c32ef 100644 --- a/src/GShark.Test.XUnit/Operation/DivideTests.cs +++ b/src/GShark.Test.XUnit/Operation/DivideTests.cs @@ -3,7 +3,6 @@ using GShark.ExtendedMethods; using GShark.Geometry; using GShark.Geometry.Interfaces; -using GShark.Operation; using GShark.Test.XUnit.Data; using System.Collections.Generic; using System.Linq; @@ -22,9 +21,11 @@ public DivideTests(ITestOutputHelper testOutput) } [Theory] + [InlineData(0.0)] [InlineData(0.25)] [InlineData(0.5)] [InlineData(0.75)] + [InlineData(1.0)] public void It_Returns_Two_Curves_Splitting_One_Curve(double parameter) { // Arrange @@ -39,7 +40,7 @@ public void It_Returns_Two_Curves_Splitting_One_Curve(double parameter) NurbsCurve curve = new NurbsCurve(pts, degree); // Act - List curves = Divide.SplitCurve(curve, parameter); + List curves = curve.SplitAt(parameter); // Assert curves.Should().HaveCount(2); @@ -57,6 +58,63 @@ public void It_Returns_Two_Curves_Splitting_One_Curve(double parameter) } } + [Fact] + public void It_Returns_A_SubCurve_Defined_By_Domain() + { + // Arrange + Interval domain = new Interval(0.65, 0.85); + int degree = 3; + List controlPts = new List + { + new Point3(2,2,0), + new Point3(4,12,0), + new Point3(7,12,0), + new Point3(15,2,5) + }; + var expectedKnotVector = new KnotVector(new List {0.65, 0.65, 0.65, 0.65, 0.85, 0.85, 0.85, 0.85}); + NurbsCurve curve = new NurbsCurve(controlPts, degree); + NurbsCurve expectedSubCurve = new NurbsCurve( + new List + { + new (8.266,8.825,1.373125), + new (9.264,8.225,1.795625), + new (10.406,7.225,2.348125), + new (11.724,5.825,3.070625) + }, + degree:3); + + // Act + ICurve subCurve = curve.SubCurve(domain); + + // Assert + subCurve.LocationPoints.SequenceEqual(expectedSubCurve.LocationPoints).Should().BeTrue(); + subCurve.ControlPoints.SequenceEqual(expectedSubCurve.ControlPoints).Should().BeTrue(); + subCurve.Knots.SequenceEqual(expectedKnotVector).Should().BeTrue(); + } + + [Fact] + public void It_Splits_A_Curve_Into_Segments_At_Given_Parameters() + { + // Arrange + var parameters = new[]{0.25, 0.5, 0.75}; + int degree = 3; + List controlPts = new List + { + new Point3(2,2,0), + new Point3(4,12,0), + new Point3(7,12,0), + new Point3(15,2,5) + }; + KnotVector knots = new KnotVector(degree, controlPts.Count); + NurbsCurve curve = new NurbsCurve(controlPts, degree); + + // Act + var segments = curve.SplitAt(parameters.ToArray()); + + // Assert + segments.Count.Should().Be(4); + } + [Fact] public void Divide_By_Number_Of_Segments_Returns_Points_And_Parameters_Along_Curve() { @@ -64,27 +122,27 @@ public void Divide_By_Number_Of_Segments_Returns_Points_And_Parameters_Along_Cur NurbsCurve curve = NurbsCurveCollection.NurbsCurvePlanarExample(); double[] tValuesExpected = { 0, - 0.12294081350167592, - 0.26515588164329529, - 0.4202931821346283, - 0.5797068178653717, - 0.73484411835670471, - 0.877059186498324, + 0.12294074023135007, + 0.26515583503755935, + 0.4202931617987752, + 0.5797068382012247, + 0.7348441649624406, + 0.87705925976865, 1 }; - var pointsExpected = tValuesExpected.Select(t => curve.PointAt(t)).ToList(); + List pointsExpected = tValuesExpected.Select(t => curve.PointAt(t)).ToList(); int segments = 7; // Act - var (points, parameters) = curve.Divide(segments); + (List Points, List Parameters) divideResult = curve.Divide(segments); // Assert - parameters.Count.Should().Be(tValuesExpected.Length).And.Be(segments + 1); + divideResult.Parameters.Count.Should().Be(tValuesExpected.Length).And.Be(segments + 1); for (int i = 0; i < tValuesExpected.Length; i++) { - parameters[i].Should().BeApproximately(tValuesExpected[i], GeoSharkMath.MaxTolerance); - points[i].EpsilonEquals(pointsExpected[i], GeoSharkMath.MaxTolerance).Should().BeTrue(); + divideResult.Parameters[i].Should().BeApproximately(tValuesExpected[i], GeoSharkMath.MaxTolerance); + divideResult.Points[i].EpsilonEquals(pointsExpected[i], GeoSharkMath.MaxTolerance).Should().BeTrue(); } } @@ -95,27 +153,27 @@ public void Divide_By_Length_Returns_Points_And_Parameters_Along_Curve() NurbsCurve curve = NurbsCurveCollection.NurbsCurvePlanarExample(); double[] tValuesExpected = { 0, - 0.12294081350167592, - 0.26515588164329529, - 0.4202931821346283, - 0.5797068178653717, - 0.73484411835670471, - 0.877059186498324, + 0.12294074023135007, + 0.26515583503755935, + 0.4202931617987752, + 0.5797068382012247, + 0.7348441649624406, + 0.87705925976865, 1 }; - var pointsExpected = tValuesExpected.Select(t => curve.PointAt(t)).ToList(); + List pointsExpected = tValuesExpected.Select(t => curve.PointAt(t)).ToList(); int steps = 7; double length = curve.Length() / steps; // Act - var (points, parameters) = curve.Divide(length); + (List Points, List Parameters) divideResult = curve.Divide(length); // Assert - parameters.Count.Should().Be(pointsExpected.Count).And.Be(steps + 1); + divideResult.Parameters.Count.Should().Be(pointsExpected.Count).And.Be(steps + 1); for (int i = 0; i < pointsExpected.Count; i++) { - parameters[i].Should().BeApproximately(tValuesExpected[i], GeoSharkMath.MaxTolerance); - points[i].EpsilonEquals(pointsExpected[i], GeoSharkMath.MaxTolerance).Should().BeTrue(); + divideResult.Parameters[i].Should().BeApproximately(tValuesExpected[i], GeoSharkMath.MaxTolerance); + divideResult.Points[i].EpsilonEquals(pointsExpected[i], GeoSharkMath.MaxTolerance).Should().BeTrue(); } } @@ -219,23 +277,23 @@ public void It_Calculates_Rotation_Minimized_Frames_Along_Curve() //Act List uValues = curve.Divide(10).Parameters; - foreach (var uValue in uValues) + foreach (double uValue in uValues) { _testOutput.WriteLine(uValue.ToString()); } List perpFrames = curve.PerpendicularFrames(uValues); - foreach (var perpFrame in perpFrames) + foreach (Plane perpFrame in perpFrames) { _testOutput.WriteLine(perpFrame.ToString()); } //Assert - for (var i = 0; i < perpFrames.Count; i++) + for (int i = 0; i < perpFrames.Count; i++) { - var perpFrame = perpFrames[i]; - var expectedPerpFrame = expectedPerpFrames[i]; + Plane perpFrame = perpFrames[i]; + Plane expectedPerpFrame = expectedPerpFrames[i]; perpFrame.Origin.EpsilonEquals(expectedPerpFrame.Origin, GeoSharkMath.MaxTolerance); perpFrame.XAxis.EpsilonEquals(expectedPerpFrame.XAxis, GeoSharkMath.MaxTolerance); perpFrame.YAxis.EpsilonEquals(expectedPerpFrame.YAxis, GeoSharkMath.MaxTolerance); diff --git a/src/GShark/Core/BoundingBoxTree/LazyCurveBBT.cs b/src/GShark/Core/BoundingBoxTree/LazyCurveBBT.cs index 627f23e5..5bfa6b61 100644 --- a/src/GShark/Core/BoundingBoxTree/LazyCurveBBT.cs +++ b/src/GShark/Core/BoundingBoxTree/LazyCurveBBT.cs @@ -3,6 +3,8 @@ using GShark.Operation; using System; using System.Collections.Generic; +using GShark.ExtendedMethods; +using GShark.Geometry.Interfaces; namespace GShark.Core.BoundingBoxTree { @@ -35,7 +37,7 @@ public Tuple, IBoundingBoxTree> Split() Random r = new Random(); double t = (_curve.Knots[_curve.Knots.Count - 1] + _curve.Knots[0]) / 2.0 + (_curve.Knots[_curve.Knots.Count - 1] - _curve.Knots[0]) * 0.1 * r.NextDouble(); - List curves = Divide.SplitCurve(_curve, t); + List curves = _curve.SplitAt(t); return new Tuple, IBoundingBoxTree> (new LazyCurveBBT(curves[0], _knotTolerance), new LazyCurveBBT(curves[1], _knotTolerance)); diff --git a/src/GShark/ExtendedMethods/Curve.cs b/src/GShark/ExtendedMethods/Curve.cs index 2638f4fb..7a83422f 100644 --- a/src/GShark/ExtendedMethods/Curve.cs +++ b/src/GShark/ExtendedMethods/Curve.cs @@ -1,9 +1,13 @@ -using GShark.Geometry; -using GShark.Geometry.Interfaces; -using GShark.Operation; -using System; +using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Text; +using System.Threading.Tasks; +using GShark.Core; +using GShark.Geometry; +using GShark.Geometry.Interfaces; +using GShark.Operation; namespace GShark.ExtendedMethods { @@ -119,11 +123,131 @@ public static List PerpendicularFrames(this ICurve curve, List uV var sNext = Vector3.CrossProduct(pointsOnCurveTan[i + 1], rNext); //compute vector s[i+1] of next frame //create output frame - var frameNext = new Plane { Origin = pointsOnCurve[i + 1], XAxis = rNext, YAxis = sNext }; + var frameNext = new Plane(); + frameNext.Origin = pointsOnCurve[i + 1]; + frameNext.XAxis = rNext; + frameNext.YAxis = sNext; perpFrames[i + 1] = frameNext; //output frame } return perpFrames.ToList(); } + + /// + /// Splits a curve into two parts at a given parameter. + /// + /// The curve object. + /// The parameter at which to split the curve. + /// Two NurbsCurve objects. + public static List SplitAt(this ICurve curve, double t) + { + int degree = curve.Degree; + + List knotsToInsert = Sets.RepeatData(t, degree + 1); + + ICurve refinedCurve = Modify.CurveKnotRefine(curve, knotsToInsert); + + int s = curve.Knots.Span(degree, t); + + KnotVector knots0 = refinedCurve.Knots.ToList().GetRange(0, s + degree + 2).ToKnot(); + KnotVector knots1 = refinedCurve.Knots.GetRange(s + 1, refinedCurve.Knots.Count - (s + 1)).ToKnot(); + + List controlPoints0 = refinedCurve.ControlPoints.GetRange(0, s + 1); + List controlPoints1 = refinedCurve.ControlPoints.GetRange(s + 1, refinedCurve.LocationPoints.Count - (s + 1)); + + return new List { new NurbsCurve(degree, knots0, controlPoints0), new NurbsCurve(degree, knots1, controlPoints1) }; + } + + /// + /// Splits a curve at given parameters and returns the segments as curves. + /// + /// The curve to split. + /// The parameters at which to split the curve. Values should be between 0 and 1. + /// Collection of curve segments. + //TODO: Should input parameters be between 0 and 1, or should we have a normalization function on ICurve? + public static List SplitAt(this ICurve curve, double[] parameters) + { + var curves = new List(); + if (parameters.Length == 0) + { + curves.Add(curve); + return curves; + } + + //TODO: sort in increasing order or throw if not? + var sortedParameters = parameters.OrderBy(x => x).ToArray(); + + //TODO: Always including ends of domain if not included. Could also just ignore and assume they are in input parameters. + if (Math.Abs(sortedParameters[0] - curve.Domain.T0) > GeoSharkMath.MaxTolerance) + { + var tempParams = new double[sortedParameters.Length + 1]; + tempParams[0] = curve.Domain.T0; + for (var i = 0; i < sortedParameters.Length; i++) + { + tempParams[i+1] = sortedParameters[i]; + } + sortedParameters = tempParams; + } + + if (Math.Abs(sortedParameters[sortedParameters.Length - 1] - curve.Domain.T1) > GeoSharkMath.MaxTolerance) + { + Array.Resize(ref sortedParameters, sortedParameters.Length + 1); + sortedParameters[sortedParameters.Length - 1] = curve.Domain.T1; + } + + for (int i = 0; i < sortedParameters.Length - 1; i++) + { + curves.Add(SubCurve(curve, new Interval(sortedParameters[i], sortedParameters[i+1]))); + } + + return curves; + } + + /// + /// Extract sub-curve defined by domain. + /// + /// The curve from which to extract the sub-curve. + /// Domain of sub-curve + /// NurbsCurve. + public static ICurve SubCurve(this ICurve curve, Interval domain) + { + int degree = curve.Degree; + int order = degree + 1; + Interval subCurveDomain = domain; + + //NOTE: Handling decreasing domain by flipping it to maintain direction of original curve in sub-curve. Is this what we want? + if (domain.IsDecreasing) + { + subCurveDomain = new Interval(domain.T1, domain.T0); + } + + var isT0AtStart = Math.Abs(subCurveDomain.T0 - curve.Knots[0]) < GeoSharkMath.MaxTolerance; + var isT1AtEnd = Math.Abs(subCurveDomain.T1 - curve.Knots[curve.Knots.Count-1]) < GeoSharkMath.MaxTolerance; + + if (isT0AtStart && isT1AtEnd) + { + return curve; + } + + if (isT0AtStart || isT1AtEnd) + { + return isT0AtStart ? curve.SplitAt(subCurveDomain.T1)[0] : curve.SplitAt(subCurveDomain.T0)[1]; + } + + KnotVector subCurveKnotVector = new KnotVector(); + List knotsToInsert = Sets.RepeatData(domain.T0, order).Concat(Sets.RepeatData(domain.T1, degree + 1)).ToList(); + ICurve refinedCurve = Modify.CurveKnotRefine(curve, knotsToInsert); + var multiplicityAtT0 = refinedCurve.Knots.Multiplicity(subCurveDomain.T0); + var multiplicityAtT1 = refinedCurve.Knots.Multiplicity(subCurveDomain.T1); + var t0Idx = refinedCurve.Knots.IndexOf(subCurveDomain.T0); + + subCurveKnotVector.AddRange(refinedCurve.Knots.GetRange(t0Idx, multiplicityAtT0 + multiplicityAtT1)); + + var subCurveControlPoints = refinedCurve.ControlPoints.GetRange(order, order); + + var subCurve = new NurbsCurve(curve.Degree, subCurveKnotVector, subCurveControlPoints); + + return subCurve; + } } } diff --git a/src/GShark/ExtendedMethods/Surface.cs b/src/GShark/ExtendedMethods/Surface.cs index 2dcae6cd..34a6c4ea 100644 --- a/src/GShark/ExtendedMethods/Surface.cs +++ b/src/GShark/ExtendedMethods/Surface.cs @@ -26,7 +26,7 @@ public static NurbsSurface[] Split(this NurbsSurface surface, double parameter, { throw new ArgumentNullException(nameof(surface)); } - + return Divide.SplitSurface(surface, parameter, direction); } } diff --git a/src/GShark/Geometry/NurbsCurve.cs b/src/GShark/Geometry/NurbsCurve.cs index 7653d568..bf7484b6 100644 --- a/src/GShark/Geometry/NurbsCurve.cs +++ b/src/GShark/Geometry/NurbsCurve.cs @@ -289,31 +289,29 @@ public NurbsCurve ClampEnds() /// /// The NURBS curve. /// Return true if the NURBS curves are equal. - public bool Equals(NurbsCurve other) + public bool Equals(NurbsCurve? other) { - List otherPts = other?.LocationPoints; - if (other == null) { return false; } - if (LocationPoints.Count != otherPts?.Count) + if (!LocationPoints.SequenceEqual(other.LocationPoints)) { return false; } - if (Knots.Count != other.Knots.Count) + if (!Knots.SequenceEqual(other.Knots)) { return false; } - if (LocationPoints.Where((t, i) => !t.Equals(otherPts[i])).Any()) + if (!Weights.SequenceEqual(other.Weights)) { return false; } - return Degree == other.Degree && Knots.All(other.Knots.Contains) && Weights.All(other.Weights.Contains); + return Degree == other.Degree; } /// @@ -322,7 +320,7 @@ public bool Equals(NurbsCurve other) /// /// The curve object. /// Return true if the NURBS curves are equal. - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is NurbsCurve curve) return Equals(curve); diff --git a/src/GShark/Geometry/Point3.cs b/src/GShark/Geometry/Point3.cs index c259183a..d4bb954a 100644 --- a/src/GShark/Geometry/Point3.cs +++ b/src/GShark/Geometry/Point3.cs @@ -187,7 +187,9 @@ public static Point3 Add(Vector3 vector, Point3 point) /// true if the coordinates of the two points are exactly equal; otherwise false. public static bool operator ==(Point3 a, Point3 b) { - return (a.X == b.X && a.Y == b.Y && a.Z == b.Z); + return (Math.Abs(a.X - b.X) < GeoSharkMath.MaxTolerance + && Math.Abs(a.Y - b.Y) < GeoSharkMath.MaxTolerance + && Math.Abs(a.Z - b.Z) < GeoSharkMath.MaxTolerance); } /// @@ -198,7 +200,9 @@ public static Point3 Add(Vector3 vector, Point3 point) /// true if the two points differ in any coordinate; false otherwise. public static bool operator !=(Point3 a, Point3 b) { - return (a.X != b.X || a.Y != b.Y || a.Z != b.Z); + return (Math.Abs(a.X - b.X) > GeoSharkMath.MaxTolerance + || Math.Abs(a.Y - b.Y) > GeoSharkMath.MaxTolerance + || Math.Abs(a.Z - b.Z) > GeoSharkMath.MaxTolerance); } /// @@ -369,7 +373,7 @@ public double this[int i] /// true if obj is a Point3 and has the same coordinates as this; otherwise false. public override bool Equals(object obj) { - return obj is Point3 point3 && this == point3; + return (obj is Point3 && this == (Point3)obj); } /// diff --git a/src/GShark/Operation/Divide.cs b/src/GShark/Operation/Divide.cs index 13640f65..cd5a2477 100644 --- a/src/GShark/Operation/Divide.cs +++ b/src/GShark/Operation/Divide.cs @@ -14,31 +14,6 @@ namespace GShark.Operation /// public class Divide { - /// - /// Splits a curve into two parts at a given parameter. - /// - /// The curve object. - /// The parameter where to split the curve. - /// Two new curves, defined by degree, knots, and control points. - public static List SplitCurve(ICurve curve, double t) - { - int degree = curve.Degree; - - List knotsToInsert = Sets.RepeatData(t, degree + 1); - - ICurve refinedCurve = Modify.CurveKnotRefine(curve, knotsToInsert); - - int s = curve.Knots.Span(degree, t); - - KnotVector knots0 = refinedCurve.Knots.ToList().GetRange(0, s + degree + 2).ToKnot(); - KnotVector knots1 = refinedCurve.Knots.GetRange(s + 1, refinedCurve.Knots.Count - (s + 1)).ToKnot(); - - List controlPoints0 = refinedCurve.ControlPoints.GetRange(0, s + 1); - List controlPoints1 = refinedCurve.ControlPoints.GetRange(s + 1, refinedCurve.ControlPoints.Count - (s + 1)); - - return new List { new NurbsCurve(degree, knots0, controlPoints0), new NurbsCurve(degree, knots1, controlPoints1) }; - } - /// /// Splits (divides) the surface into two parts at the specified parameter /// @@ -52,11 +27,11 @@ internal static NurbsSurface[] SplitSurface(NurbsSurface surface, double paramet { KnotVector knots = surface.KnotsV; int degree = surface.DegreeV; - List> pts2d = surface.ControlPoints; + List> srfCtrlPts = surface.ControlPoints; if (direction != SplitDirection.V) { - pts2d = Sets.Reverse2DMatrixData(surface.ControlPoints); + srfCtrlPts = Sets.Reverse2DMatrixData(surface.ControlPoints); knots = surface.KnotsU; degree = surface.DegreeU; } @@ -68,7 +43,7 @@ internal static NurbsSurface[] SplitSurface(NurbsSurface surface, double paramet List> surfPtsRight = new List>(); ICurve result = null; - foreach (List pts in pts2d) + foreach (List pts in srfCtrlPts) { NurbsCurve tempCurve = new NurbsCurve(degree, knots, pts); result = Modify.CurveKnotRefine(tempCurve, knotsToInsert); @@ -77,11 +52,11 @@ internal static NurbsSurface[] SplitSurface(NurbsSurface surface, double paramet surfPtsRight.Add(result.ControlPoints.GetRange(span + 1, span + 1)); } - if (result == null) throw new Exception("Could not solve the split."); + if (result == null) throw new Exception($"Could not split {nameof(surface)}."); KnotVector knotLeft = result.Knots.GetRange(0, span + degree + 2).ToKnot(); KnotVector knotRight = result.Knots.GetRange(span + 1, span + degree + 2).ToKnot(); - NurbsSurface[] surfaceResult = new NurbsSurface[] { }; + NurbsSurface[] surfaceResult = Array.Empty(); switch (direction) {