前言
关于贝塞尔曲线的原理请看我的上一篇文章
核心代码
使用递归方式计算贝塞尔曲线上的点
private List<fPoint> pCollectPoint(List<fPoint> pList, float pTime)
{
List<fPoint> nList = new List<fPoint> { }; //初始化新的坐标集合
int listNum = pList.Count; //获取原始集合坐标数量
if (listNum < 2)
{
return pList; //返回目标位置
}
for (int n = 1; n < listNum; n++)
{
float nowX = (pList[n].X - pList[n - 1].X) * pTime + pList[n - 1].X;
float nowY = (pList[n].Y - pList[n - 1].Y) * pTime + pList[n - 1].Y;
fPoint nowP = new fPoint();
nowP.X = nowX;
nowP.Y = nowY;
nList.Add(nowP);
}
List<fPoint> p = pCollectPoint(nList, pTime); //递归获取目标点(递归次数和控制点数量相等
return p; //理论上这行代码永远不会执行,呵呵呵~
}
private List<fPoint> mDataSetPoints(List<fPoint> pList, int count)
{
float seedNum = 0;
//seedNum 的值是实际生成坐标的数量
//seedNum 可以直接赋值,这个值决定生成坐标的数量
//数值越大精度越高
//==========开始计算生成点位数量=============
int bcEndNum = pList.Count - 1;
float bStartX = pList[0].X;
float bStartY = pList[0].Y;
float bEndX = pList[bcEndNum].X;
float bEndY = pList[bcEndNum].Y;
float subrX = Math.Abs(bEndX - bStartX);
float subrY = Math.Abs(bEndY - bStartY);
switch (count)
{
case -1:
seedNum = subrX < subrY ? subrX : subrY;
break;
case 0:
seedNum = subrX > subrY ? subrX : subrY;
break;
default:
seedNum = Convert.ToSingle(count);
break;
}
//==========生成数量计算完毕=============
float pStep = 1 / seedNum;
List<fPoint> rpoint = new List<fPoint> { pList[0] };
for (float pTime = pStep; pTime <= 1; pTime += pStep)
{
List<fPoint> lfpr = pCollectPoint(pList, pTime);
fPoint fpr = lfpr[0];
rpoint.Add(fpr);
}
return rpoint;
}
完整测试代码(自行绘制相关控件)
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace GongKongShanJian
{
/// <summary>
/// 单精度 位置点 结构体
/// </summary>
public struct fPoint
{
public float X;
public float Y;
}
/// <summary>
/// 整数 位置点 结构体
/// </summary>
public struct iPoint
{
public int X;
public int Y;
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btCalc_Click(object sender, EventArgs e)
{
int sx = int.Parse(tbStartX.Text); //起始点X(TextBox)
int sy = int.Parse(tbStartY.Text); //起始点Y(TextBox)
int cx = int.Parse(tbControlX.Text); //控制点X(TextBox)
int cy = int.Parse(tbControlY.Text); //控制点Y(TextBox)
int ex = int.Parse(tbEndX.Text); //终止点X(TextBox)
int ey = int.Parse(tbEndY.Text); //终止点Y(TextBox)
int num = int.Parse(tbNum.Text); //生成数量(-1,最大值差值;0,最小差值;其他值为实际数量
List<iPoint> ri = GetBezierCurve(sx, sy, cx, cy, ex, ey, num);
string rstr = string.Empty;
foreach (iPoint ip in ri)
{
string ix = ip.X.ToString();
string iy = ip.Y.ToString();
ix = ix.PadLeft(3);
iy = iy.PadLeft(3);
rstr += string.Format("{0},{1}\r\n", ix, iy);
}
tbOutput.Text = rstr; //(TextBox),多行
}
#region 对外开放的接口方法
/// <summary>
/// 获取贝塞尔曲线上所有点的集合[主开放接口]
/// </summary>
/// <param name="iList">
/// <para>基准控制点集合</para>
/// <para>第一个点必须是起始点</para>
/// <para>最后一个点必须是终止点</para>
/// </param>
/// <param name="count"><para>需要返回经过点的数量</para>
/// <para>-1取最小坐标差值</para>
/// <para>0取最小坐标差值</para>
/// <para>其他正数为自定义数量</para></param>
/// <returns>返回经过点集合</returns>
public List<iPoint> GetBezierCurve(List<iPoint> iList, int count)
{
List<fPoint> fList = new List<fPoint> { };
fPoint fc = new fPoint();
foreach (iPoint ic in iList)
{
fc.X = Convert.ToSingle(ic.X);
fc.Y = Convert.ToSingle(ic.Y);
fList.Add(fc);
}
List<fPoint> nfList = mDataSetPoints(fList, count);
return ConvertToInt(nfList);
}
/// <summary>
/// 可调用接口 获取二阶贝塞尔曲线经过轨迹点的集合
/// </summary>
/// <param name="pStart">起始点</param>
/// <param name="pControl">控制点</param>
/// <param name="pEnd">目标点</param>
/// <returns>返回经过点集合</returns>
public List<iPoint> GetBezierCurve(iPoint pStart, iPoint pControl, iPoint pEnd, int count)
{
List<iPoint> iList = new List<iPoint> { pStart, pControl, pEnd };
return GetBezierCurve(iList, count);
}
/// <summary>
/// 可调用接口 获取二阶贝塞尔曲线经过轨迹点的集合
/// </summary>
/// <param name="pStartX">起始点X整数值</param>
/// <param name="pStartY">起始点Y整数值</param>
/// <param name="pControlX">控制点X整数值</param>
/// <param name="pControlY">控制点Y整数值</param>
/// <param name="pEndX">目标点X整数值</param>
/// <param name="pEndY">目标点Y整数值</param>
/// <returns>返回经过点集合</returns>
public List<iPoint> GetBezierCurve(int pStartX, int pStartY, int pControlX, int pControlY, int pEndX, int pEndY, int count)
{
List<iPoint> iInput = new List<iPoint> { };
//封装起始点
iPoint iStart = new iPoint();
iStart.X = pStartX;
iStart.Y = pStartY;
iInput.Add(iStart);
//封装控制点
iPoint pControl = new iPoint();
pControl.X = pControlX;
pControl.Y = pControlY;
iInput.Add(pControl);
//封装目标点
iPoint pEnd = new iPoint();
pEnd.X = pEndX;
pEnd.Y = pEndY;
iInput.Add(pEnd);
return GetBezierCurve(iInput, count);
}
#endregion
#region 内部数据处理方法
/// <summary>
/// 单精度float 数值转换为整数int
/// </summary>
/// <param name="fList">单精度数据集合</param>
/// <returns></returns>
private List<iPoint> ConvertToInt(List<fPoint> fList)
{
List<iPoint> ipList = new List<iPoint> { };
foreach (fPoint sfp in fList)
{
iPoint sip = new iPoint();
sip.X = Convert.ToInt32(sfp.X);
sip.Y = Convert.ToInt32(sfp.Y);
ipList.Add(sip);
}
return ipList;
}
/// <summary>
/// 计算贝塞尔曲线上坐标点单点位置
/// </summary>
/// <param name="pList">贝塞尔条件坐标集合</param>
/// <param name="pTime">控制因数</param>
/// <returns></returns>
private List<fPoint> pCollectPoint(List<fPoint> pList, float pTime)
{
List<fPoint> nList = new List<fPoint> { }; //初始化新的坐标集合
int listNum = pList.Count; //获取原始集合坐标数量
if (listNum < 2)
{
return pList; //返回目标位置
}
for (int n = 1; n < listNum; n++)
{
float nowX = (pList[n].X - pList[n - 1].X) * pTime + pList[n - 1].X;
float nowY = (pList[n].Y - pList[n - 1].Y) * pTime + pList[n - 1].Y;
fPoint nowP = new fPoint();
nowP.X = nowX;
nowP.Y = nowY;
nList.Add(nowP);
}
List<fPoint> p = pCollectPoint(nList, pTime); //递归获取目标点(递归次数和控制点数量相等
return p; //理论上这行代码永远不会执行,呵呵呵~
}
/// <summary>
/// 收集贝塞尔曲线坐标点全部点的位置集合
/// </summary>
/// <param name="pList">
/// <para>基准控制点集合</para>
/// <para>第一个点必须是起始点</para>
/// <para>最后一个点必须是终止点</para>
/// </param>
/// <param name="count"><para>需要返回经过点的数量</para>
/// <para>-1取最小坐标差值</para>
/// <para>0取最小坐标差值</para>
/// <para>其他正数为自定义数量</para></param>
/// <returns></returns>
private List<fPoint> mDataSetPoints(List<fPoint> pList, int count)
{
float seedNum = 0;
//seedNum 的值是实际生成坐标的数量
//seedNum 可以直接赋值,这个值决定生成坐标的数量
//数值越大精度越高
//==========开始计算生成点位数量=============
int bcEndNum = pList.Count - 1;
float bStartX = pList[0].X;
float bStartY = pList[0].Y;
float bEndX = pList[bcEndNum].X;
float bEndY = pList[bcEndNum].Y;
float subrX = Math.Abs(bEndX - bStartX);
float subrY = Math.Abs(bEndY - bStartY);
switch (count)
{
case -1:
seedNum = subrX < subrY ? subrX : subrY;
break;
case 0:
seedNum = subrX > subrY ? subrX : subrY;
break;
default:
seedNum = Convert.ToSingle(count);
break;
}
//==========生成数量计算完毕=============
float pStep = 1 / seedNum;
List<fPoint> rpoint = new List<fPoint> { pList[0] };
for (float pTime = pStep; pTime <= 1; pTime += pStep)
{
List<fPoint> lfpr = pCollectPoint(pList, pTime);
fPoint fpr = lfpr[0];
rpoint.Add(fpr);
}
return rpoint;
}
#endregion
}
}