• Create Account

Banner advertising on our site currently available from just \$5!

### #ActualL. Spiro

Posted 14 July 2013 - 11:52 PM

Orientation

Objects in the 3D world of your game will need some representation of their orientation.  In geometry, orientation refers only to the rotational aspect of a body, but in video games orientation is often defined as their translations, scales, and rotations combined.  While the representations for translations and scales are straightforward (3-dimensional vectors), representing rotation is often a fuzzy area that is easy to get wrong even though there is no single best way to get it right.  One way that inexperienced programmers will often try is to use Euler angles representing how far left/right and up/down the object is facing.  This is especially often used on players and cameras, exemplifying another common mistake in which players and cameras have a different orientation system from the other game objects.  This makes it easy to modify rotations but makes the run-time slow, as updating objects over time requires an expensive sine and cosine each frame for each object.

It is generally best to use vectors for the rotation part of an orientation, but then the question becomes a matter of which vectors to store.  Some implementations save space by storing only the Forward and Up vectors and determine the Right vector when needed with a slight penalty to performance.  Because this book focuses on run-time performance and because most devices these days, including iOS devices, the primary focus of this book, have substantial memory, our implementation will also store the Right vector.  The overhead in updating objects’ rotations becomes greater, but most objects in a game scene will often be static with only a few objects updating at all, and fewer still updating frequently.  On the other hand, time is saved when generating matrices for rendering since the Right vector does not need to be re-determined each time.

The shell for the COrientation class is fairly straightforward.  It contains each of the properties an orientation needs—position, scale, and rotation—along with a dirty flag to let us know what, if anything, has been updated.  The dirty flag allows us to rebuild matrices only if they actually change, saving a lot of computational expense on objects that rarely or never move.

Listing 2.13  TKFOrientation.h

#include "../TKFFnd.h"

#include "../Matrix/TKFMatrix44.h"

#include "../Vector/TKFVector3.h"

namespace tkf {

class COrientation {

public :

COrientation();

// == Functions.

/** Gets/sets the position. */

inline CVector3 &       Pos() {

m_u32Dirty |= TKF_DF_POS;

return m_vPos;

}

/** Gets the position. */

inline const CVector3 & Pos() const {

return m_vPos;

}

/** Gets/sets the scale. */

inline CVector3 &       Scale() {

m_u32Dirty |= TKF_DF_SCALE;

return m_vScale;

}

/** Gets the scale. */

inline const CVector3 & Scale() const {

return m_vScale;

}

/** Checks the dirty flag. */

inline bool             IsDirty() const {

return m_u32Dirty != 0;

}

protected :

// == Enumerations.

/** Dirty flags. */

enum TKF_DIRTY_FLAGS {

TKF_DF_POS          = (1 << 0),

TKF_DF_SCALE        = (1 << 1),

TKF_DF_ROT          = (1 << 2),

TKF_DF_NORMALIZE    = (1 << 3),

};

// == Members.

/** Position. */

CVector3                m_vPos;

/** Scale. */

CVector3                m_vScale;

/** Right vector. */

CVector3                m_vRight;

/** Up vector. */

CVector3                m_vUp;

/** Forward vector. */

CVector3                m_vForward;

/** Dirty flag. */

u32                     m_u32Dirty;

};

}    // namespace tkf

Our use of const-correctness allows us to dirty the position and scale only if they are accessed for writing and not for reading.  The dirty flag also has a bit for normalization of the rotations, another expensive operation we wish to avoid if possible.

So far the COrientation class is very straightforward.  Each frame, objects’ orientations will be updated and then a matrix will be built for that object to pass to the renderer.  Building the matrix is also very straightforward, and is as follows.

Listing 2.14  TKFOrientation.cpp

/** Creates the matrix representing this orientation. */

void COrientation::CreateMatrix( CMatrix44 &_mMatrix ) const {

_mMatrix._11 = m_vRight.x * m_vScale.x;

_mMatrix._12 = m_vRight.y * m_vScale.x;

_mMatrix._13 = m_vRight.z * m_vScale.x;

_mMatrix._14 = 0.0f;

_mMatrix._21 = m_vUp.x * m_vScale.y;

_mMatrix._22 = m_vUp.y * m_vScale.y;

_mMatrix._23 = m_vUp.z * m_vScale.y;

_mMatrix._24 = 0.0f;

_mMatrix._31 = m_vForward.x * m_vScale.z;

_mMatrix._32 = m_vForward.y * m_vScale.z;

_mMatrix._33 = m_vForward.z * m_vScale.z;

_mMatrix._34 = 0.0f;

_mMatrix._41 = m_vPos.x;

_mMatrix._42 = m_vPos.y;

_mMatrix._43 = m_vPos.z;

_mMatrix._44 = 1.0f;

}

Additionally, a function for updating a matrix only if the dirty flag is set is added.  It clears the dirty flag after updating the matrix.

Listing 2.15  TKFOrientation.cpp

/** Updates the given matrix only if the dirty flag is set. */

inline void COrientation::UpdateMatrix( CMatrix44 &_mMatrix ) const {

if ( IsDirty() ) {

Normalize();       // Normalizes rotation vectors if flag set.

CreateMatrix( _mMatrix );

m_u32Dirty = 0UL;

}

}

Working with and updating rotations is a bit more involved but mainly revolves around keeping the 3 vectors orthogonal, which in itself is actually fairly trivial.  The most important features for the interface to have are those that allow an object to point at another object, to set the rotation directly, and to rotate around an axis by a given amount.  Just as it is easy to create a matrix with the rotation vectors as shown in the CreateMatrix() method it is easy to extract them, so we will start with the simplest methods first.

Listing 2.16  TKFOrientation.cpp

/** Sets the rotation given a matrix. */

void COrientation::SetRot( const CMatrix44 &_mMatrix ) {

m_vRight = _mMatrix.GetRow( 0 );

m_vUp = _mMatrix.GetRow( 1 );

m_vForward = _mMatrix.GetRow( 2 );

m_u32Dirty |= TKF_DF_ROT | TKF_DF_NORMALIZE;

}

Setting the rotation via a forward and up vector, like in common “look at” functions, is also straightforward.

Listing 2.17  TKFOrientation.cpp

/** Sets the rotation given a a forward and up vector. */

void COrientation::SetRot( const CVector3 &_vForward,
const CVector3 &_vUp ) {

m_vForward = _vForward;

m_vRight = _vUp % _vForward;

m_vUp = _vForward % m_vRight;

m_u32Dirty |= TKF_DF_ROT | TKF_DF_NORMALIZE;

}

Adding rotations is only slightly more difficult but it is an important foundation for other routines, such as the one we will later add to rotate around an arbitrary axis.  The most useful routine for our purposes will be to add a rotation given by a matrix.  The code looks daunting, but it’s not too complicated.

Listing 2.18  TKFOrientation.cpp

/** Adds a rotation from a matrix. */

void COrientation::AddRot( const CMatrix44 &_mMatrix ) {

m_vRight = CVector3(

m_vRight * CVector3( _mMatrix._11, _mMatrix._21, _mMatrix._31 ),

m_vRight * CVector3( _mMatrix._12, _mMatrix._22, _mMatrix._32 ),

m_vRight * CVector3( _mMatrix._13, _mMatrix._23, _mMatrix._33 ) );

m_vForward = CVector3(

m_vForward * CVector3( _mMatrix._11, _mMatrix._21, _mMatrix._31 ),

m_vForward * CVector3( _mMatrix._12, _mMatrix._22, _mMatrix._32 ),

m_vForward * CVector3( _mMatrix._13, _mMatrix._23, _mMatrix._33 ) );

m_vUp = CVector3(

m_vUp * CVector3( _mMatrix._11, _mMatrix._21, _mMatrix._31 ),

m_vUp * CVector3( _mMatrix._12, _mMatrix._22, _mMatrix._32 ),

m_vUp * CVector3( _mMatrix._13, _mMatrix._23, _mMatrix._33 ) );

m_u32Dirty |= TKF_DF_ROT | TKF_DF_NORMALIZE;

}

With this method in place, rotating around an arbitrary axis is fairly simple.  We simply construct an axis-rotation matrix and pass it to this method.

Listing 2.19  TKFOrientation.cpp

/** Rotates by the given amount around the given axis. */

void COrientation::RotateAxis( const CVector3 &_vAxis,

CMatrix44 mRot;

}

The code for CMatrix44::RotationAxis() is shown here for completeness.

Listing 2.19  TKFMatrix44.cpp

/** Builds a matrix representing a rotation around an arbitrary axis. */

CMatrix44 & CMatrix44::RotationAxis( const CVector3 &_vAxis,

f32 fS = ::sinf( _f32Radians );

f32 fC = ::cosf( _f32Radians );

f32 fT = 1.0f - fC;

register f32 fTX = fT * _vAxis.x;

register f32 fTY = fT * _vAxis.y;

register f32 fTZ = fT * _vAxis.z;

f32 fSX = fS * _vAxis.x;

f32 fSY = fS * _vAxis.y;

f32 fSZ = fS * _vAxis.z;

_11 = fTX * _vAxis.x + fC;

_12 = fTX * _vAxis.y + fSZ;

_13 = fTX * _vAxis.z - fSY;

_14 = 0.0f;

_21 = fTY * _vAxis.x - fSZ;

_22 = fTY * _vAxis.y + fC;

_23 = fTY * _vAxis.z + fSX;

_24 = 0.0f;

_31 = fTZ * _vAxis.x + fSY;

_32 = fTZ * _vAxis.y - fSX;

_33 = fTZ * _vAxis.z + fC;

_34 = 0.0f;

_41 = 0.0f;

_42 = 0.0f;

_43 = 0.0f;

_44 = 1.0f;

return (*this);

}

These methods give the orientation class a solid foundation for use within the framework.  All entities in our game world, including the camera, will make use of this utility class.

L. Spiro

### #1L. Spiro

Posted 14 July 2013 - 11:39 PM

Orientation

Objects in the 3D world of your game will need some representation of their orientation.  In geometry, orientation refers only to the rotational aspect of a body, but in video games orientation is often defined as their translations, scales, and rotations combined.  While the representations for translations and scales are straightforward (3-dimensional vectors), representing rotation is often a fuzzy area that is easy to get wrong even though there is no single best way to get it right.  One way that inexperienced programmers will often try is to use Euler angles representing how far left/right and up/down the object is facing.  This is especially often used on players and cameras, exemplifying another common mistake in which players and cameras have a different orientation system from the other game objects.  This makes it easy to modify rotations but makes the run-time slow, as updating objects over time requires an expensive sine and cosine each frame for each object.

It is generally best to use vectors for the rotation part of an orientation, but then the question becomes a matter of which vectors to store.  Some implementations save space by storing only the Forward and Up vectors and determine the Right vector when needed with a slight penalty to performance.  Because this book focuses on run-time performance and because most devices these days, including iOS devices, the primary focus of this book, have substantial memory, our implementation will also store the Right vector.  The overhead in updating objects’ rotations becomes greater, but most objects in a game scene will often be static with only a few objects updating at all, and fewer still updating frequently.  On the other hand, time is saved when generating matrices for rendering since the Right vector does not need to be re-determined each time.

The shell for the COrientation class is fairly straightforward.  It contains each of the properties an orientation needs—position, scale, and rotation—along with a dirty flag to let us know what, if anything, has been updated.  The dirty flag allows us to rebuild matrices only if they actually change, saving a lot of computational expense on objects that rarely or never move.

Listing 2.13  TKFOrientation.h

#include "../TKFFnd.h"

#include "../Matrix/TKFMatrix44.h"

#include "../Vector/TKFVector3.h"

namespace tkf {

class COrientation {

public :

COrientation();

// == Functions.

/** Gets/sets the position. */

inline CVector3 &       Pos() {

m_u32Dirty |= TKF_DF_POS;

return m_vPos;

}

/** Gets the position. */

inline const CVector3 & Pos() const {

return m_vPos;

}

/** Gets/sets the scale. */

inline CVector3 &       Scale() {

m_u32Dirty |= TKF_DF_SCALE;

return m_vScale;

}

/** Gets the scale. */

inline const CVector3 & Scale() const {

return m_vScale;

}

/** Checks the dirty flag. */

inline bool             IsDirty() const {

return m_u32Dirty != 0;

}

protected :

// == Enumerations.

/** Dirty flags. */

enum TKF_DIRTY_FLAGS {

TKF_DF_POS          = (1 << 0),

TKF_DF_SCALE        = (1 << 1),

TKF_DF_ROT          = (1 << 2),

TKF_DF_NORMALIZE    = (1 << 3),

};

// == Members.

/** Position. */

CVector3                m_vPos;

/** Scale. */

CVector3                m_vScale;

/** Right vector. */

CVector3                m_vRight;

/** Up vector. */

CVector3                m_vUp;

/** Forward vector. */

CVector3                m_vForward;

/** Dirty flag. */

u32                     m_u32Dirty;

};

}    // namespace tkf

Our use of const-correctness allows us to dirty the position and scale only if they are accessed for writing and not for reading.  The dirty flag also has a bit for normalization of the rotations, another expensive operation we wish to avoid if possible.

So far the COrientation class is very straightforward.  Each frame, objects’ orientations will be updated and then a matrix will be built for that object to pass to the renderer.  Building the matrix is also very straightforward, and is as follows.

Listing 2.14  TKFOrientation.cpp

/** Creates the matrix representing this orientation. */

void COrientation::CreateMatrix( CMatrix44 &_mMatrix ) const {

_mMatrix._11 = m_vRight.x * m_vScale.x;

_mMatrix._12 = m_vRight.y * m_vScale.x;

_mMatrix._13 = m_vRight.z * m_vScale.x;

_mMatrix._14 = 0.0f;

_mMatrix._21 = m_vUp.x * m_vScale.y;

_mMatrix._22 = m_vUp.y * m_vScale.y;

_mMatrix._23 = m_vUp.z * m_vScale.y;

_mMatrix._24 = 0.0f;

_mMatrix._31 = m_vForward.x * m_vScale.z;

_mMatrix._32 = m_vForward.y * m_vScale.z;

_mMatrix._33 = m_vForward.z * m_vScale.z;

_mMatrix._34 = 0.0f;

_mMatrix._41 = m_vPos.x;

_mMatrix._42 = m_vPos.y;

_mMatrix._43 = m_vPos.z;

_mMatrix._44 = 1.0f;

}

Additionally, a function for updating a matrix only if the dirty flag is set is added.  It clears the dirty flag after updating the matrix.

Listing 2.15  TKFOrientation.cpp

/** Updates the given matrix only if the dirty flag is set. */

inline void COrientation::UpdateMatrix( CMatrix44 &_mMatrix ) const {

if ( IsDirty() ) {

Normalize();       // Normalizes rotation vectors if flag set.

CreateMatrix( _mMatrix );

m_u32Dirty = 0UL;

}

}

Working with and updating rotations is a bit more involved but mainly revolves around keeping the 3 vectors orthogonal, which in itself is actually fairly trivial.  The most important features for the interface to have are those that allow an object to point at another object, to set the rotation directly, and to rotate around an axis by a given amount.  Just as it is easy to create a matrix with the rotation vectors as shown in the CreateMatrix() method it is easy to extract them, so we will start with the simplest methods first.

Listing 2.16  TKFOrientation.cpp

/** Sets the rotation given a matrix. */

void COrientation::SetRot( const CMatrix44 &_mMatrix ) {

m_vRight = _mMatrix.GetRow( 0 );

m_vUp = _mMatrix.GetRow( 1 );

m_vForward = _mMatrix.GetRow( 2 );

m_u32Dirty |= TKF_DF_ROT | TKF_DF_NORMALIZE;

}

Setting the rotation via a forward and up vector, like in common “look at” functions, is also straightforward.

Listing 2.17  TKFOrientation.cpp

/** Sets the rotation given a a forward and up vector. */

void COrientation::SetRot( const CVector3 &_vForward,
const CVector3 &_vUp ) {

m_vForward = _vForward;

m_vRight = _vUp % _vForward;

m_vUp = _vForward % m_vRight;

m_u32Dirty |= TKF_DF_ROT | TKF_DF_NORMALIZE;

}

Adding rotations is only slightly more difficult but it is an important foundation for other routines, such as the one we will later add to rotate around an arbitrary axis.  The most useful routine for our purposes will be to add a rotation given by a matrix.  The code looks daunting, but it’s not too complicated.

Listing 2.18  TKFOrientation.cpp

/** Adds a rotation from a matrix. */

void COrientation::AddRot( const CMatrix44 &_mMatrix ) {

m_vRight = CVector3(

m_vRight * CVector3( _mMatrix._11, _mMatrix._21, _mMatrix._31 ),

m_vRight * CVector3( _mMatrix._12, _mMatrix._22, _mMatrix._32 ),

m_vRight * CVector3( _mMatrix._13, _mMatrix._23, _mMatrix._33 ) );

m_vForward = CVector3(

m_vForward * CVector3( _mMatrix._11, _mMatrix._21, _mMatrix._31 ),

m_vForward * CVector3( _mMatrix._12, _mMatrix._22, _mMatrix._32 ),

m_vForward * CVector3( _mMatrix._13, _mMatrix._23, _mMatrix._33 ) );

m_vUp = CVector3(

m_vUp * CVector3( _mMatrix._11, _mMatrix._21, _mMatrix._31 ),

m_vUp * CVector3( _mMatrix._12, _mMatrix._22, _mMatrix._32 ),

m_vUp * CVector3( _mMatrix._13, _mMatrix._23, _mMatrix._33 ) );

m_u32Dirty |= TKF_DF_ROT | TKF_DF_NORMALIZE;

}

With this method in place, rotating around an arbitrary axis is fairly simple.  We simply construct an axis-rotation matrix and pass it to this method.

Listing 2.19  TKFOrientation.cpp

/** Rotates by the given amount around the given axis. */

void COrientation::RotateAxis( const CVector3 &_vAxis,

CMatrix44 mRot;

}

The code for CMatrix44::RotationAxis() is shown here for completeness.

Listing 2.19  TKFMatrix44.cpp

/** Builds a matrix representing a rotation around an arbitrary axis. */

CMatrix44 & CMatrix44::RotationAxis( const CVector3 &_vAxis,

f32 fS = ::sinf( _f32Radians );

f32 fC = ::cosf( _f32Radians );

f32 fT = 1.0f - fC;

register f32 fTX = fT * _vAxis.x;

register f32 fTY = fT * _vAxis.y;

register f32 fTZ = fT * _vAxis.z;

f32 fSX = fS * _vAxis.x;

f32 fSY = fS * _vAxis.y;

f32 fSZ = fS * _vAxis.z;

_11 = fTX * _vAxis.x + fC;

_12 = fTX * _vAxis.y + fSZ;

_13 = fTX * _vAxis.z - fSY;

_14 = 0.0f;

_21 = fTY * _vAxis.x - fSZ;

_22 = fTY * _vAxis.y + fC;

_23 = fTY * _vAxis.z + fSX;

_24 = 0.0f;

_31 = fTZ * _vAxis.x + fSY;

_32 = fTZ * _vAxis.y - fSX;

_33 = fTZ * _vAxis.z + fC;

_34 = 0.0f;

_41 = 0.0f;

_42 = 0.0f;

_43 = 0.0f;

_44 = 1.0f;

return (*this);

}

These methods give the orientation class a solid foundation for use within the framework.  All entities in our game world, including the camera, will make use of this utility class.

L. Spiro

PARTNERS