If you haven't done so already, be sure to visit the Wiki Portal to read about how the wiki works. Especially the Ogre Wiki Overview page.
Table of contents
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. |
|
|
|
|
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:
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. | |
| setYaw(angle), setPitch(angle), setRoll(angle) | Change the current yaw, pitch or roll values. | |
| yaw(angle), pitch(angle), roll(angle) | Add the angle to the current yaw, pitch or roll. | |
| getForward(), getRight(), getUp() | Retrieve a Vector3 which points in the specified direction relative to the euler angles. | |
| toQuaternion() | Convert the euler to a quaternion. | |
| 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. | |
| normalise(normYaw, normPitch, normRoll) | Wrap the angles around to be +/-180 degrees. The three parameters control which angles are wrapped. | |
| getRotationTo(dir, setYaw, setPitch, shortest) | Return the relative rotation needed to make the euler face dir. If shortest is true, angles are wrapped. | |
| limitYaw(angle), limitPitch(angle), limitRoll(angle) | Clamp to a range of +/-angle. | |
| (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 (euler=euler*euler is too tricky). | |
| (Vector3 = Euler * Vector3) | Rotate a vector by the euler object. | |
Euler class
For a Mogre compatible C# port, go to Euler Angle Class Mogre
// Euler class for Ogre // Euler class for Ogre, v2.0 // Author: Kojack // License: Do whatever you want with it. #ifndef OGREEULER_H #define OGREEULER_H namespace Ogre { class Euler { public: // Constructor which takes yaw, pitch and roll values. Euler(Ogre::Radian y, Ogre::Radian p = Ogre::Radian(0.0f), Ogre::Radian r = Ogre::Radian(0.0f)): mYaw(y), mPitch(p), mRoll(r), mChanged(true) { } // Constructor which takes yaw, pitch and roll values as reals (radians). Euler(Ogre::Real y = 0.0f, Ogre::Real p = 0.0f, Ogre::Real r = 0.0f): mYaw(Ogre::Radian(y)), mPitch(Ogre::Radian(p)), mRoll(Ogre::Radian(r)), mChanged(true) { } // Get the Yaw angle. inline Ogre::Radian getYaw() { return mYaw; } // Get the Pitch angle. inline Ogre::Radian getPitch() { return mPitch; } // Get the Roll angle. inline Ogre::Radian getRoll() { return mRoll; } // Set the yaw. inline Euler &setYaw(Ogre::Radian y) { mYaw = y; mChanged = true; return *this; } // Set the pitch. inline Euler &setPitch(Ogre::Radian p) { mPitch = p; mChanged = true; return *this; } // Set the roll. inline Euler &setRoll(Ogre::Radian r) { mRoll = r; mChanged = true; return *this; } // Apply a relative yaw. (Adds angle to current yaw) inline Euler &yaw(Ogre::Radian y) { mYaw += y; mChanged = true; return *this; } // Apply a relative pitch. (Adds angle to current pitch) inline Euler &pitch(Ogre::Radian p) { mPitch += p; mChanged = true; return *this; } // Apply a relative roll. (Adds angle to current roll) inline Euler &roll(Ogre::Radian r) { mRoll += r; mChanged = 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 the euler object. // The result is cached, it is only recalculated when the component euler angles are changed. inline Ogre::Quaternion toQuaternion() { if(mChanged) { mCachedQuaternion = Ogre::Quaternion(mYaw, Ogre::Vector3::UNIT_Y) * Ogre::Quaternion(mPitch, Ogre::Vector3::UNIT_X) * Ogre::Quaternion(mRoll, Ogre::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 Ogre::Quaternion() { return toQuaternion(); } // Set the yaw and pitch to face in the given direction. // The direction doesn't need to be normalised. // setYaw - if false, the yaw isn't changed. // setPitch - if false, the pitch isn't changed. // Roll is always unaffected. Euler &setDirection(const Ogre::Vector3 &v, bool setYaw = true, bool setPitch = true) { Ogre::Vector3 d(v.normalisedCopy()); if(setPitch) mPitch = Ogre::Math::ASin(d.y); if(setYaw) mYaw = Ogre::Math::ATan2(-d.x, -d.z);//+Ogre::Math::PI/2.0; mChanged = setYaw||setPitch; return *this; } // 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. // normYaw, normPitch, normRoll - only angles set to true are normalised. inline void normalise(bool normYaw = true, bool normPitch = true, bool normRoll = true) { if(normYaw) { Ogre::Real yaw = mYaw.valueRadians(); if(yaw < -Ogre::Math::PI) { yaw = fmod(yaw, Ogre::Math::PI * 2.0f); if(yaw < -Ogre::Math::PI) { yaw += Ogre::Math::PI * 2.0f; } mYaw = yaw; mChanged = true; } else if(yaw > Ogre::Math::PI) { yaw = fmod(yaw, Ogre::Math::PI * 2.0f); if(yaw > Ogre::Math::PI) { yaw -= Ogre::Math::PI * 2.0f; } mYaw = yaw; mChanged = true; } } if(normPitch) { Ogre::Real pitch = mPitch.valueRadians(); if(pitch < -Ogre::Math::PI) { pitch = fmod(pitch, Ogre::Math::PI * 2.0f); if(pitch < -Ogre::Math::PI) { pitch += Ogre::Math::PI * 2.0f; } mPitch = pitch; mChanged = true; } else if(pitch > Ogre::Math::PI) { pitch = fmod(pitch, Ogre::Math::PI * 2.0f); if(pitch > Ogre::Math::PI) { pitch -= Ogre::Math::PI * 2.0f; } mPitch = pitch; mChanged = true; } } if(normRoll) { Ogre::Real roll= mRoll.valueRadians(); if(roll < -Ogre::Math::PI) { roll = fmod(roll, Ogre::Math::PI * 2.0f); if(roll < -Ogre::Math::PI) { roll += Ogre::Math::PI * 2.0f; } mRoll = roll; mChanged = true; } else if(roll > Ogre::Math::PI) { roll = fmod(roll, Ogre::Math::PI * 2.0f); if(roll > Ogre::Math::PI) { roll -= Ogre::Math::PI * 2.0f; } mRoll = roll; mChanged = true; } } } // 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. // setYaw, setPitch - only the angles set to true are calculated. If false, the angle is set to 0. // 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. inline Euler getRotationTo(const Ogre::Vector3 &dir, bool setYaw = true, bool setPitch = true, bool shortest = true) { Euler t1; Euler t2; t1.setDirection(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 Ogre::Radian &limit) { if(mYaw > limit) { mYaw = limit; mChanged = true; } else if(mYaw < -limit) { mYaw = -limit; mChanged = true; } return *this; } // Clamp the pitch angle to a range of +/-limit. inline Euler &limitPitch(const Ogre::Radian &limit) { if(mPitch > limit) { mPitch = limit; mChanged = true; } else if(mPitch < -limit) { mPitch = -limit; mChanged = true; } return *this; } // Clamp the roll angle to a range of +/-limit. inline Euler &limitRoll(const Ogre::Radian &limit) { if(mRoll > limit) { mRoll = limit; mChanged = true; } else if(mRoll < -limit) { mRoll = -limit; mChanged = 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.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); } // Subtract two euler objects. 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*(Ogre::Real rhs) const { return Euler(mYaw * rhs, mPitch * rhs, mRoll * rhs); } // Interpolate the euler angle by lhs. inline friend Euler operator*(Ogre::Real lhs, const Euler &rhs) { return Euler(lhs * rhs.mYaw, lhs * rhs.mPitch, lhs * rhs.mRoll); } // Multiply two eulers. This has the same effect as multiplying quaternions. // The result is a quaternion. inline Quaternion operator*(const Euler &rhs) const { Euler e1(*this), e2(rhs); return e1.toQuaternion()*e2.toQuaternion(); } // Apply the euler rotation to the vector rhs. inline Ogre::Vector3 operator*(const Ogre::Vector3 &rhs) { return toQuaternion() * rhs; } protected: Ogre::Radian mYaw; // Rotation around the Y axis. Ogre::Radian mPitch; // Rotation around the X axis. Ogre::Radian mRoll; // Rotation around the Z axis. Ogre::Quaternion mCachedQuaternion; // Cached quaternion equivalent of this euler object. bool mChanged; // Is the cached quaternion out of date? }; } #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: Beauty
,
spacegaier
,
Kojack
,
jacmoe
and
Carignanboy
.
Page last modified on Monday 26 of March, 2012 22:49:26 UTC by Beauty
.
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.

