예제 #1
0
        /// <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());
        }
예제 #2
0
        /// <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());
        }
예제 #3
0
        /// <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);
        }
예제 #4
0
        /// <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));
        }
예제 #5
0
        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);
        }