There are a few thing that you should at least cover if you are talking about quaternions to use in games.
First how to create one from a rotation described by 2 vectors that isn't QuatFromAxisAngle(Normalized(Cross(v1, v2)), Acos(dot(v1, v2)));
Instead you can do Normalize(QuatFromWAndVector(1 + dot(v1, v2), cross(v1, v2)))
This works out because QuatFromWAndVector(dot(v1, v2), cross(v1, v2)) is the quaternion of the double angle so if you NLerp(DoubleQuat, Quat(1,0,0,0), 0.5) you get the quaternion you want without having to call any trig functions.
With Slerp you can optimize the acos based formula based on NLerp(q1, q2, 0.5) == SLerp(q1, q2, 0.5) and the constant velocity of slerp to bisect the slerp range and only slerp on half of it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | quat SLerp(quat q1, quat q2, r32 t)
{
quat Result;
if(Dot(q1, q2) > 0.95)
{
Result = NLerp(q1, q2, t);
}
else if(t < 0.5)
{
Result = SLerp(q1, NLerp(q1, q2, 0.5), t*2);
}
else
{
Result = SLerp(NLerp(q1, q2, 0.5), q2, t*2 - 1);
}
return(Result);
}
|
If you turn this into the iterative form you can change the stopping criteria:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | quat SLerp(quat q1, quat q2, r32 t)
{
for(u32 Step = 0;
Step < 5;
++Step)
{
quat Mid = NLerp(q1, q2, 0.5);
if(t < 0.5)
{
q2 = Mid;
t = t*2;
}
else
{
q1 = Mid;
t = t*2 - 1;
}
}
quat Result = NLerp(q1, q2, t);
return(Result);
}
|
and voilà; a function that used to take an acos and 2 sins and a normalize is changed to a function that takes a tune-able amount of normalizes. The step limit 5 that I picked is the limit to where Cos(PI/(1<<n)) becomes larger than 0.95. The branch can be optimized with a conditional mov and changing t*(1<<5) into an int you can do bit checks with.
As an added bonus unlike the acos based formula this version does not become unstable when q1 is nearly equal to q2. Only when q1 is nearly equal to -q2 but you can't do anything about that.