分段三次Hermite插值是一种光滑的分段插值。
分段三次Hermite插值函数要满足的条件:
1. 已知节点(x_i,y_i) 及微商值 k_i (i = 0 , 1, 2, ....... n);
2. 在每个小区间[x_i , x_i_1] 上是不高于三次的多项式。
Unity AnimationCurve动画曲线是根据一些关键帧的节点信息绘制的一条光滑的曲线。在每个关键帧存有节点值及微商值。
AnimationCurve类里存有关键帧的信息,public Keyframe[] keys;
现在我们根据这些关键帧信息描绘出一条分段三次Hermite样条曲线的插值函数,看是否与Unity 中的使用的方法一致:
1.先实现函数:
Evaluate 方法为函数主体,根据相邻2节点的信息生成一个Hermite曲线函数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class UKeyframe
{
/// <summary>
/// Describes the tangent when approaching this point from the previous point in the curve.
/// </summary>
public float inTangent;
/// <summary>
/// The out tangent.
/// </summary>
public float outTangent;
/// <summary>
/// The time of the keyframe.
/// </summary>
public float time;
/// <summary>
/// The value of the curve at keyframe.
/// </summary>
public float value;
public static UKeyframe GetUkeyframe (Keyframe kf)
{
UKeyframe ukf = new UKeyframe ();
ukf.time = kf.time;
ukf.value = kf.value;
ukf.inTangent = kf.inTangent;
ukf.outTangent = kf.outTangent;
return ukf;
}
}
public class UAnimationCurve : MonoBehaviour
{
public UKeyframe[] keys;
public void SetKeys (UKeyframe[] keys)
{
this.keys = keys;
}
public float Evaluate (float x)
{
if (this.keys == null || this.keys.Length == 0) {
return 0;
}
return UAnimationCurve.Evaluate (this.keys, x);
}
/// <summary>
/// 分段三次Hermite样条曲线 P(t) = B1 + B2 * t + B3 * t2 + B4 * t3
/// t 后数字是指数
/// 已知每个节点的x,y 和节点的切线值(导数,微商值) , 可根据相邻2点和微商值确定一条三次Hermite样条曲线
/// 注意,当节点数多于2个时,就是分段三次Hermite样条曲线,每一段的x,y的起点终点 与上一个节点的终点做个偏移(矫正),最后再加上偏移量即可
/// </summary>
public static float Evaluate (UKeyframe[] keys, float x)
{
var index = 0;
// 找出当前节点
for (int i = 0; i < keys.Length; i++) {
if (i == 0 && x < keys[i].time)
{
return keys[0].value;
}
if (x <= keys [i].time) {
index = i;
if (i == 0) {
index = 1;
}
break;
}
}
if (index == 0) {
// index = keys.Length - 1;
return keys [keys.Length - 1].value;
}
// 前一个节点
var startIndex = index - 1;
// 后一个节点
var endIndex = index;
// 当前时间(当前曲线的时间点)
var t = x - keys [startIndex].time;
// 当前曲线偏移的时间点
float off_t = keys [startIndex].time;
// 当前曲线偏移的值
float off_p = keys [startIndex].value;
// 当前曲线起点
var t0 = keys [startIndex].time - off_t;
// 当前曲线终点
var t1 = keys [endIndex].time - off_t;
// 求参数时用到的是一些表达式
var A = t1 - t0;
var B = t1 * t1 - t0 * t0;
var C = t1 * t1 * t1 - t0 * t0 * t0;
// 起点值(矫正当前曲线的值)
var p0 = keys [startIndex].value - off_p;
// 终点值(矫正当前曲线的值)
var p1 = keys [endIndex].value - off_p;
// 起点切线值
var p0_d = keys [startIndex].outTangent;
// 终点切线值
var p1_d = keys [endIndex].inTangent;
// 求当前曲线参数
var b4 = ((p1 - p0 - p0_d * A) / (B - 2 * A * t0 * t0) - (p1_d - p0_d) / (2 * A)) / ((C - 3 * A * t0 * t0) / (B - 2 * A * t0) - (3 * B / (2 * A)));
var b3 = (p1_d - p0_d) / (2 * A) - 3 * B / (2 * A) * b4;
var b2 = p0_d - (b3 * 2 * t0 + b4 * 3 * t0 * t0);
var b1 = p0 - (b2 * t0 + b3 * t0 * t0 + b4 * t0 * t0 * t0);
// 求当前曲线值
var pt = b1 + b2 * t + b3 * t * t + b4 * t * t * t;
return pt + off_p;
}
}
2. 根据现有Unity 曲线关键点信息用此分段三次Hermite样条函数来临摹一条曲线
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
/// <summary>
/// unity 曲线
/// </summary>
public AnimationCurve cure = new AnimationCurve ();
void Start ()
{
}
void Update ()
{
}
void OnDrawGizmos ()
{
if (cure.keys == null || cure.keys.Length == 0) {
return;
}
UKeyframe[] keys = new UKeyframe[cure.keys.Length];
for (int i = 0; i < keys.Length; i++) {
keys [i] = UKeyframe.GetUkeyframe (cure.keys [i]);
}
for (int j = 0; j <= 100; j++) {
float s = 1.5f;
var pos = new Vector3 (j * 0.01f, UAnimationCurve.Evaluate (keys, j * 0.01f), 0);
var pos2 = new Vector3 (j * 0.01f + s, cure.Evaluate (j * 0.01f) + s, 0);
Gizmos.DrawCube (pos, Vector3.one * 0.03f);
Gizmos.DrawCube (pos2, Vector3.one * 0.03f);
}
}
}
3.把脚本NewBehaviourScript挂在一个GameObject 上,然后可以在这里调节一下曲线的样子
4.在scene 中即可看到通过Unity AnimationCurve自带函数形成的曲线与我们通过分段三次Hermite插值函数形成的曲线的模样,可以发现,2条曲线是完全一样的。