想象一個物體在3D空間中移動的過程,該物體必然會涉及到旋轉。例如一個怪物,他的運動方向會改變,要改變其方向只需要對其進行旋轉即可。

旋轉的方式大致分為三種:Euler旋轉,矩陣旋轉,以及四元數旋轉。
這里稍微記錄下我目前對于四元數旋轉的理解。對于四元數方面的數學,以及其原理,這里不關心,只需要學會如何使用即可。
無論是哪一種旋轉,物體與該物體的局部坐標系之間的相對位置,相對方位都是不會改變的。因此,在進行兩個局部旋轉(即相對于局部坐標系)時,要注意結果可能不是你預期的。

對于Euler旋轉,OGRE中為SceneNode提供了yaw, pitch, roll之類的接口。這些接口默認都是參照局部坐標系旋轉,可以通過第二個參數來指定,例如 yaw( Degree( 90 ), SceneNode::TS_WORLD );

OGRE中的Quaternion類用于四元數處理。該類(也可以說是四元數本身)有四個成員:x,y,z,w。這四個數分別代表什么?
在OGRE論壇上我找到了一些可以讓人很容易理解的信息:

Quaternions can seem pretty daunting because of the use of 'imaginary' numbers. It's much easier to understand if you just ignore this concept completely. The basic formula for creating a quaternion from angle/axis is:

Q = cos (angle/2) + i (x * sin(a/2)) + j (y * sin(a/2)) + k(z * sin(a/2))

or

Code:
  1. Q.w = cos (angle / 2)
  2. Q.x = axis.x * sin (angle / 2)
  3. Q.y = axis.y * sin (angle / 2)
  4. Q.z = axis.z * sin (angle / 2)


稍微忽略下那些復數之類的概念,使用角度/軸的方式創建四元數的公式為:
Q = cos (angle/2) + i (x * sin(a/2)) + j (y * sin(a/2)) + k(z * sin(a/2))

對應的代碼為:
  1. Q.w = cos (angle / 2)
  2. Q.x = axis.x * sin (angle / 2)
  3. Q.y = axis.y * sin (angle / 2)
  4. Q.z = axis.z * sin (angle / 2)


再看一下OGRE中關于Quaternion的一個構造四元數的函數源代碼:
  1. void Quaternion::FromAngleAxis (const Radian& rfAngle,const Vector3& rkAxis)
  2. {
  3. // assert: axis[] is unit length
  4. //
  5. // The quaternion representing the rotation is
  6. // q = cos(A/2)+sin(A/2)*(x*i+y*j+z*k)
  7. Radian fHalfAngle ( 0.5*rfAngle );
  8. Real fSin = Math::Sin(fHalfAngle);
  9.   w = Math::Cos(fHalfAngle);
  10.   x = fSin*rkAxis.x;
  11.   y = fSin*rkAxis.y;
  12.   z = fSin*rkAxis.z;
  13. }


雖然可以說四元數中的w代表旋轉量,x, y, z代表對應軸,但是這也不全正確。因為我們看到,對于真正的旋轉量啊之類的數據,是需要進行有些公式變換后,才得到w, x, y, z 的。

但是,即使如此,我們還是可以這樣簡單地構造一個四元數用于旋轉:
Quaternion q( Degree( -90 ), Vector3::UNIT_X );

該構造函數第一個參數指定旋轉角度,第二個參數指定旋轉軸(可能不是),上面的代碼就表示,饒著X軸(正X方向),旋轉-90度。將該四元數用于一個Scene Node旋轉:
sceneNode->rotate( q );
即可實現該node饒X軸旋轉-90度的效果。


再看一下OGRE tutorial中的一段代碼:
  1. Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;
  2. Ogre::Quaternion quat = src.getRotationTo(mDirection);
  3. mNode->rotate(quat);

SceneNode的getOrientation獲得該node的方位,用一個四元數來表示。什么是方位?這里我也不清楚,但是對于一個四元數,它這里表示的是一種旋轉偏移,偏移于初始朝向。

OGRE論壇上有這么一段話:

The reason there's no other way to convert a quaternion to a vector is because a quaternion is relative. It has no direction.
With a direction (like a vector) you could say "face north east".

But with a quaternion, you say "face 45 degrees clockwise from whatever direction you are already facing" (very simplified example). Without knowing which way the object is already facing, a quaternion is virtually meaningless with respect to orientation. So we just default it to some initial direction, like Unit Z, and make all orientations relative to that.

然后,getOrientation() * Vector3::UINT_X又會得到什么?我可以告訴你,第一句代碼整體的作用就是獲取該物體當前面向的方向。關于四元數與向量相乘,如圖所示:


可以進一步看出,四元數表示了一個旋轉偏移,它與一個向量相乘后就獲得了另一個向量。該結果向量代表了這個旋轉偏移所確定的方向。

那么第一句代碼中為什么要乘上UNIT_X呢?因為這里所代表的物體的初始朝向就是正X方向。

第二句話由向量構造一個四元數,它表示,從當前的朝向,旋轉到目的朝向所需要的一個四元數。第三句話就直接使用該四元數來旋轉該node。但是有時候似乎旋轉不正確(在我的實驗中,我使用的模型其初始朝向是負Y方向,在初始化時我又將其饒著X軸旋轉了負90度,后來旋轉時就不正確了),這可以通過 rotate( q, SceneNode::TS_WORLD)來矯正。(所以估計是之前旋轉導致的錯誤)(有時候我在想,為什么對于所有物體的旋轉之類的變換,都不直接參照于世界坐標系?因為我們最終看到的就是在世界坐標系中。)

注意,當旋轉角度是180度時,這里就會出現錯誤,為了防止這種錯誤,可以這樣做:
  1. Vector3 src = mNode->getOrientation() * Vector3::UNIT_X;
  2. if ((1.0f + src.dotProduct(mDirection)) < 0.0001f)
  3. {
  4.   mNode->yaw(Degree(180));
  5. }
  6. else
  7. {
  8.   Ogre::Quaternion quat = src.getRotationTo(mDirection);
  9.   mNode->rotate(quat);
  10. } // else