Google

Euler angles - current bugs + improved Euler Angle Class

Discussion regarding the Managed .Net Wrapper for Ogre, MOGRE (http://sourceforge.net/projects/mogre)

Moderators: OGRE Team, MOGRE Moderators

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Wed Jan 18, 2012 11:40 pm

If you are a student of a (German) university, then you have a good chance to get many products of Microsoft for free. One of these is Visual Studio.
(Not only the seperated Express versions with reduced functionality.)
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Thu Jan 19, 2012 4:32 pm

Today I continued my test.
Here are my improved test results.

Test conditions:
(As postet some days ago.)

My test uses the YPR convention, created by this code:
Code: Select all
// calculate quaternion from yaw/pitch/roll by help of a SceneNode

node.Orientation = new Quaternion(new Degree(yaw), Vector3.UNIT_Y);
node.Pitch(new Degree(pitch), Node.TransformSpace.TS_LOCAL);
node.Roll(new Degree(roll), Node.TransformSpace.TS_LOCAL);
quat = node.Orientation;

If somebody knows how to calculate the the Quaternion without help of a SceneNode, please let me know.



To check the result of the Euler class I use this conversion steps:
Code: Select all
EulerAngles --> Quaternion --> EulerClass --> EulerAngles




Extended test results:

Everything works fine for these angles:
Code: Select all
Yaw   range from  -180  to  180  
Pitch range from   -89  to   89 
Roll  range from  -180  to  180 

The output results are equal to the input results.

Note:
When an angle input is minus 180 and the output is plus 180
then the values are different (mathematically), but for Euler Angles usage the meaning is equal.
So my test code assume these cases as "is equal".


The output value range (co-domain) of Kojacks Euler class is:
Code: Select all
Yaw    -180 .. 180 
Pitch  -89.99 .. 89.99
Roll   -180 .. 180



For the case that Yaw input is in range -180 .. -90 or in range 90 .. 180,
the output is different to the input.
Reason: The Euler Class flips to an other axis.
Means: The output are large Roll values instead of large Pitch values.

Nevertheless the resulting Orientation should be equal to the input Orientation. (I didn't check that!!)
So the SceneNodes or clamped characters should have the same orientation behaviour.


NaN problem:

When the Pitch of the input angle is +/- 90 degree,
then the output of the Euler Angle class is bad.
The output of Pitch is NaN (not a number / undefined).

It doesn't matter which input values are used for Yaw and Roll.
There is always the NaN problem.
I suppose it's the Gimbal Lock case.

For this special case we should add a workaround.

Reason for the NaN problem:

......... somewhen later ........
Now I made some debugging.

The NaN value will be returned from Mogre.Matrix3.ToEulerAnglesYXZ()
which is used in this code:
Code: Select all
/// <summary>Constructor which calculates the Euler Angles from a quaternion.</summary>
public Euler(Quaternion oriantation)
{
    Matrix3 rotMat;
    rotMat = oriantation.ToRotationMatrix();
    rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);  // NaN-PROBLEM
    mChanged = true;
    mCachedQuaternion = Quaternion.IDENTITY;
}



Then I looked to the related Mogre sources for Matrix3::ToEulerAnglesYXZ().
(The 1.6.5 sources, but I think in Mogre 1.7.x it's the same.)
Code: Select all
bool Matrix3::ToEulerAnglesYXZ (Radian% rfYAngle, Radian% rfPAngle, Radian% rfRAngle)
{
    // rot =  cy*cz+sx*sy*sz  cz*sx*sy-cy*sz  cx*sy
    //        cx*sz           cx*cz          -sx
    //       -cz*sy+cy*sx*sz  cy*cz*sx+sy*sz  cx*cy

    rfPAngle = System::Math::Asin(-m12);
    if ( rfPAngle < Radian(Math::HALF_PI) )
    {
        if ( rfPAngle > Radian(-Math::HALF_PI) )
        {
            rfYAngle = System::Math::Atan2(m02,m22);
            rfRAngle = System::Math::Atan2(m10,m11);
            return true;
        }
        else
        {
            // WARNING.  Not a unique solution.
            Radian fRmY = System::Math::Atan2(-m01,m00);
            rfRAngle = Radian(0.0);  // any angle works
            rfYAngle = rfRAngle - fRmY;
            return false;
        }
    }
    else
    {
        // WARNING.  Not a unique solution.
        Radian fRpY = System::Math::Atan2(-m01,m00);
        rfRAngle = Radian(0.0);  // any angle works
        rfYAngle = fRpY - rfRAngle;
        return false;
    }
}

The problem is the same as for the Quaternion.Yaw/Pitch/Roll problems.
Mogre uses System.Math for Asin() and Atan2() calculations.
As wrote somewhere above (or in an other topic) one of these functions (or both) can return NaN values for special cases. (mathematically correct)
So we have to use Mogre.Math instead of System.Math, because for usage with Ogre there are advanced math calculations to avoid problems like this.

By the way:
What means the percent symbol % at the method parameters?


The best solution would be to
  • Figure out which math functions are "critical"
  • Fix them
    . . . . . Either by wrapping them to Ogre::Math
    . . . . . Or by porting the related Ogre code and put it into Mogre (could be better for performance)
  • Check all Mogre sources for "critical" System::Math calls
    . . . . . Especially check members of Mogre.Math and Mogre.Quaternion
  • Replace all "critical" calls of System::Math by Mogre::Math
  • After all we would need to build (or even wrap?) Mogre again, before we can use it in practice

I could help to find all related calls in the source code.
There I can add a comment like "// CRITICAL CALL, because of ...".
The code I don't want to touch myself, because my C++ knowledge is very bad.


As quick workaround for the Euler class we could create a bugfixed method of
Matrix3::ToEulerAnglesYXZ (Radian% rfYAngle, Radian% rfPAngle, Radian% rfRAngle)
and add it into the Euler class.


Kojack,
can you tell us, which math functions are "critical"?
(Functions which can return NaN values and need special internal calculations)
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Thu Jan 19, 2012 6:14 pm

Well, I found out that (at least) ASin() is critical:
Kojack wrote:getYaw does an asin operation. This MUST be given a value between -1 to 1. Outside of that range will cause a nan. Even slight floating point errors like 1.000001 will fail. Ogre provides Math::ASin, which protects asin from being called with bad values.


Interesting files for us:
OgreMath.cpp .................................. contains bullet-proof code
MogreMath.h and MogreMath.cpp ........ Mogre code



bullet-proof math methods

Now I found out, that MogreMath.cpp and by this Mogre still contains the bullet-proof code:

ASin()
Code: Select all
Radian Math::ASin (Real fValue)
{
    if ( -1.0 < fValue )
    {
        if ( fValue < 1.0 )
            return Radian(System::Math::Asin(fValue));
        else
            return Radian(HALF_PI);
    }
    else
    {
        return Radian(-HALF_PI);
    }
}


ACos()
Code: Select all
Radian Math::ACos (Real fValue)
{
    if ( -1.0 < fValue )
    {
        if ( fValue < 1.0 )
            return Radian(acos(fValue));
        else
            return Radian(0.0);
    }
    else
    {
        return Radian(PI);
    }
}



TODO

Just replace the System calls
Code: Select all
System::Math::ASin()
System::Math::ACos()

by Mogre calls in the Mogre source files
Code: Select all
Mogre::Math::ASin()
Mogre::Math::ACos()


Important:
Do it in a way (how?) that the Mogre autowrapper tool doesn't kill (overwrite) the modifications.
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Thu Jan 19, 2012 6:17 pm

Ok, I checked all files.
Only these ones have to be updated:

Code: Select all
MogreMath.cpp
MogreMatrix3.cpp
MogreQuaternion.cpp
MogreVector3.h
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby DirtyHippy » Thu Jan 19, 2012 7:04 pm

The basic Mogre utility classes (vector, matrix, etc) do not wrap the Ogre equivalents for performance reasons if I remember correctly. They actually are custom C++/CLI classes included in Mogre itself. This wouldn't affect autowrapping as that tool doesn't overwrite these files.
DirtyHippy
Gnoblar
 
Posts: 14
Kudos: 5
Joined: 10 Apr 2008

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Fri Jan 20, 2012 12:05 am

Oh yes yes. Thanks for remembering me.
I forgot this fact when I looked to the code so deeply.

By the way - in the Wiki is an overview of all related classes.
The page MOGRE pure .NET classes ist linked directly from the Mogre wiki front page.

In this case the bugfix should be easy.
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Fri Jan 20, 2012 2:12 am

Ok, now I made the changes and committed the bugfix to the official repository. :D
https://bitbucket.org/mogre/mogre/changesets

Mercurial I used the first time.
I hope I did it in a correct way. (I used "distribute" instead of "patch")

My bugfix was applied to the main branch.
I couldn't find out, how to apply it to the TerrainAndPaging branch, too.

Well, nice to have the bugfix in the source code.
For the most people the bug will still exist until we have related binaries. (including updated MogreSDK)

But the first step is done now.
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Fri Jan 20, 2012 3:00 am

Now it's in the terrain&paging branch, too.

Perhaps there is a better way to do it (insead of 2 redundant commits).
Well, I should learn more about Mercurial.
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby smiley80 » Fri Jan 20, 2012 5:49 am

Beauty wrote:Perhaps there is a better way to do it (insead of 2 redundant commits).

1) make your changes in the 'default' branch and commit:
Code: Select all
hg update -C default
//change files
hg commit -m "bugfix"

2) switch to the 'TerrainAndPaging' branch:
Code: Select all
hg update -C TerrainAndPaging

3) merge with 'default':
Code: Select all
hg merge default

4) commit:
Code: Select all
hg commit -m "merge with default"
There was a SIGNATURE here. It's gone now.

For this message the author smiley80 has received kudos
smiley80
Gnoll
 
Posts: 670
Kudos: 47
Joined: 13 Nov 2008
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby kojack » Fri Jan 20, 2012 7:21 pm

Kojack,
can you tell us, which math functions are "critical"?
(Functions which can return NaN values and need special internal calculations)

asin and acos are the ones. Slight floating point error can cause them to generate nans, because it's mathematically impossible to use them for certain values.

If somebody knows how to calculate the the Quaternion without help of a SceneNode, please let me know.

Code: Select all
quat = new Quaternion(yaw, Vector3.UNIT_Y) * new Quaternion(pitch, Vector3.UNIT_X) * new Quaternion(roll, Vector3.UNIT_Z);

For this message the author kojack has received kudos
User avatar
kojack
Gnoblar
 
Posts: 17
Kudos: 2
Joined: 10 Feb 2006

Re: Euler angles - current bugs + improved Euler Angle Class

Postby zarfius » Sat Jan 21, 2012 12:51 pm

Beauty wrote:Ok, now I made the changes and committed the bugfix to the official repository. :D
https://bitbucket.org/mogre/mogre/changesets
...
Well, nice to have the bugfix in the source code.
For the most people the bug will still exist until we have related binaries. (including updated MogreSDK)


I've just had a look at the source code that I used to create the binaries over in this topic. It appears that they have been built with the changes you made according to the changeset. For example, here's a bit of code from my Main/include/Custom/MogreVector3.h file:

Code: Select all
      
inline bool DirectionEquals(Vector3 rhs, Radian tolerance)
{
   Real dot = DotProduct(rhs);
   Radian angle = Mogre::Math::ACos(dot);

   return Math::Abs(angle.ValueRadians) <= tolerance.ValueRadians;
}


Someone might like to download and test to see if the changes have fixed the bugs in those binaries.
Craftwork Games - hand crafted entertainment.
http://www.craftworkgames.com/
User avatar
zarfius
Goblin
 
Posts: 234
Kudos: 24
Joined: 09 Jul 2008
Location: Brisbane, Australia

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Mon Jan 23, 2012 2:24 pm

Thanks for your answers.

Today I wrote a workaround for the NaN problem.
So this Euler class can be used successfully, even with buggy Mogre binaries.

The new helper method Matrix3ToEulerAnglesYXZ() I tested extensively.
(For 24 million rotation states, the output is equal to the original method. NaN values will not be returned.)

In the Mogre sources the bugs are fixed now, but I suppose the unfixed Mogre binaries will be used for a longer time. (e.g. the unofficial Cygon build + the unmaintained MogreSDK)


Here is the modificated method of the Euler Class:
Code: Select all
/// <summary>
/// Constructor which calculates the Euler Angles from a quaternion.
/// </summary>
public Euler(Quaternion oriantation)
{
    Matrix3 rotMat;
    rotMat = oriantation.ToRotationMatrix();
       
    // BUGGY METHOD (NaN return in some cases)
    // rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);

    // WORKAROUND
    Boolean success;
    Matrix3ToEulerAnglesYXZ(rotMat, out mYaw, out mPitch, out mRoll, out success);

    mChanged = true;
    mCachedQuaternion = Quaternion.IDENTITY;
}


And here is the added method Matrix3ToEulerAnglesYXZ() as replacement for the buggy method Matrix3::ToEulerAnglesYXZ():

Code: Select all
/// <summary>
/// Port of method <c>Matrix3.ToEulerAnglesYXZ()</c>, from MogreMatrix3.cpp as a workaround for a bug in the Math class.
/// (The bug was fixed, but is not present in common used binary files.)
/// </summary>
/// <param name="matrix">Rotation matrix</param>
/// <param name="success">This COULD BE a flag for "success", but it's not documented in the original code.</param>
public static void Matrix3ToEulerAnglesYXZ(Matrix3 matrix,
    out Radian rfYAngle, out Radian rfPAngle, out Radian rfRAngle, out Boolean success)
{
    rfPAngle = Mogre.Math.ASin(-matrix.m12);

    if (rfPAngle < new Radian(Math.HALF_PI))
    {
        if (rfPAngle > new Radian(-Math.HALF_PI))
        {
            rfYAngle = Math.ATan2(matrix.m02, matrix.m22);
            rfRAngle = Math.ATan2(matrix.m10, matrix.m11);
            success = true;
            return;
        }
        else
        {
            // WARNING.  Not a unique solution.
            Radian fRmY = Math.ATan2(-matrix.m01, matrix.m00);
            rfRAngle = new Radian(0f);  // any angle works
            rfYAngle = rfRAngle - fRmY;
            success = false;
            return;
        }
    }
    else
    {
        // WARNING.  Not a unique solution.
        Radian fRpY = Math.ATan2(-matrix.m01, matrix.m00);
        rfRAngle = new Radian(0f);  // any angle works
        rfYAngle = fRpY - rfRAngle;
        success = false;
        return;
    }



    // "Original" CODE FROM CLASS Matrix3.ToEulerAnglesYXZ()

    //rfPAngle = Mogre::Math::ASin(-m12);
    //if ( rfPAngle < Radian(Math::HALF_PI) )
    //{
    //    if ( rfPAngle > Radian(-Math::HALF_PI) )
    //    {
    //        rfYAngle = System::Math::Atan2(m02,m22);
    //        rfRAngle = System::Math::Atan2(m10,m11);
    //        return true;
    //    }
    //    else
    //    {
    //        // WARNING.  Not a unique solution.
    //        Radian fRmY = System::Math::Atan2(-m01,m00);
    //        rfRAngle = Radian(0.0);  // any angle works
    //        rfYAngle = rfRAngle - fRmY;
    //        return false;
    //    }
    //}
    //else
    //{
    //    // WARNING.  Not a unique solution.
    //    Radian fRpY = System::Math::Atan2(-m01,m00);
    //    rfRAngle = Radian(0.0);  // any angle works
    //    rfYAngle = fRpY - rfRAngle;
    //    return false;
    //}


} // Matrix3ToEulerAnglesYXZ()
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Mon Jan 23, 2012 5:58 pm

Partially wrong values in Gimbal Lock case.
(For the Euler Angle class it means: When Pitch is +/- 90 degree)

Now for several cases I get correct results.
But for several other cases I also get incorrect results.
Well, at least there are no more NaN values, which can cause crashes in further calculations.

Examples:
Code: Select all
        Yaw Pitch Roll

Before:  -095  090  -060
After:    000  090   000

Before:   175  090  -065
After:    120  090   000

Before:   165  090  -095
After:    100  090   000


So be distrustfully in the cases that Euler returns Pitch = +/- 90
In this case the represented pitch value is correct, but Yaw and Roll can be wrong.

All test results are correct for the case that the input value of Pitch is greater than -90 and smaller than +90 degree.
(This statement is related to my test converter chain, as I told above.)
For input pitch values out of the range, I can't say if the Euler class gets back the correct orientation from the quaternion.

Perhaps Kojack can test my current code with his Sinbad model test rotation application. To have a visual result for the input and output rotation.

The NaN problem is solved.

Here is the current state of my Euler Angle source code.
Code: Select all
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Mogre;
using Math = Mogre.Math;


namespace MogreEulerAngles
{

// ABOUT THIS CODE:
//
// Developed by user Kojack in January 2012
// Extended by Beauty and Tubulii
//
// Detailed information about usage:
// http://www.ogre3d.org/tikiwiki/Euler+Angle+Class
//
// "official place" of C# version:
// http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre
//
// For questions and bug reports use this forum topic:
// viewtopic.php?f=8&t=29262
//



 
/// <summary>
/// This struct manages rotations by use of Euler Angles.
/// The rotation will be applied in the order Yaw-Pitch-Roll, which are related to the axis order Y-X-Z.
/// It's fully compatible with Ogre::Matrix3::FromEulerAnglesYXZ and Ogre::Matrix3::ToEulerAnglesYXZ.
/// For the Yaw angle the standard anticlockwise right handed rotation is used (as common in Ogre).
/// The Yaw-Pitch-Roll ordering is most convenient for upright character controllers and cameras.
/// </summary>
public struct Euler
{

    /// <summary>Get or set the Yaw angle.</summary>
    public Radian Yaw
    {
        get { return mYaw; }
        set
        {
            mYaw = value;
            mChanged = true;
        }
    }


    /// <summary>Get or set the Pitch angle.</summary>
    public Radian Pitch
    {
        get { return mPitch; }
        set
        {
            mPitch = value;
            mChanged = true;
        }
    }


    /// <summary>Get or set the Roll angle.</summary>
    public Radian Roll
    {
        get { return mRoll; }
        set
        {
            mRoll = value;
            mChanged = true;
        }
    }
 

    /// <summary>
    /// Constructor to create a new Euler Angle struct from angle values.
    /// The rotation will be applied in the order Yaw-Pitch- Roll, which are related to the axis order Y-X-Z.
    /// </summary>
    public Euler(Radian yaw, Radian pitch, Radian roll)
    {
        mYaw = yaw;
        mPitch = pitch;
        mRoll = roll;
        mChanged = true;
        mCachedQuaternion = Quaternion.IDENTITY;
    }
 

    /// <summary>
    /// Constructor which calculates the Euler Angles from a quaternion.
    /// </summary>
    public Euler(Quaternion oriantation)
    {
        Matrix3 rotMat;
        rotMat = oriantation.ToRotationMatrix();
       
        // BUGGY METHOD (NaN return in some cases)
        // rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);

        // WORKAROUND
        Boolean success;
        Matrix3ToEulerAnglesYXZ(rotMat, out mYaw, out mPitch, out mRoll, out success);

        mChanged = true;
        mCachedQuaternion = Quaternion.IDENTITY;
    }

   

    /// <summary>
    /// Apply a relative yaw. (Adds the angle to the current yaw value)
    /// </summary>
    /// <param name="yaw">Yaw value as radian</param>
    public void AddYaw(Radian yaw)
    {
        mYaw += yaw;
        mChanged = true;
    }
 

    /// <summary>
    /// Apply a relative pitch. (Adds the angle to the current pitch value)
    /// </summary>
    /// <param name="pitch">Pitch value as radian</param>
    public void AddPitch(Radian pitch)
    {
        mPitch += pitch;
        mChanged = true;
    }
 

    /// <summary>
    /// Apply a relative roll. (Adds the angle to the current roll value)
    /// </summary>
    /// <param name="roll">Roll value as radian</param>
    public void AddRoll(Radian roll)
    {
        mRoll += roll;
        mChanged = true;
    }


    /// <summary>
    /// Apply a relative yaw. (Adds the angle to the current yaw value)
    /// </summary>
    /// <param name="yaw">Yaw value as degree</param>
    public void AddYaw(Degree yaw)
    {
        mYaw += yaw;
        mChanged = true;
    }


    /// <summary>
    /// Apply a relative pitch. (Adds the angle to the current pitch value)
    /// </summary>
    /// <param name="pitch">Pitch value as degree</param>
    public void AddPitch(Degree pitch)
    {
        mPitch += pitch;
        mChanged = true;
    }


    /// <summary>
    /// Apply a relative roll. (Adds the angle to the current roll value)
    /// </summary>
    /// <param name="roll">Roll value as degree</param>
    public void AddRoll(Degree roll)
    {
        mRoll += roll;
        mChanged = true;
    }


    /// <summary>Get a vector pointing forwards. </summary>
    public Vector3 Forward
    {
        get { return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z; }
    }
 

   /// <summary>Get a vector pointing to the right.</summary>
   public Vector3 Right
   {
        get { return ToQuaternion() * Vector3.UNIT_X; }
   }
 

   /// <summary> Get a vector pointing up.</summary>
   public Vector3 Up
   {
        get { return ToQuaternion() * Vector3.UNIT_Y; }
   }
 
       
    /// <summary>
    /// Calculate the quaternion of the euler object.
    /// The result is cached. It will only be recalculated when the component euler angles are changed.
    /// </summary>
   public Quaternion ToQuaternion()
   {
      if(mChanged)
      {
         mCachedQuaternion = new Quaternion(mYaw, Vector3.UNIT_Y) * new Quaternion(mPitch, Vector3.UNIT_X) * new Quaternion(mRoll, Vector3.UNIT_Z);
         mChanged = false;
      }
      return mCachedQuaternion;
   }
 

    /// <summary>
    /// Return a String with degree values of the axis rotations (human readable style).
    /// For example "X-axis: 0°   Y-axis: 36°   Z-axis: 90°"
    /// </summary>
    public String ToAxisString()
    {
        return String.Format("X: {0:00}°   Y: {1:00}°   Z: {2:00}°",
            Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees);
    }




    /// <summary>
    /// Return a String with degree values in the applied rotation order (human readable style).
    /// For example "Yaw: 0°   Pitch: 36°   Roll: 90°"
    /// </summary>
    public String ToYawPitchRollString()
    {
        return String.Format("Yaw: {0:00}°   Pitch: {1:00}°   Roll: {2:00}°",
            Yaw.ValueDegrees, Pitch.ValueDegrees, Roll.ValueDegrees);
    }




    /// <summary>
    /// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators.
    /// Input order for rotation values:: Yaw, Pitch, Roll (all in degree)
    /// If success, an Euler struct will be returned.
    /// If parsing failed, a FormatException will be thrown.
    /// Example: "111  .99   -66"
    /// </summary>
    /// <param name="valueString">String which contains 3 floating point values</param>
    /// <remarks>
    /// Multiple and mixed usage of space/tabulator/commas are possible.
    /// As decimal seperator a dot "." is expected.
    /// </remarks>
    /// <returns>Returns an Euler struct or a FormatException</returns>
    public static Euler ParseStringYawPitchRoll(String valueString)
    {
        Single[] values = Parse3Params(valueString);

        if (values == null)
            throw new FormatException(String.Format("Can't parse floating point values of string '{0}'", valueString));

        return new Euler(new Degree(values[0]), new Degree(values[1]), new Degree(values[2]));
    }



    /// <summary>
    /// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
    /// Input order for rotation values: X-axis, Y-axis, Z-axis (all in degree)
    /// If success, an Euler struct will be returned.
    /// If parsing failed, a FormatException will be thrown.
    /// Example: "111  .99   -66"
    /// </summary>
    /// <param name="valueString">String which contains 3 floating point values</param>
    /// <remarks>
    /// Multiple and mixed usage of space/tabulator/commas are possible.
    /// As decimal seperator a dot "." is expected.
    /// </remarks>
    /// <returns>Returns an Euler struct or a FormatException</returns>
    public static Euler ParseStringAxisXYZ(String valueString)
    {
        Single[] values = Parse3Params(valueString);

        if (values == null)
            throw new FormatException(String.Format("Can't parse floating point values of string '{0}'", valueString));

        return new Euler(new Degree(values[1]), new Degree(values[0]), new Degree(values[2]));
    }



    /// <summary>
    /// Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
    /// If parsing failed, null will be returned.
    /// Example: "111  .99   -66"
    /// </summary>
    /// <param name="valueString">String which contains 3 floating point values</param>
    /// <remarks>
    /// Multiple and mixed usage of space/tabulator/commas are possible.
    /// As decimal seperator a dot "." is expected.
    /// </remarks>
    /// <returns>Returns 3 Single values or null</returns>
    private static Single[] Parse3Params(String valueString)
    {
        // Some Regex explanation:
        //
        // The "@" prefix in front of the String means:
        //         Backslash are processed as Text instead of special symbols.
        //         Advantage: Just write "\" instead of "\\" for each backslash
        //
        // "^" at first position means:  No text is allowed before
        // "$" at the end means:         No text is allowed after that
        //
        // Floating point values are matched
        //         Expression: "-?\d*\.?\d+"
        //         Examples:   "111",  "0.111",  ".99",  "-66"
        //
        // Seperator can be tabs or spaces or commas (at least one symbol; mixing is possible)
        //         Expression: "[, \t]+"


        String val = @"[-\d\.]+";     // simplified (faster) floating point pattern (exact pattern would be @"-?\d*\.?\d+" )
        String sep = @"[, \t]+";      // seperator pattern

        // build target pattern
        String searchPattern = "^(" + val + ")" + sep + "(" + val + ")" + sep + "(" + val + ")$";

        Match match = Regex.Match(valueString, searchPattern);

        try
        {
            if (match.Success)
            {
                // Force to parse "." as decimal char.  (Can be different with other culture settings. E.g. German culture expect "," instad of ".")
                System.Globalization.CultureInfo englishCulture = new System.Globalization.CultureInfo("en-US");

                Single[] result = new Single[3];
                result[0] = Convert.ToSingle(match.Groups[1].Value, englishCulture);
                result[1] = Convert.ToSingle(match.Groups[2].Value, englishCulture);
                result[2] = Convert.ToSingle(match.Groups[3].Value, englishCulture);
                return result;
            }
            else
                return null;
        }
        catch (FormatException) { return null; }
        catch (OverflowException) { return null; }

    } // Parse3Params()




    /// <summary>
    /// Return the Euler rotation state as quaternion.
    /// </summary>
    /// <param name="e">Euler Angle state</param>
    /// <returns>Rotation state as Quaternion</returns>
    public static implicit operator Quaternion(Euler e)
    {
        return e.ToQuaternion();
    }
 


    /// <summary>
    /// Set the yaw and pitch to face in the given direction.
    /// The direction doesn't need to be normalised.
    /// Roll is always unaffected.
    /// </summary>
    /// <param name="directionVector">Vector which points to the wanted direction</param>
    /// <param name="setYaw">if false, the yaw isn't changed.</param>
    /// <param name="setPitch">if false, the pitch isn't changed.</param>
   public void SetDirection(Vector3 directionVector, Boolean setYaw, Boolean setPitch)
   {
      Vector3 d = directionVector.NormalisedCopy;
      if(setPitch)
         mPitch = Math.ASin(d.y);
      if(setYaw)
         mYaw = Math.ATan2(-d.x, -d.z);//+Math.PI/2.0;
      mChanged = setYaw || setPitch;
   }
 


    /// <summary>
    /// Normalise the selected rotations to be within the +/-180 degree range.
    /// The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees,
    /// and -190 degrees becomes 170.
    /// By the parameters it's possible to choose which angles should be normalised.
    /// </summary>
    /// <param name="normYaw">If true, the angle will be normalised.</param>
    /// <param name="normPitch">If true, the angle will be normalised.</param>
    /// <param name="normRoll">If true, the angle will be normalised.</param>
    /// <remarks></remarks>
    public void Normalise(Boolean normYaw, Boolean normPitch, Boolean normRoll)
   {
      if(normYaw)
      {
         Single yaw = mYaw.ValueRadians;
         if(yaw < -Math.PI)
         {
            yaw = (Single)System.Math.IEEERemainder(yaw, Math.PI * 2.0);
            if(yaw < -Math.PI)
            {
               yaw += Math.PI * 2.0f;
            }
            mYaw = yaw;
            mChanged = true;
         }
         else if(yaw > Math.PI)
         {
                yaw = (Single)System.Math.IEEERemainder(yaw, Math.PI * 2.0f);
            if(yaw > Math.PI)
            {
               yaw -= Math.PI * 2.0f;
            }
            mYaw = yaw;
            mChanged = true;
         }
      }

      if(normPitch)
      {
            Single pitch = mPitch.ValueRadians;
         if(pitch < -Math.PI)
         {
                pitch = (Single)System.Math.IEEERemainder(pitch, Math.PI * 2.0f);
            if(pitch < -Math.PI)
            {
               pitch += Math.PI * 2.0f;
            }
            mPitch = pitch;
            mChanged = true;
           
                if (Single.IsNaN(mPitch.ValueDegrees)) // DEBUGGING
                {
                }  // add breakpoint here
            }
         else if(pitch > Math.PI)
         {
                pitch = (Single)System.Math.IEEERemainder(pitch, Math.PI * 2.0f);
            if(pitch > Math.PI)
            {
               pitch -= Math.PI * 2.0f;
            }
            mPitch = pitch;
            mChanged = true;

                if (Single.IsNaN(mPitch.ValueDegrees)) // DEBUGGING
                {
                }  // add breakpoint here
         }
      }

      if(normRoll)
      {
         Single roll= mRoll.ValueRadians;
         if(roll < -Math.PI)
         {
                roll = (Single)System.Math.IEEERemainder(roll, Math.PI * 2.0f);
            if(roll < -Math.PI)
            {
               roll += Math.PI * 2.0f;
            }
            mRoll = roll;
            mChanged = true;
         }
         else if(roll > Math.PI)
         {
                roll = (Single)System.Math.IEEERemainder(roll, Math.PI * 2.0f);
            if(roll > Math.PI)
            {
               roll -= Math.PI * 2.0f;
            }
            mRoll = roll;
            mChanged = true;
         }
      }
   } // Normalise()
 



    /// <summary>
    /// Return the relative euler angles required to rotate from the current forward direction to the specified direction vector.
    /// The result euler can then be added to the current euler to immediately face dir.
    /// Rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
    /// (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
    /// the same way as (0,0,-1).
    /// The rotation won't flip upside down then roll instead of a 180 degree yaw.
    /// </summary>
    /// <param name="direction">...TODO...</param>
    /// <param name="shortest">If false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction.</param>
    /// <param name="setYaw">If true the angles are calculated. If false, the angle is set to 0. </param>
    /// <param name="setPitch">If true the angles are calculated. If false, the angle is set to 0. </param>
    public Euler GetRotationTo(Vector3 direction, Boolean setYaw, Boolean setPitch, Boolean shortest)
   {
      Euler t1 = Euler.IDENTITY;
      Euler t2;
      t1.SetDirection(direction, setYaw, setPitch);
      t2 = t1 - this;
      if(shortest && setYaw)
      {
         t2.Normalise(true, true, true);
      }
      return t2;
   }
 


    /// <summary>
    /// Clamp the yaw angle to a range of +/-limit.
    /// </summary>
    /// <param name="limit">Wanted co-domain for the Yaw angle.</param>
    public void LimitYaw(Radian limit)
    {
        if (mYaw > limit)
        {
            mYaw = limit;
            mChanged = true;
        }
        else if (mYaw < -limit)
        {
            mYaw = -limit;
            mChanged = true;
        }
    }
 


    /// <summary>
    /// Clamp the pitch angle to a range of +/-limit.
    /// </summary>
    /// <param name="limit">Wanted co-domain for the Pitch angle.</param>
    public void LimitPitch(Radian limit)
    {
        if (mPitch > limit)
        {
            mPitch = limit;
            mChanged = true;
        }
        else if (mPitch < -limit)
        {
            mPitch = -limit;
            mChanged = true;
        }
    }
 


    /// <summary>
    /// Clamp the roll angle to a range of +/-limit.
    /// </summary>
    /// <param name="limit">Wanted co-domain for the Roll angle.</param>
    public void LimitRoll(Radian limit)
    {
        if (mRoll > limit)
        {
            mRoll = limit;
            mChanged = true;
        }
        else if (mRoll < -limit)
        {
            mRoll = -limit;
            mChanged = true;
        }
    }




    /// <summary>
    /// Port of method <c>Matrix3.ToEulerAnglesYXZ()</c>, from MogreMatrix3.cpp as a workaround for a bug in the Math class.
    /// (The bug was fixed, but is not present in common used binary files.)
    /// </summary>
    /// <param name="matrix">Rotation matrix</param>
    /// <param name="success">This COULD BE a flag for "success", but it's not documented in the original code.</param>
    public static void Matrix3ToEulerAnglesYXZ(Matrix3 matrix,
        out Radian rfYAngle, out Radian rfPAngle, out Radian rfRAngle, out Boolean success)
    {
        rfPAngle = Mogre.Math.ASin(-matrix.m12);

        if (rfPAngle < new Radian(Math.HALF_PI))
        {
            if (rfPAngle > new Radian(-Math.HALF_PI))
            {
                rfYAngle = Math.ATan2(matrix.m02, matrix.m22);
                rfRAngle = Math.ATan2(matrix.m10, matrix.m11);
                success = true;
                return;
            }
            else
            {
                // WARNING.  Not a unique solution.
                Radian fRmY = Math.ATan2(-matrix.m01, matrix.m00);
                rfRAngle = new Radian(0f);  // any angle works
                rfYAngle = rfRAngle - fRmY;
                success = false;
                return;
            }
        }
        else
        {
            // WARNING.  Not a unique solution.
            Radian fRpY = Math.ATan2(-matrix.m01, matrix.m00);
            rfRAngle = new Radian(0f);  // any angle works
            rfYAngle = fRpY - rfRAngle;
            success = false;
            return;
        }



        // "Original" CODE FROM CLASS Matrix3.ToEulerAnglesYXZ()

        //rfPAngle = Mogre::Math::ASin(-m12);
        //if ( rfPAngle < Radian(Math::HALF_PI) )
        //{
        //    if ( rfPAngle > Radian(-Math::HALF_PI) )
        //    {
        //        rfYAngle = System::Math::Atan2(m02,m22);
        //        rfRAngle = System::Math::Atan2(m10,m11);
        //        return true;
        //    }
        //    else
        //    {
        //        // WARNING.  Not a unique solution.
        //        Radian fRmY = System::Math::Atan2(-m01,m00);
        //        rfRAngle = Radian(0.0);  // any angle works
        //        rfYAngle = rfRAngle - fRmY;
        //        return false;
        //    }
        //}
        //else
        //{
        //    // WARNING.  Not a unique solution.
        //    Radian fRpY = System::Math::Atan2(-m01,m00);
        //    rfRAngle = Radian(0.0);  // any angle works
        //    rfYAngle = fRpY - rfRAngle;
        //    return false;
        //}


    } // Matrix3ToEulerAnglesYXZ()








    /// <summary>
    /// Add two euler objects.
    /// </summary>
    /// <returns>Calculation result</returns>
    public static Euler operator +(Euler lhs, Euler rhs)
    {
        return new Euler(lhs.Yaw + rhs.Yaw, lhs.Pitch + rhs.Pitch, lhs.Roll + rhs.Roll);
    }
 

    /// <summary>
    /// Subtract two euler objects. This finds the difference as relative angles.
    /// </summary>
    /// <returns>Calculation result</returns>
   public static Euler operator-(Euler lhs, Euler rhs)
   {
        return new Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll);
   }
 

    /// <summary>
    /// Interpolate each euler angle by the given factor.
    /// (Each angle will be multiplied with the factor.)
    /// </summary>
    /// <returns>Calculation result</returns>
    public static Euler operator *(Euler lhs, Single factor)
    {
        return new Euler(lhs.Yaw * factor, lhs.Pitch * factor, lhs.Roll * factor);
    }
 

    /// <summary>
    /// Interpolate the euler angles by lhs.
    /// (Each angle will be multiplied with the factor.)
    /// </summary>
    /// <returns>Calculation result</returns>
    public static Euler operator *(Single factor, Euler rhs)
    {
        return new Euler(factor * rhs.Yaw, factor * rhs.Pitch, factor * rhs.Roll);
    }
 

    /// <summary>
    /// Apply the euler rotation to the vector rhs.
    /// The calculation is equal to: quaternion*vector
    /// </summary>
    /// <returns>Calculation result</returns>
    public static Vector3 operator *(Euler lhs, Vector3 rhs)
    {
        return lhs.ToQuaternion() * rhs;
    }
 

    /// <summary>Base settings (all angles are 0)</summary>
    public static Euler IDENTITY = new Euler(new Radian(0), new Radian(0), new Radian(0));
 
    /// <summary>Rotation around the Y axis.</summary>
    private Radian mYaw;

    /// <summary>Rotation around the X axis.</summary>
    private Radian mPitch;

    /// <summary>Rotation around the Z axis.</summary>
    private Radian mRoll;

    /// <summary>Is the cached quaternion out of date?</summary>
    private bool mChanged;

    /// <summary>Cached quaternion equivalent of this euler object.</summary>
    private Quaternion mCachedQuaternion;
 
}  // struct Euler



Here is my latest testing code:

Code: Select all
public class EulerTesting
{

    /// <summary>
    /// To check the result of the Euler class this method uses these conversion steps:
    /// EulerAngles --> Quaternion --> EulerClass --> EulerAngles
    /// Then the input angles and output angles are compared.
    /// </summary>
    public static void Test()
    {

        //-- TEST PARAMETERS --

        Boolean printDivergences = false;  // print all problematic angle combinations to console
                                           // Note: Will need much more process time!!

        Boolean printProblemsNAN = false;  // print angles which returns an NaN value
                                           // Note: Will also print if "printProblems" is false

        Boolean printProblems = false;      // print angles, which causes a different quaterion result than input angles


        Single stepSize = 10; // in degree

        Boolean customTestRange = false;  // in normal case Pitch greater than -90 and lower than +90



        Single yawStart, yawEnd, pitchStart, pitchEnd, rollStart, rollEnd;

        if (customTestRange == false)
        {
            // "normal" test range
            yawStart   = -180;
            yawEnd     =  180;
            pitchStart = -90 + stepSize;
            pitchEnd   =  90 - stepSize;
            rollStart  = -180;
            rollEnd    =  180;

        }
        else
        {
            // custom test range
            yawStart   = -180;
            yawEnd     =  180;
            pitchStart = -180;
            pitchEnd   = -180;
            rollStart  = -180;
            rollEnd    =  180;
        }


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

        Single yaw = 0;
        Single pitch = 0;
        Single roll = 0;

        Single ymin = 0;
        Single ymax = 0;
        Single pmin = 0;
        Single pmax = 0;
        Single rmin = 0;
        Single rmax = 0;

        Single yy, pp, rr;  // new values

        Int32 equalCounter = 0;
        Int32 differentCounter = 0;
        Int32 problemCounter = 0;
        Quaternion quat;

        Console.WriteLine("Start calculation ...\n");

        for (yaw = yawStart;   yaw <= yawEnd;   yaw += stepSize)
        {
            for (pitch = pitchStart;   pitch <= pitchEnd;   pitch += stepSize)
            {
                for (roll = rollStart;   roll <= rollEnd;   roll += stepSize)
                {

                    //-- calculate quaternion from yaw/pitch/roll --

                    quat = new Quaternion(yaw, Vector3.UNIT_Y) * new Quaternion(pitch, Vector3.UNIT_X) * new Quaternion(roll, Vector3.UNIT_Z);


                    //-- convert quaternion back to y/p/r by Kojacks Euler Angle Class --

                    Euler euler = new Euler(quat);
                    yy = euler.Yaw.ValueDegrees;
                    pp = euler.Pitch.ValueDegrees;
                    rr = euler.Roll.ValueDegrees;

                    //-- check co-domain --

                    if (yy < ymin)
                        ymin = yy;
                    if (yy > ymax)
                        ymax = yy;
                    if (pp < pmin)
                        pmin = pp;
                    if (pp > pmax)
                        pmax = pp;
                    if (rr < rmin)
                        rmin = rr;
                    if (rr > rmax)
                        rmax = rr;


                    //-- compare Euler values before/after  --

                    Boolean isDifferent = (!IsEqual(yy, yaw) || !IsEqual(pp, pitch) || !IsEqual(rr, roll));
                    Boolean isProblem = false;

                    if (isDifferent)
                    {
                        differentCounter++;

                        if (IsEqualQuaternion(yaw, pitch, roll, yy, pp, rr) == false)
                        {
                            problemCounter++;
                            isProblem = true;
                        }
                    }
                    else
                        equalCounter++;


                    // NaN test
                    Boolean hasNAN = false;
                    if (isDifferent && (Single.IsNaN(yy) || Single.IsNaN(pp) || Single.IsNaN(rr)))
                        hasNAN = true;

                    // debug print (different or NaN)
                    if (isDifferent &&
                        (printDivergences || (printProblemsNAN && hasNAN)))
                    {
                        Console.WriteLine(String.Format(
                            "Yaw Pitch Roll:  {0:000}  {1:000}  {2:000}\n" +
                            "            -->  {3:000}  {4:000}  {5:000}\n",
                            yaw, pitch, roll,
                            yy, pp, rr
                            ));
                    }

                    // debug print (rotation problem)
                    if (isProblem && printProblems)
                    {
                        Console.WriteLine(String.Format(
                            "Yaw Pitch Roll:  {0:000}  {1:000}  {2:000}\n" +
                            "            -->  {3:000}  {4:000}  {5:000}\n",
                            yaw, pitch, roll,
                            yy, pp, rr
                            ));
                    }


                   
                    //if (isDifferent)
                    //    Console.WriteLine("                                -->  DIFFERENT !!");


                    //Vector3 yprNew = EulerAngles.GetAsDegrees_3(quat);
                    //Single yawOg  = EulerAngles.GetYawAngle(ref pubVar, ref quat, Mogre.Math.AngleUnit.AU_DEGREE, EulerAngles.YawConvention.Ogre);
                    //Single yawNED = EulerAngles.GetYawAngle(ref pubVar, ref quat, Mogre.Math.AngleUnit.AU_DEGREE, EulerAngles.YawConvention.NED);

                } // for roll

            } // for pitch

            //Console.Write(".");
            if ((yaw % 10) == 0)
                Console.WriteLine("Current calculation state in yaw loop: " + yaw); // show current state of calculation

            if (equalCounter % 10000 == 0)
                System.Windows.Forms.Application.DoEvents();

        } // for yaw

        Console.WriteLine("\n\nCalculation ready.\n");
        Console.WriteLine("==================================\n\n");

        Console.WriteLine(String.Format(
            "INPUT TEST CONDITIONS:  \n\n" +
            "Yaw   range from  {0}  to  {1}  \n" +
            "Pitch range from  {2}  to  {3}  \n" +
            "Roll  range from  {4}  to  {5}  \n" +
            "Step size:  {6} \n\n",
            yawStart, yawEnd, pitchStart, pitchEnd, rollStart, rollEnd, stepSize
            ));


        Console.WriteLine("RESULTS: \n");

        Console.Write("co-domains:\n");
        Console.Write(String.Format("Yaw    {0} .. {1} \n", ymin, ymax));
        Console.Write(String.Format("Pitch  {0} .. {1} \n", pmin, pmax));
        Console.Write(String.Format("Roll   {0} .. {1} \n", rmin, rmax));

        Console.WriteLine();
        Console.WriteLine("Equality counter:    " + equalCounter.ToString());
        Console.WriteLine("Differeces counter:  " + differentCounter.ToString() + "    (Other angle values, but same resulting orientation)");
        Console.WriteLine("Problem counter:     " + problemCounter.ToString() + "    (Unknown test result - possibly wrong)");

        Console.ReadLine();

    }



    /// <summary>check if equal Euler angle values</summary>
    private static Boolean IsEqual(Single val1, Single val2)
    {
        Single test = val1 - val2;
       
        // make positive
        if (test < 0)
            test *= 1;

        if (test < 0.05f)
            return true;

        // special case where  val1 == 180   and   val2 == -180  (second is negative)
        if ((test > 359.9) && (test < 360.1))
            return true;

        return false;
    }



    /// <summary>Simple check, based on quaternion calculation.</summary>
    private static Boolean IsEqualQuaternion(Degree yaw1, Degree pitch1, Degree roll1, Degree yaw2, Degree pitch2, Degree roll2)
    {
        Single pBak = pitch1.ValueDegrees;

        Quaternion q1 = new Quaternion(yaw1, Vector3.UNIT_Y)
                        * new Quaternion(pitch1, Vector3.UNIT_X)
                        * new Quaternion(roll1, Vector3.UNIT_Z);

        Quaternion q2 = new Quaternion(yaw2, Vector3.UNIT_Y)
                        * new Quaternion(pitch2, Vector3.UNIT_X)
                        * new Quaternion(roll2, Vector3.UNIT_Z);

        Boolean possiblyWrong = false;

        // compare
        if (System.Math.Abs(q1.w - q2.w) > 0.1f)
            possiblyWrong = true;
        if (System.Math.Abs(q1.x - q2.x) > 0.1f)
            possiblyWrong = true;
        if (System.Math.Abs(q1.y - q2.y) > 0.1f)
            possiblyWrong = true;
        if (System.Math.Abs(q1.z - q2.z) > 0.1f)
            possiblyWrong = true;

        if (possiblyWrong)
        {
            // Gimbal Lock case
            if (System.Math.Abs(pitch1.ValueDegrees) == 90f &&
                System.Math.Abs(pitch2.ValueDegrees) == 90f)
            {
                Single diff1 = System.Math.Abs(yaw1.ValueDegrees + roll1.ValueDegrees);
                Single diff2 = System.Math.Abs(yaw2.ValueDegrees + roll2.ValueDegrees);

                Single diff11 = System.Math.Abs(yaw1.ValueDegrees - roll1.ValueDegrees);
                Single diff22 = System.Math.Abs(yaw2.ValueDegrees - roll2.ValueDegrees);

                if (System.Math.Abs(diff1 - diff2) < 0.1f  ||
                    (System.Math.Abs(diff11 - diff22) < 0.1f))
                    return true;
                else
                    return false;
            }

            // ... TODO: further checks (compare quaternion divergence) ...

            return false;
        }
       

        return true;
    }



    //private Single AngleBetweenQuaternions(Quaternion q1, Quaternion q2)
    //{

        //// compare rotation divergence - code from
        //// http://www.ogre3d.org/forums/viewtopic.php?t=44704


        //Single lenProduct = q1.XAxis.Length * q2.XAxis.Length;;

        //// Divide by zero check
        //if(lenProduct < 1e-6f)
        //lenProduct = 1e-6f;

        //Single f = dotProduct(dest) / lenProduct;  // ??

        //f = Math::Clamp(f, (Real)-1.0, (Real)1.0);
        //return Math::ACos(f);
           

       


        // ORIGINAL

        //Real lenProduct = length() * dest.length();

        //// Divide by zero check
        //if(lenProduct < 1e-6f)
        //   lenProduct = 1e-6f;

        //Real f = dotProduct(dest) / lenProduct;

        //f = Math::Clamp(f, (Real)-1.0, (Real)1.0);
        //return Math::ACos(f);

    //}

} // Euler struct

} // namespace



Now I want to stop my work on this.
I spent much time. My diploma thesis have a higher priority.
But feedback is still welcome. :wink:
Last edited by Beauty on Wed Jan 25, 2012 2:09 am, edited 2 times in total.
Reason: added "}" to close the namespace (-;
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Tubulii » Mon Jan 23, 2012 6:51 pm

Thank you very much for your effort!
And good luck :D

The VB.Net Code:
Code: Select all
Imports System.Collections.Generic
Imports System.Text
Imports System.Text.RegularExpressions
Imports Mogre
Imports Math = Mogre.Math


Namespace MogreEulerAngles

   ' ABOUT THIS CODE:
   '
   ' Developed by user Kojack in January 2012
   ' Extended by Beauty and Tubulii
   '
   ' Detailed information about usage:
   ' http://www.ogre3d.org/tikiwiki/Euler+Angle+Class
   '
   ' "official place" of C# version:
   ' http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre
   '
   ' For questions and bug reports use this forum topic:
   ' viewtopic.php?f=8&t=29262
   '




   ''' <summary>
   ''' This struct manages rotations by use of Euler Angles.
   ''' The rotation will be applied in the order Yaw-Pitch-Roll, which are related to the axis order Y-X-Z.
   ''' It's fully compatible with Ogre::Matrix3::FromEulerAnglesYXZ and Ogre::Matrix3::ToEulerAnglesYXZ.
   ''' For the Yaw angle the standard anticlockwise right handed rotation is used (as common in Ogre).
   ''' The Yaw-Pitch-Roll ordering is most convenient for upright character controllers and cameras.
   ''' </summary>
   Public Structure Euler

      ''' <summary>Get or set the Yaw angle.</summary>
      Public Property Yaw() As Radian
         Get
            Return mYaw
         End Get
         Set
            mYaw = value
            mChanged = True
         End Set
      End Property


      ''' <summary>Get or set the Pitch angle.</summary>
      Public Property Pitch() As Radian
         Get
            Return mPitch
         End Get
         Set
            mPitch = value
            mChanged = True
         End Set
      End Property


      ''' <summary>Get or set the Roll angle.</summary>
      Public Property Roll() As Radian
         Get
            Return mRoll
         End Get
         Set
            mRoll = value
            mChanged = True
         End Set
      End Property


      ''' <summary>
      ''' Constructor to create a new Euler Angle struct from angle values.
      ''' The rotation will be applied in the order Yaw-Pitch- Roll, which are related to the axis order Y-X-Z.
      ''' </summary>
      Public Sub New(yaw As Radian, pitch As Radian, roll As Radian)
         mYaw = yaw
         mPitch = pitch
         mRoll = roll
         mChanged = True
         mCachedQuaternion = Quaternion.IDENTITY
      End Sub


      ''' <summary>
      ''' Constructor which calculates the Euler Angles from a quaternion.
      ''' </summary>
      Public Sub New(oriantation As Quaternion)
         Dim rotMat As Matrix3
         rotMat = oriantation.ToRotationMatrix()

         ' BUGGY METHOD (NaN return in some cases)
         ' rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);

         ' WORKAROUND
         Dim success As [Boolean]
         Matrix3ToEulerAnglesYXZ(rotMat, mYaw, mPitch, mRoll, success)

         mChanged = True
         mCachedQuaternion = Quaternion.IDENTITY
      End Sub



      ''' <summary>
      ''' Apply a relative yaw. (Adds the angle to the current yaw value)
      ''' </summary>
      ''' <param name="yaw">Yaw value as radian</param>
      Public Sub AddYaw(yaw As Radian)
         mYaw += yaw
         mChanged = True
      End Sub


      ''' <summary>
      ''' Apply a relative pitch. (Adds the angle to the current pitch value)
      ''' </summary>
      ''' <param name="pitch">Pitch value as radian</param>
      Public Sub AddPitch(pitch As Radian)
         mPitch += pitch
         mChanged = True
      End Sub


      ''' <summary>
      ''' Apply a relative roll. (Adds the angle to the current roll value)
      ''' </summary>
      ''' <param name="roll">Roll value as radian</param>
      Public Sub AddRoll(roll As Radian)
         mRoll += roll
         mChanged = True
      End Sub


      ''' <summary>
      ''' Apply a relative yaw. (Adds the angle to the current yaw value)
      ''' </summary>
      ''' <param name="yaw">Yaw value as degree</param>
      Public Sub AddYaw(yaw As Degree)
         mYaw += yaw
         mChanged = True
      End Sub


      ''' <summary>
      ''' Apply a relative pitch. (Adds the angle to the current pitch value)
      ''' </summary>
      ''' <param name="pitch">Pitch value as degree</param>
      Public Sub AddPitch(pitch As Degree)
         mPitch += pitch
         mChanged = True
      End Sub


      ''' <summary>
      ''' Apply a relative roll. (Adds the angle to the current roll value)
      ''' </summary>
      ''' <param name="roll">Roll value as degree</param>
      Public Sub AddRoll(roll As Degree)
         mRoll += roll
         mChanged = True
      End Sub


      ''' <summary>Get a vector pointing forwards. </summary>
      Public ReadOnly Property Forward() As Vector3
         Get
            Return ToQuaternion() * Vector3.NEGATIVE_UNIT_Z
         End Get
      End Property


      ''' <summary>Get a vector pointing to the right.</summary>
      Public ReadOnly Property Right() As Vector3
         Get
            Return ToQuaternion() * Vector3.UNIT_X
         End Get
      End Property


      ''' <summary> Get a vector pointing up.</summary>
      Public ReadOnly Property Up() As Vector3
         Get
            Return ToQuaternion() * Vector3.UNIT_Y
         End Get
      End Property


      ''' <summary>
      ''' Calculate the quaternion of the euler object.
      ''' The result is cached. It will only be recalculated when the component euler angles are changed.
      ''' </summary>
      Public Function ToQuaternion() As Quaternion
         If mChanged Then
            mCachedQuaternion = New Quaternion(mYaw, Vector3.UNIT_Y) * New Quaternion(mPitch, Vector3.UNIT_X) * New Quaternion(mRoll, Vector3.UNIT_Z)
            mChanged = False
         End If
         Return mCachedQuaternion
      End Function


      ''' <summary>
      ''' Return a String with degree values of the axis rotations (human readable style).
      ''' For example "X-axis: 0°   Y-axis: 36°   Z-axis: 90°"
      ''' </summary>
      Public Function ToAxisString() As [String]
         Return [String].Format("X: {0:00}°   Y: {1:00}°   Z: {2:00}°", Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees)
      End Function




      ''' <summary>
      ''' Return a String with degree values in the applied rotation order (human readable style).
      ''' For example "Yaw: 0°   Pitch: 36°   Roll: 90°"
      ''' </summary>
      Public Function ToYawPitchRollString() As [String]
         Return [String].Format("Yaw: {0:00}°   Pitch: {1:00}°   Roll: {2:00}°", Yaw.ValueDegrees, Pitch.ValueDegrees, Roll.ValueDegrees)
      End Function




      ''' <summary>
      ''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators.
      ''' Input order for rotation values:: Yaw, Pitch, Roll (all in degree)
      ''' If success, an Euler struct will be returned.
      ''' If parsing failed, a FormatException will be thrown.
      ''' Example: "111  .99   -66"
      ''' </summary>
      ''' <param name="valueString">String which contains 3 floating point values</param>
      ''' <remarks>
      ''' Multiple and mixed usage of space/tabulator/commas are possible.
      ''' As decimal seperator a dot "." is expected.
      ''' </remarks>
      ''' <returns>Returns an Euler struct or a FormatException</returns>
      Public Shared Function ParseStringYawPitchRoll(valueString As [String]) As Euler
         Dim values As [Single]() = Parse3Params(valueString)

         If values Is Nothing Then
            Throw New FormatException([String].Format("Can't parse floating point values of string '{0}'", valueString))
         End If

         Return New Euler(New Degree(values(0)), New Degree(values(1)), New Degree(values(2)))
      End Function



      ''' <summary>
      ''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
      ''' Input order for rotation values: X-axis, Y-axis, Z-axis (all in degree)
      ''' If success, an Euler struct will be returned.
      ''' If parsing failed, a FormatException will be thrown.
      ''' Example: "111  .99   -66"
      ''' </summary>
      ''' <param name="valueString">String which contains 3 floating point values</param>
      ''' <remarks>
      ''' Multiple and mixed usage of space/tabulator/commas are possible.
      ''' As decimal seperator a dot "." is expected.
      ''' </remarks>
      ''' <returns>Returns an Euler struct or a FormatException</returns>
      Public Shared Function ParseStringAxisXYZ(valueString As [String]) As Euler
         Dim values As [Single]() = Parse3Params(valueString)

         If values Is Nothing Then
            Throw New FormatException([String].Format("Can't parse floating point values of string '{0}'", valueString))
         End If

         Return New Euler(New Degree(values(1)), New Degree(values(0)), New Degree(values(2)))
      End Function



      ''' <summary>
      ''' Try to parse 3 floating point values from a string, which are seperated by spaces or tabulators or comma.
      ''' If parsing failed, null will be returned.
      ''' Example: "111  .99   -66"
      ''' </summary>
      ''' <param name="valueString">String which contains 3 floating point values</param>
      ''' <remarks>
      ''' Multiple and mixed usage of space/tabulator/commas are possible.
      ''' As decimal seperator a dot "." is expected.
      ''' </remarks>
      ''' <returns>Returns 3 Single values or null</returns>
      Private Shared Function Parse3Params(valueString As [String]) As [Single]()
         ' Some Regex explanation:
         '
         ' The "@" prefix in front of the String means:
         '         Backslash are processed as Text instead of special symbols.
         '         Advantage: Just write "\" instead of "\\" for each backslash
         '
         ' "^" at first position means:  No text is allowed before
         ' "$" at the end means:         No text is allowed after that
         '
         ' Floating point values are matched
         '         Expression: "-?\d*\.?\d+"
         '         Examples:   "111",  "0.111",  ".99",  "-66"
         '
         ' Seperator can be tabs or spaces or commas (at least one symbol; mixing is possible)
         '         Expression: "[, \t]+"


         Dim val As [String] = "[-\d\.]+"
         ' simplified (faster) floating point pattern (exact pattern would be @"-?\d*\.?\d+" )
         Dim sep As [String] = "[, \t]+"
         ' seperator pattern
         ' build target pattern
         Dim searchPattern As [String] = "^(" & val & ")" & sep & "(" & val & ")" & sep & "(" & val & ")$"

         Dim match As Match = Regex.Match(valueString, searchPattern)

         Try
            If match.Success Then
               ' Force to parse "." as decimal char.  (Can be different with other culture settings. E.g. German culture expect "," instad of ".")
               Dim englishCulture As New System.Globalization.CultureInfo("en-US")

               Dim result As [Single]() = New [Single](2) {}
               result(0) = Convert.ToSingle(match.Groups(1).Value, englishCulture)
               result(1) = Convert.ToSingle(match.Groups(2).Value, englishCulture)
               result(2) = Convert.ToSingle(match.Groups(3).Value, englishCulture)
               Return result
            Else
               Return Nothing
            End If
         Catch generatedExceptionName As FormatException
            Return Nothing
         Catch generatedExceptionName As OverflowException
            Return Nothing
         End Try

      End Function
      ' Parse3Params()



      ''' <summary>
      ''' Return the Euler rotation state as quaternion.
      ''' </summary>
      ''' <param name="e">Euler Angle state</param>
      ''' <returns>Rotation state as Quaternion</returns>
      Public Shared Widening Operator CType(e As Euler) As Quaternion
         Return e.ToQuaternion()
      End Operator



      ''' <summary>
      ''' Set the yaw and pitch to face in the given direction.
      ''' The direction doesn't need to be normalised.
      ''' Roll is always unaffected.
      ''' </summary>
      ''' <param name="directionVector">Vector which points to the wanted direction</param>
      ''' <param name="setYaw">if false, the yaw isn't changed.</param>
      ''' <param name="setPitch">if false, the pitch isn't changed.</param>
      Public Sub SetDirection(directionVector As Vector3, setYaw As [Boolean], setPitch As [Boolean])
         Dim d As Vector3 = directionVector.NormalisedCopy
         If setPitch Then
            mPitch = Math.ASin(d.y)
         End If
         If setYaw Then
            mYaw = Math.ATan2(-d.x, -d.z)
         End If
         '+Math.PI/2.0;
         mChanged = setYaw OrElse setPitch
      End Sub



      ''' <summary>
      ''' Normalise the selected rotations to be within the +/-180 degree range.
      ''' The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees,
      ''' and -190 degrees becomes 170.
      ''' By the parameters it's possible to choose which angles should be normalised.
      ''' </summary>
      ''' <param name="normYaw">If true, the angle will be normalised.</param>
      ''' <param name="normPitch">If true, the angle will be normalised.</param>
      ''' <param name="normRoll">If true, the angle will be normalised.</param>
      ''' <remarks></remarks>
      Public Sub Normalise(normYaw As [Boolean], normPitch As [Boolean], normRoll As [Boolean])
         If normYaw Then
            Dim yaw As [Single] = mYaw.ValueRadians
            If yaw < -Math.PI Then
               yaw = CType(System.Math.IEEERemainder(yaw, Math.PI * 2.0), [Single])
               If yaw < -Math.PI Then
                  yaw += Math.PI * 2F
               End If
               mYaw = yaw
               mChanged = True
            ElseIf yaw > Math.PI Then
               yaw = CType(System.Math.IEEERemainder(yaw, Math.PI * 2F), [Single])
               If yaw > Math.PI Then
                  yaw -= Math.PI * 2F
               End If
               mYaw = yaw
               mChanged = True
            End If
         End If

         If normPitch Then
            Dim pitch As [Single] = mPitch.ValueRadians
            If pitch < -Math.PI Then
               pitch = CType(System.Math.IEEERemainder(pitch, Math.PI * 2F), [Single])
               If pitch < -Math.PI Then
                  pitch += Math.PI * 2F
               End If
               mPitch = pitch
               mChanged = True

               If [Single].IsNaN(mPitch.ValueDegrees) Then
                  ' DEBUGGING
                  ' add breakpoint here
               End If
            ElseIf pitch > Math.PI Then
               pitch = CType(System.Math.IEEERemainder(pitch, Math.PI * 2F), [Single])
               If pitch > Math.PI Then
                  pitch -= Math.PI * 2F
               End If
               mPitch = pitch
               mChanged = True

               If [Single].IsNaN(mPitch.ValueDegrees) Then
                  ' DEBUGGING
                  ' add breakpoint here
               End If
            End If
         End If

         If normRoll Then
            Dim roll As [Single] = mRoll.ValueRadians
            If roll < -Math.PI Then
               roll = CType(System.Math.IEEERemainder(roll, Math.PI * 2F), [Single])
               If roll < -Math.PI Then
                  roll += Math.PI * 2F
               End If
               mRoll = roll
               mChanged = True
            ElseIf roll > Math.PI Then
               roll = CType(System.Math.IEEERemainder(roll, Math.PI * 2F), [Single])
               If roll > Math.PI Then
                  roll -= Math.PI * 2F
               End If
               mRoll = roll
               mChanged = True
            End If
         End If
      End Sub
      ' Normalise()



      ''' <summary>
      ''' Return the relative euler angles required to rotate from the current forward direction to the specified direction vector.
      ''' The result euler can then be added to the current euler to immediately face dir.
      ''' Rotation is found to face the correct direction. For example, when false a yaw of 1000 degrees and a dir of
      ''' (0,0,-1) will return a -1000 degree yaw. When true, the same yaw and dir would give 80 degrees (1080 degrees faces
      ''' the same way as (0,0,-1).
      ''' The rotation won't flip upside down then roll instead of a 180 degree yaw.
      ''' </summary>
      ''' <param name="direction">...TODO...</param>
      ''' <param name="shortest">If false, the full value of each angle is used. If true, the angles are normalised and the shortest rotation is found to face the correct direction.</param>
      ''' <param name="setYaw">If true the angles are calculated. If false, the angle is set to 0. </param>
      ''' <param name="setPitch">If true the angles are calculated. If false, the angle is set to 0. </param>
      Public Function GetRotationTo(direction As Vector3, setYaw As [Boolean], setPitch As [Boolean], shortest As [Boolean]) As Euler
         Dim t1 As Euler = Euler.IDENTITY
         Dim t2 As Euler
         t1.SetDirection(direction, setYaw, setPitch)
         t2 = t1 - Me
         If shortest AndAlso setYaw Then
            t2.Normalise(True, True, True)
         End If
         Return t2
      End Function



      ''' <summary>
      ''' Clamp the yaw angle to a range of +/-limit.
      ''' </summary>
      ''' <param name="limit">Wanted co-domain for the Yaw angle.</param>
      Public Sub LimitYaw(limit As Radian)
         If mYaw > limit Then
            mYaw = limit
            mChanged = True
         ElseIf mYaw < -limit Then
            mYaw = -limit
            mChanged = True
         End If
      End Sub



      ''' <summary>
      ''' Clamp the pitch angle to a range of +/-limit.
      ''' </summary>
      ''' <param name="limit">Wanted co-domain for the Pitch angle.</param>
      Public Sub LimitPitch(limit As Radian)
         If mPitch > limit Then
            mPitch = limit
            mChanged = True
         ElseIf mPitch < -limit Then
            mPitch = -limit
            mChanged = True
         End If
      End Sub



      ''' <summary>
      ''' Clamp the roll angle to a range of +/-limit.
      ''' </summary>
      ''' <param name="limit">Wanted co-domain for the Roll angle.</param>
      Public Sub LimitRoll(limit As Radian)
         If mRoll > limit Then
            mRoll = limit
            mChanged = True
         ElseIf mRoll < -limit Then
            mRoll = -limit
            mChanged = True
         End If
      End Sub




      ''' <summary>
      ''' Port of method <c>Matrix3.ToEulerAnglesYXZ()</c>, from MogreMatrix3.cpp as a workaround for a bug in the Math class.
      ''' (The bug was fixed, but is not present in common used binary files.)
      ''' </summary>
      ''' <param name="matrix">Rotation matrix</param>
      ''' <param name="success">This COULD BE a flag for "success", but it's not documented in the original code.</param>
      Public Shared Sub Matrix3ToEulerAnglesYXZ(matrix As Matrix3, ByRef rfYAngle As Radian, ByRef rfPAngle As Radian, ByRef rfRAngle As Radian, ByRef success As [Boolean])
         rfPAngle = Mogre.Math.ASin(-matrix.m12)

         If rfPAngle < New Radian(Math.HALF_PI) Then
            If rfPAngle > New Radian(-Math.HALF_PI) Then
               rfYAngle = Math.ATan2(matrix.m02, matrix.m22)
               rfRAngle = Math.ATan2(matrix.m10, matrix.m11)
               success = True
               Return
            Else
               ' WARNING.  Not a unique solution.
               Dim fRmY As Radian = Math.ATan2(-matrix.m01, matrix.m00)
               rfRAngle = New Radian(0F)
               ' any angle works
               rfYAngle = rfRAngle - fRmY
               success = False
               Return
            End If
         Else
            ' WARNING.  Not a unique solution.
            Dim fRpY As Radian = Math.ATan2(-matrix.m01, matrix.m00)
            rfRAngle = New Radian(0F)
            ' any angle works
            rfYAngle = fRpY - rfRAngle
            success = False
            Return
         End If



         ' "Original" CODE FROM CLASS Matrix3.ToEulerAnglesYXZ()

         'rfPAngle = Mogre::Math::ASin(-m12);
         'if ( rfPAngle < Radian(Math::HALF_PI) )
         '{
         '    if ( rfPAngle > Radian(-Math::HALF_PI) )
         '    {
         '        rfYAngle = System::Math::Atan2(m02,m22);
         '        rfRAngle = System::Math::Atan2(m10,m11);
         '        return true;
         '    }
         '    else
         '    {
         '        // WARNING.  Not a unique solution.
         '        Radian fRmY = System::Math::Atan2(-m01,m00);
         '        rfRAngle = Radian(0.0);  // any angle works
         '        rfYAngle = rfRAngle - fRmY;
         '        return false;
         '    }
         '}
         'else
         '{
         '    // WARNING.  Not a unique solution.
         '    Radian fRpY = System::Math::Atan2(-m01,m00);
         '    rfRAngle = Radian(0.0);  // any angle works
         '    rfYAngle = fRpY - rfRAngle;
         '    return false;
         '}


      End Sub
      ' Matrix3ToEulerAnglesYXZ()







      ''' <summary>
      ''' Add two euler objects.
      ''' </summary>
      ''' <returns>Calculation result</returns>
      Public Shared Operator +(lhs As Euler, rhs As Euler) As Euler
         Return New Euler(lhs.Yaw + rhs.Yaw, lhs.Pitch + rhs.Pitch, lhs.Roll + rhs.Roll)
      End Operator


      ''' <summary>
      ''' Subtract two euler objects. This finds the difference as relative angles.
      ''' </summary>
      ''' <returns>Calculation result</returns>
      Public Shared Operator -(lhs As Euler, rhs As Euler) As Euler
         Return New Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll)
      End Operator


      ''' <summary>
      ''' Interpolate each euler angle by the given factor.
      ''' (Each angle will be multiplied with the factor.)
      ''' </summary>
      ''' <returns>Calculation result</returns>
      Public Shared Operator *(lhs As Euler, factor As [Single]) As Euler
         Return New Euler(lhs.Yaw * factor, lhs.Pitch * factor, lhs.Roll * factor)
      End Operator


      ''' <summary>
      ''' Interpolate the euler angles by lhs.
      ''' (Each angle will be multiplied with the factor.)
      ''' </summary>
      ''' <returns>Calculation result</returns>
      Public Shared Operator *(factor As [Single], rhs As Euler) As Euler
         Return New Euler(factor * rhs.Yaw, factor * rhs.Pitch, factor * rhs.Roll)
      End Operator


      ''' <summary>
      ''' Apply the euler rotation to the vector rhs.
      ''' The calculation is equal to: quaternion*vector
      ''' </summary>
      ''' <returns>Calculation result</returns>
      Public Shared Operator *(lhs As Euler, rhs As Vector3) As Vector3
         Return lhs.ToQuaternion() * rhs
      End Operator


      ''' <summary>Base settings (all angles are 0)</summary>
      Public Shared IDENTITY As New Euler(New Radian(0), New Radian(0), New Radian(0))

      ''' <summary>Rotation around the Y axis.</summary>
      Private mYaw As Radian

      ''' <summary>Rotation around the X axis.</summary>
      Private mPitch As Radian

      ''' <summary>Rotation around the Z axis.</summary>
      Private mRoll As Radian

      ''' <summary>Is the cached quaternion out of date?</summary>
      Private mChanged As Boolean

      ''' <summary>Cached quaternion equivalent of this euler object.</summary>
      Private mCachedQuaternion As Quaternion

   End Structure
   ' struct Euler
End Namespace

And there is missing "}" in the last line of the c# code ;)
Last edited by Beauty on Mon Jan 23, 2012 11:49 pm, edited 1 time in total.
Reason: renamed namespace name
User avatar
Tubulii
Goblin
 
Posts: 222
Kudos: 15
Joined: 24 Jan 2010
Location: Germany

Re: Euler angles - current bugs + improved Euler Angle Class

Postby Beauty » Tue Jan 24, 2012 1:06 am

@smiley80
Thanks for your Mercurial instructions.

@Kojack
Thanks for sharing your specialist knowledge.

@All
Thanks to all, who are following this topic and took time to read my long posts.

zarifus wrote:binaries [...] have been built with the changes you made according to the changeset

Nice to know.

Tubulii wrote:And there is missing "}" in the last line of the c# code ;)

Oh, I killed the namespace :mrgreen:

If there is no bug report in the next time, I will update the code in the wiki.
Help to add information to the wiki. Also tiny edits will let it grow ... :idea:
Add your country to your profile ... it's interesting to know from where of the world you are.
IRC chat ... Mogre: irc://freenode/#mogre ... Ogre: irc://freenode/#ogre3d
User avatar
Beauty
OGRE Community Helper
 
Posts: 1598
Kudos: 35
Joined: 09 May 2007
Location: Germany

PreviousNext

Return to MOGRE

Who is online

Users browsing this forum: No registered users and 1 guest