mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-03-13 04:59:26 +08:00
207 lines
8.1 KiB
C#
207 lines
8.1 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|