Hermite (埃尔米特)曲线
Hermite 曲线
已知曲线的两个端点坐标P0、P1,和端点处的切线R0、R1,确定的一条曲线。
参数方程
1. 几何形式

2. 矩阵形式

3. 推导






例子分析

如上图有四个点,假如P0、P2是端点,那么向量R0=(P1-P0),R1=(P3-P2),将数据带入调和函数,即求得曲线。
在程序中,我们通常会使用特殊方法处理顶点之间的关系。

图中含有3个顶点,我们把每挨着的两个顶点看做是一条Hermite曲线,P0和P1是两个端点,那么现在,我们如何求得R1呢? 我们现在构建连个参考点F1,F2。
令 F1 = P0; F2 = P2;
那么 R1 = P1-F1; R2 = F2-P1;
然后将此值带入曲线函数,即可为求得的曲线。
程序代码
该代码是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 - < )
{
Debug.LogError("Please add at least two points.");
return;
} //非闭合曲线与闭合曲线的顶点时间计算:非闭合曲线,最后个顶点的时间为1.0,闭合曲线的最后个顶点的时间为倒数第二个顶点,因为他的最后个点是原点。
float t;
if (closedCurve)
{
t = 1.0f / (mTrans.Length - );
nodePath = new List<NodePath>();
for (int i = ; i < mTrans.Length; i++)//根节点不参与路径节点的计算
{
nodePath.Add(new NodePath(mTrans[i].transform.position, (i - ) * t));
}
//闭合曲线完整的节点
AddClosedCurveNode();
}
else
{
t = 1.0f / (mTrans.Length - );
nodePath = new List<NodePath>();
for (int i = ; i < mTrans.Length; i++)//根节点不参与路径节点的计算
{
nodePath.Add(new NodePath(mTrans[i].transform.position, (i - ) * t));
}
//非闭合曲线完整的节点
AddCurveNode();
}
} // Use this for initialization
void Start () { DadaCorrection(); ComputeNode(); node = new Vector3[lineCount+];
//Vector3 start = nodePath[1].point;
//Vector3 end; node[] = nodePath[].point;
Vector3 end;
//绘制节点
for (int i = ; i <= lineCount; i++)
{
float ti = i / (float)lineCount;
end = GetHermitAtTime(ti);
if (node != null)
{
node[i] = end;
}
} T = new GameObject().transform; curPos = node[];
nextPos = node[];
moveTime = (nextPos - curPos).magnitude / Speed; Tar.transform.position = curPos;
Tar.transform.transform.LookAt(nextPos);
curRotate = Tar.transform.rotation;
T.position = node[];
T.LookAt(node[]);
nextRotate = T.rotation;
curTime = ;
index = ;
} // 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 + ) % (lineCount + ) == ) ? : index + ;
}
else
{
index = ((index + ) % (lineCount+) == ) ? index : index + ;
}
curPos = nextPos;
nextPos = node[index]; curTime = ;
moveTime = (nextPos - curPos).magnitude / Speed; T.position = node[index];
curRotate = nextRotate; if (closedCurve)
{
int c1;
if (index == node.Length-)
{
c1 = ;
}
else
{
c1 = index + ;
}
T.LookAt(node[c1]);
}
else
{
int c1;
if (index == node.Length - )
{
c1 = ;
}
else
{
c1 = index + ;
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[].point;
Vector3 end; Gizmos.color = _Color;
//绘制节点
for (int i = ; 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 - ].point);
}
else
{
Vector3 start = nodePath[].point;
Vector3 end; Gizmos.color = _Color;
//绘制节点
for (int i = ; 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 - ].point);
}
} /// <summary>
/// 在Scene场景中,绘制Hermite曲线
/// </summary>
void OnDrawGizmos()
{
/*数据校正*/
DadaCorrection();
/*计算顶点*/
ComputeNode();
/*计算曲线*/
DrawCurve();
} /// <summary>
/// 1. 非闭合曲线
/// </summary>
public void AddCurveNode()
{
nodePath.Insert(, nodePath[]);
nodePath.Add(nodePath[nodePath.Count - ]);
} /// <summary>
/// 2. 闭合曲线
/// </summary>
public void AddClosedCurveNode()
{
//nodePath.Insert(0, nodePath[0]);
nodePath.Add(new NodePath(nodePath[]));
nodePath[nodePath.Count - ].time = 1.0f; Vector3 vInitDir = (nodePath[].point - nodePath[].point).normalized;
Vector3 vEndDir = (nodePath[nodePath.Count - ].point - nodePath[nodePath.Count - ].point).normalized;
float firstLength = (nodePath[].point - nodePath[].point).magnitude;
float lastLength = (nodePath[nodePath.Count - ].point - nodePath[nodePath.Count - ].point).magnitude; NodePath firstNode = new NodePath(nodePath[]);
firstNode.point = nodePath[].point + vEndDir * firstLength; NodePath lastNode = new NodePath(nodePath[nodePath.Count - ]);
lastNode.point = nodePath[].point + vInitDir * lastLength; nodePath.Insert(, 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 - ].time)
{
return nodePath[nodePath.Count - ].point;
}
for (k = ; k < nodePath.Count-; k++)
{
if (nodePath[k].time > t)
break;
} k = k - ;
float param = (t - nodePath[k].time) / (nodePath[k+].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 - ].point;
Vector3 P1 = nodePath[index].point;
Vector3 P2 = nodePath[index + ].point;
Vector3 P3 = nodePath[index + ].point; //调和函数
float h1 = * t * t * t - * t * t + ;
float h2 = - * t * t * t + * t * t;
float h3 = t * t * t - * 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的增量作为下一个点位置,进行插值移动。就实现了路径漫游,同时进行朝向下一个顶点旋转,就可以使看的方向随着曲线变化。
Unity 3D 项目工程
http://download.csdn.net/detail/familycsd000/9365859
Hermite (埃尔米特)曲线的更多相关文章
- 数值分析:Hermite多项式
http://blog.csdn.net/pipisorry/article/details/49366047 Hermite埃尔米特多项式 在数学中,埃尔米特多项式是一种经典的正交多项式族,得名于法 ...
- 骨骼蒙皮动画(SkinnedMesh Animation)的实现
http://blog.csdn.net/zjull/article/details/11529695 1.简介 骨骼蒙皮动画,简称骨骼动画,因其占用磁盘空间少并且动画效果好被广泛用于3D游戏中,它把 ...
- 样条之埃尔米特(Hermite)
埃尔米特(Charles Hermite,1822—1901) 法国数学家.巴黎综合工科学校毕业.曾任法兰西学院.巴黎高等师范学校.巴黎大学教授.法兰西科学院院士.在函数论.高等代数.微分方程等方面都 ...
- 样条之埃尔米特(Hermite)插值函数
核心代码: ////////////////////////////////////////////////////////////////////// // 埃尔米特等距插值 /////////// ...
- Hermite曲线插值
原文 Hermite Curve Interpolation Hermite Curve Interpolation Hamburg (Germany), the 30th March 1998. W ...
- hermite插值
Hermite 插值就是要求插值函数不仅经过所给节点,而且要保证在该点的导数也相等.<备注:虽然还不理解这句话,但是还是先放这里!> 所谓样条曲线(Spline Curves)是指给定一组 ...
- Moore-Penrose Matrix Inverse 摩尔-彭若斯广义逆 埃尔米特矩阵 Hermitian matrix
http://mathworld.wolfram.com/Moore-PenroseMatrixInverse.html 显然,埃尔米特矩阵主对角线上的元素都是实数的,其特征值也是实数.对于只包含实数 ...
- caffe的python接口学习(7):绘制loss和accuracy曲线
使用python接口来运行caffe程序,主要的原因是python非常容易可视化.所以不推荐大家在命令行下面运行python程序.如果非要在命令行下面运行,还不如直接用 c++算了. 推荐使用jupy ...
- 埃尔米特插值问题——用Python进行数值计算
当插值的要求涉及到对插值函数导数的要求时,普通插值问题就变为埃尔米特插值问题.拉格朗日插值和牛顿插值的要求较低,只需要插值函数的函数值在插值点与被插函数的值相等,以此来使得在其它非插值节点插值函数的值 ...
随机推荐
- _bzoj1503 [NOI2004]郁闷的出纳员【Splay】
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1503 由于初始工资未达到下限而离开的员工不算在离开人数之内...坑爹... 然后就是写kth ...
- RHEL5.6配置本地yum源
试验环境:RedHat Enterprise Linux 5.6(虚拟机) 一.挂载镜像 配置yum源第一步需要挂载镜像,或者直接复制操作系统的光盘文件至操作系统目录中. 挂载镜像命令如下: moun ...
- Spring配置文件中未引入dubbo命名空间头部配置而引起的错误案例
问题描述: Spring配置文件中未引入dubbo命名空间的头部配置而引起项目启动时报出如下错误信息: org.springframework.beans.factory.xml.XmlBeanDef ...
- 449 Serialize and Deserialize BST 序列化和反序列化二叉搜索树
详见:https://leetcode.com/problems/serialize-and-deserialize-bst/description/ C++: /** * Definition fo ...
- 214 Shortest Palindrome 最短回文串
给一个字符串 S, 你可以通过在字符串前面添加字符将其转换为回文串.找到并返回可以用这种方式转换的最短回文串.例如:给出 "aacecaaa",返回 "aaacecaaa ...
- colormap画出的图不是彩色问题
针对matlab2017渲染出的彩色图是黑白的问题. t=labels; t(tstSet(:,end-))=Relabels; t=reshape(t,,); t=t'; figure imshow ...
- ScrollView嵌套GridView,GridView显示不全
最近开发有个需求是以A-Z的方式区分全国城市(搜索功能),并实现字母索引的功能(有点类似微信,不过比较坑的是用的是GridView, 并且GridView上面还有几个LinearLayout). 详细 ...
- struts2 <allowed-methods > 标签配置
1.在struts2 2.5版本中添加了对方法访问的权限,如果没有被添加到<allow-method> 方法的标签,将会报一下错误 5:05:18.078 [http-apr-8020 ...
- ubuntu 下安装redis
获取Redis 1.通过官网http://redis.io/获取稳定版源码包下载地址: 2.通过wget http://download.redis.io/releases/redis-3.0.2.t ...
- GA详解
转:http://blog.csdn.net/u010451580/article/details/51178225 本文是去年课题组周报中的一个专题讲解,详细讲了GA,由于是周报,所以十分详细.很适 ...
