几何向量:贝塞尔曲线

刚好又用上了二维贝塞尔曲线,顺便写一篇博客。
贝塞尔曲线算是二维开发中偶尔会碰到的,我依稀记得我第一次认识贝塞尔曲线是选修photoshop课程用钢笔工具拉曲线。工作中第一次用贝塞尔计算,是做手游的时候,做了一个怪物爆装物品散乱掉落动画的功能。现在做的二维项目中一个模块功能又需要用到了,所以上百度百科:贝塞尔曲线
贝塞尔曲线的核心:根据两个或更多顶点绘制一条光滑的曲线(或直线),当然使用最频繁的是四个顶点组成的贝塞尔曲线。下面实现以下常见的贝塞尔曲线:

1.两个顶点
给定点p1、p2,线性贝塞尔曲线只是一条两点之间的直线,如图:
在这里插入图片描述
虽然数学上直线是矢量的,但是方程的解是离散的,我们假设t=1/100,也就是说在p1p2上每隔1/100长度算一次p(x,y)的坐标,求解的p点的点云(99个顶点)也可以看做一条直线。
这个一眼都看得出来我就不写验证了。

2.三个顶点
三个顶点就开始呈现曲线的样式了,如下:
在这里插入图片描述
假设二维平面上存在p1、p2、p3三个顶点,a1、a2分别为p1p2、p2p3上的动点,且|p1a1|/|p1p2| = |p2a2|/|p2p3| = t,同时a1、a2组成的线段a1a2上,存在动点p,同时|a1p|/|a1a2| = t,随着t由0-1离散的增大,也可以求得一个p点的点云数组,而且呈现出平滑的曲线。
下面我们就来实现一下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ThreePointBizierCurve : MonoBehaviour
{
    [Range(0, 1)]
    public float T = 0;

    public RectTransform P1;
    public RectTransform P2;
    public RectTransform P3;

    public RectTransform P;

    private void Update()
    {
#if UNITY_EDITOR
        Vector2 halfscreen = new Vector2(Screen.width / 2f, Screen.height / 2f);
        Debug.DrawLine(P1.anchoredPosition + halfscreen, P2.anchoredPosition + halfscreen, Color.red);
        Debug.DrawLine(P2.anchoredPosition + halfscreen, P3.anchoredPosition + halfscreen, Color.yellow);
#endif
        Vector2 p = CalculateBizierCurvePoint(P1.anchoredPosition, P2.anchoredPosition, P3.anchoredPosition, T);
#if UNITY_EDITOR
        Debug.LogFormat("curve point p = {0}", p);
#endif
        P.anchoredPosition = p;
    }

    private Vector2 CalculateBizierCurvePoint(Vector2 p1, Vector2 p2, Vector2 p3, float t)
    {
        Vector2 p = (1 - t) * (1 - t) * p1 + 2 * t * (1 - t) * p2 + t * t * p3;
        return p;
    }
}

效果如下:
在这里插入图片描述
为了方便看出白色小圆球的离散运动轨迹,我特意调整了camera->don’tclear,可以叠加帧缓冲的图像。

3.四个顶点
四个顶点的情况也类似,就是三个顶点的延伸,如下:
在这里插入图片描述
主要贝塞尔曲线的规则,让我们可以直接将4个顶点的问题降到3个顶点的条件来计算求解,如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FourPointBezierCurve : MonoBehaviour
{
    [Range(0, 1)]
    public float T = 0;

    public RectTransform P1;
    public RectTransform P2;
    public RectTransform P3;
    public RectTransform P4;

    public RectTransform P;

    void Start()
    {

    }

    void Update()
    {
#if UNITY_EDITOR
        Vector2 halfscreen = new Vector2(Screen.width / 2f, Screen.height / 2f);
        Debug.DrawLine(P1.anchoredPosition + halfscreen, P2.anchoredPosition + halfscreen, Color.red);
        Debug.DrawLine(P2.anchoredPosition + halfscreen, P3.anchoredPosition + halfscreen, Color.yellow);
        Debug.DrawLine(P3.anchoredPosition + halfscreen, P4.anchoredPosition + halfscreen, Color.blue);
#endif
        Vector2 p1 = P1.anchoredPosition;
        Vector2 p2 = P2.anchoredPosition;
        Vector2 p3 = P3.anchoredPosition;
        Vector2 p4 = P4.anchoredPosition;
        float t = T;
        Vector2 p5 = CalucuateTwoPointCurvePoint(p1, p2, t);
        Vector2 p6 = CalucuateTwoPointCurvePoint(p2, p3, t);
        Vector2 p7 = CalucuateTwoPointCurvePoint(p3, p4, t);
        Vector2 p = CalculateThreePointCurvePoint(p5, p6, p7, t);
#if UNITY_EDITOR
        Debug.LogFormat("curve point p = {0}", p);
#endif
        P.anchoredPosition = p;
    }

    private Vector2 CalucuateTwoPointCurvePoint(Vector2 p1, Vector2 p2, float t)
    {
        Vector2 p = (1 - t) * p1 + t * p2;
        return p;
    }

    private Vector2 CalculateThreePointCurvePoint(Vector2 p1, Vector2 p2, Vector2 p3, float t)
    {
        Vector2 p = (1 - t) * (1 - t) * p1 + 2 * t * (1 - t) * p2 + t * t * p3;
        return p;
    }
}

效果如图:
在这里插入图片描述
4.n个顶点
因为贝塞尔曲线的规则可以让n个顶点的问题降维到n-1的顶点的计算,那么我们递归套娃,是不是将n个顶点的问题变成由最基本的3个顶点计算规则来实现呢?
例如:5个顶点的问题,先降到成4个顶点,再降维到3个顶点,就能得到离散的曲线?我觉得是可以的,下面继续实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MultiPointBezierCurve : MonoBehaviour
{
    [Range(0, 1)]
    public float T = 0;

    public RectTransform[] Ps;

    public RectTransform P;

    private Vector2[] origPoints;                    //储存原始坐标点
    private Vector2[] reducePoints;                  //储存降维坐标点

    private Vector2 halfScreen;

    void Start()
    {
        int pslen = Ps.Length;
        origPoints = new Vector2[pslen];
        reducePoints = new Vector2[pslen];
        for (int i = 0; i < Ps.Length; i++)
        {
            origPoints[i] = Ps[i].anchoredPosition;
            reducePoints[i] = Ps[i].anchoredPosition;
        }
        halfScreen = new Vector2(Screen.width / 2f, Screen.height / 2f);
    }

    void Update()
    {
#if UNITY_EDITOR
        for (int i = 1; i < origPoints.Length; i++)
        {
            Vector2 fpos = origPoints[i - 1] + halfScreen;
            Vector2 tpos = origPoints[i] + halfScreen;
            float colr = (float)i / (float)origPoints.Length;
            float colg = (float)(origPoints.Length - i) / (float)origPoints.Length;
            Debug.DrawLine(fpos, tpos, new Color(colr, colg, colr, 1));
        }
#endif
        float t = T;
        while (reducePoints.Length > 3)
        {
            reducePoints = ReduceBezierPoints(reducePoints, t);
        }
        Vector2 p1 = reducePoints[0];
        Vector2 p2 = reducePoints[1];
        Vector2 p3 = reducePoints[2];
        Vector2 p = CalculateThreePointCurvePoint(p1, p2, p3, t);
#if UNITY_EDITOR
        Debug.LogFormat("curve point p = {0}", p);
#endif
        P.anchoredPosition = p;
        //还原坐标点,刷新计算
        reducePoints = new Vector2[origPoints.Length];
        for (int i = 0; i < origPoints.Length; i++)
        {
            reducePoints[i] = origPoints[i];
        }
    }
    /// <summary>
    /// 降维n个坐标点到n-1个坐标点
    /// </summary>
    /// <param name="pts"></param>
    /// <param name="t"></param>
    /// <returns></returns>
    private Vector2[] ReduceBezierPoints(Vector2[] pts, float t)
    {
        int len = pts.Length;
        Vector2[] dpoints = new Vector2[len - 1];
        for (int i = 0; i < dpoints.Length; i++)
        {
            Vector2 fpoint = pts[i];
            Vector2 tpoint = pts[i + 1];
            Vector2 ppoint = CalucuateTwoPointCurvePoint(fpoint, tpoint, t);
            dpoints[i] = ppoint;
        }
        return dpoints;
    }

    private Vector2 CalucuateTwoPointCurvePoint(Vector2 p1, Vector2 p2, float t)
    {
        Vector2 p = (1 - t) * p1 + t * p2;
        return p;
    }

    private Vector2 CalculateThreePointCurvePoint(Vector2 p1, Vector2 p2, Vector2 p3, float t)
    {
        Vector2 p = (1 - t) * (1 - t) * p1 + 2 * t * (1 - t) * p2 + t * t * p3;
        return p;
    }
}

代码核心就做了一下n到n-1个顶点的降维处理,效果如下:
在这里插入图片描述
这里我测试绘制了7个顶点的贝塞尔曲线,还是很ok的,那么说明我们的想法没错。
好,明天又是周一上班了,以后有时间我们继续。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值