kdxcxs
kdxcxs

Reputation: 46

Rotate quaternion and convert to euler angle

I'm trying to rotate a quaternion and convert it to Euler angle(for CSS) in javascript. Here is my general idea:

①Get quaternion in the current state.

②Calculate the rotate axis vector in local coordinate(e.g. for z-axis in local coordinate, just multiply [0, 0, 1] by ①).

③Substitute vector v from ② and rotate angle θ into 𝑞 = [cos(θ), sin(θ)v]

④Multiply ① by ③ and that should be the new rotated quaternion.

function rotate( axisVector, rotateDegree ) {
    axisVector = getRotatedVectorQuaternion( ...axisVector );
    const roamRotateQuaternion = [
        Math.cos( rotateDegree / 2 ),
        Math.sin( rotateDegree / 2 ) * axisVector[ 1 ],
        Math.sin( rotateDegree / 2 ) * axisVector[ 2 ],
        Math.sin( rotateDegree / 2 ) * axisVector[ 3 ]
    ];
    const roamedEuler = eulerFromQuaternion( multiplyQuaternions( getPresentQuaternion(), roamRotateQuaternion ), ( presentStep.dataset.rotateOrder || "xyz" ).toUpperCase() );
    presentStep.dataset.rotateX = roamedEuler[0] * 180 / Math.PI;
    presentStep.dataset.rotateY = roamedEuler[1] * 180 / Math.PI;
    presentStep.dataset.rotateZ = roamedEuler[2] * 180 / Math.PI;
}

function multiplyQuaternions( qa, qb ) {
    const qaw = qa[ 0 ], qax = qa[ 1 ], qay = qa[ 2 ], qaz = qa[ 3 ];
    const qbw = qb[ 0 ], qbx = qb[ 1 ], qby = qb[ 2 ], qbz = qb[ 3 ];
    return [
        qaw * qbw - qax * qbx - qay * qby - qaz * qbz,
        qax * qbw + qaw * qbx + qay * qbz - qaz * qby,
        qay * qbw + qaw * qby + qaz * qbx - qax * qbz,
        qaz * qbw + qaw * qbz + qax * qby - qay * qbx
    ];
}

function getRotatedVectorQuaternion( ...dirVec ) {
    var vectorQuaternion = [
        0,
        ...dirVec
    ];
    const rotateQuaternion = getPresentQuaternion();
    const rotateConjugate = [
        rotateQuaternion[ 0 ],
        rotateQuaternion[ 1 ] * -1,
        rotateQuaternion[ 2 ] * -1,
        rotateQuaternion[ 3 ] * -1
    ];
    return multiplyQuaternions( multiplyQuaternions( rotateQuaternion, vectorQuaternion ), rotateConjugate );
}

function eulerFromQuaternion( quaternion, order ) {
    // Quaternion to matrix.
    const w = quaternion[0], x = quaternion[1], y = quaternion[2], z = quaternion[3];
    const x2 = x + x, y2 = y + y, z2 = z + z;
    const xx = x * x2, xy = x * y2, xz = x * z2;
    const yy = y * y2, yz = y * z2, zz = z * z2;
    const wx = w * x2, wy = w * y2, wz = w * z2;
    const matrix = [
        1 - ( yy + zz ),
        xy + wz,
        xz - wy,
        0,
        xy - wz,
        1 - ( xx + zz ),
        yz + wx,
        0,
        xz + wy,
        yz - wx,
        1 - ( xx + yy ),
        0,
        0,
        0,
        0,
        1
    ];
    // Matrix to euler
    function clamp( value, min, max ) {
        return Math.max( min, Math.min( max, value ) );
    }
    const m11 = matrix[ 0 ], m12 = matrix[ 4 ], m13 = matrix[ 8 ];
    const m21 = matrix[ 1 ], m22 = matrix[ 5 ], m23 = matrix[ 9 ];
    const m31 = matrix[ 2 ], m32 = matrix[ 6 ], m33 = matrix[ 10 ];
    var euler = [ 0, 0, 0 ];
    switch ( order ) {
        case "XYZ":
            euler[1] = Math.asin( clamp( m13, - 1, 1 ) );
            if ( Math.abs( m13 ) < 0.9999999 ) {
                euler[0] = Math.atan2( - m23, m33 );
                euler[2] = Math.atan2( - m12, m11 );
            } else {
                euler[0] = Math.atan2( m32, m22 );
                euler[2] = 0;
            }
            break;
        case "YXZ":
            euler[0] = Math.asin( - clamp( m23, - 1, 1 ) );
            if ( Math.abs( m23 ) < 0.9999999 ) {
                euler[1] = Math.atan2( m13, m33 );
                euler[2] = Math.atan2( m21, m22 );
            } else {
                euler[1] = Math.atan2( - m31, m11 );
                euler[2] = 0;
            }
            break;
        case "ZXY":
            euler[0] = Math.asin( clamp( m32, - 1, 1 ) );
            if ( Math.abs( m32 ) < 0.9999999 ) {
                euler[1] = Math.atan2( - m31, m33 );
                euler[2] = Math.atan2( - m12, m22 );
            } else {
                euler[1] = 0;
                euler[2] = Math.atan2( m21, m11 );
            }
            break;
        case "ZYX":
            euler[1] = Math.asin( - clamp( m31, - 1, 1 ) );
            if ( Math.abs( m31 ) < 0.9999999 ) {
                euler[0] = Math.atan2( m32, m33 );
                euler[2] = Math.atan2( m21, m11 );
            } else {
                euler[0] = 0;
                euler[2] = Math.atan2( - m12, m22 );
            }
            break;
        case "YZX":
            euler[2] = Math.asin( clamp( m21, - 1, 1 ) );
            if ( Math.abs( m21 ) < 0.9999999 ) {
                euler[0] = Math.atan2( - m23, m22 );
                euler[1] = Math.atan2( - m31, m11 );
            } else {
                euler[0] = 0;
                euler[1] = Math.atan2( m13, m33 );
            }
            break;
        case "XZY":
            euler[2] = Math.asin( - clamp( m12, - 1, 1 ) );
            if ( Math.abs( m12 ) < 0.9999999 ) {
                euler[0] = Math.atan2( m32, m22 );
                euler[1] = Math.atan2( m13, m11 );
            } else {
                euler[0] = Math.atan2( - m23, m33 );
                euler[1] = 0;
            }
            break;
    }
    return euler;
}

But it rotates weirdly. I am searching for a long time on the net and also tried the slerp function. But no use. Please help or try to give some ideas on how to achieve this.

Upvotes: 0

Views: 2059

Answers (1)

Trentium
Trentium

Reputation: 3729

Within the eulerFromQuaternion() function, the Euler calculation in the "ZYX" path...

        case "ZYX":
            euler[1] = Math.asin( - clamp( m31, - 1, 1 ) ); // m31 = xz - wy
            if ( Math.abs( m31 ) < 0.9999999 ) {
                euler[0] = Math.atan2( m32, m33 );      // m32 = yz + wx, m33 = 1 - ( xx + yy )
                euler[2] = Math.atan2( m21, m11 );      // m21 = xy + wz, m33 = 1 - ( yy + zz )
            } else {
                euler[0] = 0;
                euler[2] = Math.atan2( - m12, m22 );
            }

...matches the "XYZ" (roll,pitch,yaw) coordinates in the C++ source code at https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles.

In other words, is your "ZYX" case really the "XYZ" case?

(As an aside, the code is hard to read and trace, primarily because of the use of arrays. Ie, no where are the array vectors actually leveraged as arrays in any functions, but rather, are all decomposed into individual variables. The code will be much cleaner and easier to read if simply using an object to define the eulers and quaternions, eg, q = {w:1, x:2, y:3, z:4}, and referencing the w, x, y, & z values accordingly...)

Upvotes: 1

Related Questions