Two kinds of Quaternion SlerpImp (Unity)

时间:2021-11-17 23:21:06

using UnityEngine;
using System.Collections;

public class SlerpImp
{
static float Dot(Quaternion a, Quaternion b)
{
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}

static Quaternion Lerp(Quaternion a, Quaternion b, float t)
{
return new Quaternion(a.x * (1 -t) + b.x * t,
a.y * (1 -t) + b.y * t,
a.z * (1 -t) + b.z * t,
a.w * (1 -t) + b.w * t);
}

static Quaternion Inverse(Quaternion a)
{
a.x = -a.x;
a.y = -a.y;
a.z = -a.z;

return a;
}

public static Quaternion Slerp1(Quaternion a, Quaternion b, float t)
{
t = Mathf.Clamp01 (t);

float similar = Dot(a,b);
float sign = 1.0f;
if(similar >= 1f - Mathf.Epsilon)
return a == Quaternion.identity ? Quaternion.identity : Lerp(a,b,t);
else if (similar < 0f)
{
a.x = -a.x;
a.y = -a.y;
a.z = -a.z;
a.w = -a.w;
sign = -1f;
}

Quaternion result = Quaternion.identity;

// 0-pi, otherwise, inverse the xyz axis space
float aw = Mathf.Acos(a.w);
float bw = Mathf.Acos(b.w);

float saw = new Vector3(a.x, a.y, a.z).magnitude;
float sbw = new Vector3(b.x, b.y, b.z).magnitude;

aw = saw != 0.0f ? aw * (1 - t) / saw : 0.0f;
bw = sbw != 0.0f ? bw * t / sbw : 0.0f;
result.x = a.x * aw + b.x * bw;
result.y = a.y * aw + b.y * bw;
result.z = a.z * aw + b.z * bw;

Vector3 v = new Vector3(result.x, result.y, result.z);
float theta = v.magnitude;
if (theta == 0f)
return Quaternion.identity;

float sintheta = sign * Mathf.Sin(theta) / theta;
result.x *= sintheta;
result.y *= sintheta;
result.z *= sintheta;
result.w = sign * Mathf.Cos(theta);

return result;
}

public static Quaternion Slerp2(Quaternion a, Quaternion b, float t)
{
t = Mathf.Clamp01 (t);

float sign = 1.0f;
float similar = Dot(a,b);
if(similar >= 1f - Mathf.Epsilon)
return a == Quaternion.identity ? Quaternion.identity : Lerp(a,b,t);
else if (similar < 0f)
{
a.x = -a.x;
a.y = -a.y;
a.z = -a.z;
a.w = -a.w;
sign = -1.0f;
}

Quaternion result = Inverse(a);
result *= b;
Vector3 v = new Vector3(result.x, result.y, result.z);
float sintheta = v.magnitude;
// 0-pi, otherwise, inverse the xyz axis space
float theta = Mathf.Acos(result.w);
theta *= t;
sintheta = sign * Mathf.Sin(theta) / sintheta;
result.x *= sintheta;
result.y *= sintheta;
result.z *= sintheta;
result.w = sign * Mathf.Cos(theta);
return a * result;
}

}

[ExecuteInEditMode]
public class SlerpTest : MonoBehaviour {

public int callTimes = 1000000;
[Range(0,1)]
public float t = 0.3f;
// Use this for initialization
void Start ()
{

int callCount = callTimes;
float time = Time.realtimeSinceStartup;
while(callCount-- > 0)
{
SlerpImp.Slerp1(this.transform.rotation, Camera.main.transform.rotation, t);
}
Debug.LogWarning("Slerp1 "+ callTimes + " calls took: " + (Time.realtimeSinceStartup - time));

callCount = callTimes;
time = Time.realtimeSinceStartup;
while(callCount-- > 0)
{
SlerpImp.Slerp2(this.transform.rotation, Camera.main.transform.rotation, t);
}
Debug.LogWarning("Slerp2 "+ callTimes + " calls took: " + (Time.realtimeSinceStartup - time));

callCount = callTimes;
time = Time.realtimeSinceStartup;
while(callCount-- > 0)
{
Quaternion.Slerp(this.transform.rotation, Camera.main.transform.rotation, t);
}
Debug.LogWarning("UnityS "+ callTimes + " calls took: " + (Time.realtimeSinceStartup - time));
}

void Update()
{
Debug.LogWarning("------------------");
Debug.LogWarning("Slerp1: " + SlerpImp.Slerp1(this.transform.rotation, Camera.main.transform.rotation, t).eulerAngles);
Debug.LogWarning("Slerp2: " + SlerpImp.Slerp2(this.transform.rotation, Camera.main.transform.rotation, t).eulerAngles);
Debug.LogWarning("UnitySLerp: " + Quaternion.Slerp(this.transform.rotation, Camera.main.transform.rotation, t).eulerAngles);
}
}