有些时候希望从一个向量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; }

评论