OpenGL 用虚拟球实现场景自由旋转(转)

模型自由旋转的数学基础

  我们用鼠标实现模型的旋转,就好像手握一个包含模型的虚拟球一样。按一下鼠标,即在这个虚拟球上确定了一点,而拖动鼠标就是移动那个点,这样就实现了对虚拟球的旋转,同时达到旋转模型的目的。

OpenGL <wbr>用虚拟球实现场景自由旋转(转)

  这个虚拟球的中心位于显示屏的中心,这样球的一半则位于显示屏以外(外半球,如图1所示)。我们用鼠标点击的点将定义为外半球上的点。这种映射关系的数学定义为:

OpenGL <wbr>用虚拟球实现场景自由旋转(转)

  其中(x,y)是以球心为原点的屏幕坐标,R为球的半径。

  接下来要做的就是在球上给定两个点后(起始点和终点)怎样确定旋转的轴和角度。从图2中可以看出:旋转轴是两个鼠标矢量(m1和m2)所张成的平面的法向量,所以可以通过求m1和m2的叉乘得到,即:

Axis = m1 x m2 (式2)

  而旋转角度就是m1和m2之间的夹角a,因此:

a = acos (m1 * m2) (式3)

OpenGL <wbr>用虚拟球实现场景自由旋转(转)

  在实际应用中,我们更习惯取a的两倍值进行旋转。因为这样将更有效地旋转模型。如果用鼠标点击视图的左中边缘,然后拖动至视图的右中边缘,则可实现模型以y轴为旋转轴的360度旋转。

  从图3可以看出:在旋转的过程中,两个弧(R1和R2)的合成所形成的旋转弧等于R1的起始点和R2的终点形成的旋转弧。即意味着我们定义的虚拟球的旋转运动只决定于起始点和终点。

 

OpenGL <wbr>用虚拟球实现场景自由旋转(转)

编程实现自由旋转的方法和经验

  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();

3

1

阅读 (2512)   评论  (1) 收藏 (0)  转载 (1)   喜欢   打印 举报
已投稿到:
  • 用户3245541750

    博主,你好:
        如果我想获取当前鼠标移动的经度,纬度值,要怎么求,从Transform.m中可以得到吗?

    2015-1-17  14:00回复(0)

发评论

登录名: 密码: 找回密码 注册

    

按住左边滑块,拖动完成上方拼图

发评论

以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

猜你喜欢

转载自blog.csdn.net/lv0918_qian/article/details/77097711
今日推荐