Search code examples
javamathmatrixskeletal-meshjoml

How to calculate the corresponding matrix to combine with in order to achieve a specific target Euler rotation in XYZ?


I am given a rotation matrix constant that is immutable and should not be modified with rotations of 30, 20, and 10 degrees about XYZ.

I need to create a matrix attempt that, when either pre or post multiplied with constant will create a matrix with rotations of 90, -20, and -10 degrees about XYZ and equal to target.

Matrix4d target = new Matrix4d()
    .rotationXYZ(Math.toRadians(30 + 60), Math.toRadians(20 - 40), Math.toRadians(10 - 20)); // 90, -20, -10

System.out.println(new Vector3d(8.0).mulProject(target)); // Target * V = (5.973 -10.687 6.489)

Matrix4d constant = new Matrix4d()
    .rotationXYZ(Math.toRadians(30), Math.toRadians(20), Math.toRadians(10)); // A

Matrix4d attempt = new Matrix4d()
    .rotationXYZ(Math.toRadians(60), Math.toRadians(-40), Math.toRadians(-20)); // B

Matrix4d combined = constant.mulLocal(attempt, new Matrix4d()); // C = B * A
System.out.println(new Vector3d(8.0).mulProject(combined)); // Combined * V = (1.84 -10.747 8.55)

As shown above, the attempt matrix rotates by the extra amount required in each axis after the constant. This resulting combined matrix does not equal the target though.

Matrix4d attempt = new Matrix4d()
    .rotationXYZ(Math.toRadians(90), Math.toRadians(-20), Math.toRadians(-10))
    .mul(constant.invert(new Matrix4d()));

This second attempt works, but it requires that I inverse the work of constant first which is a waste of resources. I would like to find the correct rotation values to give to rotationXYZ or rotate with a quaternion without inversing the previous matrix.


Solution

  • First we must know precisely what rotateXYZ does. From https://joml-ci.github.io/JOML/apidocs/org/joml/Matrix4f.html#rotateXYZ(float,float,float,org.joml.Matrix4f)

    Apply rotation of angleX radians about the X axis, followed by a rotation of angleY radians about the Y axis and followed by a rotation of angleZ radians about the Z axis.

    So we can think of this as apply rotations Rx, Ry, Rz to our starting matrix M to give resulting matrix M'.

    M' = Rz Ry Rx M
    

    Now a very important point about matrix multiplication is that order of applying matrices is important, i.e. it is not commutative. So using a different order say

    M'' = Rx Ry Rz M
    

    will in general be different to M'.

    Here we have two matrices

    target = Rz(-10) * Ry(-20) * Rx(90)
    constant = Rz(10) * Ry(20) * Rx(10) 
    

    and wish to find matrix attempt such that

    target = attempt * constant
    

    Or

    target = attempt * Rz(10) * Ry(20) * Rx(10)

    Now we can apply the inverse of the compentent rotations. Note that the inverse of a rotation matrix Rx(theta) is the rotation matrix Rx(-theta), (a clockwise rotation is the inverse of an anti-clockwise rotation).

    So we apply the rotations applying the inverses on the right

    target = attempt * Rz(10) * Ry(20) * Rx(10) 
    target * Rx(-10) = attempt * Rz(10) * Ry(20)  
    target * Rx(-10) * Ry(-20) = attempt * Rz(10)
    target * Rx(-10) * Ry(-20) * Rz(-10) = attempt
    

    Note the difference in order the last matrices are applied in the order Z, Y, X.

    Now JOML has method for matrices in every possible order and the method we want is

    public Matrix4f rotateZYX(Vector3f angles)

    Apply rotation of angles.z radians about the Z axis, followed by a rotation of angles.y radians about the Y axis and followed by a rotation of angles.x radians about the X axis.

    So to find attempt we can take

    inverse = new Matrix4d()
    .rotationZYX(Math.toRadians(-10), Math.toRadians(-20), Math.toRadians(-10))
    attempt = target.clone()
    attempt.mul(inverse) // i.e. attempt = target * inverse
    

    Or more simply

    attempt = target.clone()
    attempt.rotationZYX(Math.toRadians(-10), Math.toRadians(-20), Math.toRadians(-10))