採用Cardinal法構造插枝分段三次樣條曲線 : 程式碼篇
說明:Spline類就是Cardinal樣條曲線了,這個類裡面記錄了4個控制點:m_startControlPoint, m_startPoint, m_endPoint, m_endControlPoint, 分別按順序對應Pk-1, Pk, Pk+1, Pk+2, 由於Cardinal是用多個線段去模擬曲線,所以我們在這裡取20個點,這樣,在Pk 和 Pk+1之間,有20個樣本點,用畫直線的方法將這些點按順序連起來,連成的曲線段就是我們所要的Cardinal 曲線了
本文由timewolf完成,首發於CSDN,作者保留版權。 |
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace CardinalTest
{
/// <summary>
/// the declaration of the Spline, each spline contains 4 control points
/// </summary>
public class Spline
{
// it's the "Pk-1" point
private Point3D m_startControlPoint;
// it's the "Pk" point
private Point3D m_startPoint;
// it's the "Pk+1" point
private Point3D m_endPoint;
// it's the "Pk+2" point
private Point3D m_endControlPoint;
// that means we'll generate 20 sample points between Pk & Pk+1, so "u" will start from 0.00F
// and the increase step is 0.05F
private static readonly int m_sampleCount = 20;
// the "t" in Cardinal altorithm
private static readonly float m_tension = 0.0F;
// store the result sample points
private Point3D[] m_ctrlPoints;
// indicate whether this spline is the first, if it is,
//the m_startControlPoint & m_startPoint will be the same
// because the 4 points can just determine the spline between Pk & Pk+1, so we add a point ahead
// of the Pk-1 manually, so that we can draw the spline between Pk-1 & Pk+1
// similarly, the last Spline's "Pk+2" will be the same as its "Pk+1"
// so that we can draw the spline between Pk+1 & Pk+2
private bool m_isFirst = false;
#region Associated Property Block
public Point3D StartControlPoint
{
get
{
return this.m_startControlPoint;
}
set
{
this.m_startControlPoint = value;
}
}
public Point3D StartPoint
{
get
{
return this.m_startPoint;
}
set
{
this.m_startPoint = value;
}
}
public Point3D EndPoint
{
get
{
return this.m_endPoint;
}
set
{
this.m_endPoint = value;
}
}
public Point3D EndControlPoint
{
get
{
return this.m_endControlPoint;
}
set
{
this.m_endControlPoint = value;
}
}
public Point3D[] CtrlPoints
{
get
{
return this.m_ctrlPoints;
}
}
public bool IsFirst
{
get
{
return this.m_isFirst;
}
set
{
this.m_isFirst = value;
}
}
#endregion
public Spline()
{
m_startControlPoint = new Point3D();
m_startPoint = new Point3D();
m_endPoint = new Point3D();
m_endControlPoint = new Point3D();
m_ctrlPoints = new Point3D[m_sampleCount + 1];
for(int i = 0; i < m_ctrlPoints.Length; i++)
{
m_ctrlPoints[i] = new Point3D();
}
}
/// <summary>
/// A new control point added to the control points, so the previous splines should be updated
/// </summary>
public void AddJoint(Spline prevSpline, Point3D currentPoint)
{
// the previous spline is null, so there's just one point in the control point list;
// so the 4 control points are all the same
// while the 2nd & later points added into the list, the 1st spline's Pk+1 & Pk+2
// will be updated
if (null == prevSpline)
{
this.m_startControlPoint = currentPoint;
this.m_startPoint = currentPoint;
this.m_endPoint = currentPoint;
this.m_endControlPoint = currentPoint;
this.m_isFirst = true;
}
// the previous spline is not null, so update the previous spline's control points
// and update this Spline's points
else
{
// the previous spline is the 1st spline, update its Pk+1 & Pk+2
if (true == prevSpline.m_isFirst)
{
this.m_startControlPoint = prevSpline.StartControlPoint;
this.m_startPoint = prevSpline.StartPoint;
this.m_endPoint = currentPoint;
this.m_endControlPoint = currentPoint;
GenerateSamplePoint();
return;
}
// the previous spline is not the 1st, just update its Pk+2
else
{
prevSpline.EndControlPoint = currentPoint;
prevSpline.GenerateSamplePoint();
// simulate the sample points of current Spline
this.m_startControlPoint = prevSpline.m_startPoint;
this.m_startPoint = prevSpline.m_endPoint;
this.m_endPoint = currentPoint;
this.m_endControlPoint = currentPoint;
GenerateSamplePoint();
}
}
}
/// <summary>
/// use Cardinal altorithm to generate the sample points
/// </summary>
public void GenerateSamplePoint()
{
Point3D startControlPoint = this.StartControlPoint;
Point3D startPoint = this.StartPoint;
Point3D endPoint = this.EndPoint;
Point3D endControlPoint = this.EndControlPoint;
float step = 1.0F / (float)m_sampleCount;
float uValue = 0.00F;
for (int i = 0; i < m_sampleCount; i++)
{
Point3D pointNew = Cardinal(uValue, startControlPoint, startPoint, endPoint, endControlPoint);
this.CtrlPoints[i] = new Point3D(pointNew);
uValue += step;
}
this.CtrlPoints[m_ctrlPoints.Length - 1] = endPoint;
}
public void Draw()
{
for (int i = 0; i < m_ctrlPoints.Length - 1; i++ )
{
Point3D lastP3D = m_ctrlPoints[i];
Point3D nextP3D = m_ctrlPoints[i + 1];
DrawLine(lastP3D, nextP3D, dexStart + i);
}
}
#region Cardinal algorithm
/// <summary>
/// Get a point on the result curve, this point is between startPoint & endPoint
/// </summary>
/// <param name="u">
/// the variable between 0 & 1, the input parameter
/// </param>
/// <param name="startControlPoint">
/// the control point in front of start point, help to determine the appearance of the result curve
/// </param>
/// <param name="startPoint">
/// the start point of the result curve, when u == 0, the returned result is startPoint
/// </param>
/// <param name="endPoint">
/// the end point of the result curve, when u == 1, the returned result is endPoint
/// </param>
/// <param name="endControlPoint">
/// the control point after start point, help to determine the appearance of the result curve
/// </param>
/// <returns>
/// the result point between startPoint & endPoint
/// </returns>
private Point3D Cardinal(float u, Point3D startControlPoint,
Point3D startPoint, Point3D endPoint, Point3D endControlPoint)
{
float s = (1 - m_tension) / 2;
Point3D resultPoint = new Point3D();
resultPoint.X = GetResult(startControlPoint.X, startPoint.X, endPoint.X, endControlPoint.X, s, u);
resultPoint.Y = GetResult(startControlPoint.Y, startPoint.Y, endPoint.Y, endControlPoint.Y, s, u);
resultPoint.Z = GetResult(startControlPoint.Z, startPoint.Z, endPoint.Z, endControlPoint.Z, s, u);
return resultPoint;
}
private float GetResult(float a, float b, float c, float d, float s, float u)
{
float result = 0.0F;
result = a*(2*s*u*u - s*u*u*u - s*u) + b*((2-s)*u*u*u + (s-3)*u*u + 1) +
c*((s-2)*u*u*u + (3-2*s)*u*u + s*u) + d*(s*u*u*u - s*u*u);
return result;
}
#endregion
}
}
namespace CardinalTest
{
/// <summary>
/// Summary description for Point3D.
/// </summary>
public class Point3D
{
private float m_x;
private float m_y;
private float m_z;
public float sizeModulus;
#region Associated Property Declaration
public float X
{
get
{
return m_x;
}
set
{
m_x = value;
}
}
public float Y
{
get
{
return m_y;
}
set
{
m_y = value;
}
}
public float Z
{
get
{
return m_z;
}
set
{
m_z = value;
}
}
#endregion
public Point3D()
{
m_x = 0.0F;
m_y = 0.0F;
m_z = 0.0F;
}
public Point3D(Point3D sourcePoint)
{
m_x = sourcePoint.X;
m_y = sourcePoint.Y;
m_z = sourcePoint.Z;
}
public Point3D(float x, float y, float z)
{
m_x = x;
m_y = y;
m_z = z;
}
public Point3D(double x, double y, double z)
{
m_x = (float)x;
m_y = (float)y;
m_z = (float)z;
}
}
}
使用例項:
定義一個arraylist: m_currentSplines,用來記錄所有的spline
在mousedown裡面呼叫addJoint函式,如果畫完了,就呼叫draw函式~~
/// <summary>
/// Add joint to the path
/// </summary>
/// <param name="nowP3D"></param>
public void AddJoint(Point3D nowP3D)
{
if (null == m_currentSplines || 0 == m_currentSplines.Count)
{
Spline splineNew = new Spline();
splineNew.AddJoint(null, nowP3D);
m_currentSplines.Add(splineNew);
}
else
{
Spline splineNew = new Spline();
Spline lastNew = m_currentSplines[m_currentSplines.Count - 1] as Spline;
splineNew.AddJoint(lastNew, nowP3D);
m_currentSplines.Add(splineNew);
}
}
/// <summary>
/// Redraw the path and joints
/// </summary>
/// <returns></returns>
public void Draw()
{
int count = m_currentSplines.Count;
for (int i = 0; i < count; i++)
{
Spline spline = m_currentSplines[i] as Spline;
if (true == spline.IsFirst)
{
continue;
}
spline.Draw();
}
}