/// <summary> /// Determines the derivatives of a curve at a given parameter.<br/> /// <em>Corresponds to algorithm 3.2 from The NURBS Book by Piegl and Tiller.</em> /// </summary> /// <param name="curve">The curve object.</param> /// <param name="parameter">Parameter on the curve at which the point is to be evaluated.</param> /// <param name="numberDerivs">Integer number of basis functions - 1 = knots.length - degree - 2.</param> /// <returns>The derivatives.</returns> internal static List <Point4> CurveDerivatives(NurbsBase curve, double parameter, int numberDerivs) { List <Point4> curveHomogenizedPoints = curve.ControlPoints; int n = curve.Knots.Count - curve.Degree - 2; int derivateOrder = numberDerivs < curve.Degree ? numberDerivs : curve.Degree; Point4[] ck = new Point4[numberDerivs + 1]; int knotSpan = curve.Knots.Span(n, curve.Degree, parameter); List <Vector> derived2d = DerivativeBasisFunctionsGivenNI(knotSpan, parameter, curve.Degree, derivateOrder, curve.Knots); for (int k = 0; k < derivateOrder + 1; k++) { for (int j = 0; j < curve.Degree + 1; j++) { double valToMultiply = derived2d[k][j]; Point4 pt = curveHomogenizedPoints[knotSpan - curve.Degree + j]; for (int i = 0; i < pt.Size; i++) { ck[k][i] = ck[k][i] + (valToMultiply * pt[i]); } } } return(ck.ToList()); }
public void It_Returns_A_Circle3D_With_Its_Nurbs_Representation() { // Arrange var ptsExpected = new List <Point3> { new Point3(74.264416, 36.39316, -1.884313), new Point3(62.298962, 25.460683, 1.083287), new Point3(73.626287, 13.863582, 4.032316), new Point3(84.953611, 2.266482, 6.981346), new Point3(96.919065, 13.198959, 4.013746), new Point3(108.884519, 24.131437, 1.046146), new Point3(97.557194, 35.728537, -1.902883), new Point3(86.22987, 47.325637, -4.851913), new Point3(74.264416, 36.39316, -1.88431) }; // Act NurbsBase circleNurbs = _circle3D; // Assert circleNurbs.Knots.GetDomain(circleNurbs.Degree).Length.Should().Be(1.0); for (int ptIndex = 0; ptIndex < ptsExpected.Count; ptIndex++) { circleNurbs.ControlPointLocations[ptIndex].EpsilonEquals(ptsExpected[ptIndex], GSharkMath.MaxTolerance); } }
/// <summary> /// Approximates the parameter at a given length on a curve. /// </summary> /// <param name="curve">The curve object.</param> /// <param name="segmentLength">The arc length for which to do the procedure.</param> /// <param name="tolerance">If set less or equal 0.0, the tolerance used is 1e-10.</param> /// <returns>The parameter on the curve.</returns> internal static double ParameterAtLength(NurbsBase curve, double segmentLength, double tolerance = -1) { if (segmentLength < GSharkMath.Epsilon) { return(curve.Knots[0]); } if (Math.Abs(curve.Length - segmentLength) < GSharkMath.Epsilon) { return(curve.Knots[curve.Knots.Count - 1]); } List <NurbsBase> curves = Modify.Curve.DecomposeIntoBeziers(curve); int i = 0; double curveLength = -GSharkMath.Epsilon; double segmentLengthLeft = segmentLength; // Iterate through the curves consuming the bezier's, summing their length along the way. while (curveLength < segmentLength && i < curves.Count) { double bezierLength = BezierLength(curves[i]); curveLength += bezierLength; if (segmentLength < curveLength + GSharkMath.Epsilon) { return(BezierParameterAtLength(curves[i], segmentLengthLeft, tolerance)); } i++; segmentLengthLeft -= bezierLength; } return(-1); }
public void It_Refines_The_Curve_Knot(double val, int insertion) { // Arrange int degree = 3; List <double> newKnots = new List <double>(); for (int i = 0; i < insertion; i++) { newKnots.Add(val); } List <Point3> pts = new List <Point3>(); for (int i = 0; i <= 12 - degree - 2; i++) { pts.Add(new Point3(i, 0.0, 0.0)); } NurbsCurve curve = new NurbsCurve(pts, degree); // Act NurbsBase curveAfterRefine = KnotVector.Refine(curve, newKnots); Point3 p0 = curve.PointAt(2.5); Point3 p1 = curveAfterRefine.PointAt(2.5); // Assert (curve.Knots.Count + insertion).Should().Be(curveAfterRefine.Knots.Count); (pts.Count + insertion).Should().Be(curveAfterRefine.ControlPointLocations.Count); (p0 == p1).Should().BeTrue(); }
public void It_Returns_A_Curve_Where_Degree_Is_Reduced_From_5_To_4() { // Arrange // Followed example Under C1 constrain condition https://www.hindawi.com/journals/mpe/2016/8140427/tab1/ List <Point3> pts = new List <Point3> { new Point3(-5.0, 0.0, 0.0), new Point3(-7.0, 2.0, 0.0), new Point3(-3.0, 5.0, 0.0), new Point3(2.0, 6.0, 0.0), new Point3(5.0, 3.0, 0.0), new Point3(3.0, 0.0, 0.0) }; int degree = 5; double tolerance = 10e-2; NurbsCurve curve = new NurbsCurve(pts, degree); Point3 ptOnCurve0 = curve.PointAt(0.5); Point3 ptOnCurve1 = curve.PointAt(0.25); // Act NurbsBase reducedCurve = curve.ReduceDegree(tolerance); Point3 ptOnReducedDegreeCurve0 = reducedCurve.PointAt(0.5); Point3 ptOnReducedDegreeCurve1 = reducedCurve.PointAt(0.25); // Assert reducedCurve.Degree.Should().Be(degree - 1); ptOnCurve0.DistanceTo(ptOnReducedDegreeCurve0).Should().BeLessThan(GSharkMath.MinTolerance); ptOnCurve1.DistanceTo(ptOnReducedDegreeCurve1).Should().BeLessThan(tolerance); }
/// <summary> /// Samples a curve at equally spaced parametric intervals. /// </summary> /// <param name="curve">The curve object.</param> /// <param name="numSamples">Number of samples.</param> /// <returns>A tuple with the set of points and the t parameter where the point was evaluated.</returns> internal static (List <double> tvalues, List <Point3> pts) RegularSample(NurbsBase curve, int numSamples) { if (numSamples < 1) { throw new Exception("Number of sample must be at least 1 and not negative."); } double start = curve.Knots[0]; double end = curve.Knots[curve.Knots.Count - 1]; double span = (end - start) / (numSamples - 1); List <Point3> pts = new List <Point3>(); List <double> tValues = new List <double>(); for (int i = 0; i < numSamples; i++) { double t = start + span * i; Point3 ptEval = curve.PointAt(t); pts.Add(ptEval); tValues.Add(t); } return(tValues, pts); }
/// <summary> /// Computes the intersection between a curve and a plane.<br/> /// https://www.parametriczoo.com/index.php/2020/03/31/plane-and-curve-intersection/ /// </summary> /// <param name="crv">The curve to intersect.</param> /// <param name="pl">The plane to intersect with the curve.</param> /// <param name="tolerance">Tolerance set per default at 1e-6.</param> /// <returns>If intersection found a collection of <see cref="CurvePlaneIntersectionResult"/> otherwise the result will be empty.</returns> public static List <CurvePlaneIntersectionResult> CurvePlane(NurbsBase crv, Plane pl, double tolerance = 1e-6) { List <NurbsBase> bBoxRoot = BoundingBoxOperations.BoundingBoxPlaneIntersection(new LazyCurveBBT(crv), pl); List <CurvePlaneIntersectionResult> intersectionResults = bBoxRoot.Select( x => IntersectionRefiner.CurvePlaneWithEstimation(crv, pl, x.Knots[0], x.Knots[0], tolerance)).ToList(); return(intersectionResults); }
/// <summary> /// Divides a curve for a given number of time, including the end points.<br/> /// The result is not split curves but a collection of t values and lengths that can be used for splitting.<br/> /// As with all arc length methods, the result is an approximation. /// </summary> /// <param name="curve">The curve object to divide.</param> /// <param name="divisions">The number of parts to split the curve into.</param> /// <returns>A tuple define the t values where the curve is divided and the lengths between each division.</returns> internal static List <double> ByCount(NurbsBase curve, int divisions) { double approximatedLength = Analyze.Curve.Length(curve); double arcLengthSeparation = approximatedLength / divisions; var divisionByLength = ByLength(curve, arcLengthSeparation); var tValues = divisionByLength.tValues; return(tValues); }
/// <summary> /// Computes the self intersections of a curve. /// </summary> /// <param name="crv">The curve for self-intersections.</param> /// <param name="tolerance">Tolerance set per default at 1e-6.</param> /// <returns>If intersection found a collection of <see cref="CurvesIntersectionResult"/> otherwise the result will be empty.</returns> public static List <CurvesIntersectionResult> CurveSelf(NurbsBase crv, double tolerance = 1e-6) { List <Tuple <NurbsBase, NurbsBase> > bBoxTreeIntersections = BoundingBoxOperations.BoundingBoxTreeIntersection(new LazyCurveBBT(crv), 0); List <CurvesIntersectionResult> intersectionResults = bBoxTreeIntersections .Select(x => IntersectionRefiner.CurvesWithEstimation(x.Item1, x.Item2, x.Item1.Knots[0], x.Item2.Knots[0], tolerance)) .Where(crInRe => Math.Abs(crInRe.ParameterA - crInRe.ParameterB) > tolerance) .Unique((a, b) => Math.Abs(a.ParameterA - b.ParameterA) < tolerance * 5); return(intersectionResults); }
public void It_Returns_Parameter_At_The_Given_Length(double segmentLength, double tValueExpected) { // Arrange NurbsBase curve = NurbsCurveCollection.PlanarCurveDegreeThree(); // Act double parameter = curve.ParameterAtLength(segmentLength); // Assert parameter.Should().BeApproximately(tValueExpected, GSharkMath.MinTolerance); }
public void It_Returns_True_If_The_NurbsBase_Form_Of_A_Line_Is_Correct() { //Act NurbsBase nurbsLine = _exampleLine; // Assert nurbsLine.ControlPointLocations.Count.Should().Be(2); nurbsLine.ControlPointLocations[0].Equals(_exampleLine.StartPoint).Should().BeTrue(); nurbsLine.ControlPointLocations[1].Equals(_exampleLine.EndPoint).Should().BeTrue(); nurbsLine.Degree.Should().Be(1); }
public void It_Returns_The_Segment_At_Length(double length, int segmentIndex) { //Arrange NurbsBase expectedSegment = _polycurve.Segments[segmentIndex]; //Act NurbsBase segmentResult = _polycurve.SegmentAtLength(length); //Assert segmentResult.Should().BeSameAs(expectedSegment); }
/// <summary> /// Samples a curve in an adaptive way. <br/> /// <em>Corresponds to this algorithm http://ariel.chronotext.org/dd/defigueiredo93adaptive.pdf </em> /// </summary> /// <param name="curve">The curve to sampling.</param> /// <param name="tolerance">The tolerance for the adaptive division.</param> /// <returns>A tuple collecting the parameter where it was sampled and the points.</returns> public static (List <double> tValues, List <Point3> pts) AdaptiveSample(NurbsBase curve, double tolerance = GSharkMath.MinTolerance) { if (curve.Degree != 1) { return(AdaptiveSampleRange(curve, curve.Knots[0], curve.Knots[curve.Knots.Count - 1], tolerance)); } KnotVector copyKnot = new KnotVector(curve.Knots); copyKnot.RemoveAt(0); copyKnot.RemoveAt(copyKnot.Count - 1); return(copyKnot, curve.ControlPointLocations); }
public void It_Returns_The_Length_Of_The_Curve() { // Arrange NurbsBase curve = NurbsCurveCollection.PlanarCurveDegreeThree(); double expectedLength = 50.334675; // Act double crvLength = curve.Length; // Assert crvLength.Should().BeApproximately(expectedLength, GSharkMath.MinTolerance); }
public void Returns_The_Surface_Isocurve_At_U_Direction() { // Arrange NurbsSurface surface = NurbsSurfaceCollection.SurfaceFromPoints(); Point3 expectedPt = new Point3(3.591549, 10, 4.464789); // Act NurbsBase Isocurve = surface.IsoCurve(0.3, SurfaceDirection.U); // Assert Isocurve.ControlPointLocations[1].DistanceTo(expectedPt).Should().BeLessThan(GSharkMath.MinTolerance); }
/// <summary> /// Refines an intersection pair for two curves given an initial guess.<br/> /// This is an unconstrained minimization, so the caller is responsible for providing a very good initial guess. /// </summary> /// <param name="crv0">The first curve.</param> /// <param name="crv1">The second curve.</param> /// <param name="firstGuess">The first guess parameter.</param> /// <param name="secondGuess">The second guess parameter.</param> /// <param name="tolerance">The value tolerance for the intersection.</param> /// <returns>The results collected into the object <see cref="CurvesIntersectionResult"/>.</returns> internal static CurvesIntersectionResult CurvesWithEstimation(NurbsBase crv0, NurbsBase crv1, double firstGuess, double secondGuess, double tolerance) { IObjectiveFunction objectiveFunctions = new CurvesIntersectionObjectives(crv0, crv1); Minimizer min = new Minimizer(objectiveFunctions); MinimizationResult solution = min.UnconstrainedMinimizer(new Vector { firstGuess, secondGuess }, tolerance * tolerance); // These are not the same points, also are used to filter where the intersection is not happening. Point3 pt1 = crv0.PointAt(solution.SolutionPoint[0]); Point3 pt2 = crv1.PointAt(solution.SolutionPoint[1]); return(new CurvesIntersectionResult(pt1, pt2, solution.SolutionPoint[0], solution.SolutionPoint[1])); }
public void It_Returns_The_Closest_Point_And_Parameter(double[] ptToCheck, double[] ptExpected, double tValExpected) { // Arrange NurbsBase curve = NurbsCurveCollection.PlanarCurveDegreeThree(); Point3 testPt = new Point3(ptToCheck[0], ptToCheck[1], ptToCheck[2]); Point3 expectedPt = new Point3(ptExpected[0], ptExpected[1], ptExpected[2]); // Act Point3 pt = curve.ClosestPoint(testPt); double parameter = curve.ClosestParameter(testPt); // Assert parameter.Should().BeApproximately(tValExpected, GSharkMath.MaxTolerance); pt.EpsilonEquals(expectedPt, GSharkMath.MaxTolerance).Should().BeTrue(); }
public void Returns_The_Surface_Isocurve_At_V_Direction() { // Arrange NurbsSurface surface = NurbsSurfaceCollection.SurfaceFromPoints(); Point3 expectedPt = new Point3(5, 4.615385, 2.307692); Point3 expectedPtAt = new Point3(5, 3.913043, 1.695652); // Act NurbsBase Isocurve = surface.IsoCurve(0.3, SurfaceDirection.V); Point3 ptAt = Isocurve.PointAt(0.5); // Assert Isocurve.ControlPointLocations[1].DistanceTo(expectedPt).Should().BeLessThan(GSharkMath.MinTolerance); ptAt.DistanceTo(expectedPtAt).Should().BeLessThan(GSharkMath.MinTolerance); }
/// <summary> /// Computes the approximate length of a rational bezier curve by gaussian quadrature - assumes a smooth curve. /// </summary> /// <param name="curve">The curve object.</param> /// <param name="u">The parameter at which to approximate the length.</param> /// <param name="gaussDegIncrease"> /// the degree of gaussian quadrature to perform. /// A higher number yields a more exact result, default set to 17. /// </param> /// <returns>The approximate length of a bezier.</returns> internal static double BezierLength(NurbsBase curve, double u = -1.0, int gaussDegIncrease = 17) { double uSet = u < 0.0 ? curve.Knots.Last() : u; double z = (uSet - curve.Knots[0]) / 2; double sum = 0.0; int gaussDegree = curve.Degree + gaussDegIncrease; for (int i = 0; i < gaussDegree; i++) { double cu = z * LegendreGaussData.tValues[gaussDegree][i] + z + curve.Knots[0]; List <Vector3> tan = Evaluate.Curve.RationalDerivatives(curve, cu); sum += LegendreGaussData.cValues[gaussDegree][i] * tan[1].Length; } return(z * sum); }
/// <summary> /// Refines an intersection between a curve and a plane given an initial guess.<br/> /// This is an unconstrained minimization, so the caller is responsible for providing a very good initial guess. /// </summary> /// <param name="crv">The curve to intersect.</param> /// <param name="plane">The plane to intersect with the curve.</param> /// <param name="firstGuess">The first guess parameter.</param> /// <param name="secondGuess">The second guess parameter.</param> /// <param name="tolerance">The value tolerance for the intersection.</param> /// <returns>The results collected into the object <see cref="CurvePlaneIntersectionResult"/>.</returns> internal static CurvePlaneIntersectionResult CurvePlaneWithEstimation(NurbsBase crv, Plane plane, double firstGuess, double secondGuess, double tolerance) { IObjectiveFunction objectiveFunctions = new CurvePlaneIntersectionObjectives(crv, plane); Minimizer min = new Minimizer(objectiveFunctions); MinimizationResult solution = min.UnconstrainedMinimizer(new Vector { firstGuess, secondGuess }, tolerance * tolerance); Point3 pt = crv.PointAt(solution.SolutionPoint[0]); (double u, double v)parameters = plane.ClosestParameters(pt); Vector uv = new Vector { parameters.u, parameters.v, 0.0 }; return(new CurvePlaneIntersectionResult(pt, solution.SolutionPoint[0], uv)); }
public void It_Returns_True_If_The_NurbsBase_Form_Of_A_Polygon_Is_Correct() { // Arrange PolyLine polygon = new Polygon(Planar2D); double lengthSum = 0.0; // Act NurbsBase polygonCurve = polygon; // Assert polygonCurve.Degree.Should().Be(1); polygonCurve.ControlPointLocations[0] .EpsilonEquals(polygonCurve.ControlPointLocations.Last(), GSharkMath.MinTolerance).Should().BeTrue(); for (int i = 0; i < polygon.SegmentsCount; i++) { lengthSum += polygon.Segments[i].Length; polygon.ControlPointLocations[i + 1].EpsilonEquals(polygonCurve.PointAtLength(lengthSum), GSharkMath.MaxTolerance).Should().BeTrue(); } }
/// <summary> /// Decompose a curve into a collection of bezier curves.<br/> /// </summary> /// <param name="curve">The curve object.</param> /// <param name="normalize">Set as per default false, true normalize the knots between 0 to 1.</param> /// <returns>Collection of curve objects, defined by degree, knots, and control points.</returns> internal static List <NurbsBase> DecomposeIntoBeziers(NurbsBase curve, bool normalize = false) { int degree = curve.Degree; List <Point4> controlPoints = curve.ControlPoints; KnotVector knots = curve.Knots; // Find all of the unique knot values and their multiplicity. // For each, increase their multiplicity to degree + 1. Dictionary <double, int> knotMultiplicities = knots.Multiplicities(); int reqMultiplicity = degree + 1; // Insert the knots. foreach (KeyValuePair <double, int> kvp in knotMultiplicities) { if (kvp.Value >= reqMultiplicity) { continue; } List <double> knotsToInsert = CollectionHelpers.RepeatData(kvp.Key, reqMultiplicity - kvp.Value); NurbsBase curveTemp = new NurbsCurve(degree, knots, controlPoints); NurbsBase curveResult = KnotRefine(curveTemp, knotsToInsert); knots = curveResult.Knots; controlPoints = curveResult.ControlPoints; } int crvKnotLength = reqMultiplicity * 2; List <NurbsBase> curves = new List <NurbsBase>(); int i = 0; while (i < controlPoints.Count) { KnotVector knotsRange = (normalize) ? knots.GetRange(i, crvKnotLength).ToKnot().Normalize() : knots.GetRange(i, crvKnotLength).ToKnot(); List <Point4> ptsRange = controlPoints.GetRange(i, reqMultiplicity); NurbsBase tempCrv = new NurbsCurve(degree, knotsRange, ptsRange); curves.Add(tempCrv); i += reqMultiplicity; } return(curves); }
/// <summary> /// Computes the approximate length of a rational curve by gaussian quadrature - assumes a smooth curve. /// </summary> /// <param name="curve">The curve object.</param> /// <param name="u">The parameter at which to approximate the length.</param> /// <param name="gaussDegIncrease"> /// The degree of gaussian quadrature to perform. /// A higher number yields a more exact result, default set to 17. /// </param> /// <returns>The approximate length.</returns> internal static double Length(NurbsBase curve, double u = -1.0, int gaussDegIncrease = 17) { double uSet = u < 0.0 ? curve.Knots.Last() : u; List <NurbsBase> crvs = Modify.Curve.DecomposeIntoBeziers(curve); double sum = 0.0; foreach (NurbsBase bezier in crvs) { if (!(bezier.Knots[0] + GSharkMath.Epsilon < uSet)) { break; } double param = Math.Min(bezier.Knots.Last(), uSet); sum += BezierLength(bezier, param, gaussDegIncrease); } return(sum); }
/// <summary> /// Computes the curve parameter at a given length. /// </summary> /// <param name="curve">The curve object.</param> /// <param name="segmentLength">The length to find the parameter.</param> /// <param name="tolerance">If set less or equal 0.0, the tolerance used is 1e-10.</param> /// <returns>The parameter at the given length.</returns> internal static double BezierParameterAtLength(NurbsBase curve, double segmentLength, double tolerance) { if (segmentLength < 0) { return(curve.Knots[0]); } // We compute the whole length. double curveLength = BezierLength(curve); if (segmentLength > curveLength) { return(curve.Knots[curve.Knots.Count - 1]); } // Divide and conquer. double setTolerance = tolerance <= 0.0 ? GSharkMath.Epsilon : tolerance; double startT = curve.Knots[0]; double startLength = 0.0; double endT = curve.Knots[curve.Knots.Count - 1]; double endLength = curveLength; while (endLength - startLength > setTolerance) { double midT = (startT + endT) / 2; double midLength = BezierLength(curve, midT); if (midLength > segmentLength) { endT = midT; endLength = midLength; } else { startT = midT; startLength = midLength; } } return((startT + endT) / 2); }
/// <summary> /// Divides a curve for a given length, including the end points.<br/> /// The result is not split curves but a collection of t values and lengths that can be used for splitting.<br/> /// As with all arc length methods, the result is an approximation. /// </summary> /// <param name="curve">The curve object to divide.</param> /// <param name="length">The length separating the resultant samples.</param> /// <returns>A tuple define the t values where the curve is divided and the lengths between each division.</returns> internal static (List <double> tValues, List <double> lengths) ByLength(NurbsBase curve, double length) { List <NurbsBase> curves = Modify.Curve.DecomposeIntoBeziers(curve); List <double> curveLengths = curves.Select(NurbsBase => Analyze.Curve.BezierLength(NurbsBase)).ToList(); double totalLength = curveLengths.Sum(); List <double> tValues = new List <double> { curve.Knots[0] }; List <double> divisionLengths = new List <double> { 0.0 }; if (length > totalLength) { return(tValues, divisionLengths); } int i = 0; double sum = 0.0; double sum2 = 0.0; double segmentLength = length; while (i < curves.Count) { sum += curveLengths[i]; while (segmentLength < sum + GSharkMath.Epsilon) { double t = Analyze.Curve.BezierParameterAtLength(curves[i], segmentLength - sum2, GSharkMath.MinTolerance); tValues.Add(t); divisionLengths.Add(segmentLength); segmentLength += length; } sum2 += curveLengths[i]; i++; } return(tValues, divisionLengths); }
public void It_Is_A_Curve_Representation_Of_ExampleArc3D() { // Arrange double[] weightChecks = { 1.0, 0.7507927793532885, 1.0, 0.7507927793532885, 1.0, 0.7507927793532885, 1.0 }; Point3[] ptChecks = { new Point3(74.264416, 36.39316, -1.8843129999999997), new Point3(63.73736394529969, 26.774907230101093, 0.7265431054950776), new Point3(72.2808868605866, 15.429871621311115, 3.6324963299804987), new Point3(80.8244097758736, 4.084836012521206, 6.538449554465901), new Point3(93.52800280921122, 10.812836698886068, 4.6679117389561), new Point3(106.23159584254901, 17.54083738525103, 2.797373923446271), new Point3(100.92443, 30.599893, -0.5851159999999997) }; // Act NurbsBase arc = _exampleArc3D; // Assert arc.ControlPointLocations.Count.Should().Be(7); arc.Degree.Should().Be(2); for (int i = 0; i < ptChecks.Length; i++) { arc.ControlPointLocations[i].EpsilonEquals(ptChecks[i], GSharkMath.MaxTolerance).Should().BeTrue(); arc.ControlPoints[i].W.Should().Be(weightChecks[i]); if (i < 3) { arc.Knots[i].Should().Be(0); arc.Knots[i + 7].Should().Be(1); } else if (i < 5) { arc.Knots[i].Should().Be(0.3333333333333333); } else { arc.Knots[i].Should().Be(0.6666666666666666); } } }
public void It_Returns_A_Curve_Where_Degree_Is_Elevated_From_1_To_Elevated_Degree_Value(int finalDegree) { // Arrange List <Point3> pts = new List <Point3> { new Point3(0.0, 0.0, 1.0), new Point3(7.0, 3.0, -10), new Point3(5.2, 5.2, -5), }; int degree = 1; NurbsCurve curve = new NurbsCurve(pts, degree); Point3 ptOnCurve = curve.PointAt(0.5); // Act NurbsBase elevatedDegreeCurve = curve.ElevateDegree(finalDegree); Point3 ptOnElevatedDegreeCurve = elevatedDegreeCurve.PointAt(0.5); // Assert elevatedDegreeCurve.Degree.Should().Be(finalDegree); ptOnElevatedDegreeCurve.DistanceTo(ptOnCurve).Should().BeLessThan(GSharkMath.MinTolerance); }
/// <summary> /// Calculates all the extrema on a curve.<br/> /// Extrema are calculated for each dimension, rather than for the full curve, <br/> /// so that the result is not the number of convex/concave transitions, but the number of those transitions for each separate dimension. /// </summary> /// <param name="curve">Curve where the extrema are calculated.</param> /// <returns>The extrema.</returns> internal static Extrema ComputeExtrema(NurbsBase curve) { var derivPts = DerivativeCoordinates(curve.ControlPointLocations); Extrema extrema = new Extrema(); int dim = derivPts[0][0].Size; for (int j = 0; j < dim; j++) { List <double> p0 = new List <double>(); for (int i = 0; i < derivPts[0].Count; i++) { p0.Add(derivPts[0][i][j]); } List <double> result = new List <double>(DerivativesRoots(p0)); if (curve.Degree == 3) { IList <double> p1 = new List <double>(); for (int i = 0; i < derivPts[1].Count; i++) { p1.Add(derivPts[1][i][j]); } result = result.Concat(DerivativesRoots(p1).ToList()).ToList(); } result = result.Where((t) => t >= 0 && t <= 1).ToList(); result.Sort(); extrema[j] = result; } extrema.Values = extrema[0].Union(extrema[1]).Union(extrema[2]).OrderBy(x => x).ToList(); return(extrema); }
/// <summary> /// Performs a knot refinement on a surface by inserting knots at various parameters.<br/> /// <em>Implementation of Algorithm A5.5 of The NURBS Book by Piegl and Tiller.</em> /// </summary> internal static NurbsSurface SurfaceKnotRefine(NurbsSurface surface, IList <double> knotsToInsert, SurfaceDirection direction) { List <List <Point4> > modifiedControlPts = new List <List <Point4> >(); List <List <Point4> > controlPts = surface.ControlPoints; KnotVector knots = surface.KnotsV; int degree = surface.DegreeV; if (direction != SurfaceDirection.V) { controlPts = CollectionHelpers.Transpose2DArray(surface.ControlPoints); knots = surface.KnotsU; degree = surface.DegreeU; } NurbsBase curve = null; foreach (List <Point4> pts in controlPts) { curve = Curve.KnotRefine(new NurbsCurve(degree, knots, pts), knotsToInsert); modifiedControlPts.Add(curve.ControlPoints); } if (curve == null) { throw new Exception( "The refinement was not be able to be completed. A problem occur refining the internal curves."); } if (direction != SurfaceDirection.V) { var reversedControlPts = CollectionHelpers.Transpose2DArray(modifiedControlPts); return(new NurbsSurface(surface.DegreeU, surface.DegreeV, curve.Knots, surface.KnotsV.Copy(), reversedControlPts)); } return(new NurbsSurface(surface.DegreeU, surface.DegreeV, surface.KnotsU.Copy(), curve.Knots, modifiedControlPts)); }
/// <summary> /// Samples a curve in an adaptive way. <br/> /// <em>Corresponds to this algorithm http://ariel.chronotext.org/dd/defigueiredo93adaptive.pdf <br/> /// https://www.modelical.com/en/grasshopper-scripting-107/ </em> /// </summary> /// <param name="curve">The curve to sampling.</param> /// <param name="start">The start parameter for sampling.</param> /// <param name="end">The end parameter for sampling.</param> /// <param name="tolerance">Tolerance for the adaptive scheme. The default tolerance is set as (1e-6).</param> /// <returns>A tuple with the set of points and the t parameter where the point was evaluated.</returns> public static (List <double> tValues, List <Point3> pts) AdaptiveSampleRange(NurbsBase curve, double start, double end, double tolerance = GSharkMath.MinTolerance) { // Sample curve at three pts. Random random = new Random(); double t = 0.5 + 0.2 * random.NextDouble(); double mid = start + (end - start) * t; Point3 pt1 = Point4.PointDehomogenizer(Evaluate.Curve.PointAt(curve, start)); Point3 pt2 = Point4.PointDehomogenizer(Evaluate.Curve.PointAt(curve, mid)); Point3 pt3 = Point4.PointDehomogenizer(Evaluate.Curve.PointAt(curve, end)); Vector3 diff = pt1 - pt3; Vector3 diff2 = pt1 - pt2; if ((Vector3.DotProduct(diff, diff) < tolerance && Vector3.DotProduct(diff2, diff2) > tolerance) || !Trigonometry.ArePointsCollinear(pt1, pt2, pt3, tolerance)) { // Get the exact middle value or a random value start + (end - start) * (0.45 + 0.1 * random.NextDouble()); double tMiddle = start + (end - start) * 0.5; // Recurse the two halves. (List <double> tValues, List <Point3> pts)leftHalves = AdaptiveSampleRange(curve, start, tMiddle, tolerance); (List <double> tValues, List <Point3> pts)rightHalves = AdaptiveSampleRange(curve, tMiddle, end, tolerance); leftHalves.tValues.RemoveAt(leftHalves.tValues.Count - 1); List <double> tMerged = leftHalves.tValues.Concat(rightHalves.tValues).ToList(); leftHalves.pts.RemoveAt(leftHalves.pts.Count - 1); List <Point3> ptsMerged = leftHalves.pts.Concat(rightHalves.pts).ToList(); return(tMerged, ptsMerged); } return(new List <double> { start, end }, new List <Point3> { pt1, pt3 }); }