Euler Angle Class         An Ogre compatible class for euler angles
Print

Overview

Ogre internally uses Quaternions for orientation. There are many advantages to this, but there is one main disadvantage: quaternions are usually confusing to beginners. An alternative method of orientation is Euler angles (commonly called yaw, pitch and roll). Euler angles have many disadvantages, but they are also easy to understand. The code in this article provides a Euler class, capable of operating on Euler angles and generating ogre quaternions. It is possible to give a Euler object directly to any Ogre function which normally takes a quaternion, the conversion is automatic.

Euler angles consist of 3 rotations, often known as Yaw, Pitch and Roll. Each of these rotations is around a specific axis.

Yaw is a rotation around the Y axis.Pitch is a rotation around the X axis.Roll is a rotation around the Z axis.
Image Image Image

Euler angles are best suited to situations with restricted orientations. For example, game cameras can usually yaw (rotate around the Y axis, like a person looking left or right) and pitch (rotate around the X axis, like a person looking up or down), but not roll (rotate around the Z axis, like a person tilting their head to either side).

The Euler class allows you to directly set/get absolute pitch, yaw or roll values without affecting each other, for example setting the orientation to face east while preserving the pitch and roll.

Ogre provides methods to use euler angles with matrices and quaternions, however there are problems that make this inconvenient. The primary problem is that multiple combinations of euler angles can produce the same quaternion or matrix. This means that converting eulers into a quaternion then back to euler can give different (but equivilant) results than what you started with. This is often a problem when attempting to apply constraints to an orientation (such as limiting a camera's pitch).

Euler angles are non commutative. This means that the order you apply the rotations matters. Different applications use different orderings. The code provided here uses the YXZ (Yaw Pitch Roll) order. This means that the object performs local yaw, then local pitch, then local roll. This is the same as performing world roll, then world pitch, then world yaw.
This code also follows ogre's coordinate system standard: right handed cartesian coordinates with U axis up and -Z axis forward, and right handed rotations. A positive yaw, pitch or roll operates in the direction shown in the animated images above. Positive yaw is anticlockwise, positive pitch looks up and positive roll is anticlockwise (when looking along -Z). Setting all three angles to 0 gives the same orientation as an identity quaternion or matrix, in other words a model facing -Z will remain facing that direction (-Z is the ogre standard for forwards).

All three euler angles are unlimited in range. This means you can have a yaw of 10000 degrees, or a pitch of -400 degrees. This is impossible in quaternions and matrices, they can only represent the final orientation, not how many rotations took place to get there. Methods are provided to either clamp each angle to a +/-limit range, or normalise the euler object so all angles are wrapped around to the +/-180 degree range.

It is possible to treat the euler class as either absolute or relative. This works like a Vector3, which can represent either an absolute position in space or an offset from a position. For example, subtracting one euler from another gives a euler containing the angular offset between the initial two.

The three angles which make up a euler orientation are stored separately. It is possible to set or get the value of the yaw, pitch or roll without affecting the other angles.

To increase performance, the Euler class stores a cached copy of the last generated quaternion. Any time an angle is changed, a flag is set to indicate the cached copy is out of date. Next time a conversion to quaternion is requested, a full calculation will be done, with the result cached for future reuse. This means the class memory footprint is larger than needed (7 floats and a bool, rather than 3 floats), but quaternion generation involves several sin/cos operations (for a full euler it is typically 3 sin, 3 cos and a bunch of algebra).

Many methods of the euler class return a self reference, allowing you to chain multiple methods together.

 

Behaviour video

Kojack created a video to show the general Ogre behaviour and the EulerAngleClass one.

Here's a video showing the issue:

Flash player not available.

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.

 

Basic API Info

getYaw(), getPitch(), getRoll() Retrieve the current yaw, pitch or roll values.
getForward(), getRight(), getUp() Retrieve a Vector3 which points in the specified direction relative to the euler angles.
getRotationTo(dir, setYaw, setPitch, shortest) Return the relative rotation needed to make the euler face dir. If shortest is true, angles are wrapped.
setYaw(angle), setPitch(angle), setRoll(angle) Change the current yaw, pitch or roll values.
setDirection(dir, setYaw, setPitch) Sets the euler to face dir. If setYaw or setPitch are false, the matching angle isn't modified. Roll is always preserved.
setRotation(yaw, pitch, roll) Change the current yaw, pitch or roll values.
yaw(angle), pitch(angle), roll(angle) Add the angle to the current yaw, pitch or roll.
rotate(yaw, pitch, roll) Add all three angles to the current angles.
limitYaw(angle), limitPitch(angle), limitRoll(angle) Clamp to a range of +/-angle.
normalise(normYaw, normPitch, normRoll) Wrap the angles around to be +/-180 degrees. The three parameters control which angles are wrapped.
toQuaternion() Convert the euler to a quaternion.
(Euler = Euler + Euler) Add angles. For example, (90y,0p,0r) added to (20y,0p,0r) will give (110y,0p,0r).
(Euler = Euler - Euler) Subtract angles.
(Euler = Euler * Real) or (Euler = Real * Euler) Interpolation of the euler angles by Real.
(Quaternion = Euler * Euler) Rotate one euler object by another, returning a quaternion.
(Vector3 = Euler * Vector3) Rotate a vector by the euler object.

 

Euler class

For a Mogre compatible C# port, go to Euler Angle Class Mogre

/**
    @file Euler.h
    @brief %Euler class for %Ogre
    @details License: Do whatever you want with it.
    @version 2.3
    @author Kojack 
    @author Transporter
    @author Klaim
 
Extracted From: http://www.ogre3d.org/tikiwiki/tiki-ind ... e=Cookbook
*/
#ifndef HGUARD_OGRE_MATHS_EULER_H
#define HGUARD_OGRE_MATHS_EULER_H
 
#include "OgreMaths.hpp"
#include "OgreVector3.hpp"
#include "OgreQuaternion.hpp"
#include "OgreMatrix3.hpp"
 
namespace Ogre {
 
    /**
    @class Euler
    @brief Class for %Euler rotations
 
    <table><tr><td>Yaw is a rotation around the Y axis.</td><td>Pitch is a rotation around the X axis.</td><td>Roll is a rotation around the Z axis.</td></tr>
    <tr><td><img src="http://www.ogre3d.org/tikiwiki/tiki-download_file.php?fileId=2112" /></td><td><img src="http://www.ogre3d.org/tikiwiki/tiki-download_file.php?fileId=2113" /></td><td><img src="http://www.ogre3d.org/tikiwiki/tiki-download_file.php?fileId=2114" /></td></tr></table>
    */
    class Euler
    {
    public:
       /// Default constructor.
       Euler()
          : mYaw(Radian(0.0f)), mPitch(Radian(0.0f)), mRoll(Radian(0.0f)), mChanged(true) 
       {
       }
 
       /**
       @brief Constructor which takes yaw, pitch and roll values.   
       @param y Starting value for yaw
       @param p Starting value for pitch
       @param r Starting value for roll
       */
       Euler(const Radian &y, const Radian &p = Radian(0.0f), const Radian &r = Radian(0.0f))
          : mYaw(y), mPitch(p), mRoll(r), mChanged(true) 
       {
       }
 
        /**
       @brief Constructor which takes yaw, pitch and roll values as reals (radians).
       @param y Starting value for yaw [radian]
       @param p Starting value for pitch [radian]
       @param r Starting value for roll [radian]
       */
       Euler(Real y, Real p = 0.0f, Real r = 0.0f)
          : mYaw(Radian(y)), mPitch(Radian(p)), mRoll(Radian(r)), mChanged(true) 
       {
       }
 
       /**
       @brief Default constructor with presets.
       @param quaternion Calculate starting values from this quaternion
       */
       explicit Euler(const Quaternion &quaternion)
       {
          fromQuaternion(quaternion);
       }
 
       explicit Euler(const Matrix3 &matrix)
       {
          fromMatrix3(matrix);
       }
 
       /// Get the Yaw angle.
       inline Radian yaw() const { return mYaw; }
 
       /// Get the Pitch angle.
       inline Radian pitch() const { return mPitch; }
 
       /// Get the Roll angle.
       inline Radian roll() const { return mRoll; }
 
       /**
       @brief Set the yaw.
       @param y New value for yaw
       */
       inline Euler &setYaw(Radian y)
       {
          mYaw = y; 
          mChanged = true; 
          return *this;
       }
 
       /**
       @brief Set the pitch.
       @param p New value for pitch
       */
       inline Euler &setPitch(Radian p) 
       {
          mPitch = p; 
          mChanged = true; 
          return *this;
       }
 
       /**
       @brief Set the roll.
       @param r New value for roll
       */
       inline Euler &setRoll(Radian r) 
       {
          mRoll = r; 
          mChanged = true; 
          return *this;
       }
 
       /**
       @brief Set all rotations at once.
       @param y New value for yaw
       @param p New value for pitch
       @param r New value for roll
       */
       inline Euler &orientation(const Radian &y, const Radian &p, const Radian &r)
       {
          mYaw = y;
          mPitch = p;
          mRoll = r; 
          mChanged = true; 
          return *this;
       }
 
       /**
       @brief Apply a relative yaw.
       @param y Angle to add on current yaw
       */
       inline Euler &yaw(const Radian &y) 
       {
          mYaw += y; 
          mChanged = true; 
          return *this;
       }
 
       /**
       @brief Apply a relative pitch.
       @param p Angle to add on current pitch
       */
       inline Euler &pitch(const Radian &p) 
       {
          mPitch += p; 
          mChanged = true; 
          return *this;
       }
 
       /**
       @brief Apply a relative roll.
       @param r Angle to add on current roll
       */
       inline Euler &roll(const Radian &r) 
       {
          mRoll += r; 
          mChanged = true; 
          return *this;
       }
 
       /**
       @brief Apply all relative rotations at once.
       @param y Angle to add on current yaw
       @param p Angle to add on current pitch
       @param r Angle to add on current roll
       */
       inline Euler &rotate(const Radian &y, const Radian &p, const Radian &r)
       {
          mYaw += y;
          mPitch += p;
          mRoll += r; 
          mChanged = true; 
          return *this;
       }
 
       /// Get a vector pointing forwards.
       inline Vector3 forward() const { return toQuaternion() * Vector3::NEGATIVE_UNIT_Z; }
 
       /// Get a vector pointing to the right.
       inline Vector3 right() const { return toQuaternion() * Vector3::UNIT_X; }
 
       /// Get a vector pointing up.
       inline Vector3 up() const { return toQuaternion() * Vector3::UNIT_Y; }
 
       /**
       @brief Calculate the quaternion of the euler object.
       @details The result is cached, it is only recalculated when the component euler angles are changed.
       */
       inline Quaternion toQuaternion() const
       {
          if(mChanged) 
          {
             mCachedQuaternion = Quaternion(mYaw, Vector3::UNIT_Y) * Quaternion(mPitch, Vector3::UNIT_X) * Quaternion(mRoll, Vector3::UNIT_Z); 
             mChanged = false;
          }
          return mCachedQuaternion;
       }
 
       /// Casting operator. This allows any ogre function that wants a Quaternion to accept a Euler instead.
       inline operator Quaternion() const
       {
          return toQuaternion();
       }
 
       /**
       @brief Calculate the current euler angles of a given quaternion object.
       @param quaternion Quaternion which is used to calculate current euler angles.
       */
       inline Euler &fromQuaternion(const Quaternion &quaternion)
       {
          Matrix3 rotmat;
          quaternion.ToRotationMatrix(rotmat);
          fromMatrix3(rotmat);
          return *this;
       }
 
       /**
       @brief Calculate the current euler angles of a given matrix object.
       @param matrix Matrix3 which is used to calculate current euler angles.
       */
       inline Euler &fromMatrix3(const Matrix3 &matrix)
       {
          matrix.ToEulerAnglesYXZ(mYaw,mPitch,mRoll);
          mChanged = true;
          return *this;
       }
 
       /**
       @brief Set the yaw and pitch to face in the given direction.
       @details The direction doesn't need to be normalised. Roll is always unaffected.
       @param setYaw If false, the yaw isn't changed.
       @param setPitch If false, the pitch isn't changed.
       */
       inline Euler &direction(const Vector3 &v, bool setYaw = true, bool setPitch = true)
       {
          Vector3 d(v.normalisedCopy());
          if(setPitch)
             mPitch = Math::ASin(d.y);
          if(setYaw)
             mYaw = Math::ATan2(-d.x, -d.z);
          mChanged = setYaw||setPitch;
           return *this;
       }
 
       /**
       @brief Normalise the selected rotations to be within the +/-180 degree range.
       @details The normalise uses a wrap around, so for example a yaw of 360 degrees becomes 0 degrees, and -190 degrees becomes 170.
       @param normYaw If false, the yaw isn't normalized.
       @param normPitch If false, the pitch isn't normalized.
       @param normRoll If false, the roll isn't normalized.
       */
       inline Euler &normalise(bool normYaw = true, bool normPitch = true, bool normRoll = true)
       {
            if(normYaw)
              wrapAngle( mYaw );
 
          if(normPitch)
              wrapAngle( mPitch );
 
          if(normRoll)
              wrapAngle( mRoll );
 
          return *this;
       }
 
       /**
       @brief Return the relative euler angles required to rotate from the current forward direction to the specified dir vector.
       @details The result euler can then be added to the current euler to immediately face dir.
       The rotation won't flip upside down then roll instead of a 180 degree yaw.
       @param setYaw If false, the angle is set to 0. If true, the angle is calculated.
       @param setPitch If false, the angle is set to 0. If true, the angle is calculated.
       @param 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).
       */
       inline Euler rotationTo(const Vector3 &dir, bool setYaw = true, bool setPitch = true, bool shortest = true) const
       {
          Euler t1;
          Euler t2;
          t1.direction(dir, setYaw, setPitch);
          t2 = t1 - *this;
          if(shortest && setYaw)
          {
             t2.normalise();
          }
          return t2;
       }
 
       /// Clamp the yaw angle to a range of +/-limit.
       inline Euler &limitYaw(const Radian &limit)
       {
          limitAngle( mYaw, limit );
          return *this;
       }
 
       /// Clamp the pitch angle to a range of +/-limit.
       inline Euler &limitPitch(const Radian &limit)
       {
          limitAngle( mPitch, limit );
          return *this;
       }
 
       /// Clamp the roll angle to a range of +/-limit.
       inline Euler &limitRoll(const Radian &limit)
       {
          limitAngle( mRoll, limit );
          return *this;
       }
 
       /// Stream operator, for printing the euler component angles to a stream
       inline friend std::ostream &operator<<(std::ostream &o, const Euler &e)
       {
          o << "<Y:" << e.mYaw << ", P:" << e.mPitch << ", R:" << e.mRoll << ">";
          return o;
       }
 
       /// Add two euler objects.
       inline Euler operator+(const Euler &rhs) const 
       {
          return Euler(mYaw + rhs.mYaw, mPitch + rhs.mPitch, mRoll + rhs.mRoll); 
       }
 
       /**
       @brief Subtract two euler objects.
       @details This finds the difference as relative angles.
       */
        inline Euler operator-(const Euler &rhs) const 
       { 
          return Euler(mYaw - rhs.mYaw, mPitch - rhs.mPitch, mRoll - rhs.mRoll); 
       }
 
       /// Interpolate the euler angles by rhs.
       inline Euler operator*(Real rhs) const 
       { 
          return Euler(mYaw * rhs, mPitch * rhs, mRoll * rhs); 
       }
 
       /// Interpolate the euler angle by lhs.
       inline friend Euler operator*(Real lhs, const Euler &rhs)
       { 
          return Euler(lhs * rhs.mYaw, lhs * rhs.mPitch, lhs * rhs.mRoll); 
       }
 
       /**
       @brief Multiply two eulers.
       @details This has the same effect as multiplying quaternions.
       @returns The result is a quaternion.
       */
       inline Quaternion operator*( Euler rhs) const
       {
          Euler e1(*this), e2(rhs);
          return e1.toQuaternion()*e2.toQuaternion();
       }
 
 
       /// Apply the euler rotation to the vector rhs.
       inline Vector3 operator*(const Vector3 &rhs) const
       { 
          return toQuaternion() * rhs; 
       }
 
 
       /// Copy assignment operator (Euler)
       inline Euler& operator=(const Euler& src)
       {
          orientation(src.yaw(), src.pitch(), src.roll());
          return *this;
       }
 
       /// Copy assignment operator (Quaternion)
       inline Euler& operator=(const Quaternion &quaternion)
       {
          fromQuaternion(quaternion);
          return *this;
       }
 
       /// Copy assignment operator (Matrix3)
       inline Euler& operator=(const Matrix3 &matrix)
       {
          fromMatrix3(matrix);
          return *this;
       }
 
        inline friend bool operator==( const Euler& left, const Euler& right )
        {
            return left.mYaw   == right.mYaw
                && left.mPitch == right.mPitch
                && left.mRoll  == right.mRoll
            ;
        }
 
        inline friend bool operator!=( const Euler& left, const Euler& right )
        {
            return !( left == right );
        }
 
        inline friend bool sameOrientation( const Euler& left, const Euler& right )
        {
            // I'm comparing resulting vectors to avoid having to compare angles that are the same but in different values.
            // Only the resulting oriented vectors really have any meaning in the end.
            return left.forward().positionEquals( right.forward() )
                && left.up().positionEquals( right.up() )
                ;
        }
 
    protected:
       Radian mYaw;                           //!< Rotation around the Y axis.
       Radian mPitch;                            //!< Rotation around the X axis.
       Radian mRoll;                           //!< Rotation around the Z axis.
       mutable Quaternion mCachedQuaternion;       //!< Cached quaternion equivalent of this euler object.
       mutable bool mChanged;                  //!< Is the cached quaternion out of date?
 
        inline void wrapAngle( Radian& angle )
        {
            Real rangle = angle.valueRadians();
         if(rangle < -Math::PI)
         {
            rangle = fmod(rangle, -Math::TWO_PI);
            if(rangle < -Math::PI)
            {
               rangle += Math::TWO_PI;
            }
            angle = rangle;
            mChanged = true;
         }
         else if(rangle > Math::PI)
         {
            rangle = fmod(rangle, Math::TWO_PI);
            if(rangle > Math::PI)
            {
               rangle -= Math::TWO_PI;
            }
            angle = rangle;
            mChanged = true;
         }
 
        }
 
        inline void limitAngle( Radian& angle, const Radian& limit )
        {
             if(angle > limit)
          {
             angle = limit;
             mChanged = true;
          }
          else if(angle < -limit)
          {
             angle = -limit;
             mChanged = true;
          }
        }
    };
 
}
 
#endif

 

Examples

Making and using a euler object:

Euler euler;
sceneNode->setOrientation(euler);   // Euler auto converts to a quaternion if you pass it to any Ogre function which expects a quaternion.

 
Make a scene node face the camera (both yaw and pitch):

Euler euler;
euler.setDirection(camera->getPosition() - sceneNode->getPosition());   // setDirection normalises the vector
sceneNode->setOrientation(euler);

 
Make a scene node face the camera using only the yaw (good for character controllers):

Euler euler;
euler.setDirection(camera->getPosition() - sceneNode->getPosition(), true, false);   // the bools control whether the yaw or pitch are changed
sceneNode->setOrientation(euler);

 
Make a scene node face the camera with a limit of +/-45 degrees on the pitch and unlimited yaw:

Euler euler;
euler.setDirection(camera->getPosition() - sceneNode->getPosition());
euler.limitPitch(Degree(45));
sceneNode->setOrientation(euler);

 
Perform a corkscrew motion (move vertical as the yaw increases:

sceneNode->setOrientation(euler);
Vector3 pos = sceneNode->getPosition();
pos.y = euler.getYaw();
sceneNode->setPosition(pos);

 
Chain together several methods (in this case apply OIS mouse input to yaw and pitch the node, plus set it's roll to zero).

const MouseState &ms = mMouse->getMouseState();
// add mouse x to yaw, add mouse y to pitch, and reset roll to 0.
euler.yaw(Radian(ms.X.rel)).pitch(Radian(ms.Y.rel)).setRoll(Degree(0));
sceneNode->setOrientation(euler);

 
Move forward using the euler direction:

sceneNode->setPosition(sceneNode->getPosition + euler.getForward());

 
Cancel out excess rotations (return all angles to the +-180 degree range):

euler.normalise();

 
Rotate the head of the Sinbad mesh to face the camera, with slow head turning and pitch/yaw constraints:
(This is using a modified version of the Sinbad mesh which is 2m tall and faces -Z)

//In the initialisation:
Bone *bone = entity->getSkeleton()->getBone("Head");
bone->setManuallyControlled(true);
Euler headFacing;
 
 
// In the update logic:
Vector3 lookat = m_nodes[0]->convertWorldToLocalPosition(m_camera->getPosition()) - Vector3(0, 1.9, 0);
Euler temp = headFacing.getRotationTo(lookat);   // temp is how much you need to rotate to get from the current orientation to the new orientation
temp.limitYaw(Ogre::Radian(deltaT*3.0));   // limit the offset so the head turns at a maximum of 3.0 radians per second
temp.limitPitch(Ogre::Radian(deltaT*3.0));
 
headFacing = headFacing + temp;   // add the offset to the current orientation
e2.limitYaw(Ogre::Degree(60));   // make sure the head doesn't turn too far. Clamp it to +/- 60 degrees of yaw and pitch.
e2.limitPitch(Ogre::Degree(60));
node->setOrientation(headFacing);

Sinbad's current orientation is taken into account by the convertWorldToLocalPosition call, so you can rotate him in any direction and he will still try to turn his head to the camera (if the limits allow).


Alias: Euler_Angle_Class


Contributors to this page: Klaim440 points  , Transporter481 points  , spacegaier5974 points  , Kojack903 points  , jacmoe177744 points  , Carignanboy and Beauty14435 points  .
Page last modified on Sunday 16 of February, 2014 18:54:59 UTC by Klaim440 points .


The content on this page is licensed under the terms of the Creative Commons Attribution-ShareAlike License.
As an exception, any source code contributed within the content is released into the Public Domain.