[Include] i_quat - Quaternions and spatial geometry
#1

i_quat
Download
Notice: GetVehicleRotationQuat may return incorrect results for vehicles which are unoccupied. Better check if there's a driver inside, if you want the correct rotation.
What does this do?
This library will make your spatial geometry dreams come true! It contains various functions to convert between relative and absolute coordinates and rotation, as well as Euler and quaternion conversion and basic quaternion manipulation.

What the sprunk is a quaternion?
SA-MP uses mostly rotation expressed in Euler angles (XYZ). However, one function (and more with YSF) uses a different method of rotation - quaternions. A quaternion (unit quaternion, specifically, also known as a versor) is a four-component "number" which represents a 3D rotation. It has many advantages over Euler angles, including unambiguity, easy composition and decomposition, and painless-to-implement operations. A quaternion represents an axis, specified by a unit vector, and a rotation around that axis, instead of 3 axes like in Euler. Algebraically, quaternions are an extension of complex numbers.

Changelog
1.4 - added GetQuatInverse (was in the native list, but without an actual implementation), corrected GetQuaternionAngles to account for gimbal lock (was returning imprecise values near the poles), and added a bunch of utility macros.
1.3 – added GetVehicleRotationQuatFixed which returns correct values for unoccupied vehicles. GetVehicleMatrix must be provided by a plugin.
1.2 – trigonometric functions won't return NaN for invalid values, rather they clamp their arguments to (-1; 1).
1.1 – fixed math operations.
1.0 – initial release.

What are the functions?

The most useful functions
GetVehicleRotation(vehicleid, &Float:x, &Float:y, &Float:z)
Returns a vehicle's rotation in Euler angles

The following bunch of functions are meant for conversion between absolute and relative coordinates. By absolute I don't mean world coordinates, but simply coordinates unattached to any body. A body can be an object, vehicle or player, which can have objects attached to. The body is represented with a quaternion or Euler angles specifying its rotation. The second parameter is an input, either a 3D vector (XYZ offsets) or an Euler or quaternion rotation. The third parameter is the output of the transformation.

The first part of the function's name is what will be converted, the second part is whether from relative to absolute or vice versa, and optinally it can end with Quat, meaning the rotation of the body is specified with a quaternion, otherwise with Euler angles.
VectorRelToAbsQuat(Float:q[4], Float:v1[3], Float:v2[3])
Converts a vector in coordinates relative to a body with rotation specified by a quaternion to a vector in absolute coordinates
RotationRelToAbsQuat(Float:q[4], Float:r1[3], Float:r2[3])
Converts an Euler rotation relative to a body with rotation specified by a quaternion to an absolute rotation
stock
QuaternionRelToAbsQuat(Float:q[4], Float:q1[4], Float:q2[4])
Converts a quaternion rotation relative to a body with rotation specified by a quaternion to an absolute rotation
VectorRelToAbs(Float:r[3], Float:v1[3], Float:v2[3])
Converts a vector in coordinates relative to a body with rotation specified by Euler angles to a vector in absolute coordinates
RotationRelToAbs(Float:r[3], Float:r1[3], Float:r2[3])
Converts an Euler rotation relative to a body with rotation specified by Euler angles to an absolute rotation
stock
QuaternionRelToAbs(Float:r[3], Float:q1[4], Float:q2[4])
Converts a quaternion rotation relative to a body with rotation specified by Euler angles to an absolute rotation
stock
VectorAbsToRelQuat(Float:q[4], Float:v1[3], Float:v2[3])
Converts a vector in absolute coordinates to a vector in coordinates relative to a body with rotation specified by a quaternion
RotationAbsToRelQuat(Float:q[4], Float:r1[3], Float:r2[3])
Converts an Euler rotation to a rotation relative to a body with rotation specified by a quaternion
QuaternionAbsToRelQuat(Float:q[4], Float:q1[4], Float:q2[4])
Converts an absolute quaternion rotation to a rotation relative to a body with rotation specified by Euler angles
VectorAbsToRel(Float:r[3], Float:v1[3], Float:v2[3])
Converts a vector in absolute coordinates to a vector in coordinates relative to a body with rotation specified by Euler angles
RotationAbsToRel(Float:r[3], Float:r1[3], Float:r2[3])
Converts an Euler rotation to a rotation relative to a body with rotation specified by Euler angles
QuaternionAbsToRel(Float:r[3], Float:q1[4], Float:q2[4])
Converts an absolute quaternion rotation to a rotation relative to a body with rotation specified by Euler angles

Quaternion operations
IsValidQuaternion(Float:q[4])
Checks if a quaternion is a valid rotation quaternion
GetQuaternionAngles(Float:w, Float:x, Float:y, Float:z, &Float:xa, &Float:ya, &Float:za)
Returns a set of Euler angles from a quaternion
GetRotationQuaternion(Float:x, Float:y, Float:z, &Float:qw, &Float:qx, &Float:qy, &Float:qz)
Creates a quaternion from Euler angles
GetQuaternionVector(Float:qw, Float:qx, Float:qy, Float:qz, &Float:x, &Float:y, &Float:z)
Returns the vector component of a quaternion
GetQuaternionAngle(Float:w, &Float:a)
Returns the angle component of a quaternion
RotateVectorQuat(Float:v1[3], Float:q[4], Float:v2[3])
Rotates a vector with a specified quaternion performing a conjugation v2 = q v1 q*
GetQuatConjugate(Float:q1[4], Float:q2[4])
Returns the conjugate of a quaternion, with all non-real component inversed
GetQuatInverse(Float:q1[4], Float:q2[4])
Returns the inverse of a quaternion, that is the conjugate divided by the norm squared.
GetQuatProduct(Float:q1[4], Float:q2[4], Float:q3[4])
Returns the Hamilton product of two quaternions

There are also macros to help you working with arrays, like SET_A3 to copy one array (size 3) to another, ADD_A3 to add two arrays, or MUL_A3 to multiply an array with a number. To expand an array when passing its values as parameters, you can use the EXP_A3 macro.

What can this be used for?
  • Editing attached objects. Normally, you can't use EditObject on an attached object, like neons on a vehicle for example, but if you obtain its attached offsets and rotation, you can use VectorRelToAbsQuat and RotationRelToAbsQuat to "remove" the vehicle's rotation from them and then simply detach the object (attach it to INVALID_VEHICLE_ID) and set its position to that. After you're done editing, repeat the process backwards, this time with VectorAbsToRelQuat and RotationAbsToRelQuat:
    Code:
    stock UnattachObject(objId, veh) //Unattachs the object from a vehicle
    {
        new Float:q[4];
        GetVehicleRotationQuat(veh, q[0], q[1], q[2], q[3]);
        if(!IsValidQuaternion(q)) return; //Vehicles left unoccupied for some time will return wrong values
        
        new Float:a[3], Float:ar[3];
        GetObjectAttachedOffset(objId, a[0], a[1], a[2], ar[0], ar[1], ar[2]); //requires YSF
        
        VectorRelToAbsQuat(q, a, a); //a is now a world-relative vector
        RotationRelToAbsQuat(q, ar, ar); //ar is now a world-relative rotation
        
        new Float:x, Float:y, Float:z;
        GetVehiclePos(veh, x, y, z);
        
        AttachObjectToVehicle(objId, INVALID_VEHICLE_ID, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
        SetObjectPos(objId, x+a[0], y+a[1], z+a[2]);
        SetObjectRot(objId, ar[0], ar[1], ar[2]);
    }
    
    stock ReattachObject(objId, veh) //Reattachs the object to a vehicle
    {
        new Float:q[4];
        GetVehicleRotationQuat(veh, q[0], q[1], q[2], q[3]);
        if(!IsValidQuaternion(q)) return INVALID_VEHICLE_ID;
        
        new Float:a[3], Float:ar[3];
        GetObjectPos(objId, a[0], a[1], a[2]);
        GetObjectRot(objId, ar[0], ar[1], ar[2]);
        
        new Float:x, Float:y, Float:z;
        GetVehiclePos(veh, x, y, z);
        a[0] -= x;
        a[1] -= y;
        a[2] -= z;
        
        VectorAbsToRelQuat(q, a, a); //a is now a vehicle-relative vector
        RotationAbsToRelQuat(q, ar, ar); //a is now a vehicle-relative rotation
        
        AttachObjectToVehicle(objId, veh, a[0], a[1], a[2], ar[0], ar[1], ar[2]);
    }
  • Boosting vehicle forward:
    Code:
    new veh = GetPlayerVehicleID(playerid);
    new Float:q[4];
    GetVehicleRotationQuat(veh, q[0], q[1], q[2], q[3]);
    new Float:v[3];
    v[1] = 10.0; //10 m/s forward
    VectorRelToAbsQuat(q, v, v); //v is now an absolute vector, usable in the world coordinate system
    SetVehicleVelocity(veh, v[0], v[1], v[2]);
  • If you are building a movable (space)ship with attached objects in a map editor, you can compute their relative offsets and rotations with VectorAbsToRel and RotationAbsToRel, specifying the ship's body rotation.
  • And much more...
How did you come up with all this maths stuff?
Well, for my mapping/filming/freeroam/anything server, a possibility to edit an attached object would have had been always neat for mappers. However, I had struggled with correctly converting the Euler angles to quaternion and vice-versa, and many formulas I've found on the internet were simply incorrect. The reason is rather unusual ordering and orientation of the axes and angles in GTA. Finally, thanks to brilliant people on math@stackexchange, I was able to get the correct conversion formula and begin to implement all the functions.
Reply


Messages In This Thread

Forum Jump:


Users browsing this thread: 1 Guest(s)