量子计算9 发表于 2021-12-20 09:25

简单记录在unity中使用贝塞尔曲线编辑路径

贝塞尔曲线公式:
/// <summary>
    /// 二次贝塞尔
    /// </summary>
    public static Vector3 Bezier_2(Vector3 p0, Vector3 p1, Vector3 p2, float t)
    {
      return (1 - t) * ((1 - t) * p0 + t * p1) + t * ((1 - t) * p1 + t * p2);
    }

/// <summary>
    /// 三次贝塞尔
    /// </summary>
    public static Vector3 Bezier_3(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
    {
      return (1 - t) * ((1 - t) * ((1 - t) * p0 + t * p1) + t * ((1 - t) * p1 + t * p2)) + t * ((1 - t) * ((1 - t) * p1 + t * p2) + t * ((1 - t) * p2 + t * p3));
    }

public static float Bezier3Length(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, int n)
{
   float length = 0;
   Vector3 startPos = p0;
   for (int i = 1; i < n; i++)
    {
      Vector3 endPos = Bezier_3(p0, p1, p2, p3, i * 1.0f / n);
      length += Vector3.Distance(startPos, endPos);
      startPos = endPos;
      }
   return length;
}
如何在场景中编辑贝塞尔曲线呢,就是用一组3次贝塞尔曲线组合出一个这个路径,就是在已知的路径点之间建立一个3次贝塞尔曲线
首先,建立一个编辑路径用的路径点,这个点可以作为上一个3次贝塞尔曲线的终点,所以要有个前控制点,这个点还可以作为下一个3次贝塞尔曲线的起点,还要有个后控制点,这个点衔接的两段贝塞尔曲线过度要平滑就需要这两个控制点在同一条直线上。
public class BezierGizmoNode : MonoBehaviour
{
    public GameObject preControl;
    private Vector3 oldPreControlPos = Vector3.zero;
    public GameObject nextControl;
    private Vector3 oldNextControlPos = Vector3.zero;

    public BezierGizmoNode preNode;
    public BezierGizmoNode nextNode;

    public int n = 100;//用于近似就是曲线长度的点密度

    public void DrawGizmos()
    {
      Gizmos.color = Color.green;
      Gizmos.DrawSphere(transform.position, 0.4f);
      if (preControl != null && nextControl != null)
      {
            if (oldPreControlPos != preControl.transform.position)
            {
                nextControl.transform.position = transform.position + (preControl.transform.position - transform.position).normalized * -1
                * Vector3.Distance(nextControl.transform.position, transform.position);
                oldNextControlPos = nextControl.transform.position;
                oldPreControlPos = preControl.transform.position;
            }
            else if (oldNextControlPos != nextControl.transform.position)
            {
                preControl.transform.position = transform.position + (nextControl.transform.position - transform.position).normalized * -1
                * Vector3.Distance(preControl.transform.position, transform.position);

                oldPreControlPos = preControl.transform.position;
                oldNextControlPos = nextControl.transform.position;
            }
      }
      if (preNode != null)
      {
            if (preControl == null)
            {
                preControl = new GameObject("preControl");
                preControl.transform.parent = transform;
                preControl.transform.position = transform.position + (preNode.transform.position - transform.position).normalized * 0.5f;
                oldPreControlPos = preControl.transform.position;
            }
            Gizmos.color = Color.red;
            Gizmos.DrawCube(preControl.transform.position, new Vector3(0.4f, 0.4f, 0.4f));
            Gizmos.color = Color.yellow;
            Gizmos.DrawLine(transform.position, preControl.transform.position);
      }
      else
      {
            if (preControl != null)
            {
                GameObject.DestroyImmediate(preControl);
                preControl = null;
            }
      }
      if (nextNode != null)
      {
            if (nextControl == null)
            {
                nextControl = new GameObject("nextControl");
                nextControl.transform.parent = transform;
                if (preControl != null)
                {
                  nextControl.transform.position = transform.position + (preControl.transform.position - transform.position).normalized * -0.5f;
                  oldNextControlPos = nextControl.transform.position;
                }
                else
                {
                  nextControl.transform.position = transform.position + (nextNode.transform.position - transform.position).normalized * 0.5f;
                  oldNextControlPos = nextControl.transform.position;
                }
            }
            Gizmos.color = Color.red;
            Gizmos.DrawCube(nextControl.transform.position, new Vector3(0.4f, 0.4f, 0.4f));
            Gizmos.color = Color.yellow;
            Gizmos.DrawLine(transform.position, nextControl.transform.position);
      }
      else
      {
            if (nextControl != null)
            {
                GameObject.DestroyImmediate(nextControl);
                nextControl = null;
            }
      }
      if (nextNode != null && nextNode.preControl != null)
      {
            Handles.DrawBezier(transform.position, nextNode.transform.position,
            nextControl.transform.position, nextNode.preControl.transform.position, Color.green, null, 3);
      }

    }
}
然后,就是管理这个路径点,用unity中的Gizmos方法,在编辑器中显示出编辑的贝塞尔曲线
public class BezierGizmoRoot : MonoBehaviour
{
    private List<BezierGizmoNode> mNodes = new List<BezierGizmoNode>();
    private void OnDrawGizmos()
    {
      mNodes.Clear();
      for (int i = 0; i < transform.childCount; i++)
      {
            Transform childTran = transform.GetChild(i);
            BezierGizmoNode childNode = childTran.GetComponent<BezierGizmoNode>();
            if (childNode != null)
            {
                mNodes.Add(childNode);
            }

      }
      BezierGizmoNode preNode = null;
      for (int i = 0; i < mNodes.Count; i++)
      {
            mNodes.preNode = preNode;
            if (i + 1 < mNodes.Count)
            {
                mNodes.nextNode = mNodes;
            }
            preNode = mNodes;
      }
      for (int i = 0; i < mNodes.Count; i++)
      {
            mNodes.gameObject.name = (i + 1).ToString();
            mNodes.DrawGizmos();
      }
    }

    public BezierData GetBezierData()
    {
      return new BezierData(mNodes);
    }
}再次,编辑好的贝塞尔曲线要保存出数据,还要有个贝塞尔曲线数据类
public struct ThreeBezierData
{
    public Vector3 StartPos;

    public Vector3 EndPos;

    public Vector3 ControlPos1;

    public Vector3 ControlPos2;

    public float Length;

    public Quaternion FromRot;

    public Quaternion ToRot;

    public Vector3 GetPos(float t)
    {
      return BezierMath.Bezier_3(StartPos, ControlPos1, ControlPos2, EndPos, t);
    }

    public Quaternion GetRot(float t)
    {
      return Quaternion.Slerp(FromRot, ToRot, t);
    }
}


public class BezierData
{

    public BezierData(List<BezierGizmoNode> nodes)
    {
      this.Update(nodes);
    }

    public void Update(List<BezierGizmoNode> nodes)
    {
      mAllData.Clear();
      TotalLength = 0;
      if (nodes != null)
      {
            for (int i = 0; i < nodes.Count; i++)
            {
                BezierGizmoNode oneNode = nodes;
                if (oneNode.nextNode != null)
                {
                  BezierGizmoNode twoNode = oneNode.nextNode;
                  ThreeBezierData data = new ThreeBezierData();
                  data.StartPos = oneNode.transform.position;
                  data.EndPos = twoNode.transform.position;
                  data.ControlPos1 = oneNode.nextControl.transform.position;
                  data.ControlPos2 = twoNode.preControl.transform.position;
                  data.FromRot = oneNode.transform.rotation;
                  data.ToRot = twoNode.transform.rotation;
                  data.Length = BezierMath.Bezier3Length(data.StartPos, data.ControlPos1, data.ControlPos2, data.EndPos, oneNode.n);
                  Debug.LogError("UpdateLength= " + data.Length);
                  TotalLength += data.Length;
                  mAllData.Add(data);
                }

            }
      }
    }

    private List<ThreeBezierData> mAllData = new List<ThreeBezierData>();

    public float TotalLength = 0;

    public Vector3 GetUniformSpeedPos(float t)
    {
      if (t < 0)
      {
            t = 0;
      }
      if (t > 1) { t = 1; }
      float eachD = 1.0f / TotalLength;
      float preDis = 0;
      for (int i = 0; i < mAllData.Count; i++)
      {
            if (t >= preDis && t < preDis + mAllData.Length * eachD)
            {
                float curT = (t - preDis) / (mAllData.Length * eachD);
                return mAllData.GetPos(curT);
            }
            preDis += mAllData.Length * eachD;
      }
      return mAllData.EndPos;
    }

    public Vector3 GetUniformTimePos(float t)
    {
      if (t < 0)
      {
            t = 0;
      }
      if (t > 1) { t = 1; }
      float eachT = 1.0f / mAllData.Count;
      int index = Mathf.FloorToInt(t / eachT);
      float curT = (t - eachT * index) / eachT;
      if (index >= mAllData.Count)
      {
            index = mAllData.Count - 1;
            curT = 1;
      }
      return mAllData.GetPos(curT);
    }
}

最后,需要测试这个编辑好的曲线
public class BezierMove : MonoBehaviour
{
    public BezierGizmoRoot root;

    public float duration = 1;

    private float t;
    private bool isUniformSpeed;
    private BezierData mBezierData;
    public void PlayByUniformSpeed()
    {
      isUniformSpeed = true;
      t = 0;
      if (root != null)
      {
            mBezierData = root.GetBezierData();
      }
    }

    public void PlayByUniformTime()
    {
      isUniformSpeed = false;
      t = 0;
      if (root != null)
      {
            mBezierData = root.GetBezierData();
      }
    }

    private void Update()
    {
      if (root != null && mBezierData != null)
      {
            t += Time.deltaTime / duration;
            if (t <= 1)
            {
                if (isUniformSpeed)
                {
                  transform.position = mBezierData.GetUniformSpeedPos(t);
                }
                else
                {
                  transform.position = mBezierData.GetUniformTimePos(t);
                }

            }
      }
    }
}



public class BezierMoveEditor : Editor
{
    public override void OnInspectorGUI()
    {
      base.OnInspectorGUI();
      BezierMove bm = (BezierMove)target;

      if (GUILayout.Button("匀速播放"))
      {
            bm.PlayByUniformSpeed();
      }

      if (GUILayout.Button("播放"))
      {
            bm.PlayByUniformTime();
      }
    }
}

xiangtingsl 发表于 2021-12-20 09:31

Bezier3Length缺少这个方法 能补一下吗 作者大大using UnityEngine;

public class BezierMath
{
    public static Vector3 Bezier_2(Vector3 p0, Vector3 p1, Vector3 p2, float t)
    {
      return (1 - t) * ((1 - t) * p0 + t * p1) + t * ((1 - t) * p1 + t * p2);
    }

    /// <summary>
    /// 三次贝塞尔
    /// </summary>
    public static Vector3 Bezier_3(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
    {
      return (1 - t) * ((1 - t) * ((1 - t) * p0 + t * p1) + t * ((1 - t) * p1 + t * p2)) + t * ((1 - t) * ((1 - t) * p1 + t * p2) + t * ((1 - t) * p2 + t * p3));
    }
}

johnsoncodehk 发表于 2021-12-20 09:33

public static float Bezier3Length(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, int n)
    {
      float length = 0;
      Vector3 startPos = p0;
      for (int i = 1; i < n; i++)
      {
            Vector3 endPos = Bezier_3(p0, p1, p2, p3, i * 1.0f / n);
            length += Vector3.Distance(startPos, endPos);
            startPos = endPos;
      }
      return length;
    }
页: [1]
查看完整版本: 简单记录在unity中使用贝塞尔曲线编辑路径