What RotationConvention follows Rotation.applyTo(Vector3D)?

Hello everyone,

I understand that RotationConvention can be tricky to understand at first but are an important feature of the way Hipparchus handles rotations, however I have an issue with the convention used in Rotation.applyTo(Vector3D). It seems natural to me that it should follow the VECTOR_OPERATOR convention since we apply the rotation to the vector and the Javadoc of this RotationConvention reads:

According to this convention, the rotation moves vectors with respect to a fixed reference frame.

The Javadoc then gives a test case:

This means that if we define rotation r is a 90 degrees rotation around the Z axis, the image of vector Vector3D.PLUS_I would be Vector3D.PLUS_J

However, I tested this statement in the following test :

public class RotationTest {

    private static final double EPSILON = 1e-6;
    
    @Test
    public void testRotationConvention() {
        // Given
        final Rotation halfPiAroundPlusK = generateHalfPiAroundPlusKRotation();
        
        // When
        final Vector3D computedVector = halfPiAroundPlusK.applyTo(Vector3D.PLUS_I);
        
        // Then 
        final Vector3D expectedVector = Vector3D.PLUS_J;
        compareVector(expectedVector, computedVector);
    }
    
    private Rotation generateHalfPiAroundPlusKRotation() {
        final double halfSqrtTwo = FastMath.sqrt(2) / 2;
        return new Rotation(halfSqrtTwo, 0, 0, halfSqrtTwo, false);
    }
    
    private void compareVector(final Vector3D expectedVector, final Vector3D computedVector) {
        Assert.assertArrayEquals(expectedVector.toArray(), computedVector.toArray(), EPSILON);
    } 
}

which fails, as the computedVector is equal to Vector3D.MINUS_J, which is the expected result if the Rotation.applyTo(Vector3D) method follows the FRAME_TRANSFORM convention.

I find this counterintuitive. I know that Rotation.applyInverseTo gives me the answer I look for, but it seems odd to me that VECTOR_OPERATOR is associated with Rotation.applyInverseTo(Vector3D) and FRAME_TRANSFORM is associated with Rotation.applyTo(Vector3D) where intuition and Javadoc would point to the opposite. Maybe adding a method Rotation.applyTo(Vector3D, RotationConvention) could help users wrap their heads around it? Although it does not solve the aforementioned confusion.

Have I missed something in my train of thought? Thank you in advance for your answers.

Cheers,
Guillaume

What you missed is that you defined the rotation by generating the quaternion by yourself, not by using axis and angle.

Thank you for your anwser Luc.

However I’m still confused, as of common definition, a rotation of angle \theta around the axis defined by the unit vector \vec{u} can be represented by the quaternion :
Q = cos \left( \frac{\theta}{2} \right ) + sin \left( \frac{\theta}{2} \right ) \vec{u}

Thus, the rotation of 90 degrees around the Z axis can be defined by :
Q = cos \left( \frac{\pi}{4} \right ) + sin \left( \frac{\pi}{4} \right ) \vec{k} = \frac{\sqrt{2}}{2} + \frac{\sqrt{2}}{2} \vec{k}

which is the rotation I defined in my previous script.

Furthermore, the following test :

@Test
public void testRotationDefinition() {
    // Given
    final Rotation rotationFromAxisAndAngle = new Rotation(Vector3D.PLUS_K, MathUtils.SEMI_PI, RotationConvention.VECTOR_OPERATOR);
        
    // When
    final double halfSqrtTwo = FastMath.sqrt(2) / 2;
    final Rotation rotationFromQuaternions = new Rotation(halfSqrtTwo, 0, 0, halfSqrtTwo, false);  
        
    // Then
    compareRotations(rotationFromAxisAndAngle, rotationFromQuaternions);
}
    
private void compareRotations(final Rotation expectedRotation, final Rotation computedRotation) {
    Assert.assertEquals(expectedRotation.getAngle(), computedRotation.getAngle(), EPSILON);
    compareVector(expectedRotation.getAxis(RotationConvention.VECTOR_OPERATOR), computedRotation.getAxis(RotationConvention.VECTOR_OPERATOR));
 }

confirms that indeed the rotations defined from the quaternions and from the axis and angle are not the same, the rotation being defined by the axis and angle corresponding to the rotation of angle \frac{-\pi}{2} around the Z axis (or \frac{\pi}{2} around the -Z axis).

I understand that this difference is caused by the RotationConvention underlying both definitions, but I don’t feel I have the answer to why the convention matching the common definition should be FRAME_TRANSFORM instead of VECTOR_OPERATOR. Applying a quaternion defined by the common definition to a vector returns the coordinates of a vector moved by the represented rotation in the initial frame, which feels to me closer to the definition of VECTOR_OPERATOR :

According to this convention, the rotation moves vectors with respect to a fixed reference frame.

than the one of FRAME_TRANSFORM :

According to this convention, the rotation considered vectors to be fixed, but their coordinates change as they are converted from an initial frame to a destination frame rotated with respect to the initial frame.

Thanks again for your anwsers and sorry for the long reply.

The common definition is indeed only a convention by itself. It comes back from the use of quaternions in frame transforms, typically among people working in attitude simulation, hence when it says a rotation of angle theta around u, it is the frame that rotates by theta.
As Hipparchus was designed to cover both the case of frames transforms and vectorial operator, we had to make one choice. We felt a long time ago (even before Hipparchus, or Apache Commons Math or even Mantissa were written) that the vector operator should be given the priority over the frame transform, and that the frame transform should nevertheless be possible.
So for a long time, we didn’t even had the RotationConvention parameter and depending on the method, one or the other convention was used and only documented as javadoc. This was confusing for many people.
The RotationConvention parameter was then introduced later on, so people at least have something to rely on in the API. However, the implementation details (here the quaternions components) still had to make one choice, the RotationConvention parameter being only used in the API to see it users wants the same convention as used internally or the other one.
So the quaternions used internally really use the vector operator convention and hence reverse the angle. If you build the quaternion by yourself following the common definition that relies on frame transforms, you get the opposite.
There is another point where this historical choice is painful, so you may also encounter it later on : when you build a rotation from Euler/Cardan angles (or when you retrieve them). Here again, the conventions make things confusing.

So I recommend users to rely on the higher level interfaces, and if they want to build a rotation of angle theta around u, then they should use the constructor with axis and ange and use the third argument to make clear what is rotating : the vector or the frame. They should rely on these constructors to be implemented properly and not build the quaternion by themselves as the cosntructor using a quaternion does not allow to provide a convention, it uses an internal one, and obviously a confusing one.

1 Like

Thanks for your detailed answer Luc !