3、些相关变量的设定
为实现旋转我们还需要一些变量:
\N\N\N\NMatrix4fT 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、更新旋转矩阵
有了以上变量的更新,接下来就是根据这些更新,实现旋转矩阵的更新:
\N\N\N\Nvoid 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); } | 其中将四元数转化为旋转矩阵的函数为:
\N\N\N\Nstatic 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文件中读入的模型:
\N\N\N\NglPushMatrix(); glMultMatrixf(Transform.M); //将旋转的矩阵作用于模型上 glBegin(DrawingMode); ………//此处为画模型的地方,即画模型各个面的地方 glEnd(); glPopMatrix(); | 旋转的结果和问题分析
自由旋转的效果如图4所示。这种虚拟球旋转3DS文件中模型的方法操作简单方便而实用,达到预期的目的,但这种方法还有值得改进的地方。这个虚拟球的中心是相对固定的(总在窗口的中心),如果模型的中心偏离虚拟球中心太远,旋转的效果就不是很好。最简单的解决办法是:用3DS MAX导出3DS文件前,把模型的中心移到坐标原点。这是一个治标的办法,但适用且简单。而治本的方法就比较麻烦了,可以通过计算模型的中心来确定虚拟球的中心,使两个中心重合。如果是多个模型,还应考虑实现鼠标捕获模型的功能,根据所选模型调节虚拟球的中心。
\N 结束语
本文着重阐述了实现3DS文件中的模型自由旋转的数学基础和编程实现的过程。这项工作是计算机辅助诊断髁上骨折项目的一个重要组成部分。它的实现有利于医生从各个角度观察骨折的模拟情况,形成较为直观的感性认识。对其它文件格式中的模型或辅助库中的模型都可以用此办法来实现自由旋转,所以具有较强的可移植性和适用价值。 [em06]
|
|
|