Jump to content
  • Advertisement
Sign in to follow this  
Alundra

Quaternion To Euler issue, fix possible ?

This topic is 1087 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites

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.

Edited by Alundra

Share this post


Link to post
Share on other sites

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.

Edited by Norman Barrows

Share this post


Link to post
Share on other sites

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.

Edited by Alundra

Share this post


Link to post
Share on other sites
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 Edited by JoeJ

Share this post


Link to post
Share on other sites
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.
Edited by Alundra

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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
Edited by Alundra

Share this post


Link to post
Share on other sites
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
Edited by JoeJ

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!