已知曲線的兩個端點座標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; } }
基類代碼
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; }
在曲線函數中,參數t取值[0,1],將曲線進行分段。那麼可以計算出每個點的位置。所以,在路徑漫遊中,咱們從原點出發,將t的增量做爲下一個點位置,進行插值移動。就實現了路徑漫遊,同時進行朝向下一個頂點旋轉,就能夠使看的方向隨着曲線變化。