DXUT中CD3DArcBall原理解析
ArcBall主要实现了,用户通过在窗口中拖动鼠标来旋转观察目标。该效果的实现经过了3次空间变换,如下图
窗口空间是二维空间,以窗口中心为原点,右上分别为X,Y轴正方向;单位球空间是三维空间,以窗口中心点为原点,坐标轴方向按DirectX的左手规则,但各轴长度限制为1,并且Z只取正半轴,也就是说,该空间是面向纸里的单位半球空间;模型空间就是被观察目标的本地空间了。用户在窗口中拖动鼠标,从起点S1拖到终点E1,这时,单位球空间的S2旋转到E2,而模型空间的S3则旋转到E3。将二维(窗口)空间的拖动映射到三维(单位球)空间的旋转,我们需要进行转换(1),而将单位球空间的旋转转换到模型空间的旋转,我们需要进行转换(2)。转换(1)主要通过三值标准化来将二维中的坐标运动转化为三维中的坐标运动,而转换(2)则根据实际情况用四元数来进行旋转放大。
转换(1)的程序代码为:
D3DXVECTOR3 CD3DArcBall::ScreenToVector(float fScreenPtX, float fScreenPtY)
{
FLOAT x = -(fScreenPtX - m_Offset.x - m_nWidth/2) / (m_fRadius*m_nWidth/2);
FLOAT y = (fScreenPtY - m_Offset.y - m_nHeight/2) / (m_fRadius*m_nHeight/2);
FLOAT z = 0.0f;
FLOAT mag = x*x + y*y;
if (mag > 1.0f)
{
FLOAT scale = 1.0f/sqrtf(mag);
x *= scale;
y *= scale;
}
else
z = sqrtf(1.0f - mag);
return D3DXVECTOR3(x, y, z);
}
三值标准化,将二维平面上点的运动转换为三维单位球上点的运动。各分量具体的计算方法为:
X:标准化到原点的距离,由于转换到三维空间后方向相反,所以取反
Y:标准化到原点的距离,转换到三维空间后方向相同
Z:根据三值标准化计算出Z。当X或Y超出限定范围时,三值标准化被破坏,直接取Z为0,然后标准化X和Y,这样就重新达到三值标准化;正常情况下,直接根据标准化公式计算Z
转换(2)的程序代码为:
D3DXQUATERNION CD3DArcBall::QuatFromBallPoints(const D3DXVECTOR3 &vFrom, const D3DXVECTOR3 &vTo)
{
D3DXVECTOR3 vPart;
float fDot = D3DXVec3Dot(&vFrom, &vTo);
D3DXVec3Cross(&vPart, &vFrom, &vTo);
return D3DXQUATERNION(vPart.x, vPart.y, vPart.z, fDot);
}
实际情况中,二维(窗口)空间中的拖动最终映射到三维(模型)空间中360度的旋转,也就是说,在二维空间中X半区间的拖动要映射为180度,而X半区间的拖动映射到单位球空间为90度旋转,因此,从单位球空间到模型空间的转换(2)要将旋转角度放大2倍。用四元数来代表旋转,旋转轴通过叉乘来求,旋转角通过点乘来求。vFrom和vTo都是转换(1)的输出,是标准化向量,因此,它们的叉乘结果满足2倍旋转角度的四元数的旋转轴,并且,点乘结果也满足2倍旋转角度的四元数的旋转角。四元数的计算为:
四元数公式: q = v*sin(angle/2)+cos(angle/2) -> v*sin(angle)+cos(angle)
叉乘结果: |v1| |v2| sin(angle)*v -> sin(angle)*v
点乘结果: |v1| |v2| cos(angle) -> cos(angle)
总的来说,ArcBall的原理就是二维空间中的拖动,通过三值标准化转换为三维单位球空间的旋转,然后再通过使用叉乘和点乘结果来组合成四元数,来完成到模型空间的2倍旋转角度的放大。