[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
#2

I would suggest you to add the very main usage it has as the example, turning a position into a vehicle's offset, people around tend to get very confused when decent examples are not offered, using all the functions provided wrong. seeing how you actually use a combination of these to turn say an object position into a vehicle's offsets to attach it to the vehicle tends to bring more casual usages of the include.

Great job either way tho, well done.
Reply
#3

Thanks for the tip, check the first example.
Reply
#4

Hi!
Looks like ReattachObject does not works fine.
https://www.youtube.com/watch?v=pxh1oGxGxyE
This happens when i try to use it.
Code:
http://pastebin.com/wNAiPqtA

If the problem is with my code, please help me fix it, because i dont know how to use quaternions..
Thanks!
Reply
#5

Oh I am sorry, I forgot to include a warning - if the vehicle is left unoccupied, the quaternion returned by GetVehicleRotationQuat will be incorrect. IsValidQuaternion tries to compensate for it, but even some invalid quaternions are technically valid, and the object will be reattached with incorrect offsets. I bet it's a meter under the vehicle. Anyway, you simply have to be in the vehicle to correctly reattach the object. Disable the engine to move the camera without driving forward.
Reply
#6

As you said, now, i was in the car and i turned off the engine.
I have a new code this time. Works almost well but when i press the save button, the object shows up randomly as you can see in the video below.
https://youtu.be/z6a6rGzT7GI

http://pastebin.com/xfh4jaXN
This time i used your original ReattachObject without Dynamic objects...
Reply
#7

Sorry, I see I've made a mistake. When I was "fixing" RotateVectorQuat to be more mathematically accurate, I forgot to update the other functions. Script updated to 1.1, please check it. I hope its all okay now.
Reply
#8

Finally it worked. Thank you

https://www.youtube.com/watch?v=ylbB...ature=youtu.be
Reply
#9

Added GetVehicleRotationQuatFixed which returns correct values for unoccupied vehicles. GetVehicleMatrix must be provided by a plugin, like YSF. If you are using VehicleMatrix.inc, it could fix a couple of cases, but it will still be mostly garbage values. A plugin is required to read the matrix from the server's memory.

Why GetVehicleMatrix? Internally, the server stores the vehicle's rotation as a 3Ч3 rotation matrix, the same way as the game does. For some reason, this matrix is not exposed to scripts, only the conversion to a quaternion. Unfortunately, unoccupied vehicle updates damage the third row of the matrix and invalidate the values, changing them to near-zeros (mostly denormal floats). I have no idea why that is, I guess some sort of a game/server bug. The internal conversion to the quaternion sadly uses the third row (though it doesn't have to, two rows of the matrix are completely sufficient), returning complete bogus in case it was damaged.

Now that I know the third row is actually redundant and is just a cross product of the other rows, if I can obtain the internal matrix, just fixing the third row via this method is sufficient and produces correct quaternions. Due to the imprecise floating point calculations, the reconstructed row is a bit inaccurate, but still really close to the correct one. It should fix 100 % of wrong quaternions.
Reply
#10

New version!

Aside from some useful macros to make working with arrays easier (check the main topic or the source code), I have corrected the core function of this include - GetQuaternionAngles.

Near the poles or singularities of the rotation system, it returned incorrect values, mainly due to float rounding errors. Unfortunately, this requires an arbitrary value (so-called epsilon) to be used to specify the limit where the calculation should be corrected (that's why quaternions are better, you don't have to handle this mess).

I have empirically measured the epsilon to be about 0.000002, but if you want to change it, simply #define QUAT_FLOAT_EPSILON with the new value. It should be positive number not larger than 1, but reasonably small. The smaller the epsilon, the higher chance of returning incorrect results. The larger the epsilon, the more imprecise the results for normal cases.

If you expect "random" quaternions, you don't have to worry about this.
Reply
#11

I was taking a look at this and it seems you have a lot of rotation problems solved. I always had trouble with this function.

Код:
CA_RayCastLineAngle(Float:StartX, Float:StartY, Float:StartZ, Float:EndX, Float:EndY, Float:EndZ, &Float:x, &Float:y, &Float:z, &Float:rx, &Float:ry, &Float:rz)
It does work to a certain extent but I could never figure out how to improve the returned rotations so you can rotate the object correctly.

This is used in Texture Studio by the mangle module which rotates objects to the rotation of the a surface.

The main issue is located here.

Код:
int ColAndreasWorld::performRayTestAngle(const btVector3& Start, const btVector3& End, btVector3& Result, btScalar& RX, btScalar& RY, btScalar& RZ, uint16_t& model)
{
	btCollisionWorld::ClosestRayResultCallback RayCallback(Start, End);

	dynamicsWorld->rayTest(Start, End, RayCallback);

	if (RayCallback.hasHit())
	{
		btVector3 Rotation = RayCallback.m_hitNormalWorld;
		RX = -(asin(Rotation.getY())*RADIAN_TO_DEG);
		RY = asin(Rotation.getX())*RADIAN_TO_DEG;
		// I think there is a way to calculate this not sure how yet
		RZ = 0.0;
		Result = RayCallback.m_hitPointWorld;
		model = RayCallback.m_collisionObject->getUserIndex();
		return 1;
	}
	return 0;
}
Maybe you have input that could offer some direction that could be taken to improve this function. If update an objects position/rotation while aiming with a weapon and use this function you will see the issue it has.
Reply
#12

Thanks for this.

I can finally edit my vehicle objects without the vehicle facing 0.0.
Reply
#13

Quote:
Originally Posted by Pottus
Посмотреть сообщение
Maybe you have input that could offer some direction that could be taken to improve this function. If update an objects position/rotation while aiming with a weapon and use this function you will see the issue it has.
I am not sure what you mean, do you want to align an object with the surface normal of the hit? I don't use ColAndreas, but if that is the case, returning the normal directly would be more practical than Euler angles. If you want to convert that to "some" rotation (there is an infinite number of them that could represent the normal), you can either create that using the product of elementary quaternions (in the correct order), or maybe try this function from my Cinematic Mode:
Код:
stock SetObjectFrontVector(objectid, Float:vx, Float:vy, Float:vz)
{
    new Float:r = VectorSize(vx, vy, vz);
    new Float:rx = acos(vz/r)+90.0;
    new Float:rz = -atan2(vx, vy)+180.0;
    SetObjectRot(objectid, rx, 0.0, rz);
}
But it wasn't tested much, to be honest.
Reply
#14

This must be great to create flight instruments for aircrafts.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)