Bezier曲線切割

貝塞爾曲線

目標:在unity中展示Bezier曲線妥衣。

貝塞爾曲線原理

用代碼實現(xiàn) bezier數(shù)學公式

  • Bezier公式(一般參數(shù)公式)

image.png
  • 注解:
    階貝茲曲線可如下推斷。給定點P0完箩、P1粘捎、…帖蔓、Pn檬贰,其貝茲曲線即:
    如上公式可如下遞歸表達: 用表示由點P0、P1遵湖、…悔政、Pn所決定的貝茲曲線。
    用平常話來說奄侠,階的貝茲曲線卓箫,即雙階貝茲曲線之間的插值。

    • 代碼實現(xiàn):
      using System;
      using System.Collections;
      using System.Collections.Generic;
      using UnityEngine;
      
      //Bezier點數(shù)據(jù)垄潮,包含該點的位置烹卒,以及該點的t參數(shù)闷盔。
      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生命周期函數(shù)
          // 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;
          }
          //組合數(shù)
          public static int CombinationNum(int totality, int atATime)
          {
              return MathExtenders.Factorial(totality) / (MathExtenders.Factorial(atATime) * MathExtenders.Factorial(totality - atATime));
      
          }
      
      }
      
      #endregion
      
      
  • 切割Bezier

    • 嘗試計算切割點
      • 條件:切割三階Bezier旅急。

      • 切割點好計算逢勾,同mesh切割。關(guān)鍵是在切割之后 計算出 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;             // 用于顯示切割結(jié)果 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切割【且只能切割一次】
        * 大致思路:有一個切割線段的 函數(shù)躯嫉、根據(jù)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($"計算結(jié)束:p1:{p1}翎猛,p2:{p2}");
            }
        
            #region 參數(shù)計算函數(shù)
        
        
            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
        
        

結(jié)果測試:

image.png

可以看到:橙色的部分的Bezier曲線 胖翰,就是當t=0.5時,切割后的線切厘。與原來的Bezier線完全重合萨咳。

我推導的公式的主要思路就是:計算出三階Bezier曲線的兩個錨點位置。通過方程組可以求解得出來疫稿。

該公式可以結(jié)合 切割算法培他。通過遍歷Bezier線的List鹃两,求出 被切割點的t 值。讓后帶入我推導出來的算法舀凛,即可得到切割后的兩個錨點位置俊扳。

點關(guān)注,不迷路

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猛遍,一起剝皮案震驚了整個濱河市馋记,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懊烤,老刑警劉巖梯醒,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腌紧,居然都是意外死亡茸习,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門寄啼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逮光,“玉大人,你說我怎么就攤上這事墩划√楦眨” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵乙帮,是天一觀的道長杜漠。 經(jīng)常有香客問我,道長察净,這世上最難降的妖魔是什么驾茴? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮氢卡,結(jié)果婚禮上锈至,老公的妹妹穿的比我還像新娘。我一直安慰自己译秦,他們只是感情好峡捡,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著筑悴,像睡著了一般们拙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阁吝,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天砚婆,我揣著相機與錄音,去河邊找鬼突勇。 笑死装盯,一個胖子當著我的面吹牛坷虑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播验夯,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼猖吴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了挥转?” 一聲冷哼從身側(cè)響起海蔽,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绑谣,沒想到半個月后党窜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡借宵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年幌衣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壤玫。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡豁护,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出欲间,到底是詐尸還是另有隱情楚里,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布猎贴,位于F島的核電站班缎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏她渴。R本人自食惡果不足惜达址,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望趁耗。 院中可真熱鬧沉唠,春花似錦、人聲如沸苛败。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽著拭。三九已至,卻和暖如春牍帚,著一層夾襖步出監(jiān)牢的瞬間儡遮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工暗赶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碉渡,地道東北人锌雀。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓蕴茴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親因惭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355