例如:
假设:x = 3 + i, y = 5i + j - 2k
那么:
x + y = 3 + 6i + j - 2k
xy =( {3 + i} )( {5i + j - 2k} ) = 15i + 3j - 6k + 5i^2 + ij - 2ik
= 15i + 3j - 6k - 5 + k + 2j = - 5 + 15i + 5j - 5k
四元数也可以表示为:
q=[w,v],
其中v=(x,y,z)是矢量,w是标量。
如果向量部分外积为零,两个四元数的乘积就可交换了。
4.模的定义
四元数也是可以归一化的,四元数的单位化与Vector类似,首先
(a^2+b^2+c^2+d^2)的平方根 称为四元数的模,即||q|| = Norm(q)=sqrt(w2 + x2 + y2 + z2),这里2指平方, 如w2指w的平方。
因为w2 + x2 + y2 + z2=1
所以Normlize(q)=q/Norm(q)=q / sqrt(w2 + x2 + y2 + z2)
5.共轭定义
若p=w+xi+yj+zk=w+v,则p*=w-xi-yj-zk=w-v 表示与p实部相等,向量部分相反的四元数,称为p的共轭。
6.逆的定义
如果
,
二、四元数与三维空间的旋转
我们要关心的是三维空间上任意的伸缩旋转变换是否可用四元数的乘积来表示,而这一点对四元数来说是完全能够胜任的。
如果已知一个三维空间的伸缩旋转的转轴方向、旋转角度和伸缩比例,来求相应的四元数,是比较容易的。
特别地,单位化的四元数用来描述旋转:
以原点为旋转中心,旋转的轴是(α, β, γ)( α^2 + β^2 + γ^2 = 1),
(右手系的坐标定义的话,望向向量(α, β, γ)的前进方向反时针) 转θ角的旋转,用四元数表示就是,
Q = (cos(θ/2); α sin(θ/2), β sin(θ/2), γ sin(θ/2))
四元数的乘法的意义类似于Matrix的乘法-可以将两个旋转合并,例如:
Q=Q1*Q2
表示Q的是先做Q2的旋转,再做Q1的旋转的结果,而多个四元数的旋转也是可以合并的,当有多次旋转操作时,使用四元数可以获得更高的计算效率。
例子代码
/// Quaternion.cpp
#include <math.h>
#include <iostream.h>
/// Define Data type
typedef struct
{
double t; // real-component
double x; // x-component
double y; // y-component
double z; // z-component
} quaternion;
//// Bill 注:Kakezan 在日语里是 “乘法”的意思
quaternion Kakezan(quaternion left, quaternion right)
{
quaternion ans;
double d1, d2, d3, d4;
d1 = left.t * right.t;
d2 = -left.x * right.x;
d3 = -left.y * right.y;
d4 = -left.z * right.z;
ans.t = d1+ d2+ d3+ d4;
d1 = left.t * right.x;
d2 = right.t * left.x;
d3 = left.y * right.z;
d4 = -left.z * right.y;
ans.x = d1+ d2+ d3+ d4;
d1 = left.t * right.y;
d2 = right.t * left.y;
d3 = left.z * right.x;
d4 = -left.x * right.z;
ans.y = d1+ d2+ d3+ d4;
d1 = left.t * right.z;
d2 = right.t * left.z;
d3 = left.x * right.y;
d4 = -left.y * right.x;
ans.z = d1+ d2+ d3+ d4;
return ans;
}
//// Make Rotational quaternion
quaternion MakeRotationalQuaternion(double radian, double AxisX, double AxisY, double AxisZ)
{
quaternion ans;
double norm;
double ccc, sss;
ans.t = ans.x = ans.y = ans.z = 0.0;
norm = AxisX * AxisX + AxisY * AxisY + AxisZ * AxisZ;
if(norm <= 0.0) return ans;
norm = 1.0 / sqrt(norm);
AxisX *= norm;
AxisY *= norm;
AxisZ *= norm;
ccc = cos(0.5 * radian);
sss = sin(0.5 * radian);
ans.t = ccc;
ans.x = sss * AxisX;
ans.y = sss * AxisY;
ans.z = sss * AxisZ;
return ans;
}
//// Put XYZ into quaternion
quaternion PutXYZToQuaternion(double PosX, double PosY, double PosZ)
{
quaternion ans;
ans.t = 0.0;
ans.x = PosX;
ans.y = PosY;
ans.z = PosZ;
return ans;
}
///// main
int main()
{
double px, py, pz;
double ax, ay, az, th;
quaternion ppp, qqq, rrr;
cout << "Point Position (x, y, z) " << endl;
cout << " x = ";
cin >> px;
cout << " y = ";
cin >> py;
cout << " z = ";
cin >> pz;
ppp = PutXYZToQuaternion(px, py, pz);
while(1) {
cout << "\nRotation Degree ? (Enter 0 to Quit) " << endl;
cout << " angle = ";
cin >> th;
if(th == 0.0) break;
cout << "Rotation Axis Direction ? (x, y, z) " << endl;
cout << " x = ";
cin >> ax;
cout << " y = ";
cin >> ay;
cout << " z = ";
cin >> az;
th *= 3.1415926535897932384626433832795 / 180.0; /// Degree -> radian;
qqq = MakeRotationalQuaternion(th, ax, ay, az);
rrr = MakeRotationalQuaternion(-th, ax, ay, az);
ppp = Kakezan(rrr, ppp);
ppp = Kakezan(ppp, qqq);
cout << "\nAnser X = " << ppp.x
<< "\n Y = " << ppp.y
<< "\n Z = " << ppp.z << endl;
}
return 0;
}
三、关于插值
使用四元数的原因就是在于它非常适合插值,这是因为他是一个可以规格化的4维向量,最简单的插值算法就是线性插值,公式如:
q(t)=(1-t)q1+t q2
一般线性插值由于长度发生变化,不能满足要求,我们需要保持向量长度不变的插值,即球面线性插值。
q(t)=((1-tq1+tq2 )/ ||(1-tq1+tq2||
这样,插值向量v(t)的端点就会沿着v1,v2端点构成的圆弧行进。因为v1,v2是等长的,这个圆弧实际上是位于v1,v2构成的球面上的一段,所以又叫球面线性插值。
但是,由于它的插值不是等角速度的,而是变速的。
要想进行等速的球面线性插值,可以
用四元数工具:
参考郑军“四元数插值算法实现游戏角色平滑旋转”一文:
主要程序代码如下所示:
double x0,y0,z0,wO;
double xl,yl,zl,wl;
//参数t为插值变量
double t;
//在参数t下输出一个四元数,得到一个插值方位
double x,y,z,w;
//计算开始方位和结束方位的点积
double cosO=xO*xl+y0*yl+zO*zl+WO*wl;
if(cosO<0){
xO=一xO;
yo=一yO;
zO=一zO;
wO=一wo;
cosO=一eosO;
}
//进行插值运算
double kO,kl;
if(cosO>0.9999){
kO=1一t;
kl=t;
}else{
double sinO=Mathf.Sqrt(1一Mathf.Pow(cosO,2));
double O=Mathf.Atan2(sinO,cosO);
float one=1.O/sinO;
kO=Mathf.Sin((1一t)
*
O)
*
one;
kl=Mathf.Sin(t
*
O)
*
one;
}
//输出在t参数下的插值结果
x=xO
*
kO+xl
*
kl:
Z=zO
*
kO+zl
*
kl:
w=wo
*
kO+wl
*
kl: