Euler angles Yaw/Pitch/Roll - differences to Ogre

Beauty

24-01-2012 13:17:17

Related to the properties:
Quaternion.Yaw
Quaternion.Pitch
Quaternion.Roll


SceneNode.Orientation is a Quaternion datatype. So this topic is also related to:
SceneNode.Orientation.Yaw
SceneNode.Orientation.Pitch
SceneNode.Orientation.Roll



Many months ago I recognized, that the Yaw/Pitch/Roll properties of Mogre Quaternions return strage values.

If you have the problem that Pitch returns NaN values (undefined), look to the topic Euler angles - current bugs + improved Euler Angle Class.
In the Mogre source code the bug is fixed now, but there will be "unfixed" binary files in the world for a longer time.
Just for information.


Different results for Mogre and Ogre

This topic I created related to the co-domain of the returned values of Yaw, Pitch and Roll.

Co-domain: Currently Mogre returns Euler values in the range
Yaw -90 .. 90
Pitch -180 .. 180
Roll -180 .. 180


The Ogre related methods getYaw(), getPitch() and getRoll() return values in a different co-domain.
Yaw -180 .. 180
Pitch ?? .. ??
Roll ?? .. ??


In most cases, the user would like to have a Yaw co-domain of -180 .. +180.
Additionally we want to have the same behaviour for Mogre and Ogre.

Related to this behaviour I had a discussion in the topic co-domains of Quaternion::getYaw(), getPitch() and getRoll().
There I got an answer for the reason:

Why Mogre returns different Euler Angle values:
Ogre's getYaw code is:
if (reprojectAxis)
{
// yaw = atan2(localz.x, localz.z)
// pick parts of zAxis() implementation that we need
Real fTx = 2.0f*x;
Real fTy = 2.0f*y;
Real fTz = 2.0f*z;
Real fTwy = fTy*w;
Real fTxx = fTx*x;
Real fTxz = fTz*x;
Real fTyy = fTy*y;

// Vector3(fTxz+fTwy, fTyz-fTwx, 1.0-(fTxx+fTyy));

return Radian(Math::ATan2(fTxz+fTwy, 1.0f-(fTxx+fTyy)));

}
else
{
// internal version
return Radian(Math::ASin(-2*(x*z - w*y)));
}

The reprojectAxis bool defaults to true.
This means that by default ogre is doing a different calculation than mogre, which is probably why I'm getting the full range while you are getting a smaller value. I'd probably get the 60,180,180 thing if I used the non default formula.



Task

Now our task is to update our Mogre source code (in the correct way).

Ogre code from file OgreQuaternion.cpp:

Radian Quaternion::getRoll(bool reprojectAxis) const
{
if (reprojectAxis)
{
// roll = atan2(localx.y, localx.x)
// pick parts of xAxis() implementation that we need
// Real fTx = 2.0*x;
Real fTy = 2.0f*y;
Real fTz = 2.0f*z;
Real fTwz = fTz*w;
Real fTxy = fTy*x;
Real fTyy = fTy*y;
Real fTzz = fTz*z;

// Vector3(1.0-(fTyy+fTzz), fTxy+fTwz, fTxz-fTwy);

return Radian(Math::ATan2(fTxy+fTwz, 1.0f-(fTyy+fTzz)));

}
else
{
return Radian(Math::ATan2(2*(x*y + w*z), w*w + x*x - y*y - z*z));
}
}


//-----------------------------------------------------------------------


Radian Quaternion::getPitch(bool reprojectAxis) const
{
if (reprojectAxis)
{
// pitch = atan2(localy.z, localy.y)
// pick parts of yAxis() implementation that we need
Real fTx = 2.0f*x;
Real fTy = 2.0f*y;
Real fTz = 2.0f*z;
Real fTwx = fTx*w;
Real fTxx = fTx*x;
Real fTyz = fTz*y;
Real fTzz = fTz*z;

// Vector3(fTxy-fTwz, 1.0-(fTxx+fTzz), fTyz+fTwx);
return Radian(Math::ATan2(fTyz+fTwx, 1.0f-(fTxx+fTzz)));
}
else
{
// internal version
return Radian(Math::ATan2(2*(y*z + w*x), w*w - x*x - y*y + z*z));
}
}


//-----------------------------------------------------------------------


Radian Quaternion::getYaw(bool reprojectAxis) const
{
if (reprojectAxis)
{
// yaw = atan2(localz.x, localz.z)
// pick parts of zAxis() implementation that we need
Real fTx = 2.0f*x;
Real fTy = 2.0f*y;
Real fTz = 2.0f*z;
Real fTwy = fTy*w;
Real fTxx = fTx*x;
Real fTxz = fTz*x;
Real fTyy = fTy*y;

// Vector3(fTxz+fTwy, fTyz-fTwx, 1.0-(fTxx+fTyy));

return Radian(Math::ATan2(fTxz+fTwy, 1.0f-(fTxx+fTyy)));

}
else
{
// internal version
return Radian(Math::ASin(-2*(x*z - w*y)));
}
}



Mogre code from file MogreQuaternion.cpp:

Radian Quaternion::Roll::get()
{
return Radian(Math::ATan2(2*(x*y + w*z), w*w + x*x - y*y - z*z));
}

//-----------------------------------------------------------------------

Radian Quaternion::Pitch::get()
{
return Radian(Math::ATan2(2*(y*z + w*x), w*w - x*x - y*y + z*z));
}

//-----------------------------------------------------------------------

Radian Quaternion::Yaw::get()
{
return Radian(Mogre::Math::ASin(-2*(x*z - w*y)));
}



How we could do

I'm not shure how to handle the parameter.
I propose to
  1. Keep the properties Yaw/Pitch/Roll[/*:m]
  2. Add the Ogre code as additional methods (instead of put them into the properties)
    . . . . . private or public?
    . . . . . I think private as long as we don't know when we need false for reprojectAxis.
    . . . . . To keep it private would avoid confusions for users.[/*:m]
  3. Call that methods from the Properties[/*:m][/list:u]

    For "internal usage" the parameter reprojectAxis is false.
    I don't know the background and which "internal" methods call getYaw() / getPitch() / getRoll().

    Perhaps there are also other parts of the math related classes, which should be updated.


    Pure .NET classes

    Just for information:
    About 20 classes are not wrapped, because of a better performance. Instead they will be compiled for .NET (as C++ code).
    The downside is, that we need to update them manually, although I think that changes are very rare.
    All "custom" classes are listed in the wiki (here).
    In the source code you find them here:
    Mogre\Main\include\Custom\ // header files
    Mogre\Main\src\Custom\ // source files