Bezier曲线切割

贝塞尔曲线

目标:在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 值。让后带入我推导出来的算法,即可得到切割后的两个锚点位置。

点关注,不迷路

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345