美文网首页unity
Bezier曲线切割

Bezier曲线切割

作者: Qkuang | 来源:发表于2020-07-21 16:24 被阅读0次

    贝塞尔曲线

    目标:在unity中展示Bezier曲线。

    贝塞尔曲线原理

    用代码实现 bezier数学公式

    • Bezier公式(一般参数公式)

    image.png
    • 注解:
      阶贝兹曲线可如下推断。给定点P0、P1、…、Pn,其贝兹曲线即:
      如上公式可如下递归表达: 用表示由点P0、P1、…、Pn所决定的贝兹曲线。
      用平常话来说,阶的贝兹曲线,即双阶贝兹曲线之间的插值。

      • 代码实现:
        using System;
        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
        
        //Bezier点数据,包含该点的位置,以及该点的t参数。
        public struct BezierPos
        {
            public Vector3 bp;
            public float t;
            public BezierPos(Vector3 _pos)
            {
                this.bp = _pos;
                this.t = 0f;
            }
            public static BezierPos Zero
            {
                get
                {
                    return new BezierPos(Vector3.zero);
                }
            }
        }
        
        public class Bezier : MonoBehaviour
        {
            private bool isStart;
            private List<Transform> _Target;
            private List<Vector2> vector2s;
            private List<BezierPos> bezierPoints;
            private OperateBezier operateBezier;
            #region unity生命周期函数
            // Start is called before the first frame update
            void Start()
            {
                isStart = true;
                _Target = new List<Transform>();
                vector2s = new List<Vector2>();
                bezierPoints = new List<BezierPos>();
                operateBezier = new OperateBezier(bezierPoints, vector2s);
                Vector2 buff = Vector2.zero;
                foreach (Transform item in transform)
                {
                    buff.x = item.position.x;
                    buff.y = item.position.z;
                    vector2s.Add(buff);
                    _Target.Add(item);
                }
        
                for (int i = 0; i < 20; i++)
                {
                    bezierPoints.Add(BezierPos.Zero);
                }
                //print(bezierPoints.Count);
                //Frame();
                //foreach (var item in bezierPoints)
                //{
                //    print(item);
                //}
            }
        
            // Update is called once per frame
            void Update()
            {
                Frame();
            
            }
        
            public void Frame()
            {
                Vector2 buff = Vector2.zero;
                for (int i = 0; i < _Target.Count; i++)
                {
                    buff.x = _Target[i].position.x;
                    buff.y = _Target[i].position.z;
                    vector2s[i] = buff;
                }
                operateBezier.CalculateOnce();
        
            }
        
            private void OnDrawGizmos()
            {
        
                if (!isStart)
                    return;
        
                Gizmos.color = Color.blue;
        
                for (int i = 0; i < bezierPoints.Count; i++)
                {
                    //Gizmos.DrawLine(bezierPoints[i], bezierPoints[i + 1]);
                    Gizmos.DrawCube(bezierPoints[i].bp, Vector3.one / 5);
                }
            }
            #endregion
        }
        
        #region bezier曲线的计算核心
        
        
        public class OperateBezier
        {
        
            public List<BezierPos> pointList;                 // 曲线点
            public List<Vector2> bezierHandList;        // 外框点
            public OperateBezier(List<BezierPos> beziers, List<Vector2> hands)
            {
                this.pointList = beziers;
                this.bezierHandList = hands;
            }
            public Vector2 OperateP(float t)
            {
                Vector2 _result = Vector2.zero;
                int n = bezierHandList.Count - 1;
        
                if (t > 1 || t < 0)
                {
                    return _result;
                }
        
                for (int i = 0; i < bezierHandList.Count; i++)
                {
                    _result += bezierHandList[i] * Mathf.Pow(1 - t, n - i) * Mathf.Pow(t, i) * MathExtenders.CombinationNum(n, i);
                    //Debug.Log("------" + bezierHandList[i] + "输出" + _result);
                }
        
                return _result;
            }
            // 计算一次曲线。
            public void CalculateOnce()
            {
                int pNum = pointList.Count - 1;
                float t;
                Vector2 buff1 = Vector2.zero;
                BezierPos buff2 = BezierPos.Zero;
                for (int i = 0; i < pointList.Count; i++)
                {
                    t = (float)i / (float)pNum;
                    buff1 = OperateP(t);
                    //Debug.Log(buff1);
                    buff2.bp.x = buff1.x;
                    buff2.bp.z = buff1.y;
                    buff2.t = t;
                    pointList[i] = buff2;
                
                }
            }
        }
        
        #endregion
        
        
        #region 对unity做扩展
        
        
        public static class Extends
        {
            public static Vector2 v3tov2 (this Vector3 v3)
            {
                return new Vector2(v3.x, v3.z);
            }
        
            public static Vector3 v2tov3(this Vector2 v2)
            {
                return new Vector3(v2.x, 0, v2.y);
            }
        }
        
        public class MathExtenders
        {
            //阶乘
            public static int Factorial(int num)
            {
                if (num < 0)
                {
                    return -1;
                }
                else if (num == 0)
                {
                    return 1;
                }
                int _value = 1;
                for (int i = num; i > 0; i--)
                {
                    _value *= i;
                }
                return _value;
            }
            //组合数
            public static int CombinationNum(int totality, int atATime)
            {
                return MathExtenders.Factorial(totality) / (MathExtenders.Factorial(atATime) * MathExtenders.Factorial(totality - atATime));
        
            }
        
        }
        
        #endregion
        
        
    • 切割Bezier

      • 尝试计算切割点
        • 条件:切割三阶Bezier。

        • 切割点好计算,同mesh切割。关键是在切割之后 计算出 Bezier点的外框点位置。

        • 尝试过 从切割出,取往后取两个Bezier点的。通过方程组解出剩余的两个外框点。

        • 理论上应该可以,但是未成功。【后面一次又成功了,原因:前一次精度不足】

        • 公式推导:


          bezier切割公式.png
        • 代码:

          
          using System.Collections;
          using System.Collections.Generic;
          using UnityEngine;
          
          public class Cut : MonoBehaviour
          {
              // Start is called before the first frame update 
              public Bezier r_bezierOrigin;       // 起源bezier
              public Bezier r_bezierShow;             // 用于显示切割结果 bezier
              private CutMath _cutMath;
          
              public float t0;
              public float t1;
              public float t2;
              public Vector2 p0;
              public Vector2 p3;
              public Vector2 B1;
              public Vector2 B2;
          
              void Start()
              {
                  _cutMath = new CutMath();
              }
          
              // Update is called once per frame
              void Update()
              {
                  if (Input.GetKeyDown(KeyCode.A))
                  {
                      OperateCut();
                  }else if (Input.GetKeyDown(KeyCode.B))
                  {
                      print("t0:" + T2t(t0));
                      print("t1:" + T2t(t1));
                      print("t2:" + T2t(t2));
                  }
              }
          
              public float T2t(float _t)
              {
                  float b = t0 / (1 - t0);
                  _t = (_t / (1 - t0)) - b;
                  return _t;
              }
          
              public void OperateCut()
              {
                  this.t0 = r_bezierOrigin.BezierPoints[9].t;
                  this.t1 = r_bezierOrigin.BezierPoints[10].t;
                  this.t2 = r_bezierOrigin.BezierPoints[11].t;
                  this.t1 = T2t(this.t1);
                  this.t2 = T2t(this.t2);
                  this.p0 = r_bezierOrigin.BezierPoints[9].bp.v3tov2();
                  this.p3 = r_bezierOrigin.BezierPoints[19].bp.v3tov2();
                  this.B1 = r_bezierOrigin.BezierPoints[10].bp.v3tov2();
                  this.B2 = r_bezierOrigin.BezierPoints[11].bp.v3tov2();
                  _cutMath.Start(t1, t2, p0, p3, B1, B2);
                  print($"p1:{_cutMath.p1}、p2:{_cutMath.p2}");
                  r_bezierShow.Target[0].position = this.p0.v2tov3();
                  r_bezierShow.Target[1].position = _cutMath.p1.v2tov3();
                  r_bezierShow.Target[2].position = _cutMath.p2.v2tov3();
                  r_bezierShow.Target[3].position = this.p3.v2tov3();
              }
          
          
          }
          
          
          #region 切割核心算法
          
          /* 三阶bezier切割【且只能切割一次】
          * 大致思路:有一个切割线段的 函数、根据bezier曲线的收尾外框点,判断存在切割,则开始切割
          *  开始切割,遍历Bezier的线点,两个一个线段,判断是否切割。
          *  如该出存在切割,则开始切割。三阶变wei
          * 
          * 切割只支持 三阶,P0、1、2、3 四个点。 需要用算法 解出切割后的 P1、2点。
          * 
          */
          
          // 计算 P1、2
          public class CutMath
          {
              public float t1;
              public float t2;
              public Vector2 p0;
              public Vector2 p1;  //待求
              public Vector2 p2;  // 待求
              public Vector2 p3;
              public Vector2 B1;
              public Vector2 B2;
          
              private float k1, k2, l1, l2, j1, j2, i1, i2;
              private Vector2 E;
              private float T;
          
              public void Start(float _t1, float _t2, Vector2 _p0, Vector2 _p3, Vector2 _B1, Vector2 _B2)
              {
                  //初始化
                  this.t1 = _t1;
                  this.t2 = _t2;
                  this.p0 = _p0;
                  this.p3 = _p3;
                  this.B1 = _B1;
                  this.B2 = _B2;
                  //计算
                  this.k1 = ComputeK(t1);
                  this.k2 = ComputeK(t2);
                  this.l1 = ComputeL(t1);
                  this.l2 = ComputeL(t2);
                  this.j1 = ComputeJ(t1);
                  this.j2 = ComputeJ(t2);
                  this.i1 = ComputeI(t1);
                  this.i2 = ComputeI(t2);
                  this.E = ComputeE();
                  this.T = ComputeT();
                  this.p2 = ComputeP2();
                  this.p1 = ComputeP1();
                  Debug.Log($"计算结束:p1:{p1},p2:{p2}");
              }
          
              #region 参数计算函数
          
          
              private float ComputeK(float t)
              {
                  return Mathf.Pow(1 - t, 3);
              }
              private float ComputeL(float t)
              {
                  return Mathf.Pow(1 - t, 2) * t * 3;
              }
              private float ComputeJ(float t)
              {
                  return Mathf.Pow(t, 2) * (1 - t) * 3;
              }
              private float ComputeI(float t)
              {
                  return Mathf.Pow(t, 3);
              }
          
              private Vector2 ComputeE()
              {
                  return (B2 - k2 * p0 - i2 * p3) / l2;
              }
          
              private float ComputeT()
              {
                  return j2 / l2;
              }
          
              private Vector2 ComputeP2()
              {
                  return (B1 - k1 * p0 - l1 * E - i1 * p3) / (j1 - l1 * T);
              }
          
              private Vector2 ComputeP1()
              {
                  return E - T * p2;
              }
          
              #endregion
          }
          
          #endregion
          
          

    结果测试:

    image.png

    可以看到:橙色的部分的Bezier曲线 ,就是当t=0.5时,切割后的线。与原来的Bezier线完全重合。

    我推导的公式的主要思路就是:计算出三阶Bezier曲线的两个锚点位置。通过方程组可以求解得出来。

    该公式可以结合 切割算法。通过遍历Bezier线的List,求出 被切割点的t 值。让后带入我推导出来的算法,即可得到切割后的两个锚点位置。

    点关注,不迷路

    相关文章

      网友评论

        本文标题:Bezier曲线切割

        本文链接:https://www.haomeiwen.com/subject/xeaikktx.html