编程实现自由旋转的方法和经验
1、首先建立一个虚拟球类
用面向对象的方法来解决问题,能使解决方案有很好的可移植性和可维护性。而VC++是功能强大的面向对象编程的工具,所以我们使用VC++面向对象程序设计的方案来实现自由旋转功能。虚拟球类的声明如下:
class VirtualBall
{
protected:
void _mapToSphere(const Point2fT* NewPt, Vector3fT* NewVec) const;
public: //构造和析构函数
VirtualBall(GLfloat NewWidth, GLfloat NewHeight);
~VirtualBall() { /* 不做任何事*/ };
//设置边界, 当窗口大小改变时,使虚拟球与窗口大小相适应
void setBounds(GLfloat NewWidth, GLfloat NewHeight)
void click(const Point2fT* NewPt);// 鼠标按下,映射起始点到虚拟球
//鼠标拖动,第二个鼠标坐标在这里得到更新,并映射到虚拟球上,计算旋转
//轴的向量和夹角的信息,将它们保存到一个四元数NewRot中(前3个元素为
//坐标信息,最后一个元素为关于夹角的信息,其实就是两个向量的点乘)
void drag(const Point2fT* NewPt, Quat4fT* NewRot);
protected:
Vector3fT StVec; //保存鼠标点击时的向量(起始点)
Vector3fT EnVec; //保存拖动时的向量(终点)
GLfloat AdjustWidth; //setBounds函数用其来调整窗口
GLfloat AdjustHeight;
} |
2、把鼠标坐标映射为虚拟球上的坐标
通过虚拟球的旋转来达到旋转模型的目的,关键在于把视图中鼠标点击和拖动的坐标映射为虚拟球上的坐标。
为此,我们首先简单的把鼠标点击和拖动的范围[0~width),[0~height)映射到 [-1~1],[1~ -1](在映射中我们颠倒了y坐标的符号,不然OpenGL中得不到正确的结果)。这样做可以使数学计算变得简单些,其映射如下:
MousePt.X = ((MousePt.X / ((Width – 1) / 2)) – 1);
MousePt.Y = -((MousePt.Y / ((Height – 1) / 2)) – 1); |
其次,计算鼠标矢量,将鼠标坐标映射到虚拟球上,可以根据式1的定义完成这一步工作。
3、些相关变量的设定
为实现旋转我们还需要一些变量:
Matrix4fT Transform // 最终的变换,4*4矩阵,初始化为单位矩阵
Matrix3fT LastRot // 上一次的旋转,3*3矩阵,需要它是因为旋转的结果是要叠加起来的
Matrix3fT ThisRot //这次的旋转,3*3矩阵。
Point2fT MousePt; // 当前的鼠标坐标
bool isClicked = false; // 鼠标按下的标识
bool isRClicked = false; // 右键点击的标识
bool isDragging = false; //鼠标拖动的标识 |
其中Transform是我们的最终变换结果,LastRot是上一次鼠标拖动得到的旋转结果,而ThisRot是当前鼠标拖动的结果。它们都被初始化为单位矩阵。
当我们点击鼠标时,我们从单位旋转矩阵开始旋转。当拖动鼠标时,我们计算从初始点到拖动点的旋转。尽管我们用这信息旋转屏幕上的模型,但值得注意的是我 们并不是真的旋转虚拟球自身。所以要得到累积的旋转结果,我们必须自己想办法,这也就是引入LastRot的原因。如果不累积旋转,模型就会在我们点击鼠 标时突然跑回到原始的状态。例如,如果关于X轴旋转90度后再旋转45度,希望得到135度的结果,但实际上得到的是45度。在下一次点击鼠标时,又会回 到原始的0度状态。
其他的变量,我们要做的就是在适当的时间和地点更新它们。虚拟球需要在窗口大小改变时重新设置它的边界; MousePt在鼠标点击和拖动时得到更新;isClicked和isRClicked分别标识鼠标的左键和右键是否按下,isClicked用来判断是 否处于按下和拖动状态,我们用isRClicked来重置所有的旋转,使其回到单位矩阵状态。
4、更新旋转矩阵
有了以上变量的更新,接下来就是根据这些更新,实现旋转矩阵的更新:
void CRenderView::OnTimer(UINT nIDEvent)
{
if(m_Completed)
{
m_Completed = false;
if (isRClicked) // 如果点击右键,重置旋转
{
Matrix3fSetIdentity(&LastRot); //把LastRot重置为单位矩阵
Matrix3fSetIdentity(&ThisRot); //把ThisRot重置为单位矩阵
Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot); }
if (!isDragging) // 没有拖动
{
if (isClicked) // 第一次点击
{
isDragging = true; // 为拖动作准备
LastRot = ThisRot;
VirtualBall.click(&MousePt);
}
} // 更新起始点,为拖动作准备
else
{
if (isClicked) // 鼠标仍然被按下,说明仍处于拖动状态
{
Quat4fT ThisQuat; //一个四元数,用来存旋转的信息
ArcBall.drag(&MousePt, &ThisQuat);
//将四元数转化为旋转矩阵
Matrix3fSetRotationFromQuat4f(&ThisRot, &ThisQuat);
Matrix3fMulMatrix3f(&ThisRot, &LastRot); //累积旋转结果
//得到我们最终的旋转结果
Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);
}
else //没有拖动的
isDragging = false;
}
m_OpenGLDisplay.DisplayScene(m_p3DModel);//
m_Completed = true;
}
CView::OnTimer(nIDEvent);
} |
其中将四元数转化为旋转矩阵的函数为:
static void Matrix3fSetRotationFromQuat4f(Matrix3fT* NewObj, const Quat4fT* q1)
{
GLfloat n, s;
GLfloat xs, ys, zs;
GLfloat wx, wy, wz;
GLfloat xx, xy, xz;
GLfloat yy, yz, zz;
assert(NewObj && q1);
n = (q1->s.X * q1->s.X) + (q1->s.Y * q1->s.Y) + (q1->s.Z * q1->s.Z) + (q1->s.W * q1->s.W);
s = (n > 0.0f) ? (2.0f / n) : 0.0f;
xs = q1->s.X * s; ys = q1->s.Y * s; zs = q1->s.Z * s;
wx = q1->s.W * xs; wy = q1->s.W * ys; wz = q1->s.W * zs;
xx = q1->s.X * xs; xy = q1->s.X * ys; xz = q1->s.X * zs;
yy = q1->s.Y * ys; yz = q1->s.Y * zs; zz = q1->s.Z * zs;
NewObj->s.XX = 1.0f - (yy + zz);
NewObj->s.YX = xy - wz;
NewObj->s.ZX = xz + wy;
NewObj->s.XY = xy + wz;
NewObj->s.YY = 1.0f - (xx + zz);
NewObj->s.ZY = yz - wx;
NewObj->s.XZ = xz - wy;
NewObj->s.YZ = yz + wx;
NewObj->s.ZZ = 1.0f - (xx + yy);
} |
最后,把变换的结果应用于从3DS文件中读入的模型:
glPushMatrix();
glMultMatrixf(Transform.M); //将旋转的矩阵作用于模型上
glBegin(DrawingMode);
………//此处为画模型的地方,即画模型各个面的地方
glEnd();
glPopMatrix(); |
旋转的结果和问题分析
自由旋转的效果如图4所示。这种虚拟球旋转3DS文件中模型的方法操作简单方便而实用,达到预期的目的,但这种方法还有值得改进的地方。这个虚拟球的中 心是相对固定的(总在窗口的中心),如果模型的中心偏离虚拟球中心太远,旋转的效果就不是很好。最简单的解决办法是:用3DS MAX导出3DS文件前, 把模型的中心移到坐标原点。这是一个治标的办法,但适用且简单。而治本的方法就比较麻烦了,可以通过计算模型的中心来确定虚拟球的中心,使两个中心重合。 如果是多个模型,还应考虑实现鼠标捕获模型的功能,根据所选模型调节虚拟球的中心。
结束语
本文着重阐述了实现3DS文件中的模型自由旋转的数学基础和编程实现的过程。这项工作是计算机辅助诊断髁上骨折项目的一个重要组成部分。它的实现有利于 医生从各个角度观察骨折的模拟情况,形成较为直观的感性认识。对其它文件格式中的模型或辅助库中的模型都可以用此办法来实现自由旋转,所以具有较强的可移 植性和适用价值。