Search code examples
rotationquaternions

Component of a quaternion rotation around an axis


I'm having trouble finding any good information on this topic. Basically I want to find the component of a quaternion rotation, that is around a given axis (not necessarily X, Y or Z - any arbitrary unit vector). Sort of like projecting a quaternion onto a vector. So if I was to ask for the rotation around some axis parallel to the quaternion's axis, I'd get the same quaternion back out. If I was to ask for the rotation around an axis orthogonal to the quaternion's axis, I'd get out an identity quaternion. And in-between... well, that's what I'd like to know how to work out :)


Solution

  • I spent the other day trying to find the exact same thing for an animation editor; here is how I did it:

    1. Take the axis you want to find the rotation around, and find an orthogonal vector to it.

    2. Rotate this new vector using your quaternion.

    3. Project this rotated vector onto the plane the normal of which is your axis

    4. The acos of the dot product of this projected vector and the original orthogonal is your angle.

       public static float FindQuaternionTwist(Quaternion q, Vector3 axis)
       {
           axis.Normalize();
      
           // Get the plane the axis is a normal of
           Vector3 orthonormal1, orthonormal2;
           ExMath.FindOrthonormals(axis, out orthonormal1, out orthonormal2);
      
           Vector3 transformed = Vector3.Transform(orthonormal1, q);
      
           // Project transformed vector onto plane
           Vector3 flattened = transformed - (Vector3.Dot(transformed, axis) * axis);
           flattened.Normalize();
      
           // Get angle between original vector and projected transform to get angle around normal
           float a = (float)Math.Acos((double)Vector3.Dot(orthonormal1, flattened));
      
           return a;
       }
      

    Here is the code to find the orthonormals however you can probably do much better if you only want the one for the above method:

    private static Matrix OrthoX = Matrix.CreateRotationX(MathHelper.ToRadians(90));
    private static Matrix OrthoY = Matrix.CreateRotationY(MathHelper.ToRadians(90));
    
    public static void FindOrthonormals(Vector3 normal, out Vector3 orthonormal1, out Vector3 orthonormal2)
    {
        Vector3 w = Vector3.Transform(normal, OrthoX);
        float dot = Vector3.Dot(normal, w);
        if (Math.Abs(dot) > 0.6)
        {
            w = Vector3.Transform(normal, OrthoY);
        }
        w.Normalize();
    
        orthonormal1 = Vector3.Cross(normal, w);
        orthonormal1.Normalize();
        orthonormal2 = Vector3.Cross(normal, orthonormal1);
        orthonormal2.Normalize();
    }
    

    Though the above works you may find it doesn't behave as you'd expect. For example, if your quaternion rotates a vector 90 deg. around X and 90 deg. around Y you'll find if you decompose the rotation around Z it will be 90 deg. as well. If you imagine a vector making these rotations then this makes perfect sense but depending on your application it may not be desired behaviour. For my application - constraining skeleton joints - I ended up with a hybrid system. Matrices/Quats used throughout but when it came to the method to constrain the joints I used euler angles internally, decomposing the rotation quat to rotations around X, Y, Z each time.