Advertisement

Handling rotation on local axis properly

Started by June 04, 2023 03:38 AM
1 comment, last by JoeJ 1 year, 6 months ago

Hey,

I've just recently started learning graphics programming so please bear with me!

While learning, i learned that when applying rotation in the order RyRxRz (the standard way for euler angles), such that the model matrix is composed by doing SRzRxRyT (given i'm using column matrices) there will be some combination of angles that rotate around "world axis" since y axis rotation is applied first.

So i thought maybe i should construct a quaternion based on the getting the local axis from the rotation matrix inside the model model matrix, but this leads to a few issues which i will mention after giving my code below:

void drawCube(Vec3f pos, Quatf rot, vec3f scale)
{
		Matrix4f model = MATRIX4F_IDEN;
        model = matrix4fMult(model, matrix4fGiveTranslate(pos.x, pos.y, pos.z));
        
        Matrix4f rotmat = quatfToMatrix4f(rot);

        model._00 = rotmat._00;
        model._01 = rotmat._01;
        model._02 = rotmat._02;
        model._10 = rotmat._10;
        model._11 = rotmat._11;
        model._12 = rotmat._12;
        model._20 = rotmat._20;
        model._21 = rotmat._21;
        model._22 = rotmat._22;

        model = matrix4fMult(model, matrix4fGiveScale(scale.x, scale.y, scale.z));
        
        /// .... do a bunch of other stuff below
}

Vec3f cubePos;
Vec3f cubeRot; //stores the angles in each axis in degrees, so it can be changed in the editor u.i
Vec3f cubeScale;
Quatf cubeRotQuat;
void render(void)
{
		// Matrix3f rotMat = quatfToMatrix3f(cubeRotQuat);
    	// Vec3f up = matrix3fMultVector3f(rotMat, (Vec3f){0, 1, 0});
	
    	// Vec3f right = matrix3fMultVector3f(rotMat, (Vec3f){1, 0, 0});

    	// Vec3f fwd = matrix3fMultVector3f(rotMat, (Vec3f){0, 0, 1});
	
    	// Quatf rotX = quatfMakeWithRotationDeg(cubeRot.x, right);
    	// Quatf rotY = quatfMakeWithRotationDeg(cubeRot.y, up);
    	// Quatf rotZ = quatfMakeWithRotationDeg(cubeRot.z, fwd);

	
    	// cubeRotQuat = quatfMult(rotY, quatfMult(rotZ, rotX));
    
		cubeRotQuat = quatfMakeWithRotationDegYXZ(cubeRot.x, cubeRot.y, cubeRot.z);

    	drawCube(cubePos, cubeRotQuat, cubeScale, cubeScale);
    	
    	/// .. other stuff below
}

The code above shows a snippet of what im doing, i cant show all of the code since it would be too much. The commented out code in render function is what does the rotation about local axis's.

The issue I'm having with this approach is that when rotating this way, on the local axis's, while rotating the axis's can be changed so it leads to the cube glitching. In other words, if I rotate around the cubes local y axis which lets say points in the direction (-1, 1, 0) normalised, then while rotating, the z and x local axis will be getting updated too, so the code that is commented will then rotate around those updated axis's with the rotation amount passed in, which then leads the cube vertices to jump back and forward and start glitching out.

So this leads to my question of, how can i structure the code or do this in such a way, where all rotation in my engine is done around the local axis - for the general case. Unless i have some u.i setting to switch it to global axis.

awkwardbanana123 said:
So this leads to my question of, how can i structure the code or do this in such a way, where all rotation in my engine is done around the local axis - for the general case. Unless i have some u.i setting to switch it to global axis.

In case you do not know, local vs. global depends on the order of operands for matrix or quaternion multiplication.

Some example using quaternions:

Quat someXrotation = QuatFromAxisAndAngle(vec(1,0,0), 0.2f);
Quat myObjectRotatedLocally = myObject.orientation * someXrotation;
Quat myObjectRotatedGlobally = someXrotation * myObject.orientation;

However, the actual order depends on the convention of your matrix / quaternion libraries. So maybe it's vice versa from my example. You need to test and then memorize which order does what.

I assume what went wrong with your commented code is something like this:

You have 3 rotations, one for each euler angle. And you need to perform them in the order as specified from your euler angles convention. It can be XYZ, or YZY, etc. Notice: When working with euler angles we also always must specify the order in which the 3 rotations have to be done. And if we trasfer our euler angle data to another application, we might need to convert the data if order specification differs. That's one reason why euler angles suck. Just said fyi.

The other reason is Gimbal Lock, which likely is your problem and what you mean with ‘glitch out’.
If you want to perform the 3 rotations in local space, you have to do the first rotation to get the new axis to perform the next and second rotation around this new axis. The second rotation then gives the third and final axis for the next and last.
Likely you did some the rotations in accidentally worldspace by using the wrong multiplication order (not to be confused with euler angles order). As a result, rotation axis are not orthogonal to each other, and the behavior is somehow unexpected.

So if you have the patience to try out all kinds of potential angle and multiplication orders, you should be able to fix the commented code to get the same result as the single line calling quatfMakeWithRotationDegYXZ().

The third reason why euler angles suck is that they actually perform 3 rotations in order just to build a single one. That's inefficient and should be avoided if possible.
Usually the only reason to use euler angles is to have editable numbers in a user interface, but you want to convert it to a better format for internal use as soon as possible, both to minimize uncertainty and to optimize performance.

3D rotations are always difficult. The best way to imagine them is to use the concept of axis and angle rotation. This avoids confusion about ordering multiple rotations about basis vectors, and it makes sense geometrically.
It's also very helpful to differentiate between ‘rotation’ and ‘orientation’, both mentally and for variable names. It's similar to differnetiating points and vectors, just points and vectors are so easy we usually don't have to.

With this in mind, i can suggest which data structures match rotation concepts most intuitively:
Orienation: 3x3 matrix. Intuitive because we can just visualize the base vectors in red, green, blue to see how the orientation currently is.
Rotation: Quaternion. Intuitive because quaternion closely relates to the axis and angle concept and is easy to convert to / from it. And axis and angle is mot intuitive to describe a rotation as a change of orientation.
Angular velocity: 'Rotation Vector' (just the term i'm using for that). Rotation vector is simply axis times angle, so just 3 numbers and a simple vec3. Unlike any other data structure we can add or subtract rotation vectors and we still get a rotation. Just like with linear velocity.

So, that's all general concepts maybe not applicable to your current problem, but it really helps to minimize 3D rotation confusion on the long run.

This topic is closed to new replies.

Advertisement