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 » Tue Jan 17, 2012 5:32 pm

Kojack, I also added a notice in the Axiom forum.
http://axiomengine.sourceforge.net/foru ... f=1&t=1299
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 kojack » Tue Jan 17, 2012 6:38 pm

I haven't touched c# for around 6 months. 99% of my time is c++. My brain needs time to break all my c++ habbits. Plus it's 3:20am wednesday and I've been awake since 5pm monday (coding and gaming), so I like to put disclaimers on code that was developed entirely in that time period. :)

In your code is the line sceneNode.Orientation = e;
So I suppose that e is a quaternion (because SceneNode.Orientation is a quaternion property).

As a result e.AddYaw(...); should be a member of the Quaternion class.

And the setter of property e.Pitch should be wrapped to a Quaternion::setPitch() function in Ogre.
(But in the current Quaternion class documentation I don't find a setter for pitch. So I supposed it's an other missing member.)

Nope, e isn't a quaternion, it's a euler. That's the feature I've mentioned a few times (but probably not clearly), the euler struct has an overloaded implicit cast operator (in both c++ and c#) so any time you try to use a euler when ogre really wants a quaternion, it will actually calculate an equivalent quaternion (using ToQuaternion()) and pass that instead.
User avatar
kojack
Gnoblar
 
Posts: 17
Kudos: 2
Joined: 10 Feb 2006

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

Postby Beauty » Tue Jan 17, 2012 6:58 pm

kojack wrote:Here's the C# version of the new euler class: http://www.ogre3d.org/tikiwiki/Euler+Angle+Class+Mogre


I made additions to the wiki page.
Also I started to convert your plain comment lines to "official C# documentation style". Then Visual Studio shows a description text of the members on-the-fly at coding.
When I'm ready I will upload it to the wiki.

VS shows a conflict between System.Math and Mogre.Math.
There are some calculations in your code with explicide usage of System.Math. So I suppose the others should use Mogre.Math.

To the namespace usage list I added this line.
If the usage of Mogre.Math as default is wrong, please tell me.
Code: Select all
using Math = Mogre.Math;
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 » Tue Jan 17, 2012 7:37 pm

Beauty wrote:
Kojack wrote:Which members?

In your code is the line sceneNode.Orientation = e;
So I suppose that e is a quaternion (because SceneNode.Orientation is a quaternion property).

As a result e.AddYaw(...); should be a member of the Quaternion class.

And the setter of property e.Pitch should be wrapped to a Quaternion::setPitch() function in Ogre.
(But in the current Quaternion class documentation I don't find a setter for pitch. So I supposed it's an other missing member.)

This is the way how I understand your code snippets. Am I wrong?


I think Kojack overloaded the CType operator (Vb.Net, "implicit operator" in C#) -> converts an euler into a quaternion
SceneNode.Orientation = e <- e is an euler and it's converted to a quaternion

Edit: Oh, Kojack answered it already :wink:

I converted the Kojack's euler class to VB.Net, added comments (put them in valid xml-comments), added constructor for a quaternion
and added ToDisplayString and ParseDisplayString
e.g. ToDisplayString returns "X: 0° Y: 36°Z: 90°"
ParseDisplayString simply parses such a string into a valid euler class

Code: Select all
Public Structure Euler
    Public Property Yaw() As Radian
        Get
            Return mYaw
        End Get
        Set(ByVal value As Radian)
            mYaw = value
            mChanged = True
        End Set
    End Property
    Public Property Pitch() As Radian
        Get
            Return mPitch
        End Get
        Set(ByVal value As Radian)
            mPitch = value
            mChanged = True
        End Set
    End Property
    Public Property Roll() As Radian
        Get
            Return mRoll
        End Get
        Set(ByVal value As Radian)
            mRoll = value
            mChanged = True
        End Set
    End Property

    Public Sub New(ByVal yaw As Radian, ByVal pitch As Radian, ByVal roll As Radian)
        mYaw = yaw
        mPitch = pitch
        mRoll = roll
        mChanged = True
        mCachedQuaternion = Quaternion.IDENTITY
    End Sub

    Public Sub New(ByVal Oriantation As Quaternion)
        MyClass.New(Oriantation.Yaw, Oriantation.Pitch, Oriantation.Roll)
    End Sub
    ''' <summary>
    ''' Apply a relative yaw. (Adds angle to current yaw)
    ''' </summary>
    ''' <param name="y"></param>
    ''' <remarks></remarks>
    Public Sub AddYaw(ByVal y As Radian)
        mYaw += y
        mChanged = True
    End Sub

    ''' <summary>
    ''' Apply a relative pitch. (Adds angle to current pitch)
    ''' </summary>
    ''' <param name="p"></param>
    ''' <remarks></remarks>
    Public Sub AddPitch(ByVal p As Radian)
        mPitch += p
        mChanged = True
    End Sub

    ''' <summary>
    ''' Apply a relative roll. (Adds angle to current roll)
    ''' </summary>
    ''' <param name="r"></param>
    ''' <remarks></remarks>
    Public Sub AddRoll(ByVal r As Radian)
        mRoll += r
        mChanged = True
    End Sub

    ''' <summary>
    ''' Get a vector pointing forwards.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    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>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Right() As Vector3
        Get
            Return ToQuaternion() * Vector3.UNIT_X
        End Get
    End Property

    ''' <summary>
    ''' Get a vector pointing up.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    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 is only recalculated when the component euler angles are changed.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    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
    Public Function ToDisplayString() As String
        Return String.Format("X: {0:00}° Y: {1:00}° Z: {2:00}°", Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees)
    End Function
    Public Shared Function ParseDisplayString(ByVal str As String) As Euler
        Dim data = str.Split(" ")
        If data.Length = 6 Then
            Dim roll = Single.Parse(data(5))
            Dim pitch = Single.Parse(data(1))
            Dim yaw = Single.Parse(data(3))
            Return New Euler(New Degree(yaw), New Degree(pitch), New Degree(roll))
        Else
            Return Nothing
        End If
    End Function

    Public Shared Widening Operator CType(ByVal 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="v"></param>
    ''' <param name="setYaw">if false, the yaw isn't changed.</param>
    ''' <param name="setPitch">if false, the pitch isn't changed.</param>
    ''' <remarks></remarks>
    Public Sub SetDirection(ByVal v As Vector3, ByVal setYaw As Boolean, ByVal setPitch As Boolean)
        Dim d As Vector3 = v.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.
    ''' </summary>
    ''' <param name="normYaw">only angles set to true are normalised.</param>
    ''' <param name="normPitch">only angles set to true are normalised.</param>
    ''' <param name="normRoll">only angles set to true are normalised.</param>
    ''' <remarks></remarks>
    Public Sub Normalise(ByVal normYaw As Boolean, ByVal normPitch As Boolean, ByVal normRoll As Boolean)
        If normYaw Then
            Dim yaw As Single = mYaw.ValueRadians
            If yaw < -Math.PI Then
                yaw = CSng(System.Math.IEEERemainder(yaw, Math.PI * 2.0))
                If yaw < -Math.PI Then
                    yaw += Math.PI * 2.0F
                End If
                mYaw = yaw
                mChanged = True
            ElseIf yaw > Math.PI Then
                yaw = CSng(System.Math.IEEERemainder(yaw, Math.PI * 2.0F))
                If yaw > Math.PI Then
                    yaw -= Math.PI * 2.0F
                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 = CSng(System.Math.IEEERemainder(pitch, Math.PI * 2.0F))
                If pitch < -Math.PI Then
                    pitch += Math.PI * 2.0F
                End If
                mPitch = pitch
                mChanged = True
            ElseIf pitch > Math.PI Then
                pitch = CSng(System.Math.IEEERemainder(pitch, Math.PI * 2.0F))
                If pitch > Math.PI Then
                    pitch -= Math.PI * 2.0F
                End If
                mPitch = pitch
                mChanged = True
            End If
        End If
        If normRoll Then
            Dim roll As Single = mRoll.ValueRadians
            If roll < -Math.PI Then
                roll = CSng(System.Math.IEEERemainder(roll, Math.PI * 2.0F))
                If roll < -Math.PI Then
                    roll += Math.PI * 2.0F
                End If
                mRoll = roll
                mChanged = True
            ElseIf roll > Math.PI Then
                roll = CSng(System.Math.IEEERemainder(roll, Math.PI * 2.0F))
                If roll > Math.PI Then
                    roll -= Math.PI * 2.0F
                End If
                mRoll = roll
                mChanged = True
            End If
        End If
    End Sub

    ''' <summary>
    ''' Return the relative euler angles required to rotate from the current forward direction to the specified dir vector.
    ''' The result euler can then be added to the current euler to immediately face dir.
    ''' </summary>
    ''' <param name="dir"></param>
    ''' <param name="setYaw">only the angles set to true are calculated. If false, the angle is set to 0.</param>
    ''' <param name="setPitch">only the angles set to true are calculated. If false, the angle is set to 0.</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. 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.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetRotationTo(ByVal dir As Vector3, ByVal setYaw As Boolean, ByVal setPitch As Boolean, ByVal shortest As Boolean) As Euler
        Dim t1 As Euler = Euler.IDENTITY
        Dim t2 As Euler
        t1.SetDirection(dir, 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"></param>
    ''' <remarks></remarks>
    Public Sub LimitYaw(ByVal 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"></param>
    ''' <remarks></remarks>
    Public Sub LimitPitch(ByVal 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"></param>
    ''' <remarks></remarks>
    Public Sub LimitRoll(ByVal limit As Radian)
        If mRoll > limit Then
            mRoll = limit
            mChanged = True
        ElseIf mRoll < -limit Then
            mRoll = -limit
            mChanged = True
        End If
    End Sub

    ''' <summary>
    '''  Add two euler objects.
    ''' </summary>
    ''' <param name="lhs"></param>
    ''' <param name="rhs"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Operator +(ByVal lhs As Euler, ByVal 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>
    ''' <param name="lhs"></param>
    ''' <param name="rhs"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Operator -(ByVal lhs As Euler, ByVal rhs As Euler) As Euler
        Return New Euler(lhs.Yaw - rhs.Yaw, lhs.Pitch - rhs.Pitch, lhs.Roll - rhs.Roll)
    End Operator

    ''' <summary>
    ''' Interpolate the euler angles by rhs.
    ''' </summary>
    ''' <param name="lhs"></param>
    ''' <param name="rhs"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Operator *(ByVal lhs As Euler, ByVal rhs As Single) As Euler
        Return New Euler(lhs.Yaw * rhs, lhs.Pitch * rhs, lhs.Roll * rhs)
    End Operator

    ''' <summary>
    '''  Interpolate the euler angles by lhs.
    ''' </summary>
    ''' <param name="lhs"></param>
    ''' <param name="rhs"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Operator *(ByVal lhs As Single, ByVal rhs As Euler) As Euler
        Return New Euler(lhs * rhs.Yaw, lhs * rhs.Pitch, lhs * rhs.Roll)
    End Operator

    ''' <summary>
    ''' Apply the euler rotation to the vector rhs.
    ''' </summary>
    ''' <param name="lhs"></param>
    ''' <param name="rhs"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Operator *(ByVal lhs As Euler, ByVal rhs As Vector3) As Vector3
        Return lhs.ToQuaternion() * rhs
    End Operator

    Public Shared ReadOnly IDENTITY As New Euler(New Radian(0), New Radian(0), New Radian(0))

    Private mYaw As Radian
    ' Rotation around the Y axis.
    Private mPitch As Radian
    ' Rotation around the X axis.
    Private mRoll As Radian
    ' Rotation around the Z axis.
    Private mChanged As Boolean
    ' Is the cached quaternion out of date?
    Private mCachedQuaternion As Quaternion
    ' Cached quaternion equivalent of this euler object.
End Structure

And the C# version (port):
Code: Select all
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
public struct Euler
{
   public Radian Yaw {
      get { return mYaw; }
      set {
         mYaw = value;
         mChanged = true;
      }
   }
   public Radian Pitch {
      get { return mPitch; }
      set {
         mPitch = value;
         mChanged = true;
      }
   }
   public Radian Roll {
      get { return mRoll; }
      set {
         mRoll = value;
         mChanged = true;
      }
   }

   public Euler(Radian yaw, Radian pitch, Radian roll)
   {
      mYaw = yaw;
      mPitch = pitch;
      mRoll = roll;
      mChanged = true;
      mCachedQuaternion = Quaternion.IDENTITY;
   }

   public Euler(Quaternion Oriantation) : this(Oriantation.Yaw, Oriantation.Pitch, Oriantation.Roll)
   {
   }
   /// <summary>
   /// Apply a relative yaw. (Adds angle to current yaw)
   /// </summary>
   /// <param name="y"></param>
   /// <remarks></remarks>
   public void AddYaw(Radian y)
   {
      mYaw += y;
      mChanged = true;
   }

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

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

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

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

   /// <summary>
   /// Get a vector pointing up.
   /// </summary>
   /// <value></value>
   /// <returns></returns>
   /// <remarks></remarks>
   public Vector3 Up {
      get { return ToQuaternion() * Vector3.UNIT_Y; }
   }


   /// <summary>
   /// Calculate the quaternion of the euler object.
   /// The result is cached, it is only recalculated when the component euler angles are changed.
   /// </summary>
   /// <returns></returns>
   /// <remarks></remarks>
   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;
   }
   public string ToDisplayString()
   {
      return string.Format("X: {0:00}° Y: {1:00}° Z: {2:00}°", Pitch.ValueDegrees, Yaw.ValueDegrees, Roll.ValueDegrees);
   }
   public static Euler ParseDisplayString(string str)
   {
      dynamic data = str.Split(" ");
      if (data.Length == 6) {
         dynamic roll = float.Parse(data(5));
         dynamic pitch = float.Parse(data(1));
         dynamic yaw = float.Parse(data(3));
         return new Euler(new Degree(yaw), new Degree(pitch), new Degree(roll));
      } else {
         return null;
      }
   }

   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="v"></param>
   /// <param name="setYaw">if false, the yaw isn't changed.</param>
   /// <param name="setPitch">if false, the pitch isn't changed.</param>
   /// <remarks></remarks>
   public void SetDirection(Vector3 v, bool setYaw, bool setPitch)
   {
      Vector3 d = v.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.
   /// </summary>
   /// <param name="normYaw">only angles set to true are normalised.</param>
   /// <param name="normPitch">only angles set to true are normalised.</param>
   /// <param name="normRoll">only angles set to true are normalised.</param>
   /// <remarks></remarks>
   public void Normalise(bool normYaw, bool normPitch, bool normRoll)
   {
      if (normYaw) {
         float yaw = mYaw.ValueRadians;
         if (yaw < -Math.PI) {
            yaw = Convert.ToSingle(System.Math.IEEERemainder(yaw, Math.PI * 2.0));
            if (yaw < -Math.PI) {
               yaw += Math.PI * 2f;
            }
            mYaw = yaw;
            mChanged = true;
         } else if (yaw > Math.PI) {
            yaw = Convert.ToSingle(System.Math.IEEERemainder(yaw, Math.PI * 2f));
            if (yaw > Math.PI) {
               yaw -= Math.PI * 2f;
            }
            mYaw = yaw;
            mChanged = true;
         }
      }
      if (normPitch) {
         float pitch = mPitch.ValueRadians;
         if (pitch < -Math.PI) {
            pitch = Convert.ToSingle(System.Math.IEEERemainder(pitch, Math.PI * 2f));
            if (pitch < -Math.PI) {
               pitch += Math.PI * 2f;
            }
            mPitch = pitch;
            mChanged = true;
         } else if (pitch > Math.PI) {
            pitch = Convert.ToSingle(System.Math.IEEERemainder(pitch, Math.PI * 2f));
            if (pitch > Math.PI) {
               pitch -= Math.PI * 2f;
            }
            mPitch = pitch;
            mChanged = true;
         }
      }
      if (normRoll) {
         float roll = mRoll.ValueRadians;
         if (roll < -Math.PI) {
            roll = Convert.ToSingle(System.Math.IEEERemainder(roll, Math.PI * 2f));
            if (roll < -Math.PI) {
               roll += Math.PI * 2f;
            }
            mRoll = roll;
            mChanged = true;
         } else if (roll > Math.PI) {
            roll = Convert.ToSingle(System.Math.IEEERemainder(roll, Math.PI * 2f));
            if (roll > Math.PI) {
               roll -= Math.PI * 2f;
            }
            mRoll = roll;
            mChanged = true;
         }
      }
   }

   /// <summary>
   /// Return the relative euler angles required to rotate from the current forward direction to the specified dir vector.
   /// The result euler can then be added to the current euler to immediately face dir.
   /// </summary>
   /// <param name="dir"></param>
   /// <param name="setYaw">only the angles set to true are calculated. If false, the angle is set to 0.</param>
   /// <param name="setPitch">only the angles set to true are calculated. If false, the angle is set to 0.</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. 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.</param>
   /// <returns></returns>
   /// <remarks></remarks>
   public Euler GetRotationTo(Vector3 dir, bool setYaw, bool setPitch, bool shortest)
   {
      Euler t1 = Euler.IDENTITY;
      Euler t2 = default(Euler);
      t1.SetDirection(dir, 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"></param>
   /// <remarks></remarks>
   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"></param>
   /// <remarks></remarks>
   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"></param>
   /// <remarks></remarks>
   public void LimitRoll(Radian limit)
   {
      if (mRoll > limit) {
         mRoll = limit;
         mChanged = true;
      } else if (mRoll < -limit) {
         mRoll = -limit;
         mChanged = true;
      }
   }

   /// <summary>
   ///  Add two euler objects.
   /// </summary>
   /// <param name="lhs"></param>
   /// <param name="rhs"></param>
   /// <returns></returns>
   /// <remarks></remarks>
   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>
   /// <param name="lhs"></param>
   /// <param name="rhs"></param>
   /// <returns></returns>
   /// <remarks></remarks>
   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 the euler angles by rhs.
   /// </summary>
   /// <param name="lhs"></param>
   /// <param name="rhs"></param>
   /// <returns></returns>
   /// <remarks></remarks>
   public static Euler operator *(Euler lhs, float rhs)
   {
      return new Euler(lhs.Yaw * rhs, lhs.Pitch * rhs, lhs.Roll * rhs);
   }

   /// <summary>
   ///  Interpolate the euler angles by lhs.
   /// </summary>
   /// <param name="lhs"></param>
   /// <param name="rhs"></param>
   /// <returns></returns>
   /// <remarks></remarks>
   public static Euler operator *(float lhs, Euler rhs)
   {
      return new Euler(lhs * rhs.Yaw, lhs * rhs.Pitch, lhs * rhs.Roll);
   }

   /// <summary>
   /// Apply the euler rotation to the vector rhs.
   /// </summary>
   /// <param name="lhs"></param>
   /// <param name="rhs"></param>
   /// <returns></returns>
   /// <remarks></remarks>
   public static Vector3 operator *(Euler lhs, Vector3 rhs)
   {
      return lhs.ToQuaternion() * rhs;
   }


   public static readonly Euler IDENTITY = new Euler(new Radian(0), new Radian(0), new Radian(0));
   private Radian mYaw;
   // Rotation around the Y axis.
   private Radian mPitch;
   // Rotation around the X axis.
   private Radian mRoll;
   // Rotation around the Z axis.
   private bool mChanged;
   // Is the cached quaternion out of date?
   private Quaternion mCachedQuaternion;
   // Cached quaternion equivalent of this euler object.
}

For this message the author Tubulii has received kudos
User avatar
Tubulii
Goblin
 
Posts: 222
Kudos: 15
Joined: 24 Jan 2010
Location: Germany

Re: Is there a way to redefine a nodes roll and pitch axis?

Postby Tubulii » Tue Jan 17, 2012 8:15 pm

Beauty wrote:So even if you are no senior hyper developer guru, I think it would be useful when you touch the official repository.
If I understood right, you improved the code of Cygon a little bit.
If you add this to the Mogre repository there would be important benifits, because
* your code is based on Cygons code,
* Cygons code is based on mstoykes terrain/paging branch of the official repository
* and the terrain/paging branch is an improved version of the current Mogre trunk.

Ahm, I think you understood me wrong, I am still using 1.6.5.
Nevertheless I am willing to help but my time is VERY limited (exams). I will take a look at the source code anyway.
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 17, 2012 10:27 pm

I posted again and again. Now after hours I recognized that you replied in the

Kojack wrote:I haven't touched c# for around 6 months. 99% of my time is c++.

Now you know how I feel with C++. My relation is upside down. :lol:


Kojack wrote:the euler struct has an overloaded implicit cast operator

Now I learned something now.
As consequence I should remove the tasks from the Mogre bugtracker again. :oops:


Kojack wrote:That's the feature I've mentioned a few times (but probably not clearly)

1.)
You could create a deticated topic for that. This is more clear than telling it somewhere hidden in a post.

2.)
You could apply the additions in your own repository.
..... Then it's just needed to push the related code to the main repository.
..... It's much less effort for the core code maintainers.
..... This could increade the motivation to say aggree to your improvement.
Or are you also part of the core team?
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 » Tue Jan 17, 2012 11:12 pm

Kojack wrote:it's 3:20am wednesday and I've been awake since 5pm monday (coding and gaming)

I understand. Now your brain only works fine, when you don't need to concentrate. :lol:


Tubulii wrote:I think Kojack overloaded the CType operator (Vb.Net, "implicit operator" in C#)

After reading it twice, I can keep it in mind for shure. :mrgreen:


Tubulii wrote:I converted the Kojack's euler class to VB.Net, added comments (put them in valid xml-comments), added constructor for a quaternion
and added ToDisplayString and ParseDisplayString

As I posted a half hour before your post, I converted the comments to regular XML comment, too.
Tomorrow I want to merge your and my version.

Tubulii wrote:Ahm, I think you understood me wrong.

Oh, you are right. The post was not from you. It was from DirtyHippy.
He built Mogre from scratch. (his post is here).


Tubulii wrote:I am still using 1.6.5.

Oh, it's like my application.

Off topic:
In the past I tested the 1.7 binaries, but had trouble with the related version of the MogreNewt library.
Some months ago I tried the 1.7.3 Cygon builds, which includes MogreNewt, too. With this binaries my application worked.
Cygon updated and built everything from the scratch. Very useful!
But until today I didn't upgrade the "main code" of my application.
Well, I should do.
Benefits for my application would be:
  • Possibility to add my external WPF based tool into my main application (because of Mogre .NET 4.0 support).
    With .NET 3.5 I had the problem, that WPF text is blurry and some wanted WPF features are not supported.
  • I can use a new 1.7 feature, which improves my FPS rate.
    Reason: For a top view map I have a second ViewPort. With Mogre 1.7 I can refresh both viewports with a different speed. (e.g. once per second for the top view map)

Why do you stick with Mogre 1.6?
Did you have problems with 1.7 or one of it's add-ons?


Tubulii wrote:Nevertheless I am willing to help but my time is VERY limited (exams).

It's like me. I have to take care that I don't spend more time for Ogre/Mogre than for my diploma thesis.
Keep it mind that your exams have much higher priority than Mogre stuff.

Tubulii wrote:I will take a look at the source code anyway.

I will collect all needed information. So it's more easy for anybody who wants to do it.
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 » Wed Jan 18, 2012 11:10 am

An interesting post of a parallel topic. (posted here)



Kojack wrote:I'm just talking about general ogre behaviour.

Here's a video showing the issue:

[youtube]-boq7NPi1m8[/youtube]
...... OH SHIT, our Add-ons forum can't embed videos.
...... So LOOK HERE to see it.
Kojack wrote:The right sinbad uses a euler object with keyboard control (he also has head tracking on, but that's not important here). The euler object is used to set his orientation quaternion.
The middle sinbad reads the right sinbad's orientation (as a quaternion), calls getYaw, getPitch and getRoll on it, puts them into a second euler object and uses it as the orientation.
The left sinbad reads the right sinbad's orientation as well, but it converts the orientation quaternion into a matrix, then uses the matrix ToEulerAnglesYXZ to get the yaw, pitch and roll and puts them in a third euler object and then use that for orientation.

As you can see, the left and right ones are identical. The problem is that the rotation order for getYaw, getPitch and getRoll doesn't match my euler code. But matrices let you choose, so I just picked the correct one.
Different combinations can be chosen based on coordinate system (does yaw go around the y or z axis?) and gimbal lock. When the second angle is 90 or -90 degrees, the first and third angles gimbal lock. So in mine if you look up (pitch), the yaw and roll gimbal lock. If looking up is common, a different ordering would move the gimbal lock to a less important angle.

I'm pretty sure this is also why you were getting yaw in the +/-90 range. If you don't want to flip upside down (or sideways, or whatever), the first angle in the euler combination is usually used with a 360 degree range, while the second only has a 180 degree range (so a camera could look around 360 degrees, but usually you don't pitch it until it's upside down to look behind yourself). Ogre's quaternions don't use yaw/pitch/roll order, so yaw isn't the first angle so it isn't considered the primary one you rotate to face a direction.

To bypass the range issue, instead of this:
Code: Select all
Quaternion q = node->getOrientation();
Radian yaw = q.getYaw();
Radian pitch = q.getPitch();
Radian roll = q.getRoll();

you can do this:
Code: Select all
Quaternion q = node->getOrientation();
Radian yaw;
Radian pitch;
Radian roll;
Matrix3 m;
q.ToRotationMatrix(m);
m.ToEulerAnglesYXZ(yaw,pitch,roll);
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 » Wed Jan 18, 2012 3:49 pm

Tubulii wrote:I added constructor for a quaternion


It's a good idea.
The downside:
Your constructor grabbed the euler values from the buggy properties.
Solution:
I replaced it with math calculations, which were proposed by Kojack after I told him our bug problems.

Old:
Code: Select all
public Euler(Quaternion Oriantation) : this(Oriantation.Yaw, Oriantation.Pitch, Oriantation.Roll)
{
}


New:
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);
    mChanged = true;
    mCachedQuaternion = Quaternion.IDENTITY;
}


------------------------------------

Tubulii wrote:added ToDisplayString()


I made two different methods of it:
Code: Select all
/// <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);
}



------------------------------------

Tubulii wrote:added ParseDisplayString()


My Visual Studio doesn't like your code.
Now I replaced it by new methods, based on regular expressions.
  • ParseStringYawPitchRoll()
  • ParseStringAxisXYZ()
  • Parse3Params() ..... helper method

The documentation needed more time than the programming. :evil:
I hope my spend time is useful for somebody.

Your code:
Code: Select all
public static Euler ParseDisplayString(string str)
{
    dynamic data = str.Split(" ");
    if (data.Length == 6)
    {
        dynamic roll = float.Parse(data(5));
        dynamic pitch = float.Parse(data(1));
        dynamic yaw = float.Parse(data(3));
        return new Euler(new Degree(yaw), new Degree(pitch), new Degree(roll));
    }
    else
    {
        return null;
    }
}


Here my code:
Code: Select all
/// <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 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 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 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()




------------------------------------

TODO: Add a description for param direction.
(Currently my brain is tortured so much, that I'm not shure if I understand it correctly.)
Code: Select all
public Euler GetRotationTo(Vector3 direction, bool setYaw, bool setPitch, bool shortest)


Here the current state of the full description:
Code: Select all
ary>
/// 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>
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 » Wed Jan 18, 2012 4:41 pm

Here is my polished code!

After some hours of work I finished the job.
My code contains ideas and extensions of Tubilii. (the differences I listed above)
And it contains descriptions from Kojack, which I found in the forums.

It is NOT TESTED.
If you try it, please give feedback.


Main changes to Kojacks version:
  • Created XML documentation for all members and parameters (including modified and additional text).
  • Renamed some parameter names (e.g. y, p, r,v, lhs) for a better overview
  • new constructor: public Euler(Quaternion oriantation)
  • new: public String ToAxisString()
  • new: public String ToYawPitchRollString()
  • new: public static Euler ParseStringYawPitchRoll(String valueString) ... NOT TESTED
  • new: public static Euler ParseStringAxisXYZ(String valueString) ... NOT TESTED
  • new helper: private static Single[] Parse3Params(String valueString) ... NOT TESTED
  • new: Overloaded versions of AddYaw(), AddPitch(), AddRoll() for parameter type Degree

Descriptions of the new methods you find in the 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();
        rotMat.ToEulerAnglesYXZ(out mYaw, out mPitch, out mRoll);
        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;
         }
         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(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>
    /// 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


} // 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 Beauty » Wed Jan 18, 2012 7:20 pm

I started to perform some tests.

In a loop for many angle combinations I perform this conversion steps:

Code: Select all
EulerAngless --> Quaternion --> EulerClass --> EulerAngles


The quaternion calculation I do by this:
Code: Select all
// node is a SceneNode - I use it as workaround to apply rotations

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;


Kojack, if there is something wrong in that way, please tell me.

For many cases the conversion results seems to be well (in a quick test).
update:
After looking to the test results later, I'm not shure if this impression was correct. I need to do more tests.



Co-domains of the returned values:
Code: Select all
Yaw    -180 .. 180 
Pitch  -90 .. 90        <-- UPDATED
Roll   -180 .. 180


Well, I would prefer a pitch co-domain of -90 .. 90.
For example to get a camera or character pitch.
Perhaps this could be done by additional API calls.

Kojack, was it your intension to avoid negative Pitch values?

update:
I had incorrect start values in my test loops. Negative Pitch and Roll values were not testet.
This was the reason for the strange co-domain of Pitch.




Problems:

Euler.Pitch returns NaN for many (all?) cases when the Quaternion was created by pitch=90 (degree).

Kojack,
is it your intension to return NaN for some cases?
(The buggy properties of Mogre are not touched by your code. So I suppose the source is inside of your calculation process.)


In some NaN cases it also happens that the other angle values are strange, too.
Examples:
Code: Select all
        Yaw Pitch Roll

Before:  030  090  030
After:   000  NaN  000


Code: Select all
        Yaw Pitch Roll

Before:  030  090  150
After:   120  NaN  000


Here is the complete list for all angle combinations with different output (checked in 30 degree steps).
So you see the problematic angle combinations.
Some of them are still correct (as wanted), because roll greater than +/-90 and pitch greater than +/-90 are often not usal in common practice.

Code: Select all
Yaw Pitch Roll:  -180  090  000
            -->  -180  NaN  000

Yaw Pitch Roll:  -180  090  030
            -->  -150  NaN  000

Yaw Pitch Roll:  -180  090  060
            -->  -120  NaN  000

Yaw Pitch Roll:  -180  090  090
            -->  -090  NaN  000

Yaw Pitch Roll:  -180  090  120
            -->  -060  NaN  000

Yaw Pitch Roll:  -180  090  150
            -->  -030  NaN  000

Yaw Pitch Roll:  -180  090  180
            -->  000  NaN  000

Yaw Pitch Roll:  -180  120  000
            -->  000  060  -180

Yaw Pitch Roll:  -180  120  030
            -->  000  060  -150

Yaw Pitch Roll:  -180  120  060
            -->  000  060  -120

Yaw Pitch Roll:  -180  120  090
            -->  000  060  -090

Yaw Pitch Roll:  -180  120  120
            -->  000  060  -060

Yaw Pitch Roll:  -180  120  150
            -->  000  060  -030

Yaw Pitch Roll:  -180  120  180
            -->  000  060  000

Yaw Pitch Roll:  -180  150  000
            -->  000  030  -180

Yaw Pitch Roll:  -180  150  030
            -->  000  030  -150

Yaw Pitch Roll:  -180  150  060
            -->  000  030  -120

Yaw Pitch Roll:  -180  150  090
            -->  000  030  -090

Yaw Pitch Roll:  -180  150  120
            -->  000  030  -060

Yaw Pitch Roll:  -180  150  150
            -->  000  030  -030

Yaw Pitch Roll:  -180  150  180
            -->  000  030  000

Yaw Pitch Roll:  -180  180  000
            -->  000  000  180

Yaw Pitch Roll:  -180  180  030
            -->  000  000  -150

Yaw Pitch Roll:  -180  180  060
            -->  000  000  -120

Yaw Pitch Roll:  -180  180  090
            -->  000  000  -090

Yaw Pitch Roll:  -180  180  120
            -->  000  000  -060

Yaw Pitch Roll:  -180  180  150
            -->  000  000  -030

Yaw Pitch Roll:  -180  180  180
            -->  000  000  000

Yaw Pitch Roll:  -150  090  000
            -->  150  NaN  000

Yaw Pitch Roll:  -150  090  030
            -->  180  NaN  000

Yaw Pitch Roll:  -150  090  060
            -->  -150  NaN  000

Yaw Pitch Roll:  -150  090  090
            -->  -120  NaN  000

Yaw Pitch Roll:  -150  090  120
            -->  -090  NaN  000

Yaw Pitch Roll:  -150  090  150
            -->  -060  090  000

Yaw Pitch Roll:  -150  090  180
            -->  -030  NaN  000

Yaw Pitch Roll:  -150  120  000
            -->  030  060  -180

Yaw Pitch Roll:  -150  120  030
            -->  030  060  -150

Yaw Pitch Roll:  -150  120  060
            -->  030  060  -120

Yaw Pitch Roll:  -150  120  090
            -->  030  060  -090

Yaw Pitch Roll:  -150  120  120
            -->  030  060  -060

Yaw Pitch Roll:  -150  120  150
            -->  030  060  -030

Yaw Pitch Roll:  -150  120  180
            -->  030  060  000

Yaw Pitch Roll:  -150  150  000
            -->  030  030  180

Yaw Pitch Roll:  -150  150  030
            -->  030  030  -150

Yaw Pitch Roll:  -150  150  060
            -->  030  030  -120

Yaw Pitch Roll:  -150  150  090
            -->  030  030  -090

Yaw Pitch Roll:  -150  150  120
            -->  030  030  -060

Yaw Pitch Roll:  -150  150  150
            -->  030  030  -030

Yaw Pitch Roll:  -150  150  180
            -->  030  030  000

Yaw Pitch Roll:  -150  180  000
            -->  030  000  -180

Yaw Pitch Roll:  -150  180  030
            -->  030  000  -150

Yaw Pitch Roll:  -150  180  060
            -->  030  000  -120

Yaw Pitch Roll:  -150  180  090
            -->  030  000  -090

Yaw Pitch Roll:  -150  180  120
            -->  030  000  -060

Yaw Pitch Roll:  -150  180  150
            -->  030  000  -030

Yaw Pitch Roll:  -150  180  180
            -->  030  000  000

Yaw Pitch Roll:  -120  090  000
            -->  120  NaN  000

Yaw Pitch Roll:  -120  090  030
            -->  150  NaN  000

Yaw Pitch Roll:  -120  090  060
            -->  -180  NaN  000

Yaw Pitch Roll:  -120  090  090
            -->  -150  NaN  000

Yaw Pitch Roll:  -120  090  120
            -->  -120  NaN  000

Yaw Pitch Roll:  -120  090  150
            -->  -090  NaN  000

Yaw Pitch Roll:  -120  090  180
            -->  -060  NaN  000

Yaw Pitch Roll:  -120  120  000
            -->  060  060  -180

Yaw Pitch Roll:  -120  120  030
            -->  060  060  -150

Yaw Pitch Roll:  -120  120  060
            -->  060  060  -120

Yaw Pitch Roll:  -120  120  090
            -->  060  060  -090

Yaw Pitch Roll:  -120  120  120
            -->  060  060  -060

Yaw Pitch Roll:  -120  120  150
            -->  060  060  -030

Yaw Pitch Roll:  -120  120  180
            -->  060  060  000

Yaw Pitch Roll:  -120  150  000
            -->  060  030  -180

Yaw Pitch Roll:  -120  150  030
            -->  060  030  -150

Yaw Pitch Roll:  -120  150  060
            -->  060  030  -120

Yaw Pitch Roll:  -120  150  090
            -->  060  030  -090

Yaw Pitch Roll:  -120  150  120
            -->  060  030  -060

Yaw Pitch Roll:  -120  150  150
            -->  060  030  -030

Yaw Pitch Roll:  -120  150  180
            -->  060  030  000

Yaw Pitch Roll:  -120  180  000
            -->  060  000  -180

Yaw Pitch Roll:  -120  180  030
            -->  060  000  -150

Yaw Pitch Roll:  -120  180  060
            -->  060  000  -120

Yaw Pitch Roll:  -120  180  090
            -->  060  000  -090

Yaw Pitch Roll:  -120  180  120
            -->  060  000  -060

Yaw Pitch Roll:  -120  180  150
            -->  060  000  -030

Yaw Pitch Roll:  -120  180  180
            -->  060  000  000

Yaw Pitch Roll:  -090  090  000
            -->  090  NaN  000

Yaw Pitch Roll:  -090  090  030
            -->  120  NaN  000

Yaw Pitch Roll:  -090  090  060
            -->  150  NaN  000

Yaw Pitch Roll:  -090  090  090
            -->  180  NaN  000

Yaw Pitch Roll:  -090  090  120
            -->  -150  NaN  000

Yaw Pitch Roll:  -090  090  150
            -->  -120  NaN  000

Yaw Pitch Roll:  -090  090  180
            -->  -090  NaN  000

Yaw Pitch Roll:  -090  120  000
            -->  090  060  180

Yaw Pitch Roll:  -090  120  030
            -->  090  060  -150

Yaw Pitch Roll:  -090  120  060
            -->  090  060  -120

Yaw Pitch Roll:  -090  120  090
            -->  090  060  -090

Yaw Pitch Roll:  -090  120  120
            -->  090  060  -060

Yaw Pitch Roll:  -090  120  150
            -->  090  060  -030

Yaw Pitch Roll:  -090  120  180
            -->  090  060  000

Yaw Pitch Roll:  -090  150  000
            -->  090  030  180

Yaw Pitch Roll:  -090  150  030
            -->  090  030  -150

Yaw Pitch Roll:  -090  150  060
            -->  090  030  -120

Yaw Pitch Roll:  -090  150  090
            -->  090  030  -090

Yaw Pitch Roll:  -090  150  120
            -->  090  030  -060

Yaw Pitch Roll:  -090  150  150
            -->  090  030  -030

Yaw Pitch Roll:  -090  150  180
            -->  090  030  000

Yaw Pitch Roll:  -090  180  000
            -->  090  000  180

Yaw Pitch Roll:  -090  180  030
            -->  090  000  -150

Yaw Pitch Roll:  -090  180  060
            -->  090  000  -120

Yaw Pitch Roll:  -090  180  090
            -->  090  000  -090

Yaw Pitch Roll:  -090  180  120
            -->  090  000  -060

Yaw Pitch Roll:  -090  180  150
            -->  090  000  -030

Yaw Pitch Roll:  -090  180  180
            -->  090  000  000

Yaw Pitch Roll:  -060  090  000
            -->  060  NaN  000

Yaw Pitch Roll:  -060  090  030
            -->  090  NaN  000

Yaw Pitch Roll:  -060  090  060
            -->  120  NaN  000

Yaw Pitch Roll:  -060  090  090
            -->  150  NaN  000

Yaw Pitch Roll:  -060  090  120
            -->  -180  NaN  000

Yaw Pitch Roll:  -060  090  150
            -->  -150  NaN  000

Yaw Pitch Roll:  -060  090  180
            -->  -120  NaN  000

Yaw Pitch Roll:  -060  120  000
            -->  120  060  180

Yaw Pitch Roll:  -060  120  030
            -->  120  060  -150

Yaw Pitch Roll:  -060  120  060
            -->  120  060  -120

Yaw Pitch Roll:  -060  120  090
            -->  120  060  -090

Yaw Pitch Roll:  -060  120  120
            -->  120  060  -060

Yaw Pitch Roll:  -060  120  150
            -->  120  060  -030

Yaw Pitch Roll:  -060  120  180
            -->  120  060  000

Yaw Pitch Roll:  -060  150  000
            -->  120  030  180

Yaw Pitch Roll:  -060  150  030
            -->  120  030  -150

Yaw Pitch Roll:  -060  150  060
            -->  120  030  -120

Yaw Pitch Roll:  -060  150  090
            -->  120  030  -090

Yaw Pitch Roll:  -060  150  120
            -->  120  030  -060

Yaw Pitch Roll:  -060  150  150
            -->  120  030  -030

Yaw Pitch Roll:  -060  150  180
            -->  120  030  000

Yaw Pitch Roll:  -060  180  000
            -->  120  000  180

Yaw Pitch Roll:  -060  180  030
            -->  120  000  -150

Yaw Pitch Roll:  -060  180  060
            -->  120  000  -120

Yaw Pitch Roll:  -060  180  090
            -->  120  000  -090

Yaw Pitch Roll:  -060  180  120
            -->  120  000  -060

Yaw Pitch Roll:  -060  180  150
            -->  120  000  -030

Yaw Pitch Roll:  -060  180  180
            -->  120  000  000

Yaw Pitch Roll:  -030  090  000
            -->  030  NaN  000

Yaw Pitch Roll:  -030  090  030
            -->  060  NaN  000

Yaw Pitch Roll:  -030  090  060
            -->  090  NaN  000

Yaw Pitch Roll:  -030  090  090
            -->  120  NaN  000

Yaw Pitch Roll:  -030  090  120
            -->  150  NaN  000

Yaw Pitch Roll:  -030  090  150
            -->  180  NaN  000

Yaw Pitch Roll:  -030  090  180
            -->  -150  NaN  000

Yaw Pitch Roll:  -030  120  000
            -->  150  060  180

Yaw Pitch Roll:  -030  120  030
            -->  150  060  -150

Yaw Pitch Roll:  -030  120  060
            -->  150  060  -120

Yaw Pitch Roll:  -030  120  090
            -->  150  060  -090

Yaw Pitch Roll:  -030  120  120
            -->  150  060  -060

Yaw Pitch Roll:  -030  120  150
            -->  150  060  -030

Yaw Pitch Roll:  -030  120  180
            -->  150  060  000

Yaw Pitch Roll:  -030  150  000
            -->  150  030  180

Yaw Pitch Roll:  -030  150  030
            -->  150  030  -150

Yaw Pitch Roll:  -030  150  060
            -->  150  030  -120

Yaw Pitch Roll:  -030  150  090
            -->  150  030  -090

Yaw Pitch Roll:  -030  150  120
            -->  150  030  -060

Yaw Pitch Roll:  -030  150  150
            -->  150  030  -030

Yaw Pitch Roll:  -030  150  180
            -->  150  030  000

Yaw Pitch Roll:  -030  180  000
            -->  150  000  180

Yaw Pitch Roll:  -030  180  030
            -->  150  000  -150

Yaw Pitch Roll:  -030  180  060
            -->  150  000  -120

Yaw Pitch Roll:  -030  180  090
            -->  150  000  -090

Yaw Pitch Roll:  -030  180  120
            -->  150  000  -060

Yaw Pitch Roll:  -030  180  150
            -->  150  000  -030

Yaw Pitch Roll:  -030  180  180
            -->  150  000  000

Yaw Pitch Roll:  000  090  000
            -->  000  NaN  000

Yaw Pitch Roll:  000  090  030
            -->  030  NaN  000

Yaw Pitch Roll:  000  090  060
            -->  060  NaN  000

Yaw Pitch Roll:  000  090  090
            -->  090  NaN  000

Yaw Pitch Roll:  000  090  120
            -->  120  NaN  000

Yaw Pitch Roll:  000  090  150
            -->  150  NaN  000

Yaw Pitch Roll:  000  090  180
            -->  -180  NaN  000

Yaw Pitch Roll:  000  120  000
            -->  180  060  180

Yaw Pitch Roll:  000  120  090
            -->  180  060  -090

Yaw Pitch Roll:  000  120  120
            -->  180  060  -060

Yaw Pitch Roll:  000  120  150
            -->  180  060  -030

Yaw Pitch Roll:  000  120  180
            -->  180  060  000

Yaw Pitch Roll:  000  150  000
            -->  180  030  180

Yaw Pitch Roll:  000  150  030
            -->  180  030  -150

Yaw Pitch Roll:  000  150  090
            -->  180  030  -090

Yaw Pitch Roll:  000  150  120
            -->  180  030  -060

Yaw Pitch Roll:  000  150  150
            -->  180  030  -030

Yaw Pitch Roll:  000  150  180
            -->  180  030  000

Yaw Pitch Roll:  000  180  000
            -->  180  000  180

Yaw Pitch Roll:  000  180  030
            -->  180  000  -150

Yaw Pitch Roll:  000  180  060
            -->  180  000  -120

Yaw Pitch Roll:  000  180  090
            -->  180  000  -090

Yaw Pitch Roll:  000  180  120
            -->  180  000  -060

Yaw Pitch Roll:  000  180  150
            -->  180  000  -030

Yaw Pitch Roll:  000  180  180
            -->  180  000  000

Yaw Pitch Roll:  030  090  000
            -->  -030  NaN  000

Yaw Pitch Roll:  030  090  030
            -->  000  NaN  000

Yaw Pitch Roll:  030  090  060
            -->  030  NaN  000

Yaw Pitch Roll:  030  090  090
            -->  060  NaN  000

Yaw Pitch Roll:  030  090  120
            -->  090  NaN  000

Yaw Pitch Roll:  030  090  150
            -->  120  NaN  000

Yaw Pitch Roll:  030  090  180
            -->  150  NaN  000

Yaw Pitch Roll:  030  150  000
            -->  -150  030  180

Yaw Pitch Roll:  030  180  000
            -->  -150  000  180

Yaw Pitch Roll:  060  090  000
            -->  -060  NaN  000

Yaw Pitch Roll:  060  090  030
            -->  -030  NaN  000

Yaw Pitch Roll:  060  090  060
            -->  000  NaN  000

Yaw Pitch Roll:  060  090  090
            -->  030  NaN  000

Yaw Pitch Roll:  060  090  120
            -->  060  NaN  000

Yaw Pitch Roll:  060  090  150
            -->  090  090  000

Yaw Pitch Roll:  060  090  180
            -->  120  090  000

Yaw Pitch Roll:  060  180  000
            -->  -120  000  180

Yaw Pitch Roll:  090  090  000
            -->  -090  NaN  000

Yaw Pitch Roll:  090  090  030
            -->  -060  NaN  000

Yaw Pitch Roll:  090  090  060
            -->  -030  NaN  000

Yaw Pitch Roll:  090  090  090
            -->  000  NaN  000

Yaw Pitch Roll:  090  090  120
            -->  030  NaN  000

Yaw Pitch Roll:  090  090  150
            -->  060  NaN  000

Yaw Pitch Roll:  090  090  180
            -->  090  NaN  000

Yaw Pitch Roll:  090  120  000
            -->  -090  060  180

Yaw Pitch Roll:  090  150  000
            -->  -090  030  180

Yaw Pitch Roll:  090  180  000
            -->  -090  000  180

Yaw Pitch Roll:  120  090  000
            -->  -120  NaN  000

Yaw Pitch Roll:  120  090  030
            -->  -090  NaN  000

Yaw Pitch Roll:  120  090  060
            -->  -060  NaN  000

Yaw Pitch Roll:  120  090  090
            -->  -030  NaN  000

Yaw Pitch Roll:  120  090  120
            -->  000  NaN  000

Yaw Pitch Roll:  120  090  150
            -->  030  NaN  000

Yaw Pitch Roll:  120  090  180
            -->  060  NaN  000

Yaw Pitch Roll:  120  120  000
            -->  -060  060  180

Yaw Pitch Roll:  120  150  000
            -->  -060  030  180

Yaw Pitch Roll:  120  180  000
            -->  -060  000  180

Yaw Pitch Roll:  150  090  000
            -->  -150  NaN  000

Yaw Pitch Roll:  150  090  030
            -->  -120  NaN  000

Yaw Pitch Roll:  150  090  060
            -->  -090  NaN  000

Yaw Pitch Roll:  150  090  090
            -->  -060  NaN  000

Yaw Pitch Roll:  150  090  120
            -->  -030  NaN  000

Yaw Pitch Roll:  150  090  150
            -->  000  NaN  000

Yaw Pitch Roll:  150  090  180
            -->  030  NaN  000

Yaw Pitch Roll:  150  120  000
            -->  -030  060  180

Yaw Pitch Roll:  150  150  000
            -->  -030  030  180

Yaw Pitch Roll:  150  180  000
            -->  -030  000  180

Yaw Pitch Roll:  180  090  000
            -->  180  NaN  000

Yaw Pitch Roll:  180  090  030
            -->  -150  NaN  000

Yaw Pitch Roll:  180  090  090
            -->  -090  NaN  000

Yaw Pitch Roll:  180  090  120
            -->  -060  NaN  000

Yaw Pitch Roll:  180  090  150
            -->  -030  NaN  000

Yaw Pitch Roll:  180  090  180
            -->  000  NaN  000

Yaw Pitch Roll:  180  120  000
            -->  000  060  180

Yaw Pitch Roll:  180  150  000
            -->  000  030  180

Yaw Pitch Roll:  180  180  000
            -->  000  000  180



Here is my testing code for the case that somebody wants to play with it.

In the current state it only returns angle combinations, where the result is different to the start value.
The SceneManager reference is needed (and not null), because of the usage of a helper SceneNode.
In the first line you can set the variable "stepSize" to check more/less cases.

Code: Select all
public class EulerTesting
{

    public static void Test(SceneManager Smgr)
    {

        Single stepSize = 30; // in degree
       
        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 y, p, r;

        Quaternion quat = new Quaternion(new Degree(yaw), Vector3.UNIT_Y);
        SceneNode node = new SceneNode(Smgr);
        Console.WriteLine("Start calculation ...\n");

        for (yaw = -180;   yaw <= 180;   yaw += stepSize)
        {
            for (pitch = -180;   pitch <= 180;   pitch += stepSize)
            {
                for (roll = -180;   roll <= 180;   roll += stepSize)
                {

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

                    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;


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

                    Euler euler = new Euler(quat);
                    y = euler.Yaw.ValueDegrees;
                    p = euler.Pitch.ValueDegrees;
                    r = euler.Roll.ValueDegrees;


                    //-- check co-domain --

                    if (y < ymin)
                        ymin = y;
                    if (y > ymax)
                        ymax = y;
                    if (p < pmin)
                        pmin = p;
                    if (p > pmax)
                        pmax = p;
                    if (r < rmin)
                        rmin = r;
                    if (r > rmax)
                        rmax = r;


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

                    Boolean isDifferent = (!IsEqual(y, yaw) || !IsEqual(p, pitch) || !IsEqual(r, roll));


                    if (isDifferent)
                        Console.WriteLine(String.Format(
                            "Yaw Pitch Roll:  {0:000}  {1:000}  {2:000}\n" +
                            "            -->  {3:000}  {4:000}  {5:000}\n",
                            yaw, pitch, roll,
                            y, p, r
                            ));

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

                }

            }
            //Console.Write(".");
            //if ((yaw % 10) == 0)
            //    Console.Write(yaw); // show current state of calculation

        }
        Console.WriteLine("\n\nCalculation ready.\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.ReadLine();

    }





    private static Boolean IsEqual(Single val1, Single val2)
    {
        Single test = val1 - val2;
       
        // make positive
        if (test < 0)
            test *= 1;

        if (test < 0.01f)
            return true;

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


        return false;
    }


}



update:
I had incorrect start values in my test loops.

OLD PART OF TESTING CODE:
for (yaw = -180; yaw <= 180; yaw += stepSize)
{
for (pitch = 0; pitch <= 180; pitch += stepSize)
{
for (roll = 0; roll <= 180; roll += stepSize)
{

Now the start values for pitch/roll are corrected to -180.
Last edited by Beauty on Thu Jan 19, 2012 1:16 pm, edited 3 times in total.
Reason: added important notes (blue colour)
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 » Wed Jan 18, 2012 8:01 pm

Beauty wrote:The documentation needed more time than the programming. :evil:
I hope my spend time is useful for somebody.


Yes, of course! Thank you very much!

Beauty wrote:Why do you stick with Mogre 1.6?
Did you have problems with 1.7 or one of it's add-ons?

I started with 1.6.5 and tried to switch to 1.7.x but Hydrax was compiled against 1.6.x (I think that problem is solved now)
-> reverted
Later I tried to use MyGUI with 1.7.x but it failed again (because it COUDN'T FIND the dll :?: , I am not the only one with this problem, I think it is Net.4)
-> didn't revert because I used a test project ;)
=> I will stick to 1.6.5 until MyGUI 3.2 or higher is released (or compiled by myself).
I think all my problems could be solved if I compile MyGUI with Net4. or Mogre with 3.5 but I am too lazy ;)
Until now I am quite happy with 1.6.5

VB.Net code of Euler class:
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()
         rotMat.ToEulerAnglesYXZ(mYaw, mPitch, mRoll)
         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
            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
            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>
      ''' 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
' namespace
User avatar
Tubulii
Goblin
 
Posts: 222
Kudos: 15
Joined: 24 Jan 2010
Location: Germany

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

Postby Tubulii » Wed Jan 18, 2012 8:18 pm

Beauty wrote:The documentation needed more time than the programming. :evil:
I hope my spend time is useful for somebody.


Yes, of course! Thank you very much!

Beauty wrote:Why do you stick with Mogre 1.6?
Did you have problems with 1.7 or one of it's add-ons?

I started with 1.6.5 and tried to switch to 1.7.x but Hydrax was compiled against 1.6.x (I think that problem is solved now)
-> reverted
Later I tried to use MyGUI with 1.7.x but it failed again (because it COUDN'T FIND the dll :?: , I am not the only one with this problem, I think it is Net.4)
-> didn't revert because I used a test project ;)
=> I will stick to 1.6.5 until MyGUI 3.2 or higher is released (or compiled by myself).
I think all my problems could be solved if I compile MyGUI with Net4. or Mogre with 3.5 but I am too lazy ;)
Until now I am quite happy with 1.6.5

VB.Net code of Euler class:
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()
         rotMat.ToEulerAnglesYXZ(mYaw, mPitch, mRoll)
         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
            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
            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>
      ''' 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
' namespace

Beauty wrote:The documentation needed more time than the programming. :evil:

VB.Net Express has a context entry for generating a xml-body for a selected method/class/...
But C# Express DOESN'T, until today I do not understand why there are different "versions" of the same UI in VS. (e.g. the C# version has an option to generate a xml documentation, vb.net version does not, ... )

Beauty wrote:TODO: Add a description for param direction.

I think it should be something like "The direction which you want the rotation to". It that correct? I didn't add it to my code above.

Beauty wrote:After reading it twice, I can keep it in mind for shure. :mrgreen:

I had to look it up ;)

With the rotation problem, I cannot help you, it only tortures my brain :(
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 » Wed Jan 18, 2012 8:23 pm

VB.Net Express has a context entry for generating a xml-body for a selected method/class/...
But C# Express DOESN'T

I just type in 3 slash symbols in the line above a class member.
Then Visual Studio generates the empty XML commentation automatically. Most easy :wink:
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 » Wed Jan 18, 2012 8:30 pm

Mm, I tried it with VB.Net and it works there, too. Nevertheless these differences are annoying...
User avatar
Tubulii
Goblin
 
Posts: 222
Kudos: 15
Joined: 24 Jan 2010
Location: Germany

PreviousNext

Return to MOGRE

Who is online

Users browsing this forum: No registered users and 1 guest