Advertisement

Rotation matrix multiplication causes Gimbal lock

Started by July 21, 2017 07:17 AM
23 comments, last by scragglypoo 7 years, 6 months ago
22 hours ago, scragglypoo said:

How do I convert 5 degrees pitch to a non-euler method?

Here's a hopefully bite-sized explanation of euler angles vs quaternions: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/

And here's a link to a talk given at GDC, all about rotations (Euler angles, quaternions, matrices, axis/angle, etc.): http://www.essentialmath.com/GDC2012/GDC2012_JMV_Rotations.pdf

So, you are making your own math library? Besides learning the difference between quaternions, euler, and the guimball lock problem, that this vide shows very well:



Take a look at MonoGame math library:

https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Matrix.cs
https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Vector2.cs
https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Vector3.cs
https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Vector4.cs
https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Quaternion.cs

It's in C#, but very easy to translate to any language (I translated to C, btw). Imo, much more readable than GLM.

Advertisement

Depending on if you want to have true 360-degree freedom, you could implement a system where each object has an up vector, a right vector and a forward vector.  When rotation occurs, those three vectors rotate along with the object.  Whenever an object rotates, you then use one of those three axis to rotate the object around, using the arbitrary rotation axis matrix:

https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle

 Example code (axis is the axis of rotation and radians is the degrees to rotate):


axis = vector_normalize(axis);
float ct = cosf(radians);
float st = sinf(radians);
float ci = 1 - ct;
float x = axis.x, y = axis.y, z = axis.z;
return matrix_make(ct + x * x * ci, y * x * ci + z * st, z * x * ci - y * st, 0,
                   x * y * ci - z * st, ct + y * y * ci, z * y * ci + x * st, 0,
                   x * z * ci + y * st, y * z * ci - x * st, ct + z * z * ci, 0,
                   0, 0, 0, 1);

As an example, when an object pitches up, rotate the object along the right axis and also rotate the "orientation" vectors along that same axis.  When you perform another rotation (for example, turning right), rotate the object around the new up vector for the specified number of degrees (and, also rotate the orientation vectors).

Another option is to use quaternions.

Eleventy

Quaternion A angles [x: 35.21, y: 181.58, 0.0]
Apply rotation using quaternion B [x:0.0, y: 45.0, 0.0]
New angles quaternion C [x: 24.06, y: -127.67, <code doesn't print roll>]

Why is it not rotating properly? It is decreasing the pitch, the yaw is working.

i can create a quaternion at any x y angle, and then read back fine.  But problems happen when I multiply/rotate sometimes.

 

Still the only fix I have is this:


Matrix operator * ( Matrix rhs ) {
	auto x = CreateRotation( rhs.Pitch(), 0.0, 0.0 );
	auto y = CreateRotation( 0.0, rhs.Yaw(), 0.0 );
	return x * this * y; // return rhs * this; doesnt work!
}

 

I am not sure what you mean by those quaternions that are specified using only two numbers. At the very least you need three, and you have to be more descriptive that "[x: 35.21, y: 181.58]" if you want me to understand what you are saying.

Just demonstrate your last post with the exact code you are using. I'll try to reproduce the results and see if I can figure out where the confusing is coming from.

 

4 hours ago, alvaro said:

I am not sure what you mean by those quaternions that are specified using only two numbers. At the very least you need three, and you have to be more descriptive that "[x: 35.21, y: 181.58]" if you want me to understand what you are saying.

Just demonstrate your last post with the exact code you are using. I'll try to reproduce the results and see if I can figure out where the confusing is coming from.

 

 

Code is from here: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles

Edit: Code is down a post

Advertisement

That's closer, but I don't know what me is, or what SetAngles does. It probably doesn't take that much to turn what you posted into a complete C++ program that we can try.

4 hours ago, alvaro said:

That's closer, but I don't know what me is, or what SetAngles does. It probably doesn't take that much to turn what you posted into a complete C++ program that we can try.

me is basically just the camera; SetAngles basically sets euler angles

this runs in https://repl.it/languages/CPP
 


// Example program
#include <iostream>
#include <string>
#include <cmath>
#include <cstdio>

//#define M_PI 3.14159265358979323846F
#define M_DEG (180.0F / M_PI)
#define M_RAD (M_PI / 180.0F)

struct Vector {
    float x = 0.0;

	float y = 0.0;

	float z = 0.0;
};

class Quaternion  {
public :
		void CreateFromEuler( float pitch, float yaw, float roll ) {
			double t0 = std::cos( yaw * 0.5 );
			double t1 = std::sin( yaw * 0.5 );
			double t2 = std::cos( roll * 0.5 );
			double t3 = std::sin( roll * 0.5 );
			double t4 = std::cos( pitch * 0.5 );
			double t5 = std::sin( pitch * 0.5 );

			w = t0 * t2 * t4 + t1 * t3 * t5;
			x = t0 * t3 * t4 - t1 * t2 * t5;
			y = t0 * t2 * t5 + t1 * t3 * t4;
			z = t1 * t2 * t4 - t0 * t3 * t5;
		}

		Vector GetEuler() {
			double ysqr = y * y;

			// roll (x-axis rotation)
			double t0 = +2.0 * (w * x + y * z);
			double t1 = +1.0 - 2.0 * (x * x + ysqr);
			auto roll = std::atan2( t0, t1 );

			// pitch (y-axis rotation)
			double t2 = +2.0 * (w * y - z * x);
			t2 = ((t2 > 1.0) ? 1.0 : t2);
			t2 = ((t2 < -1.0) ? -1.0 : t2);
			auto pitch = std::asin( t2 );

			// yaw (z-axis rotation)
			double t3 = +2.0 * (w * z + x * y);
			double t4 = +1.0 - 2.0 * (ysqr + z * z);
			auto yaw = std::atan2( t3, t4 );
			return Vector { pitch, yaw, roll };
		}

		Quaternion operator *( const Quaternion &q ) {
			Quaternion r;
			r.w = w*q.w - x*q.x - y*q.y - z*q.z;
			r.x = w*q.x + x*q.w + y*q.z - z*q.y;
			r.y = w*q.y + y*q.w + z*q.x - x*q.z;
			r.z = w*q.z + z*q.w + x*q.y - y*q.x;
			return r;
		}

		float x = 0.0;

		float y = 0.0;

		float z = 0.0;

		float w = 1.0;
};
            


int main() {
    // How much we rotate?
    auto pitch = 0.0;
    auto yaw = 45.0;
    
    Quaternion a;
    // Extract euler angles from a matrix.  Reading stored euler angles will work too.
    //a.CreateFromEuler( camera.orient.Pitch(), camera.orient.Yaw(), 0.0 );
    auto start_pitch = 35.21;
    auto start_yaw = 181.58;
    a.CreateFromEuler( start_pitch * M_RAD, start_yaw * M_RAD, 0.0 );
    std::printf( "Orientation a [x: %.2f, y: %.2f, z: %.2f]\n", start_pitch, start_yaw, 0.0 );
    Quaternion b;
    b.CreateFromEuler( pitch * M_RAD, yaw * M_RAD, 0.0 );
    std::printf( "Orientation b [x: %.2f, y: %.2f, z: %.2f]\n", pitch, yaw, 0.0 );
        
    auto result = a * b;
    auto angles = result.GetEuler();
    //camera.SetEulerAngles( angles );
     std::printf( "Orientation result [x: %.2f, y: %.2f, z: %.2f]\n", angles.x * M_DEG, angles.y * M_DEG, angles.z * M_DEG );
}
Quote


Orientation a [x: 35.21, y: 181.58, z: 0.00]
Orientation b [x: 0.00, y: 45.00, z: 0.00]
Orientation result [x: 24.06, y: -127.67, z: 26.52]

 

On 7/22/2017 at 2:34 PM, Eleventy said:

implement a system where each object has an up vector, a right vector and a forward vector.

This is simply how a transform or rotation matrix works.  The first row is the x (right) vector, the second row is the y (up) vector, and the third row is the z (forward) vector.  At anytime, you can fetch these axes to perform local rotations.  This is what I was referring to in my post.

If I understand what the OP is trying to do, the issue here has nothing to do with Quaternions or Eulers or Gimbal or whatever.

The issue is the axis around which rotations are performed.  Perform y rotations as a regular world-space rotation.  This will allow you to maintain a constant up direction.  Perform the rest of the rotations in local space.  Do these with a "rotationAboutAxis"-type function, where your axis parameter is the right or forward vector from your matrix.  Something like this:


//Y Rotation is a regular rotation
yRotation = rotation(0, yAngle, 0);
myMatrix = myMatrix * yRotation;

//X and Z Rotations are done using the newly-transformed x and z axis from the matrix
xRotation = axisAngleRotation(myMatrix[0], xAngle);
myMatrix = myMatrix * xRotation;
zRotation = axisAngleRotation(myMatrix[2], zAngle);
myMatrix = myMatrix * zRotation;

 

21 hours ago, missionctrl said:

This is simply how a transform or rotation matrix works.  The first row is the x (right) vector, the second row is the y (up) vector, and the third row is the z (forward) vector.  At anytime, you can fetch these axes to perform local rotations.  This is what I was referring to in my post.

If I understand what the OP is trying to do, the issue here has nothing to do with Quaternions or Eulers or Gimbal or whatever.

The issue is the axis around which rotations are performed.  Perform y rotations as a regular world-space rotation.  This will allow you to maintain a constant up direction.  Perform the rest of the rotations in local space.  Do these with a "rotationAboutAxis"-type function, where your axis parameter is the right or forward vector from your matrix.  Something like this:



//Y Rotation is a regular rotation
yRotation = rotation(0, yAngle, 0);
myMatrix = myMatrix * yRotation;

//X and Z Rotations are done using the newly-transformed x and z axis from the matrix
xRotation = axisAngleRotation(myMatrix[0], xAngle);
myMatrix = myMatrix * xRotation;
zRotation = axisAngleRotation(myMatrix[2], zAngle);
myMatrix = myMatrix * zRotation;

 

Can you post code for rotationAboutAxis() I can't seem to find any code or figure it out.

As for local space and world space, I have read a * b is world space, but b * a is local space so that you don't need to create axis angles?

// Rotate around local axis of x.
matrix = x * matrix;
// Rotate around world axis of y.
matrix = matrix * y;

This topic is closed to new replies.

Advertisement