Rotation: Convertion between Quaternion and 3D Angles

I noticed that using these Opcodes gives inaccurate rotation values:
Code:
    // Objects: no opcode to get X and Y rotation values
0176: 13@ = object 0@ Z_angle
    //

    // Vehicles
077D: 1@ = car 0@ x_angle
06BE: 2@ = car 0@ y_angle
0174: 3@ = car 0@ Z_angle
    //

Luckily, there are Quaternion Opcodes in cleo which gives accurate results:
PHP:
    // objects
07C3: get_object 0@ quaternion_to 1@ 2@ 3@ 4@
07C4: set_object 0@ quaternion_to 1@ 2@ 3@ 4@
    //
    // vehicles
07C5: get_car 0@ quaternion_to 1@ 2@ 3@ 4@
07C6: set_car 0@ quaternion_to 1@ 2@ 3@ 4@
    //

So a convertion between Quaternion and 3D-Angles is possible with:
Notice about the Rotation Values:
  1. Input Angles is assumed to have the same rotation direction of the Game:
    • Pitch: -X Rotating Downwards , +X Rotating Upwards
    • Roll: -Y Rotating Rightwards , +Y Rotating Leftwards
    • Yaw: -Z Rotating Rightwards , +Z Rotating Leftwards
  2. Informal angles can be used as inputs. Eg. 847.33, -392.9
  3. Returns a formal quaternion value.
PHP:
:AnglesToQuaternion // 0AB1: @AnglesToQuaternion 3 _Pitch_X 0@ _Roll_Y 1@ _Yaw_Z 2@ _ReturnQuaternion: _X 31@ _Y 30@ _Z 29@ _W 28@
// Taint Bryan Angles(ZXY Order) into Quaternion using Hamilton Product: https://github.com/mrdoob/three.js/blob/dev/src/math/Quaternion.js#L201
    0@ *= 0.5
    1@ *= -0.5 // GTA SA Roll direction is inverted for some reason... So we put negative sign to convert into standard roll direction
    2@ *= 0.5
    02F6: 31@ = sine 0@ // (float)
    02F6: 30@ = sine 1@ // (float)
    02F6: 29@ = sine 2@ // (float)
    02F7: 28@ = cosine 0@ // (float)
    02F7: 27@ = cosine 1@ // (float)
    02F7: 26@ = cosine 2@ // (float)
        // qX = (sin(X/2)*cos(-Y/2)*cos(Z/2)) - (cos(X/2)*sin(-Y/2)*sin(Z/2))
    0087: 24@ = 31@
    006B: 24@ *= 27@  // (float)
    006B: 24@ *= 26@  // (float)
    0087: 25@ = 28@
    006B: 25@ *= 30@  // (float)
    006B: 25@ *= 29@  // (float)
    0063: 24@ -= 25@  // (float)
        //
        // qY = (cos(X/2)*sin(-Y/2)*cos(Z/2)) + (sin(X/2)*cos(-Y/2)*sin(Z/2))
    0087: 23@ = 28@
    006B: 23@ *= 30@  // (float)
    006B: 23@ *= 26@  // (float)
    0087: 25@ = 31@
    006B: 25@ *= 27@  // (float)
    006B: 25@ *= 29@  // (float)
    005B: 23@ += 25@  // (float)
        //
        // qZ = (cos(X/2)*cos(-Y/2)*sin(Z/2)) + (sin(X/2)*sin(-Y/2)*cos(Z/2))
    0087: 22@ = 28@
    006B: 22@ *= 27@  // (float)
    006B: 22@ *= 29@  // (float)
    0087: 25@ = 31@
    006B: 25@ *= 30@  // (float)
    006B: 25@ *= 26@  // (float)
    005B: 22@ += 25@  // (float)
        //
        // qW = (cos(X/2)*cos(-Y/2)*cos(Z/2)) - (sin(X/2)*sin(-Y/2)*sin(Z/2))
    0087: 21@ = 28@
    006B: 21@ *= 27@  // (float)
    006B: 21@ *= 26@  // (float)
    0087: 25@ = 31@
    006B: 25@ *= 30@  // (float)
    006B: 25@ *= 29@  // (float)
    0063: 21@ -= 25@  // (float)
        //
0AB2: cleo_return 4 24@ 23@ 22@ 21@
Notice about the rotation values:
  1. Returns Formal 3D-Angles. Specifically:
    • Pitch(X) is bounded between -180°(Downwards) to +180°(Upwards)
    • Roll(Y) is bounded between -90°(Rightwards) to +90°(Leftwards)
    • Yaw(Z) is bounded between -180°(Rightwards) to +180°(Leftwards)
  2. It is recommended to use formal quaternion values as inputs. This is to prevent any unexpected rotation values returned. Although the formula have some checks in it, it's good to be safe.
PHP:
:QuaternionToAngles // 0AB1: @QuaternionToAngles 4 _qX 0@ _qY 1@ _qZ 2@ qW 3@ _ReturnAngles: _Pitch_X 31@ _Roll_Y 30@ _Yaw_Z 29@
// Formula Used Based on: https://github.com/mrdoob/three.js/blob/dev/src/math/Euler.js#L238
// Used        m11 = 1-(2*(pow(y,2)+pow(z,2)));
// Used        m21 = 2*((x*y)+(w*z));
// Used        m31 = 2*((x*z)-(w*y));
//
// Used        m12 = 2*((x*y)-(w*z));
// Used        m22 = 1-(2*(pow(x,2)+pow(z,2)));
// Used        m32 = 2*((y*z)+(w*x));
//
// Not Used m13 = 2*((x*z)+(w*y));
// Not Used m23 = 2*((y*z)-(w*x));
// Used        m33 = 1-(2*(pow(x,2)+pow(y,2)));
//
// this._x = Math.max(-1,Math.min(1,m32))
// if ( Math.abs( m32 ) < 0.9999999 ) {
//     this._y = Math.atan2(-m31, m33 );
//     this._z = Math.atan2(-m12, m22 );
// } else {
//     this._y = 0;
//     this._z = Math.atan2( m21, m11 );
// }
    0087: 28@ = 1@
    006B: 28@ *= 2@  // (float)
    0087: 27@ = 0@
    006B: 27@ *= 3@  // (float)
    005B: 28@ += 27@  // (float)
    28@ *= 2.0
        // Pitch_X
    if 28@ >= 1.0
    then 31@ = 1.0
    else if 28@ <= -1.0
    then 31@ = -1.0
    else 0087: 31@ = 28@
    end
    end
    0AA5: _asin | 0x4207A0 1 1 | _value 31@
    0AE9: pop_float 31@ // store result from above operation
        //
    0097: make 28@ absolute_float
    if 28@ < 0.9999999
    then
            // Roll_Y
        0087: 30@ = 1@
        006B: 30@ *= 3@  // (float)
        0087: 27@ = 0@
        006B: 27@ *= 2@  // (float)
        0063: 30@ -= 27@  // (float)
        30@ *= 2.0
        0AEE: 28@ = 0@ pow 2.0 //all floats
        0AEE: 27@ = 1@ pow 2.0 //all floats
        005B: 28@ += 27@  // (float)
        28@ *= -2.0
        28@ += 1.0
        0AA5: _atan2 | 0x4207C0 2 2 | _X 28@ _Y 30@
        0AE9: pop_float 30@ // store result from above operation
        30@ *= -1.0
            //
            // Yaw_Z
        0087: 29@ = 2@
        006B: 29@ *= 3@  // (float)
        0087: 27@ = 0@
        006B: 27@ *= 1@  // (float)
        0063: 29@ -= 27@  // (float)
        29@ *= 2.0
        0AEE: 28@ = 0@ pow 2.0 //all floats
        0AEE: 27@ = 2@ pow 2.0 //all floats
        005B: 28@ += 27@  // (float)
        28@ *= -2.0
        28@ += 1.0
        0AA5: _atan2 | 0x4207C0 2 2 | _X 28@ _Y 29@
        0AE9: pop_float 29@ // store result from above operation
            //
    else
        30@ = 0.0 // Roll_Y
            // Yaw_Z
        0087: 29@ = 0@
        006B: 29@ *= 1@  // (float)
        0087: 27@ = 2@
        006B: 27@ *= 3@  // (float)
        005B: 29@ += 27@  // (float)
        29@ *= 2.0
        0AEE: 28@ = 1@ pow 2.0 //all floats
        0AEE: 27@ = 2@ pow 2.0 //all floats
        005B: 28@ += 27@  // (float)
        28@ *= -2.0
        28@ += 1.0
        0AA5: _atan2 | 0x4207C0 2 2 | _X 28@ _Y 29@
        0AE9: pop_float 29@ // store result from above operation
            //
    end
        // Radians to Degrees
    31@ *= 57.295779513082320876798154814105
    30@ *= 57.295779513082320876798154814105
    29@ *= 57.295779513082320876798154814105
        //
0AB2: cleo_return 3 31@ 30@ 29@

3D Rotation Debugger
  • Requires SAMPFUNCS though.
  • Command: /rotator <BasePitchAngle> <BaseRollAngle> <BaseYawAngle> <IncrementPitchAngle> <IncrementRollAngle> <IncrementYawAngle>
PHP:
{$CLEO}
0000:

repeat
    wait 0
until 0AFA:  is_samp_available

14@ = -1

0B34: samp register_client_command "rotator" to_label @setuprotationparameters

while true
    wait 0
    if and
        14@ <> -1 // not disabled
        0449: actor $PLAYER_ACTOR in_a_car
    then
        03C0: 20@ = actor $PLAYER_ACTOR car
        0AB1: @cheat_car_teleport 4 _car 20@ _XYZ 7@ 8@ 9@
        07DB: set_car 20@ rotation_velocity_XYZ 0.0 0.0 0.0 through_center_of_mass
        04BA: set_car 20@ speed_to 0.0

        005B: 14@ += 17@  // (float)
        if 14@ > 180.0
        then 14@ -= 360.0
        else if 14@ <= -180.0
        then 14@ += 360.0
        end
        end
        005B: 15@ += 18@  // (float)
        if 15@ > 180.0
        then 15@ -= 360.0
        else if 15@ <= -180.0
        then 15@ += 360.0
        end
        end
        005B: 16@ += 19@  // (float)
        if 16@ > 180.0
        then 16@ -= 360.0
        else if 16@ <= -180.0
        then 16@ += 360.0
        end
        end
        0AB1: @AnglesToQuaternion 3 _Pitch_X 14@ _Roll_Y 15@ _Yaw_Z 16@ _ReturnQuaternion: _X 10@ _Y 11@ _Z 12@ _W 13@
        07C6: set_car 20@ quaternion_xyzw_to 10@ 11@ 12@ 13@

        077D: 0@ = car 20@ x_angle
        06BE: 1@ = car 20@ y_angle
        0174: 2@ = car 20@ Z_angle
        0AB1: @QuaternionToAngles 4 _qX 10@ _qY 11@ _qZ 12@ qW 13@ _ReturnAngles: _Pitch_X 3@ _Roll_Y 4@ _Yaw_Z 5@

        0AF8: samp add_message_to_chat "Correct:%f %f %f {ffff00}, Opcodes:%f %f %f {00ffff}, Snippet:%f %f %f" color 0xFF00FF00 14@ 15@ 16@ 0@ 1@ 2@ 3@ 4@ 5@
    end
end

:cheat_car_teleport
    0A97: 4@ = vehicle 0@ struct
    4@ += 20
    0A8D: 4@ = read_memory 4@ size 4 virtual_protect 0
    4@ += 48 // X
    0A8C: write_memory 4@ size 4 value 1@ virtual_protect 0 // X
    4@ += 4 // Y
    0A8C: write_memory 4@ size 4 value 2@ virtual_protect 0 // Y
    4@ += 4 // Z
    0A8C: write_memory 4@ size 4 value 3@ virtual_protect 0 // Z
0AB2: cleo_return 0

:setuprotationparameters
    if 0449: actor $PLAYER_ACTOR in_a_car
    then
        0B35: samp 0@ = get_last_command_params
        if 0AD4: 0@ = scan_string 0@ format "%f %f %f %f %f %f" 1@ 2@ 3@ 4@ 5@ 6@
        then
            03C0: 20@ = actor $PLAYER_ACTOR car
            00AA: store_car 20@ position_to 7@ 8@ 9@
            0087: 14@ = 1@
            0087: 15@ = 2@
            0087: 16@ = 3@
            0087: 17@ = 4@
            0087: 18@ = 5@
            0087: 19@ = 6@
            0AF8: chatmsg "bX:%f bY:%f bZ:%f {00ffff}dX:%f dY:%f dZ:%f" 0xFFFFFF00 14@ 15@ 16@ 17@ 18@ 19@
            0B43: samp cmd_ret
        end
    end
    14@ = -1
    0AF8: chatmsg "Rotator:{ff0000}Disabled" -1
0B43: samp cmd_ret
 
Top