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进行数值计算
当插值的要求涉及到对插值函数导数的要求时,普通插值问题就变为埃尔米特插值问题.拉格朗日插值和牛顿插值的要求较低,只需要插值函数的函数值在插值点与被插函数的值相等,以此来使得在其它非插值节点插值函数的值 ...
随机推荐
- iOS生成PDF的关键代码-备忘
//此方法只是把当前页面的内容生成PDF并保存在沙盒中. //还需要做:把当前面没有显示的内容以分页的形式生成PDF,并把PDF读取并显示出来 //关于显示可以参考:念茜的博客 iOS开发笔记——PD ...
- 建造者模式以及php实现
建造者模式: 造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对 ...
- vue项目中安装cnpm和node_modules
1.安装cnpm的nodejs包管理工具,命令行: npm install -g cnpm --registry=https://registry.npm.taobao.org 2. 每个vue项 ...
- 虚拟机下安装 CentOS 7 的几个小问题
※ 网络问题(Destination Host Unreachable) 安装时网络选择的"桥接"模式, 安装完毕,并配置IP地址后,发现只能ping通自己,局域网内的其他IP无法 ...
- nginx for windows 安装
一.nginx for windows 的安装地址: http://nginx.org/en/download.html 二.nginx 安装地址: http://nginx.org/en/docs/ ...
- Apache Tomcat 之路(三 部署多个应用)
想要在一台服务器上部署多个web应用的时候有两种部署方式:1.拷贝多个tomcat 服务器,每个服务器启动不同的web应用;2.一个tomcat容器部署多个web应用 两种方式的优缺点:多个tomca ...
- R in action读书笔记(17)第十二章 重抽样与自助法
12.4 置换检验点评 除coin和lmPerm包外,R还提供了其他可做置换检验的包.perm包能实现coin包中的部分功能,因此可作为coin包所得结果的验证.corrperm包提供了有重复测量的相 ...
- VS2017 移动开发(Android and IOS) 序
序 公司原因,要求用C#开发移动端app,老板觉得用现在会的C#做会比较快... 从零开始,折腾一个多星期,重装系统三遍(强迫症),其它各种折腾,终于手机运行上了第一个APP,看看就好... 不得不吐 ...
- FlowNet: Learning Optical Flow with Convolutional Networks
作者:嫩芽33出处:http://www.cnblogs.com/nenya33/p/7122701.html 版权:本文版权归作者和博客园共有 转载:欢迎转载,但未经作者同意,必须保留此段声明:必须 ...
- CSS3 loading 和 文字颜色渐变
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
