有些时候希望从一个向量A绕一给定轴旋转到另一个向量B,并对其进行插值(比如动画)。如果给定轴恰好为AB平面的法向量,则以其为旋转轴可以直接构建四元数,取旋转角度步长为1°或者更小即可。最大旋转角为AB向量之间的夹角。当步长累积超过最大旋转角时,停止插值。但是当给定轴是其他时,最大旋转角为多大呢?
如图所示,P为给定轴,现在要对A绕P旋转到B进行插值。实际上这个问题可以看做是PA平面绕P旋转到PB平面的问题。设PA法向量为N1,PB法向量为N2。因此最大旋转角为N1和N2的夹角(两个平面的夹角)。
示例代码如下:
// vCur为当前旋转向量(in/out),dRotateStep为旋转步长,vAxis为给定轴,vOri为起始向量(A),vTar为终止向量(B)
BOOL InpByQuaternion(Vertex3d& vCur,double dRotateStep,Vertex3d& vAxis,Vertex3d& vOri,Vertex3d& vTar)
{
Vertex3d vTempCur = vCur;
vTempCur.Normailize();
Vertex3d vTempNormalOri = vAxis ^ vOri; // 计算N1
Vertex3d vTempNormalTar = vAxis ^ vTar; // 计算N2
Vertex3d vTempNormalCur = vAxis ^ vTempCur; // 计算当前N
double dRotateAlpha = ALineWithLine(vTempNormalOri,vTempNormalCur);
// 计算最大旋转角
double dMaxAlpha = ALineWithLine(vTempNormalOri,vTempNormalTar);
if((dRotateAlpha - dMaxAlpha) > -1e-6)
return FALSE;
double dTempAlpha = dRotateAlpha + dRotateStep;
double dLength = vCur.Length();
double dStep = dRotateStep;
if(dTempAlpha < dMaxAlpha)
dStep = dRotateStep;
else
dStep = dMaxAlpha - dRotateAlpha;
Vertex3d vNewAxis = vTempNormalOri ^ vTempNormalTar;
vNewAxis.Normailize();
Quaternion qq(dStep,vNewAxis);
vTempCur = qq * vCur;
vTempCur.Normailize();
vTempCur *= dLength;
vCur = vTempCur;
return TRUE;
}
下面是一些辅助小函数
double ALineWithLine(Vertex3d& vVec1,Vertex3d& vVec2)
{
return vVec1.GetCrossAcuteAngle(vVec2);
}
double Vertex3d::GetCrossAcuteAngle(const Vertex3d& rhv) const
{
double dCosAngle = GetCrossAcuteAngleCos(rhv);
double Angle;
// 计算注意,考虑到acos函数只能计算-1到1之间,因此对于1和-1分开处理
if(fabs(dCosAngle - 1) < MathEx::TOL)
Angle = 0;
else if(fabs(dCosAngle + 1) < MathEx::TOL)
Angle = MathEx::ONE_PI;
else
Angle = acos( dCosAngle );
return Angle;
}
double Vertex3d::GetCrossAcuteAngleCos(const Vertex3d& rhv) const
{
double ddd = Length() * rhv.Length();
if(fabs(ddd) < MathEx::TOL)
return 1; // 0向量与任意向量平行,其夹角可以认为是0°,cos值为1
return fabs(((*this) * rhv)) / ddd;
}
评论