Hermite (埃爾米特)曲線

Hermite 曲線

  已知曲線的兩個端點座標P0、P1,和端點處的切線R0、R1,肯定的一條曲線。node

參數方程

  1. 幾何形式編輯器

  

  2. 矩陣形式ide

   

  3. 推導函數

    

 

  

 

  

 

 

 

  

 

  

 

  

 

例子分析

  

  如上圖有四個點,假如P0、P2是端點,那麼向量R0=(P1-P0),R1=(P3-P2),將數據帶入調和函數,即求得曲線。this

  在程序中,咱們一般會使用特殊方法處理頂點之間的關係。spa

  

  圖中含有3個頂點,咱們把每挨着的兩個頂點看作是一條Hermite曲線,P0和P1是兩個端點,那麼如今,咱們如何求得R1呢? 咱們如今構建連個參考點F1,F2。.net

    令  F1 = P0;   F2 = P2;  3d

    那麼 R1 = P1-F1;  R2 = F2-P1;code

  而後將此值帶入曲線函數,便可爲求得的曲線。  orm

程序代碼

該代碼是Unity腳本代碼:

  1. 實現編輯器閉合曲線和非閉合曲線的繪製;

  2. 運行腳本,能夠實現物體跟隨曲線路徑移動,能夠勾選旋轉跟隨與不跟隨;

  3. 若是不進行自動跟隨曲線路徑,能夠修改時間值,移動物體。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class HermitCurve : Curve_Root {

    public Transform CurveParent;
    public int lineCount;
    private List<NodePath> nodePath;
    public float smoothFactor = 2.0f;

    private Transform[] transforms;
    private Vector3[] node;
    public float Speed = 10.0f;

    Vector3 curPos;
    Vector3 nextPos;
    Quaternion curRotate;
    Quaternion nextRotate;
    float moveTime;
    float curTime;
    public GameObject Tar;
    int index;
    private Transform T;

    public float time;


    /// <summary>
    /// 數據校訂
    /// </summary>
    void DadaCorrection()
    {
        //根節點驗證
        if (CurveParent == null)
        {
            Debug.LogError("Please add curve the root node.");
            return;
        }

        //修正平滑因子: 2時,較爲理想。
        smoothFactor = smoothFactor >= 10.0f ? 10.0f : smoothFactor;
        smoothFactor = smoothFactor <= 1.0f ? 1.0f : smoothFactor;
    }

    /// <summary>
    /// 計算路徑節點
    /// </summary>
    void ComputeNode()
    {
        //將節點添加入鏈表
        Component[] mTrans = CurveParent.GetComponentsInChildren(typeof(Transform));//物體和子物體的Trans
        if (mTrans.Length - 1 < 2)
        {
            Debug.LogError("Please add at least two points.");
            return;
        }

        //非閉合曲線與閉合曲線的頂點時間計算:非閉合曲線,最後個頂點的時間爲1.0,閉合曲線的最後個頂點的時間爲倒數第二個頂點,由於他的最後個點是原點。
        float t;
        if (closedCurve)
        {
            t = 1.0f / (mTrans.Length - 1);
            nodePath = new List<NodePath>();
            for (int i = 1; i < mTrans.Length; i++)//根節點不參與路徑節點的計算
            {
                nodePath.Add(new NodePath(mTrans[i].transform.position, (i - 1) * t));
            }
            //閉合曲線完整的節點
            AddClosedCurveNode();
        }
        else
        {
            t = 1.0f / (mTrans.Length - 2);
            nodePath = new List<NodePath>();
            for (int i = 1; i < mTrans.Length; i++)//根節點不參與路徑節點的計算
            {
                nodePath.Add(new NodePath(mTrans[i].transform.position, (i - 1) * t));
            }
            //非閉合曲線完整的節點
            AddCurveNode();
        }
    }


    // Use this for initialization
    void Start () {
        
        DadaCorrection();

        ComputeNode();

        node = new Vector3[lineCount+1];
        //Vector3 start = nodePath[1].point;
        //Vector3 end;

        node[0] = nodePath[1].point;
        Vector3 end;
        //繪製節點
        for (int i = 1; i <= lineCount; i++)
        {
            float ti = i / (float)lineCount;
            end = GetHermitAtTime(ti);
            if (node != null)
            {
                node[i] = end;
            }
        }
  
        T = new GameObject().transform;
        
        curPos = node[0];
        nextPos = node[1];
        moveTime = (nextPos - curPos).magnitude / Speed;

        Tar.transform.position = curPos;
        Tar.transform.transform.LookAt(nextPos);
        curRotate = Tar.transform.rotation;
        T.position = node[1];
        T.LookAt(node[2]);
        nextRotate = T.rotation;
        curTime = 0;
        index = 1;
    }
    
    // Update is called once per frame
    void Update () {

        if (AutoCurve)
        {
            if (moveTime > curTime)
            {
                Tar.transform.position = Vector3.Lerp(curPos, nextPos, curTime / moveTime);
                if (AutoRotate)
                {
                    Tar.transform.rotation = Quaternion.Slerp(curRotate, nextRotate, curTime / moveTime);
                }

                curTime += Time.deltaTime;
            }
            else
            {
                if (closedCurve)
                {
                    index = ((index + 1) % (lineCount + 1) == 0) ? 0 : index + 1;
                }
                else
                {
                    index = ((index + 1) % (lineCount+1) == 0) ? index : index + 1;
                }
                curPos = nextPos;
                nextPos = node[index];

                curTime = 0;
                moveTime = (nextPos - curPos).magnitude / Speed;

                T.position = node[index];
                curRotate = nextRotate;

                if (closedCurve)
                {
                    int c1;
                    if (index == node.Length-1)
                    {
                        c1 = 0;
                    }
                    else
                    {
                        c1 = index + 1;
                    }
                    T.LookAt(node[c1]);
                }
                else
                {
                    int c1;
                    if (index == node.Length - 1)
                    {
                        c1 = 0;
                    }
                    else
                    {
                        c1 = index + 1;
                        T.LookAt(node[c1]);
                    }
                }

                nextRotate = T.rotation;
            }
        }
        else
        {
            if (closedCurve)
            {
                if (AutoRotate)
                {
                    time = time > 1.0f ? 0.0f : time;
                    time = time < 0.0f ? 1.0f : time;
                    Vector3 cur = GetHermitAtTime(time);
                    Tar.transform.position = cur;

                    float del = 1.0f / lineCount;
                    Vector3 next0 = GetHermitAtTime(time + del);
                    Tar.transform.LookAt(next0);
                }
                else
                {
                    time = time > 1.0f ? 0.0f : time;
                    time = time < 0.0f ? 1.0f : time;
                    Vector3 cur = GetHermitAtTime(time);
                    Tar.transform.position = cur;
                }
            }
            else
            {
                if (AutoRotate)
                {
                    time = time > 1.0f ? 1.0f : time;
                    time = time < 0.0f ? 0.0f : time;
                    Vector3 cur = GetHermitAtTime(time);
                    Tar.transform.position = cur;

                    float del = 1.0f / lineCount;
                    Vector3 next0 = GetHermitAtTime(time + del);

                    Tar.transform.LookAt(next0);
                }
                else
                {
                    time = time > 1.0f ? 1.0f : time;
                    time = time < 0.0f ? 0.0f : time;
                    Vector3 cur = GetHermitAtTime(time);
                    Tar.transform.position = cur;
                }
            }

        }
        
    }



    /// <summary>
    /// 繪製曲線
    /// </summary>
    void DrawCurve()
    {
        if (closedCurve)
        {
            Vector3 start = nodePath[1].point;
            Vector3 end;

            Gizmos.color = _Color;
            //繪製節點
            for (int i = 1; i < lineCount; i++)
            {
                float time = i / (float)lineCount;
                end = GetHermitAtTime(time);
                //Debug.Log(end);
                Gizmos.DrawLine(start, end);

                start = end;
            }
            Gizmos.DrawLine(start, nodePath[nodePath.Count - 2].point);
        }
        else
        {
            Vector3 start = nodePath[1].point;
            Vector3 end;

            Gizmos.color = _Color;
            //繪製節點
            for (int i = 1; i < lineCount; i++)
            {
                float time = i / (float)lineCount;
                end = GetHermitAtTime(time);
                //Debug.Log(end);
                Gizmos.DrawLine(start, end);
                start = end;
            }
            Gizmos.DrawLine(start, nodePath[nodePath.Count - 2].point);
        }
    }

    /// <summary>
    /// 在Scene場景中,繪製Hermite曲線
    /// </summary>
    void OnDrawGizmos()
    {
        /*數據校訂*/
        DadaCorrection();
        /*計算頂點*/
        ComputeNode();
        /*計算曲線*/
        DrawCurve();
    }

    /// <summary>
    /// 1. 非閉合曲線
    /// </summary>
    public void AddCurveNode()
    {
        nodePath.Insert(0, nodePath[0]);
        nodePath.Add(nodePath[nodePath.Count - 1]);
    }

    /// <summary>
    /// 2. 閉合曲線
    /// </summary>
    public void AddClosedCurveNode()
    {
        //nodePath.Insert(0, nodePath[0]);
        nodePath.Add(new NodePath(nodePath[0]));
        nodePath[nodePath.Count - 1].time = 1.0f;

        Vector3 vInitDir = (nodePath[1].point - nodePath[0].point).normalized;
        Vector3 vEndDir = (nodePath[nodePath.Count - 2].point - nodePath[nodePath.Count - 1].point).normalized;
        float firstLength = (nodePath[1].point - nodePath[0].point).magnitude;
        float lastLength = (nodePath[nodePath.Count - 2].point - nodePath[nodePath.Count - 1].point).magnitude;

        NodePath firstNode = new NodePath(nodePath[0]);
        firstNode.point = nodePath[0].point + vEndDir * firstLength;

        NodePath lastNode = new NodePath(nodePath[nodePath.Count - 1]);
        lastNode.point = nodePath[0].point + vInitDir * lastLength;

        nodePath.Insert(0, firstNode);
        nodePath.Add(lastNode); 
    }

    /// <summary>
    /// 經過節點段數的時間大小,獲取每段節點
    /// </summary>
    /// <param name="t"></param>
    /// <returns></returns>
    public Vector3 GetHermitAtTime(float t)
    {
        //Debug.Log(t);
        int k;
        //最後一個頂點
        if (t >= nodePath[nodePath.Count - 2].time)
        {
            return nodePath[nodePath.Count - 2].point;
        }
        for (k = 1; k < nodePath.Count-2; k++)
        {
            if (nodePath[k].time > t)
                break;
        }

        k = k - 1;
        float param = (t - nodePath[k].time) / (nodePath[k+1].time - nodePath[k].time);
        return GetHermitNode(k, param);
    }

    /// <summary>
    /// Herimite曲線:獲取節點
    /// </summary>
    /// <param name="index">節點最近的頂點</param>
    /// <param name="t"></param>
    /// <returns></returns>
    public Vector3 GetHermitNode(int index,float t)
    {
        Vector3 v;
        Vector3 P0 = nodePath[index - 1].point;
        Vector3 P1 = nodePath[index].point;
        Vector3 P2 = nodePath[index + 1].point;
        Vector3 P3 = nodePath[index + 2].point;

        //調和函數
        float h1 = 2 * t * t * t - 3 * t * t + 1;
        float h2 = -2 * t * t * t + 3 * t * t;
        float h3 = t * t * t - 2 * t * t + t;
        float h4 = t * t * t - t * t;

        v = h1 * P1 + h2 * P2 + h3 * (P2 - P0) / smoothFactor + h4 * (P3 - P1) / smoothFactor;
        //Debug.Log(index + "  "+ t+"  "+v);
        return v;
    }
}


/// <summary>
/// 節點類
/// </summary>
public class NodePath
{
    public Vector3 point;
    public float time;

    public NodePath(Vector3 v,float t)
    {
        point = v;
        time = t;
    }

    public NodePath(NodePath n)
    {
        point = n.point;
        time = n.time;
    }
}
View Code

 

基類代碼

using UnityEngine;
using System.Collections;

public class Curve_Root : MonoBehaviour {

    //曲線是否爲閉合曲線
    public bool closedCurve = false;

    //曲線的顏色
    public Color _Color = Color.white;

    //自動跟隨路徑
    public bool AutoCurve = false;

    //旋轉跟隨
    public bool AutoRotate = false;
}
View Code

 

 路徑漫遊

  在曲線函數中,參數t取值[0,1],將曲線進行分段。那麼可以計算出每個點的位置。所以,在路徑漫遊中,咱們從原點出發,將t的增量做爲下一個點位置,進行插值移動。就實現了路徑漫遊,同時進行朝向下一個頂點旋轉,就能夠使看的方向隨着曲線變化。

Unity 3D 項目工程

http://download.csdn.net/detail/familycsd000/9365859

相關文章
相關標籤/搜索