n 阶贝塞尔曲线计算公式——Ts实现


1、什么是贝塞尔曲线

  Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线定义:起始点、终止点(也称锚点)、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。 1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。
  看了这段话,相信你还是不大明白贝塞尔曲线到底是怎么样的,这里放上贝塞尔曲线扫盲贴,有需要的自行阅读!这里有个贝塞尔游戏,有兴趣的可以体验一下!

2、常见贝塞尔曲线

  这里我们放上去常见的贝塞尔曲线效果演示图:

以下公式中:
B(t)为t时间下 点的坐标
P0为起点,Pn为终点,Pi为控制点

  • 一阶贝塞尔曲线(线段)

一阶贝塞尔曲线

一阶贝塞尔曲线通用公式:
B ( t ) = ( 1 − t ) P 0 + t P 1 , t ∈ [ 0 , 1 ] \begin{aligned} B(t)=(1-t)P_0+tP_1, t∈[0,1] \end{aligned} B(t)=(1t)P0+tP1t[0,1]

意义:由 P0 至 P1 的连续点, 描述的一条线段

  • 二阶贝塞尔曲线(抛物线)
    二阶贝塞尔曲线
    二阶贝塞尔曲线通用公式:
    B ( t ) = ( 1 − t ) 2 P 0 + 2 t ( 1 − t ) P 1 + t 2 P 2 , t ∈ [ 0 , 1 ] \begin{aligned} B(t)=(1-t)^2P_0+2t(1-t)P_1+t^2P_2, t∈[0,1] \end{aligned} B(t)=(1t)2P0+2t(1t)P1+t2P2t[0,1]

原理:
由 P0 至 P1 的连续点 Q0,描述一条线段。
由 P1 至 P2 的连续点 Q1,描述一条线段。
由 Q0 至 Q1 的连续点 B(t),描述一条二次贝塞尔曲线。
经验:P1-P0为曲线在P0处的切线

  • 三阶贝塞尔曲线

三阶贝塞尔曲线
三阶贝塞尔曲线通用公式:
B ( t ) = ( 1 − t ) 3 P 0 + 3 t ( 1 − t ) 2 P 1 + 3 t 2 ( 1 − t ) P 2 + t 3 P 3 , t ∈ [ 0 , 1 ] \begin{aligned} B(t)=(1-t)^3P_0+3t(1-t)^2P_1+3t^2(1-t)P_2+t^3P_3, t∈[0,1] \end{aligned} B(t)=(1t)3P0+3t(1t)2P1+3t2(1t)P2+t3P3t[0,1]

  • 四阶贝塞尔曲线

四阶贝塞尔曲线

  • 五阶贝塞尔曲线

五阶贝塞尔曲线

3、贝塞尔曲线通用公式

3.1、贝塞尔曲线通用公式

  我们查阅资料给出的一般公式是这样的:

B ( t ) = ∑ i = 0 n ( n i ) P i ( 1 − t ) n − i t i = ( n 0 ) P 0 ( 1 − t ) n t 0 + ( n 1 ) P 1 ( 1 − t ) n − 1 t 1 + . . . + ( n n − 1 ) P n − 1 ( 1 − t ) 1 t n − 1 + ( n n ) P n ( 1 − t ) 0 t n , t ∈ [ 0 , 1 ] \begin{aligned} B(t)=\sum_{i=0}^n \begin{pmatrix} n\\i \end{pmatrix} P_i(1-t)^{n-i}t^i= \begin{pmatrix} n\\0 \end{pmatrix} P_0(1-t)^nt^0+ \begin{pmatrix} n\\1 \end{pmatrix} P_1(1-t)^{n-1}t^1+...+ \begin{pmatrix} n\\n-1 \end{pmatrix} P_{n-1}(1-t)^1t^{n-1}+ \begin{pmatrix} n\\n \end{pmatrix} P_n(1-t)^0t^n, t\in[0,1] \end{aligned} B(t)=i=0n(ni)Pi(1t)niti=(n0)P0(1t)nt0+(n1)P1(1t)n1t1+...+(nn1)Pn1(1t)1tn1+(nn)Pn(1t)0tnt[0,1]

3.2、思路解析

观察上面公式,可以查看出,其公式是由一个格式固定的表达式之和来表示,这个表达式就是关键:
( n i ) P i ( 1 − t ) n − i t i , t ∈ [ 0 , 1 ] \begin{aligned} \begin{pmatrix} n\\i \end{pmatrix} P_i(1-t)^{n-i}t^i, t\in[0,1] \end{aligned} (ni)Pi(1t)nitit[0,1]

该表达式可分为四个部分看:

  • 从 i 递增到 n 的常数部分
  • P i P_i Pi 坐标部分
  • ( 1 − t ) n − i (1 - t)^{n - i} (1t)ni
  • t i t^i ti

可以看出这四部分都与 i 的值相关,此外 t 值的计算方式为:i/(n+1)

  如果直接从上面的公式上找规律比较抽象,那就从具体的例子中找规律吧。
  设 Bt 为要计算的贝塞尔曲线上的坐标,N 为控制点个数,P0,P1,P2…Pn 为贝塞尔曲线控制点的坐标,当 N 值不同时有如下计算公式:

如 N 为 3 表示贝塞尔曲线的控制点有 3 个点,这时 n 为 2 ,这三个点分别用 P0,P1,P2 表示。

  • N = 3   P = ( 1 − t ) 2 P 0 + 2 ( 1 − t ) t P 1 + t 2 P 2 P=(1-t)^2P_0 + 2(1-t)tP_1 + t^2P_2 P=(1t)2P0+2(1t)tP1+t2P2
  • N = 4   P = ( 1 − t ) 3 P 0 + 3 ( 1 − t ) 2 t P 1 + 3 ( 1 − t ) t 2 P 2 + t 3 P 3 P= (1-t)^3P_0 + 3(1-t)^2tP_1 + 3(1-t)t^2P_2 + t^3P_3 P=(1t)3P0+3(1t)2tP1+3(1t)t2P2+t3P3
  • N = 5   P = ( 1 − t ) 4 P 0 + 4 ( 1 − t ) 3 t P 1 + 6 ( 1 − t ) 2 t 2 P 2 + 4 ( 1 − t ) t 3 P 3 + t 4 P 4 P = (1-t)^4P_0 + 4(1-t)^3tP_1 + 6(1-t)^2t^2P_2 + 4(1-t)t^3P_3 + t^4P_4 P=(1t)4P0+4(1t)3tP1+6(1t)2t2P2+4(1t)t3P3+t4P4

  将贝塞尔曲线一般参数公式中的表达式用如下方式表示,设有常数 a,b 和 c,则该表达式可统一表示为如下形式:
a ( 1 − t ) b t c P n , t ∈ [ 0 , 1 ] a(1 - t)^bt^cP_n, t\in[0,1] a(1t)btcPn,t[0,1]

  分析当 N 分别为3,4,5 时对应 a,b,c 的值:
如 N = 3 时,公式有三个表达式,第一个表达式为 ( 1 − t ) 2 P 0 (1-t)^2P_0 (1t)2P0,其对应 a,b,c 值分别为:1,2,0

  • N = 3: 1,2,0 2,1,1 1,0,2
    a: 1 2 1
    b: 2 1 0
    c: 0 1 2
  • N = 4: 1,3,0 3,2,1 3,1,2 1,0,3
    a: 1 3 3 1
    b: 3 2 1 0
    c: 0 1 2 3
  • N = 5: 1,4,0 4,3,1 6,2,2 4,1,3 1,0,4
    a: 1 4 6 4 1
    b: 4 3 2 1 0
    c: 0 1 2 3 4

  根据上面的分析就可以总结出 a,b,c 对应的取值规则:

  • b: (N - 1) 递减到 0 (b 为 1-t 的幂)
  • c: 0 递增到 (N - 1) (c 为 t 的幂)
  • a: 在 N 分别为 1,2,3,4,5 时将其值用如下形式表示:
    N=1:———1
    N=2:——–1 1
    N=3:——1 2 1
    N=4:—–1 3 3 1
    N=5:—1 4 6 4 1
    a 值的改变规则为: 杨辉三角

3.3、实现方法

  好了,到这里我们基本上已经知道思路了,下面我们使用Ts写一下:

这里使用的是:
Vscode
Cocos Creator

/**
  * 
   * @param ctrlPosArr 贝塞尔曲线控制点坐标
   * @param precison 精度,需要计算的该条贝塞尔曲线上的点的数目
   * @param resArr 该条贝塞尔曲线上的点(二维坐标)
    */
    getBezierPos(ctrlPosArr:Array<cc.Vec2>,precison:number):Array<cc.Vec2>
    {
        cc.log(ctrlPosArr)
        let resArr:Array<cc.Vec2> = new Array<cc.Vec2>();

        /**贝塞尔曲线控制点数目(阶数)*/
        let number:number = ctrlPosArr.length;

        if(number < 2)
        {
            cc.log("控制点数不能小于 2");
            return resArr;
        }

        /**杨辉三角数据 */
        let yangHuiArr:Array<number> = this.getYangHuiTriangle(number);

        //计算坐标
        for (let i = 0; i < precison; i++) {
            
            let t:number = i/precison;
            let tmpX:number = 0;
            let tmpY:number = 0;

            for (let j = 0; j < number; j++) {
                
                tmpX += Math.pow(1 - t,number - j - 1) * ctrlPosArr[j].x * Math.pow(t,j) * yangHuiArr[j];

                tmpY += Math.pow(1 - t,number - j - 1) * ctrlPosArr[j].y * Math.pow(t,j) * yangHuiArr[j];
            }

            // resArr[i].x = tmpX;
            // resArr[i].y = tmpY;

            resArr[i] = new cc.Vec2(tmpX,tmpY);
        }

        return resArr;
    }

    /**
     * 获取杨辉三角对应阶数的值
     * @param num 杨辉三角阶数
     */
    getYangHuiTriangle(num:number):Array<number>
    {
        //计算杨辉三角
        let yangHuiArr = new Array<number>();

        if(num === 1)
        {
            yangHuiArr[0] = 1;
        }
        else
        {
            yangHuiArr[0] = yangHuiArr[1] = 1;

            for (let i = 3; i <= num; i++) 
            {
                let t = new Array<number>();
                for (let j = 0; j < i - 1; j++) 
                {
                    t[j] = yangHuiArr[j];
                }

                yangHuiArr[0] = yangHuiArr[i - 1] = 1;
                for (let j = 0; j < i - 2; j++) 
                {
                    yangHuiArr[j + 1] = t[j] + t[j + 1];            
                }
            }
        }

        cc.log(yangHuiArr);
        return yangHuiArr;
    }

3.4、效果展示

  下面我取几个点,做一下演示:

let p1:cc.Vec2 = cc.v2(0,0);
        let p2:cc.Vec2 = cc.v2(200,200);
        let p3:cc.Vec2 = cc.v2(400,150);
        let p4:cc.Vec2 = cc.v2(500,200);
        this.drawPoint(p1);
        this.drawPoint(p2);
        this.drawPoint(p3);
        this.drawPoint(p4);
        this.drawLine(p1,p2,cc.Color.GREEN);
        this.drawLine(p2,p3,cc.Color.GREEN);
        this.drawLine(p3,p4,cc.Color.GREEN);
        
        let posArr1:Array<cc.Vec2> = [
            cc.v2(-150,80)
            ,cc.v2(1,80)
            ,cc.v2(48,92)
            ,cc.v2(167,159)
            ,cc.v2(309,271)
            ,cc.v2(421,394)
            ,cc.v2(514,498)
            ,cc.v2(597,572)
            ,cc.v2(658,590)
            ,cc.v2(745,550)
            ,cc.v2(802,465),
            cc.v2(841,320)
            ,cc.v2(866,266)
            ,cc.v2(951,163)
            ,cc.v2(1054,133)
            ,cc.v2(1228,126)
            ,cc.v2(1278,128)
            ,cc.v2(1430,128)
        ]

  为了验证正确性,我这里先用ts自带的贝塞尔曲线公式,通过四个点来画出三阶贝塞尔曲线,再用自己的点来画一遍,然后用自己的代码来画n阶贝塞尔曲线。效果如下:
在这里插入图片描述

3.5、Demo下载

  为了方便大家,当然如果有不明白的童鞋也可以在这里点此下载Demo示例

4、结束语


The End
  好了,今天的分享就到这里,如有不足之处,还望大家及时指正,随时欢迎探讨交流!!!


喜欢的朋友们,请收藏、点赞、评论!您的肯定是我写作的不竭动力!

  • 10
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

对酒当歌﹏✍

您的鼓励是我写作的最大动力!

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

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

打赏作者

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

抵扣说明:

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

余额充值