微软DX库里提供了对Quaternion进行球面四边形内插的接口,能够在多个Quaternion之间平滑插值。这里涉及到两个接口:
void D3DXQuaternionSquadSetup(
__out D3DXQUATERNION *pAOut,
__out D3DXQUATERNION *pBOut,
__out D3DXQUATERNION *pCOut,
__in const D3DXQUATERNION *pQ0,
__in const D3DXQUATERNION *pQ1,
__in const D3DXQUATERNION *pQ2,
__in const D3DXQUATERNION *pQ3
);
D3DXQUATERNION* D3DXQuaternionSquad(
__inout D3DXQUATERNION *pOut,
__in const D3DXQUATERNION *pQ1,
__in const D3DXQUATERNION *pA,
__in const D3DXQUATERNION *pB,
__in const D3DXQUATERNION *pC,
__in FLOAT t
);
其中,D3DXQuaternionSquadSetup用于返回内插的控制点。它们具体的实现公式和用法,有兴趣的同学可以参考MSDN。在此需要说明的是,
D3DXQuaternionSquad使用了Slerp作为内部实现,会导致在两个夹角为180°左右的Quaternion之间插值会出现断裂的问题。下面代码通过
实现一个考虑了上述情况的Slerp版本,在q1和q2夹角在0°或者180°时,使用线性内插而非球面,来解决该问题。
- Quaternion QuatSlerpNoInvert(const Quaternion& q1 , const Quaternion& q2 , float t)
- {
- float cosAngle = DotProduct(q1, q2);
-
- float c1, c2;
- // Linear interpolation for close orientations
- if ((1.0f - fabs(cosAngle)) < 1e-5f)
- {
- c1 = 1.0f - t;
- c2 = t;
- }
- else
- {
- // Spherical interpolation
- float angle = acos(fabs(cosAngle));
- float sinAngle = sin(angle);
- c1 = sin(angle * (1.0f - t)) / sinAngle;
- c2 = sin(angle * t) / sinAngle;
- }
-
- Quaternion q = q1 * c1 + q2 * c2;
- q.Normalize();
-
- return q;
- }
-
- Quaternion QuatSquad(const Quaternion& p1 , const Quaternion& p2 , const Quaternion& p3 , const Quaternion& p4 , float t)
- {
- static Quaternion a , b , c;
-
- D3DXQuaternionSquadSetup((D3DXQUATERNION*)&a , (D3DXQUATERNION*)&b , (D3DXQUATERNION*)&c ,
- (D3DXQUATERNION*)&p1 , (D3DXQUATERNION*)&p2 , (D3DXQUATERNION*)&p3 , (D3DXQUATERNION*)&p4);
-
- return QuatSlerpNoInvert(QuatSlerpNoInvert(p2 , c , t) , QuatSlerpNoInvert(a , b , t) , 2 * t * (1-t));
- }
参考:
[1] http://msdn.microsoft.com/en-us/library/bb205419(v=vs.85).aspx
[2] http://msdn.microsoft.com/en-us/library/bb205420(v=vs.85).aspx