History: Euler Angle Class

Preview of version: 4

Image

Table of contents

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 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.

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, so I went for speed rather than size.

Yaw is in the range 0 to 360 degrees, and goes clockwise from the negative Z axis (0 degrees yaw).
Pitch angles up with positive angles, down with negative angles, and is horizontal for 0 degrees.
Roll angles clockwise (around the forward direction) for positive angles, anticlockwise for negative angles, and is vertical for 0 degrees.

Pitch and roll angles don't wrap, only yaw wraps to stay in the 0 to 360 range. I did this because there are a few different ways people might want pitch and roll to work, being clamped to a range, wrapping, should a pitch of over 90 degrees cause the yaw to flip 180, etc. I was going to add in optional clamping and wrapping ranges for each angle, but I haven't gotten around to that yet.

Euler class

// Euler class for Ogre
// Author: Kojack
// License: Do whatever you want with it.

class Euler
{
public:
    // Constructor which takes yaw, pitch and roll values.
    Euler(Ogre::Radian y = Ogre::Radian(0.0f), Ogre::Radian p = Ogre::Radian(0.0f), Ogre::Radian r = Ogre::Radian(0.0f)):m_yaw(y),m_pitch(p),m_roll(r),m_changed(true) 
    {
    }

    // Get the Yaw angle.
    inline Ogre::Radian getYaw() 
    {
        return m_yaw;
    }

    // Get the Pitch angle.
    inline Ogre::Radian getPitch() 
    {
        return m_pitch;
    }

    // Get the Roll angle.
    inline Ogre::Radian getRoll() 
    {
        return m_roll;
    }

    // Apply a relative yaw. (Adds angle to current yaw)
    // Angles wrap around within the range 0 to 2*PI radians (0 to 360 degrees)
    inline Euler &yaw(Ogre::Radian y) 
    {
        m_yaw += y; 
        if(m_yaw.valueRadians() < 0.0f)
        {
            m_yaw = fmod(m_yaw.valueRadians(), Ogre::Math::PI * 2.0f) + Ogre::Math::PI * 2.0f;
        }
        else if(m_yaw.valueRadians() > Ogre::Math::PI)
        {
            m_yaw = fmod(m_yaw.valueRadians(), Ogre::Math::PI * 2.0f);
        }
        m_changed = true; 
        return *this;
    }

    // Apply a relative pitch. (Adds angle to current pitch)
    inline Euler &pitch(Ogre::Radian p) 
    {
        m_pitch += p; 
        m_changed = true; 
        return *this;
    }

    // Apply a relative roll. (Adds angle to current roll)
    inline Euler &roll(Ogre::Radian r) 
    {
        m_roll += r; 
        m_changed = true; 
        return *this;
    }

    // Set the yaw.
    inline Euler &setYaw(Ogre::Radian y) 
    {
        m_yaw = y; 
        m_changed = true; 
        return *this;
    }

    // Set the pitch.
    inline Euler &setPitch(Ogre::Radian p) 
    {
        m_pitch = p; 
        m_changed = true; 
        return *this;
    }

    // Set the roll.
    inline Euler &setRoll(Ogre::Radian r) 
    {
        m_roll = r; 
        m_changed = true; 
        return *this;
    }

    // Get a vector pointing forwards.
    inline Ogre::Vector3 getForward() 
    {
        return toQuaternion() * Ogre::Vector3::NEGATIVE_UNIT_Z;
    }

    // Get a vector pointing to the right.
    inline Ogre::Vector3 getRight() 
    {
        return toQuaternion() * Ogre::Vector3::UNIT_X;
    }

    // Get a vector pointing up.
    inline Ogre::Vector3 getUp() 
    {
        return toQuaternion() * Ogre::Vector3::UNIT_Y;
    }

    // Calculate the quaternion of a euler object.
    // The result is cached, it is only recalculated when the component euler angles are changed.
    inline Ogre::Quaternion toQuaternion() 
    {
        if(m_changed) 
        {
            m_cachedQuaternion = Ogre::Quaternion(m_yaw, Ogre::Vector3::NEGATIVE_UNIT_Y) * Ogre::Quaternion(m_pitch, Ogre::Vector3::UNIT_X) * Ogre::Quaternion(m_roll, Ogre::Vector3::NEGATIVE_UNIT_Z); 
            m_changed = false;
        }
        return m_cachedQuaternion;
    }

    // Casting operator. This allows any ogre function that wants a Quaternion to accept a Euler instead.
    inline operator Ogre::Quaternion() 
    {
        return toQuaternion();
    }

    // Set the yaw and pitch to face in the given direction.
    // The direction doesn't need to be normalised.
    // Roll is unaffected.
    Euler &setDirection(const Ogre::Vector3 &v)
    {
        Ogre::Vector3 d(v.normalisedCopy());
        m_pitch = asin(d.y);
        m_yaw = atan2(d.z, d.x)+Ogre::Math::PI/2.0;
        m_changed = true;
        
        return *this;
    }

    // Get the angular difference between the current yaw and the specified yaw.
    // Only yaw is considered, pitch and roll are ignored.
    inline Ogre::Radian getYawToDirection(const Ogre::Radian &a)
    {
        Ogre::Real angle = (a - m_yaw).valueRadians();
        if(angle>Ogre::Math::PI)
            angle = -Ogre::Math::PI*2.0f + angle;
        else if(angle<-Ogre::Math::PI)
            angle = Ogre::Math::PI*2.0f + angle;
        return Ogre::Radian(angle);
    }

    // Get the angular difference between the current yaw and the specified direction vector.
    // Only yaw is considered, pitch and roll are ignored.
    inline Ogre::Radian getYawToDirection(const Ogre::Vector3 &v)
    {
        return getYawToDirection(Ogre::Radian(atan2(v.z, v.x) + Ogre::Math::PI/2.0f));
    }

    // Get the angular difference between the current yaw and the specified euler object.
    // Only yaw is considered, pitch and roll are ignored.
    inline Ogre::Radian getYawToDirection(const Euler &e)
    {
        return getYawToDirection(e.m_yaw);
    }

    // Change the yaw to face in the direction of the vector.
    // Only yaw is changed, pitch and roll are ignored.
    inline Euler &yawToDirection(const Ogre::Vector3 &v)
    {
        m_yaw = getYawToDirection(v);
        m_changed = true;
        return *this;
    }

    // Change the yaw to face in the direction of the euler object.
    // Only yaw is changed, pitch and roll are ignored.
    inline Euler &yawToDirection(const Euler &e)
    {
        m_yaw = getYawToDirection(e);
        m_changed = true;
        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.m_yaw << ", P:" << e.m_pitch << ", R:" << e.m_roll << ">";
        return o;
    }

protected:
    Ogre::Radian m_yaw;     // Rotation around the Y axis.
    Ogre::Radian m_pitch;   // Rotation around the X axis.
    Ogre::Radian m_roll;    // Rotation around the Z axis.
    Ogre::Quaternion m_cachedQuaternion;    // Cached quaternion equivalent of this euler object.
    bool m_changed;         // Is the cached quaternion out of date?
};

Examples

Making and using a euler object:

Euler euler;
sceneNode->setOrientation(euler);


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

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


Make a scene node face the camera (only yaw, the node won't tip over when the camera is higher or lower):

Euler euler;
euler.yawToDirection(camera->getPosition() - sceneNode->getPosition());
sceneNode->setOrientation(euler);


Limit the pitch to be within 20 degrees of horizontal:

Euler euler;
euler.setDirection(camera->getPosition() - sceneNode->getPosition());
if(euler.getPitch()<Degree(-20))
    euler.setPitch(Degree(-20));
if(euler.getPitch()>Degree(20))
    euler.setPitch(Degree(20));
sceneNode->setOrientation(euler);


Use the mouse (with OIS) to treat the euler object like a camera:

const MouseState &ms = mMouse->getMouseState();
euler.yaw(Radian(ms.X.rel));
euler.pitch(Radian(ms.Y.rel));
sceneNode->setOrientation(euler);


Some methods can be chained, they return a reference to the euler object:

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());


Alias: Euler_Angle_Class

History

Advanced
Information Version
Mon 09 of Nov, 2015 03:16 GMT-0000 Kojack 14
Sun 16 of Feb, 2014 18:54 GMT-0000 Klaim Fixed version 13
Sun 16 of Feb, 2014 18:54 GMT-0000 Klaim Improved Euler class, see http://www.ogre3d.org/forums/viewtopic.php?f=5&t=80057 for discussion and details. 12
Tue 27 of Aug, 2013 16:54 GMT-0000 Kojack 11
Tue 27 of Aug, 2013 16:44 GMT-0000 Kojack Removed all methods that treated Vector3s as euler angle tuples. That's confusing when this class also has methods that use Vector3s as position and direction. Removed constructor that takes int degre 10
Sat 12 of Jan, 2013 09:26 GMT-0000 Transporter 9
Mon 26 of Mar, 2012 22:49 GMT-0000 Beauty added section "Behaviour video" 8
Tue 17 of Jan, 2012 14:54 GMT-0000 Kojack 7
Tue 17 of Jan, 2012 14:50 GMT-0000 Kojack 6
Mon 16 of Jan, 2012 13:52 GMT-0000 Kojack Euler class version 2. 5
Tue 25 of Jan, 2011 08:52 GMT-0000 spacegaier added alias 4
Sat 02 of Jan, 2010 07:50 GMT-0000 jacmoe 3
Fri 25 of Dec, 2009 01:26 GMT-0000 jacmoe 2
Sun 19 of Jul, 2009 15:10 GMT-0000 Carignanboy /* Examples */ 1