Quaternion To Euler issue, fix possible ?

Started by
11 comments, last by ankhd 8 years, 3 months ago

Hi,

Here the code I use based on this link :

http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/


void CQuaternion::ToEulerAngles( float* X, float* Y, float* Z ) const
{
  const float SingularityTest = ( x * y ) + ( z * w );
  if( SingularityTest > 0.499f )
  {
    *Y = 2.0f * CMath::ATan2( x, w );
    *Z = CMath::HALF_PI;
    *X = 0.0f;
  }
  else if( SingularityTest < -0.499f )
  {
    *Y = -2.0f * CMath::ATan2( x, w );
    *Z = -CMath::HALF_PI;
    *X = 0.0f;
  }
  else
  {
    const float zz = z * z;
    *Y = CMath::ATan2( 2.0f * ( ( y * w ) - ( x * z ) ), 1.0f - 2.0f * ( ( y * y ) - zz ) );
    *Z = CMath::ASin( 2.0f * SingularityTest );
    *X = CMath::ATan2( 2.0f * ( ( x * w ) - ( y * z ) ), 1.0f - 2.0f * ( ( x * x ) - zz ) );
  }
}

Quaternion To Euler is called when gizmo is used to shows the new rotation on the rotation property in the editor.

It's also called when you attach one actor to another since the rotation is transformed to local space.

The problem is it can fastly gives bad result.

There is a fix to have correct result ?

Thanks

Advertisement
When you changed this

1.0f - 2.0f * y * y - 2.0f * zz
to this

1.0f - 2.0f * ( ( y * y ) - zz )
You didn't factor out the -2.0f correctly to give you what should be this


1.0f - 2.0f * ( ( y * y ) + zz )
Same applies to the assignment of *X
My current game project Platform RPG

Nice catch ! That works better but it's not very accurate, after a quat to euler if the euler is modified the rotation move slightly.

In some case I have really bad euler resulting from this cacule.

i prefer to convert quat to mat using DX, then convert mat to eulers using inhouse code, which i think is posted in my gamedev info journal here...

nope, it was in a reply to a post about converting mats to eulers. do a search, you'll find the code in the thread.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Here the matrix version which ends to the same issue :


void CQuaternion::ToEulerAngles( float* X, float* Y, float* Z ) const
{
  // Convert to matrix.
  const CMatrix4 Matrix = ToMatrix();

  // Extract eulers.
  if( Matrix.m44[ 1 ][ 0 ] > 0.998f )
  {
    *Y = CMath::ATan2( Matrix.m44[ 0 ][ 2 ], Matrix.m44[ 2 ][ 2 ] );
    *Z = CMath::HALF_PI;
    *X = 0.0f;
  }
  else if( Matrix.m44[ 1 ][ 0 ] < -0.998f )
  {
    *Y = CMath::ATan2( Matrix.m44[ 0 ][ 2 ], Matrix.m44[ 2 ][ 2 ] );
    *Z = -CMath::HALF_PI;
    *X = 0.0f;
  }
  else
  {
    *Y = CMath::ATan2( -Matrix.m44[ 2 ][ 0 ], Matrix.m44[ 0 ][ 0 ] );
    *Z = CMath::ASin( Matrix.m44[ 1 ][ 0 ] );
    *X = CMath::ATan2( -Matrix.m44[ 1 ][ 2 ], Matrix.m44[ 1 ][ 1 ] );
  }
}

The only way to keep correct euler angles is to track them and compute the quaternion from them apparently.

Comparing with my code i use PI where you use half pi (for matrx to euler):

sVec3 ToEulerZYX (const int order = 0x012) 
	{
		int a0 = (order>>8)&3;
		int a1 = (order>>4)&3;
		int a2 = (order>>0)&3;

		sVec3 euler;
		// Assuming the angles are in radians.
		if ((*this)[a0][a2] > (1.0-FP_EPSILON)) 
		{ // singularity at north pole
			euler[a0] = -atan2((*this)[a2][a1], (*this)[a1][a1]);
			euler[a1] = -3.1415926535897932384626433832795f/2.0f;
			euler[a2] = 0;
			return euler;
		}
		if ((*this)[a0][a2] < -(1.0-FP_EPSILON))
		{ // singularity at south pole
			euler[a0] = -atan2((*this)[a2][a1], (*this)[a1][a1]);
			euler[a1] = 3.1415926535897932384626433832795f/2.0f;
			euler[a2] = 0;
			return euler;
		}
		euler[a0] =	-atan2(-(*this)[a1][a2], (*this)[a2][a2]);
		euler[a1] =	-asin ( (*this)[a0][a2]);
		euler[a2] =	-atan2(-(*this)[a0][a1], (*this)[a0][a0]);
		return euler;
	}
I took the original code from the same site, and added custom order.
0x012 = xyz, 0x120 = yzx...

Edit: Ooops, PI is so long i've missed the /2 at the end smile.png
So maybe the bug is about the signs
That gives a result not far but very not accurate, if you render using the quaternion and the quaternion from the eulers extracted, it has lot of angles of difference.
One case where the result is just not correct at all : -160 degrees on X, -75 degrees on Y and 70 degrees on Z.
If you extract euler from this case you have : -133.219162 on X, -31.7808132 on Y and -62.0091019 on Z.
When you render using a quaternion from these euler you have a completely different result.
Like I said before, I think the only way to keep correct euler angles is to track them and compute the quaternion from them apparently.
But this is the only case for the editor because in a game you only works using quaternion, you don't care of the angle values.
Maybe it's a good way to have editor-only functions which do the angle operations and make the quaternion from them.
Can you post your quaternion numbers for your uncorrect example?
I'd like to see if i can reproduce the issue and if so, write my own code.

I do not trust the above myself, but so far i've had no issues (using it for "humans need eulers to edit animation curves" purpose only).
QuaternionToEuler : x = -160 degrees, y = -75 degrees, z = 70 degrees
Quaternion(x,y,z,w) : -0.700637, 0.361543, -0.412074, 0.456716
Euler(x,y,z) : -133.219162, -31.780813, -62.009102
Tried it but i could not reproduce exactly.
I get similar angles when transposing matrix and using YZX order, but with different signs.
I come back to the original matrix using those angles, so the ToEuler function posted above still seems correct.

I assume you have a column major vs. major column matrices issue.
I use OpenGL order and Euclidean Space site too most probably.
Maybe all you need to do is swapping matrix indices.





		sQuat quat (-0.700637, 0.361543, -0.412074, 0.456716);
		quat.Normalize();
		sMat3 matrix;
		matrix.FromQuat (quat);

		matrix = matrix.Transposed(); // column / major issue ?
		
		sVec3 radians = matrix.ToEuler(0x120);
		sVec3 verify	= radians * 180.0f/PI; // {mX=133.219284 mY=31.7808933 mZ=62.0091209 ...} 

		sMat3 matrixX = sMat3::rotationX (radians[0]);
		sMat3 matrixY = sMat3::rotationY (radians[1]);
		sMat3 matrixZ = sMat3::rotationZ (radians[2]);
		
		sMat3 verifyMatrix = matrixX * matrixZ * matrixY; // == matrix


Edit: Another Ooops - i made a mistake testing all rotation orders.
Using XZY order i get rid of the matrix transpose, but still different angle signs.


		sQuat quat (-0.700637, 0.361543, -0.412074, 0.456716);
		quat.Normalize();
		sMat3 matrix;
		matrix.FromQuat (quat);
		//matrix = matrix.Transposed();
		
		sVec3 radians = matrix.ToEuler(0x021);		
		sVec3 verify = radians * 180.0f/PI; // {mX=133.219284 mY=31.7808933 mZ=62.0091209 ...} 

		sMat3 matrixX = sMat3::rotationX (radians[0]);
		sMat3 matrixY = sMat3::rotationY (radians[1]);
		sMat3 matrixZ = sMat3::rotationZ (radians[2]);
		
		sMat3 verifyMatrix = matrixX * matrixZ * matrixY; // == matrix

This topic is closed to new replies.

Advertisement