using System; namespace AIStudio.Wpf.DiagramDesigner.Geometrys { /// <summary> /// Bezier Spline methods /// </summary> /// <remarks> /// Modified: Peter Lee (peterlee.com.cn < at > gmail.com) /// Update: 2009-03-16 /// /// see also: /// Draw a smooth curve through a set of 2D points with Bezier primitives /// http://www.codeproject.com/KB/graphics/BezierSpline.aspx /// By Oleg V. Polikarpotchkin /// /// Algorithm Descripition: /// /// To make a sequence of individual Bezier curves to be a spline, we /// should calculate Bezier control points so that the spline curve /// has two continuous derivatives at knot points. /// /// Note: `[]` denotes subscript /// `^` denotes supscript /// `'` denotes first derivative /// `''` denotes second derivative /// /// A Bezier curve on a single interval can be expressed as: /// /// B(t) = (1-t)^3 P0 + 3(1-t)^2 t P1 + 3(1-t)t^2 P2 + t^3 P3 (*) /// /// where t is in [0,1], and /// 1. P0 - first knot point /// 2. P1 - first control point (close to P0) /// 3. P2 - second control point (close to P3) /// 4. P3 - second knot point /// /// The first derivative of (*) is: /// /// B'(t) = -3(1-t)^2 P0 + 3(3t^2–4t+1) P1 + 3(2–3t)t P2 + 3t^2 P3 /// /// The second derivative of (*) is: /// /// B''(t) = 6(1-t) P0 + 6(3t-2) P1 + 6(1–3t) P2 + 6t P3 /// /// Considering a set of piecewise Bezier curves with n+1 points /// (Q[0..n]) and n subintervals, the (i-1)-th curve should connect /// to the i-th one: /// /// Q[0] = P0[1], /// Q[1] = P0[2] = P3[1], ... , Q[i-1] = P0[i] = P3[i-1] (i = 1..n) (@) /// /// At the i-th subinterval, the Bezier curve is: /// /// B[i](t) = (1-t)^3 P0[i] + 3(1-t)^2 t P1[i] + /// 3(1-t)t^2 P2[i] + t^3 P3[i] (i = 1..n) /// /// applying (@): /// /// B[i](t) = (1-t)^3 Q[i-1] + 3(1-t)^2 t P1[i] + /// 3(1-t)t^2 P2[i] + t^3 Q[i] (i = 1..n) (i) /// /// From (i), the first derivative at the i-th subinterval is: /// /// B'[i](t) = -3(1-t)^2 Q[i-1] + 3(3t^2–4t+1) P1[i] + /// 3(2–3t)t P2[i] + 3t^2 Q[i] (i = 1..n) /// /// Using the first derivative continuity condition: /// /// B'[i-1](1) = B'[i](0) /// /// we get: /// /// P1[i] + P2[i-1] = 2Q[i-1] (i = 2..n) (1) /// /// From (i), the second derivative at the i-th subinterval is: /// /// B''[i](t) = 6(1-t) Q[i-1] + 6(3t-2) P1[i] + /// 6(1-3t) P2[i] + 6t Q[i] (i = 1..n) /// /// Using the second derivative continuity condition: /// /// B''[i-1](1) = B''[i](0) /// /// we get: /// /// P1[i-1] + 2P1[i] = P2[i] + 2P2[i-1] (i = 2..n) (2) /// /// Then, using the so-called "natural conditions": /// /// B''[1](0) = 0 /// /// B''[n](1) = 0 /// /// to the second derivative equations, and we get: /// /// 2P1[1] - P2[1] = Q[0] (3) /// /// 2P2[n] - P1[n] = Q[n] (4) /// /// From (1)(2)(3)(4), we have 2n conditions for n first control points /// P1[1..n], and n second control points P2[1..n]. /// /// Eliminating P2[1..n], we get (be patient to get :-) a set of n /// equations for solving P1[1..n]: /// /// 2P1[1] + P1[2] + = Q[0] + 2Q[1] /// P1[1] + 4P1[2] + P1[3] = 4Q[1] + 2Q[2] /// ... /// P1[i-1] + 4P1[i] + P1[i+1] = 4Q[i-1] + 2Q[i] /// ... /// P1[n-2] + 4P1[n-1] + P1[n] = 4Q[n-2] + 2Q[n-1] /// P1[n-1] + 3.5P1[n] = (8Q[n-1] + Q[n]) / 2 /// /// From this set of equations, P1[1..n] are easy but tedious to solve. /// </remarks> public static class BezierSpline { /// <summary> /// Get open-ended Bezier Spline Control Points. /// </summary> /// <param name="knots">Input Knot Bezier spline points.</param> /// <param name="firstControlPoints">Output First Control points array of knots.Length - 1 length.</param> /// <param name="secondControlPoints">Output Second Control points array of knots.Length - 1 length.</param> /// <exception cref="ArgumentNullException"><paramref name="knots"/> parameter must be not null.</exception> /// <exception cref="ArgumentException"><paramref name="knots"/> array must containg at least two points.</exception> public static void GetCurveControlPoints(PointBase[] knots, out PointBase[] firstControlPoints, out PointBase[] secondControlPoints) { if (knots == null) throw new ArgumentNullException("knots"); int n = knots.Length - 1; if (n < 1) throw new ArgumentException("At least two knot points required", "knots"); if (n == 1) { // Special case: Bezier curve should be a straight line. firstControlPoints = new PointBase[1]; // 3P1 = 2P0 + P3 firstControlPoints[0] = new PointBase((2 * knots[0].X + knots[1].X) / 3, (2 * knots[0].Y + knots[1].Y) / 3); secondControlPoints = new PointBase[1]; // P2 = 2P1 – P0 secondControlPoints[0] = new PointBase(2 * firstControlPoints[0].X - knots[0].X, 2 * firstControlPoints[0].Y - knots[0].Y); return; } // Calculate first Bezier control points // Right hand side vector double[] rhs = new double[n]; // Set right hand side X values for (int i = 1; i < n - 1; ++i) rhs[i] = 4 * knots[i].X + 2 * knots[i + 1].X; rhs[0] = knots[0].X + 2 * knots[1].X; rhs[n - 1] = (8 * knots[n - 1].X + knots[n].X) / 2.0; // Get first control points X-values double[] x = GetFirstControlPoints(rhs); // Set right hand side Y values for (int i = 1; i < n - 1; ++i) rhs[i] = 4 * knots[i].Y + 2 * knots[i + 1].Y; rhs[0] = knots[0].Y + 2 * knots[1].Y; rhs[n - 1] = (8 * knots[n - 1].Y + knots[n].Y) / 2.0; // Get first control points Y-values double[] y = GetFirstControlPoints(rhs); // Fill output arrays. firstControlPoints = new PointBase[n]; secondControlPoints = new PointBase[n]; for (int i = 0; i < n; ++i) { // First control point firstControlPoints[i] = new PointBase(x[i], y[i]); // Second control point if (i < n - 1) secondControlPoints[i] = new PointBase(2 * knots[i + 1].X - x[i + 1], 2 * knots[i + 1].Y - y[i + 1]); else secondControlPoints[i] = new PointBase((knots[n].X + x[n - 1]) / 2, (knots[n].Y + y[n - 1]) / 2); } } /// <summary> /// Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. /// </summary> /// <param name="rhs">Right hand side vector.</param> /// <returns>Solution vector.</returns> private static double[] GetFirstControlPoints(double[] rhs) { int n = rhs.Length; double[] x = new double[n]; // Solution vector. double[] tmp = new double[n]; // Temp workspace. double b = 2.0; x[0] = rhs[0] / b; for (int i = 1; i < n; i++) // Decomposition and forward substitution. { tmp[i] = 1 / b; b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; x[i] = (rhs[i] - x[i - 1]) / b; } for (int i = 1; i < n; i++) x[n - i - 1] -= tmp[n - i] * x[n - i]; // Backsubstitution. return x; } } }