/// <summary> /// Builds a list of <see cref="Bezier"/> from a list of <see cref="Point"/> for /// the specified error tolerance. /// </summary> /// <param name="points"> /// A list of <see cref="Point"/> to build from. /// </param> /// <param name="tolerance"> /// The error tolerance value between 4 to 20 inclusive. /// </param> /// <returns> /// A list of <see cref="Bezier"/> from a list of <see cref="Point"/> for /// the specified error tolerance. /// </returns> public static ReadOnlyCollection <Bezier> Build( IEnumerable <Point> points, double tolerance = DefaultError) { if (points == null) { throw new ArgumentNullException("points"); } if (!tolerance.IsBetween(MinimumError, MaximumError)) { throw new ArgumentOutOfRangeException( string.Format( "The error tolerance must be between {0} and {1} inclusive.", MinimumError, MaximumError), "tolerance"); } List <Point> list = points.Filter(MinimumDistance); if (list.Count < 2) { throw new ArgumentException("There must be at least two distinct points in the list.", "points"); } List <Bezier> bezierList = new List <Bezier>(); ReadOnlyCollection <Point> bezierPoints = list.AsReadOnly(); Vector leftTangent = bezierPoints.LeftTangent(); Vector rightTangent = bezierPoints.RightTangent(); BezierBuilder bezierBuilder = new BezierBuilder( bezierPoints, leftTangent, rightTangent, tolerance); bezierBuilder.Build(bezierList); return(bezierList.AsReadOnly()); }
/// <summary> /// Reparameterization by Newton-Raphson iteration to find better root for lengh values. /// </summary> /// <param name="bezierBuilder"> /// The <see cref="BezierBuilder"/> instance. /// </param> /// <returns> /// The reparameterization by Newton-Raphson iteration to find better root for lengh values. /// </returns> internal ReadOnlyCollection <double> Reparameterize(BezierBuilder bezierBuilder) { if (bezierBuilder == null) { throw new ArgumentNullException("bezierBuilder"); } ReadOnlyCollection <Point> points = bezierBuilder.Points; ReadOnlyCollection <double> parameterizedLength = bezierBuilder.ParameterizedLength; Debug.Assert(points.Count == parameterizedLength.Count); List <double> values = new List <double>(); for (int index = 0; index < points.Count; index++) { double value = this.NewtonRaphsonRootFind(points[index], parameterizedLength[index]); values.Add(value); } return(values.AsReadOnly()); }
/// <summary> /// Calculates the maximum deviation of points to this instance. /// </summary> /// <param name="bezierBuilder"> /// The <see cref="BezierBuilder"/> instance. /// </param> /// <param name="index"> /// The index of the maximum error point. /// </param> /// <returns> /// The maximum deviation of points to this instance. /// </returns> internal double CalculateMaximumDistance(BezierBuilder bezierBuilder, out int index) { if (bezierBuilder == null) { throw new ArgumentNullException("bezierBuilder"); } ReadOnlyCollection <Point> points = bezierBuilder.Points; ReadOnlyCollection <double> parameterizedLength = bezierBuilder.ParameterizedLength; Debug.Assert(points.Count == parameterizedLength.Count); double maximumDistanceError = 0.0; index = points.Count / 2; Point[] bezierPoints = { (Point)p0, (Point)p1, (Point)p2, (Point)p3 }; for (int i = 1; i < points.Count; i++) { Point point = bezierPoints.At(parameterizedLength[i]); Vector vector = point - points[i]; double distance = vector.LengthSquared; if (distance >= maximumDistanceError) { maximumDistanceError = distance; index = i; } } return(maximumDistanceError); }
/// <summary> /// Creates a new instance of <see cref="Bezier"/> for the specified <see cref="BezierBuilder"/>. /// </summary> /// <param name="bezierBuilder"> /// The <see cref="BezierBuilder"/> instance. /// </param> /// <returns> /// The new instance of <see cref="Bezier"/> for the specified <see cref="BezierBuilder"/>. /// </returns> internal static Bezier Create(BezierBuilder bezierBuilder) { if (bezierBuilder == null) { throw new ArgumentNullException("bezierBuilder"); } ReadOnlyCollection <Point> points = bezierBuilder.Points; ReadOnlyCollection <double> parameterizedLength = bezierBuilder.ParameterizedLength; Debug.Assert(points.Count == parameterizedLength.Count); Vector leftTangent = bezierBuilder.LeftTangent; Vector rightTangent = bezierBuilder.RightTangent; // Compute the A's List <VectorPair> matrixA = new List <VectorPair>(); for (int i = 0; i < points.Count; i++) { VectorPair pair = new VectorPair(leftTangent, rightTangent, parameterizedLength[i]); matrixA.Add(pair); } // Matrix C double[,] mc = new double[2, 2]; // Matrix X double[] mx = new double[2]; // Create the C and X matrices mc[0, 0] = 0.0; mc[0, 1] = 0.0; mc[1, 0] = 0.0; mc[1, 1] = 0.0; mx[0] = 0.0; mx[1] = 0.0; for (int i = 0; i < points.Count; i++) { mc[0, 0] += matrixA[i].DotFirst(); mc[0, 1] += matrixA[i].Dot(); mc[1, 0] = mc[0, 1]; mc[1, 1] += matrixA[i].DotSecond(); Vector vector = (Vector)points[i] - ( ((Vector)points.First() * parameterizedLength[i].B0()) + ( ((Vector)points.First() * parameterizedLength[i].B1()) + ( ((Vector)points.Last() * parameterizedLength[i].B2()) + ((Vector)points.Last() * parameterizedLength[i].B3()) ) ) ); mx[0] += matrixA[i].DotFirst(vector); mx[1] += matrixA[i].DotSecond(vector); } // Compute the determinants of C and X matrices double det_C0_C1 = mc[0, 0] * mc[1, 1] - mc[1, 0] * mc[0, 1]; double det_C0_X = mc[0, 0] * mx[1] - mc[1, 0] * mx[0]; double det_X_C1 = mx[0] * mc[1, 1] - mx[1] * mc[0, 1]; // Finally, derive alpha values, left and right double alpha_l = det_C0_C1.IsCloseTo(0) ? 0.0 : det_X_C1 / det_C0_C1; double alpha_r = det_C0_C1.IsCloseTo(0) ? 0.0 : det_C0_X / det_C0_C1; // // If alpha negative, use the Wu/Barsky heuristic (see text) // (if alpha is 0, you get coincident control points that lead to // divide by zero in any subsequent NewtonRaphsonRootFind() call. // Point p0 = points.First(); Point p3 = points.Last(); double segmentLength = points.First().DistanceTo(points.Last()); double epsilon = 1.0e-6 * segmentLength; if (alpha_l < epsilon || alpha_r < epsilon) { // // Fall back on standard (probably inaccurate) formula, // and subdivide further if needed. // double distance = segmentLength / 3.0; return(new Bezier( p0, (leftTangent * distance) + p0, (rightTangent * distance) + p3, p3)); } // // First and last control points of the Bezier curve are // positioned exactly at the first and last data points // Control points 1 and 2 are positioned an alpha distance out // on the tangent vectors, left and right, respectively // return(new Bezier( p0, (leftTangent * alpha_l) + p0, (rightTangent * alpha_r) + p3, p3)); }
private void Build(List <Bezier> bezierList) { // Use heuristic if region only has two points in it if (points.Count == 2) { Point p0 = points.First(); Point p3 = points.Last(); double distance = p0.DistanceTo(p3) / 3.0; Point p1 = p0 + leftTangent * distance; Point p2 = p3 + rightTangent * distance; bezierList.Add(new Bezier(p0, p1, p2, p3)); return; } // Parameterize points, and attempt to fit curve parameterizedLength = points.Parameterize(); Bezier bezier = Bezier.Create(this); // Find max deviation of points to fitted curve int index; double maximumDistanceError = bezier.CalculateMaximumDistance(this, out index); if (maximumDistanceError < tolerance) { bezierList.Add(bezier); return; } // If error not too large, try some reparameterization and iteration double iterationError = tolerance * tolerance; if (maximumDistanceError < iterationError) { for (int i = 0; i < MaximumIterationCount; i++) { parameterizedLength = bezier.Reparameterize(this); bezier = Bezier.Create(this); maximumDistanceError = bezier.CalculateMaximumDistance(this, out index); if (maximumDistanceError < tolerance) { bezierList.Add(bezier); return; } } } // Fitting failed -- split at max error point and fit recursively Vector centerTangent = points.CenterTangent(index); BezierBuilder builder = new BezierBuilder( points .Take(index + 1) .ToList() .AsReadOnly(), leftTangent, centerTangent, tolerance); builder.Build(bezierList); centerTangent.Negate(); builder = new BezierBuilder( points .Skip(index + 1) .ToList() .AsReadOnly(), centerTangent, rightTangent, tolerance); builder.Build(bezierList); }