贝塞尔曲线开发相关总结

提示:个人学习总结,如有错误,敬请指正。



文章目录

  • 贝塞尔曲线开发相关总结
  • 一、1-3阶贝塞尔曲线
  • 1.一阶贝塞尔曲线
  • 2.二阶贝塞尔曲线
  • 3.三阶贝塞尔曲线
  • 二、过定点的二阶贝塞尔曲线以及其升阶
  • 1.过定点的二阶贝塞尔曲线
  • 2.二阶贝塞尔曲线的升阶
  • 三、三阶贝塞尔的分段
  • 1.迭代求t
  • 2.解方程求t
  • 四、贝塞尔曲线的平行线
  • 附:参考链接



一、1-3阶贝塞尔曲线

1.一阶贝塞尔曲线

一阶贝塞尔曲线公式:B(t) = P1 + (P2 − P1)t = P1(1−t)+ P2t, t∈[0,1]

一阶贝塞尔曲线是一条直线,在三阶贝塞尔中表现为调整点为起终止点

public Vector3 lineBezier(float t,Vector3 p0, Vector3 p1)
    {
        Vector3 a = p0;
        Vector3 b = p1;
        return a + (b - a) * t;
    }

2.二阶贝塞尔曲线

二阶贝塞尔曲线公式:B(t) = P1(1 - t)2 + 2P2t(1 - t) + P3t2,t∈[0,1]

二阶贝塞尔曲线有一个调整点,但画出的贝塞尔曲线没有经过该调整点

/// <summary>
    /// 获得二阶
    /// </summary>
    /// <param name="p0">起点</param>
    /// <param name="p1">调整点</param>
    /// <param name="p2">终点</param>
    /// <param name="t"></param>
    /// <returns></returns>
public Vector3 quardaticBezier(float t, Vector3 p0, Vector3 p1, Vector3 p2)
    {
        Vector3 a = p0;
        Vector3 b = p1;
        Vector3 c = p2;
        
        Vector3 aa = a + (b - a) * t;
        Vector3 bb = b + (c - b) * t;
        return aa + (bb - aa) * t;
    }

3.三阶贝塞尔曲线

三阶贝塞尔曲线公式:B(t) = P1(1 - t)3 + 3P2t(1 - t)2 + 3P3t2(1 - t) + P4t3,t∈[0,1]

/// <summary>
    /// 获得三阶
    /// </summary>
    /// <param name="p0">起点</param>
    /// <param name="p1">调整点</param>
    /// <param name="p2">调整点</param>
    /// <param name="p3">终点</param>
    /// <param name="t"></param>
    /// <returns></returns>
    public Vector3 cubicBezier(float t,Vector3 p0, Vector3 p1, Vector3 p2,Vector3 p3)
    {
        Vector3 a = p0;
        Vector3 b = p1;
        Vector3 c = p2;
        Vector3 d = p3;
        
        Vector3 aa = a + (b - a) * t;
        Vector3 bb = b + (c - b) * t;
        Vector3 cc = c + (d - c) * t;

        Vector3 aaa = aa + (bb - aa) * t;
        Vector3 bbb = bb + (cc - bb) * t;
        return aaa + (bbb - aaa) * t;
    }

二、过定点的二阶贝塞尔曲线以及其升阶

1.过定点的二阶贝塞尔曲线

在二阶贝塞尔曲线中我们知道其有一个调整点但曲线本身不经过,但在开发中会要求使用一个经过曲线的点来控制曲线的变化。为了解决这个问题,我们可以设置一个新的调整点,使得产生的新曲线经过要求的点。

/// <summary>
    /// 定点的二阶
    /// </summary>
    /// <param name="p0">起点</param>
    /// <param name="p1">调整点</param>
    /// <param name="p2">终点</param>
    /// <param name="t"></param>
    /// <returns></returns>
    public Vector3 quardaticOverPointBezier(float t,Vector3 p0, Vector3 p1, Vector3 p2)
    {
        Vector3 a = p0;
        Vector3 b = p1;
        Vector3 c = p2;
        //具体推导可以看后面的链接
        b = p1 - Mathf.Sqrt((p0 - p1).magnitude * (p2 - p1).magnitude) * ((p0 - p1) / (p0 - p1).magnitude + (p2 - p1) / (p2 - p1).magnitude) / 2;
        Vector3 aa = a + (b - a) * t;
        Vector3 bb = b + (c - b) * t;
        return aa + (bb - aa) * t;
    }

2.二阶贝塞尔曲线的升阶

由于n次贝塞尔曲线可以转换为一个形状完全相同的n+1次贝塞尔曲线
因此可以根据过定点的二阶贝塞尔转化成过顶点三阶贝塞尔

//二阶转三阶:
    A= p0;
    B= p0/3+2*p1/3;
    C= 2*p1/3+p2/3;
    D= p2;
//后续照搬上面的三阶贝塞尔

三、三阶贝塞尔的分段

如果在三阶贝塞尔曲线上添加一个点,那么由此生成的两段曲线依然是贝塞尔曲线吗?
答案是是的,链接中已给出了求添加点调整点的公式,但新的问题是如何求该点在贝塞尔曲线中的t值?

1.迭代求t

找到一段范围然后再新的分成新的范围去找满足条件的t值
迭代能保证一定的精度,但是耗时,在频繁求t值可能会出现崩溃

/// <summary>
    ///根据贝塞尔曲线上的点求t 
    /// </summary>
    /// <param name="p0"></param>
    /// <param name="p1"></param>
    /// <param name="p2"></param>
    /// <param name="p3"></param>
    /// <param name="p">该点</param>
    /// <param name="startT"></param>
    /// <param name="endT"></param>
    /// <returns></returns>
    private float GetTFormCurve(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3,Vector3 p,float startT = 0,float endT = 1)
    {
        var t = startT;
        var minDistance = Mathf.Infinity;
        var minDistanceT = 0f;
        var step = (endT - startT) / 100;
        for (int i = 0; i < 100; i++)
        {
            var point = cubicBezier(t,p0, p1, p2,p3);
            var dst = Vector3.Distance(point, p);
            if (dst < minDistance)
            {
                minDistance = dst;
                minDistanceT = t;
            }
            if (dst < 0.0001)
            {
                return t;
            }
            t += step;
        }
        return GetTFormCurve(p0, p1, p2,p3, p, minDistanceT - step, minDistanceT + step);
    }

2.解方程求t

我尝试找过三阶贝塞尔的解方程,但是难以实现。之前讲过可以用二阶升三阶,二阶贝塞尔解方程是一件容易的事情,那么可以从二阶贝塞尔曲线出发
二阶贝塞尔解方程是一元二次方程,得出在[0,1]之间的解,若两个则求坐标与点最近的那个。

//z0,z1,z2为p1,p2,p3的x值,tp为目标x
//curvedata中存储一元二次方程解的情况
//tt单解,tt1,tt2为解
private curvedata Solution(float z0, float z1, float z2, float tp)
    {
        var a = z0 - z1 * 2 + z2;
        var b = 2 * (z1 - z0);
        var c = z0 - tp;
        var tt = 0f;
        curvedata result = new curvedata();
        if (a == 0 && b != 0)
        {
            tt = -c / b;
        }
        else
        {
            var sq = Math.Sqrt(b * b - 4 * a * c);
            var tt1 = (sq - b) / (2 * a);
            var tt2 = (-sq - b) / (2 * a);
            if ((tt1 <= 1 && tt1 >= 0) && (tt2 <= 1 && tt2 >= 0))
            {               
                result.tt1 = (float)tt1;
                result.tt1 = (float)tt2;
                return result;
            }
            else if (tt1 <= 1 && tt1 >= 0)
            {
                tt = (float)tt1;
                result.t = tt;
            }
             else
            {
                tt = (float)tt2;
                result.t = tt;
            }
        }
        return result;
    }

经过上面进行判断即可求出t值

//A、B、C、D为上文的二阶转三阶
//I,J 即为所求
        var F = A * (1 - t) + B * t;
        var G = B * (1 - t) + C * t;
        var H = C * (1 - t) + D * t;

        var I = F * (1 - t) + G * t;
        var J = G * (1 - t) + H * t;

亲测得出的结果和迭代所求有可忽略的误差。
至此已经求出点的两个调整点,可实现在一段由二阶升三阶的贝塞尔曲线添加一个点,形成两段可调节的三阶贝塞尔曲线。


四、贝塞尔曲线的平行线

TODO