I noticed that using these Opcodes gives inaccurate rotation values:
Luckily, there are Quaternion Opcodes in cleo which gives accurate results:
So a convertion between Quaternion and 3D-Angles is possible with:
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:
- 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
- Informal angles can be used as inputs. Eg. 847.33, -392.9
- 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:
- 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)
- 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