分段三次Hermite样条曲线的应用(Unity 动画曲线AnimationCurve的实现方法的还原)

26 篇文章 1 订阅
1 篇文章 0 订阅

分段三次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条曲线是完全一样的。

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值