In the below code snippets, vec4 is simply an alias for std::array<float, 4> where index 0 is x, 1 is y, 2 is z, and 3 is w.
I want to find an intermediate q3 such that q3 = q1 + (q2 - q1)*ratio where ratio is an arbitrary float between 0 and 1.
At first I naively did the following:
q3 = {
q1[0] + (q2[0] - q1[0])*ratio,
q1[1] + (q2[1] - q1[1])*ratio,
q1[2] + (q2[2] - q1[2])*ratio,
q1[3] + (q2[3] - q1[3])*ratio,
};
Then I simply applied a SLERP function I found to get a "weighted average":
// from https://github.com/MartinWeigel/Quaternion/blob/master/Quaternion.c
vec4 quaternionSlerp(vec4 q1, vec4 q2, float t)
{
// Based on http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/index.htm
float cosHalfTheta = q1[3]*q2[3] + q1[0]*q2[0] + q1[1]*q2[1] + q1[2]*q2[2];
// if q1=q2 or q1=-q2 then theta = 0 and we can return q1
if (std::fabs(cosHalfTheta) >= 1.0F) {
return q1;
}
float halfTheta = std::acos(cosHalfTheta);
float sinHalfTheta = std::sqrt(1.0F - cosHalfTheta*cosHalfTheta);
// If theta = 180 degrees then result is not fully defined
// We could rotate around any axis normal to q1 or q2
if (std::fabs(sinHalfTheta) < 1e-4F) {
return {
q1[0] * 0.5F + q2[0] * 0.5F,
q1[1] * 0.5F + q2[1] * 0.5F,
q1[2] * 0.5F + q2[2] * 0.5F,
q1[3] * 0.5F + q2[3] * 0.5F
};
}
else {
// Default quaternion calculation
float ratioA = std::sin((1.0F - t) * halfTheta) / sinHalfTheta;
float ratioB = std::sin(t * halfTheta) / sinHalfTheta;
return {
q1[0] * ratioA + q2[0] * ratioB,
q1[1] * ratioA + q2[1] * ratioB,
q1[2] * ratioA + q2[2] * ratioB,
q1[3] * ratioA + q2[3] * ratioB
};
}
}
q3 = quaternionSlerp(q1, q2, ratio);
After some more digging, I found out that to get the sum of two quaternions, you multiply them, and for the difference between two quaternions, you multiply one by the conjugate/inverse of the other, so I tried this:
vec4 multiplyQuaternions(vec4 q1, vec4 q2)
{
float w1 = q1[3], x1 = q1[0], y1 = q1[1], z1 = q1[2];
float w2 = q2[3], x2 = q2[0], y2 = q2[1], z2 = q2[2];
return {
w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2, // X
w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2, // Y
w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2, // Z
w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 // W
};
}
vec4 intermediateQuaternion(vec4 q1, vec4 q2, float ratio)
{
float cosHalfTheta = q1[3]*q2[3] + q1[0]*q2[0] + q1[1]*q2[1] + q1[2]*q2[2];
float halfTheta = std::acos(cosHalfTheta);
float sinHalfTheta = std::sqrt(1.0F - cosHalfTheta*cosHalfTheta);
ratio = std::sin(ratio * halfTheta) / sinHalfTheta;
vec4 qDifference = multiplyQuaternions(q1, { -q2[0], -q2[1], -q2[2], q2[3] });
return multiplyQuaternions(q1, { qDifference[0]*ratio, qDifference[1]*ratio, qDifference[2]*ratio, qDifference[3]*ratio });
}
q3 = intermediateQuaternion(q1, q2, ratio);
I also tried some variations in the intermediateQuaternion function, but since I don't really know what I'm doing I didn't arrive at any better results.
None of this seems to produce the desired result. How do I calculate this "intermediate" value properly?
(a,bi,cj,dk)and(t,ui,vj,wk) is((a=t),(b+u)i,c+v)j,d+w)k), and their difference is(a-t,(b-u)i,(c-v)j,(d-w)k).