0

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?

6
  • I would simply like to avoid adding any dependencies, especially since I hardly use them aside from what's in the question. Commented Oct 15 at 22:01
  • Your 'further digging' is incorrect on both counts. The sum of (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). Commented Oct 16 at 0:30
  • 1
    Why would you try to avoid depdendencies, the costs of not having them is way higher. Now you have to develop, test and debug code, which at runtime is not as efficient as when you use a library. And when you use a package manager dependencies are very manageable, if you want to avoid deployment issues link statically Commented Oct 16 at 1:57
  • 1
    For a good approximation use LERP + normalize. read good article "Understanding Slerp, Then Not Using It" Commented Oct 16 at 13:50
  • 1
    If someone says that you "sum" quaternions by multiplying them I would assume they mean that you "sum" rotations described by quaternions by multiplying the quaternions - but that is sloppy terminology: when you combine the rotations you multiply them, you don't "sum" them. Commented Oct 16 at 14:42

1 Answer 1

-1

SHORT.
You can do exactly q3 = q1 + (q2 - q1)*ratio where ratio is an arbitrary float between 0 and 1.

BUT.
1. Before this, one of quaternions must be flipped (inversed signs of 4 components ) in such way that dot_product of 2 quaternions was non negative. It force use of interpolation by shortest arc.
2. After simple LERP you should normalize result quaternion if you are working with unit ones.
3. Usually it is fine enough for CG (like animations) and lot of mechanical stuff.

And last read good article "Understanding Slerp, Then Not Using It".

if (q1.dot(q2) < 0)
    q1 = -q1;
q3 = q1 + (q2 - q1)*ratio
q3.normalize()
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.