[原创] C# N阶贝塞尔曲线底层算法分享(不使用任何外部库)

前言

关于贝塞尔曲线的原理请看我的上一篇文章

核心代码

使用递归方式计算贝塞尔曲线上的点

        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


    }
}

创作不易,转载请保留出处

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工控闪剑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值