OpenGL实现模型自由旋转——之代码解析
实现一个OpenGL模型旋转的例子代码。请参考文章“OpenGL实现3DS文件中的模型自由旋转”和本例的代码以期更还理解原文。“OpenGL实现3DS文件中的模型自由旋转”出处:
http://blog.csdn.net/Ingenuus/archive/2007/11/12/1880457.aspx
基本原理在文章中说的很清楚,遗憾的是没有实现的代码,理解起来就很困难。我尝试补全它,参考下面的代码:
// oglTest.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <math.h> // sqrt typedef struct { float x, y, z; }Vector3f; typedef struct { float x, y, z, w; }Vector4f, Quat4f; typedef struct { float x, y; }Vector2f; // 屏幕鼠标点的坐标转为模拟球的球面坐标。 // 球的半径总是 1, 所以模拟球的球面坐标为单位矢量 // void Screen2Sphere(float w, // 窗口区域宽度(像素) float h, // 窗口区域高度(像素) float x, // 鼠标点的位置(x, y) float y, Vector3f *v // 球面坐标单位矢量 ) { // 鼠标点映射到 [-1,1] // if (w<2) w=2; // 防止被 0 除 if (h<2) h=2; x = ((x/((w-1)/2))-1); y = -((y/((h-1)/2))-1); // OGL需要, Y 轴反向 float d2 = x*x + y*y; float d = sqrt(d2); if (d2>1.0f){ v->x = x/d; v->y = y/d; v->z = 0.f; } else{ v->x = x; v->y = y; v->z = sqrt(1.0f-d2); } } // 使向量成为单位向量, 即模为1: x*x+y*y+z*z=1 void MakeNormal(Vector3f *v) { float m = 1.0f/sqrt(v->x*v->x + v->y*v->y + v->z*v->z); v->x *= m; v->y *= m; v->z *= m; } // 计算2矢量叉积,叉积的结果就是2矢量构成的平面的法向量n // n=a cross b; void VectorCross(const Vector3f *a, const Vector3f *b, Vector3f *n) { n->x = a->y*b->z - b->y*a->z; n->y = a->z*b->x - b->z*a->x; n->z = a->x*b->y - b->x*a->y; MakeNormal(n); } // 计算2矢量点积,点积的结果就是2矢量构成的夹角的余弦 // n=a cross b; void VectorDot(const Vector3f *a, const Vector3f *b, float *dot) { *dot = a->x*b->x+a->y*b->y+a->z*b->z; } // 计算起点(start)到拖动点(dragging)的旋转矩阵 // 说明: 鼠标左键第1次按下的点记为起点, 然后鼠标左键不松开, 移动鼠标, 此时的鼠标轨迹点都是 // 一系列拖动点(draggings). // 程序需要在WM_MOUSEMOVE事件处理中得到拖动点的窗口区域位置, 通过下面的函数计算实时的变换矩阵 // 下面的函数计算从上一点(start)到当前点(drag)拖动的过程所产生的变换结果(trans): // 旋转轴 (x,y,z) 和夹角(a) void OnDragging(float w, // 窗口的宽度(像素) float h, // 窗口的高度(像素) const Vector2f *start, const Vector2f *drag, Vector4f *trans) { // 变换到球面单位矢量 Vector3f startV; Screen2Sphere(w, h, start->x, start->y, &startV); Vector3f dragV; Screen2Sphere(w, h, drag->x, drag->y, &dragV); // 在球上给定两个点后(起始点和终点)怎样确定旋转的轴和角度? // 旋转轴是两个鼠标矢量所张成的平面的法向量, 所以可以通过2矢量的叉积得到,即: VectorCross(&startV, &dragV, (Vector3f*)trans); // 而旋转角度就是m1和m2之间的夹角a, 点积得到: VectorDot(&startV, &dragV, &trans->w); } /** 四元数到3x3旋转矩阵的变换: 公式参考:http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/index.htm 一个变换的计算器:http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToMatrix/program/index.htm 可以用来验证你的代码是否正取 m3x3=[ 1 - 2*qy*qy - 2*qz*qz, 2*qx*qy - 2*qz*qw, 2*qx*qz + 2*qy*qw 2*qx*qy + 2*qz*qw, 1 - 2*qx*qx - 2*qz*qz, 2*qy*qz - 2*qx*qw 2*qx*qz - 2*qy*qw, 2*qy*qz + 2*qx*qw, 1 - 2*qx*qx - 2*qy*qy ]; */ void QuatToMatrix(Quat4f *q, float m[3][3]) { float sqw = q->w*q->w; float sqx = q->x*q->x; float sqy = q->y*q->y; float sqz = q->z*q->z; // invs (inverse square length) is only required if quaternion is not already normalised float invs = 1.0f / (sqx + sqy + sqz + sqw); // since sqw + sqx + sqy + sqz =1/invs*invs m[0][0] = ( sqx - sqy - sqz + sqw)*invs; m[1][1] = (-sqx + sqy - sqz + sqw)*invs; m[2][2] = (-sqx - sqy + sqz + sqw)*invs; float tmp1 = q->x*q->y; float tmp2 = q->z*q->w; m[1][0] = 2.0f * (tmp1 + tmp2)*invs; m[0][1] = 2.0f * (tmp1 - tmp2)*invs; tmp1 = q->x*q->z; tmp2 = q->y*q->w; m[2][0] = 2.0f * (tmp1 - tmp2)*invs; m[0][2] = 2.0f * (tmp1 + tmp2)*invs; tmp1 = q->y*q->z; tmp2 = q->x*q->w; m[2][1] = 2.0f * (tmp1 + tmp2)*invs; m[1][2] = 2.0f * (tmp1 - tmp2)*invs; } int _tmain(int argc, _TCHAR* argv[]) { int i,j; float width=400; float height=300; Vector3f Vp; Screen2Sphere(width, height, 50, 50, &Vp); Vector2f start={50,0}; Vector2f drag={0,50}; Vector4f trans; OnDragging(width, height, &start, &drag, &trans); float m[3][3]; QuatToMatrix(&trans, m); for(i=0; i<3; i++){ for(j=0; j<3; j++){ printf(" %.4f ", m[i][j]); } printf("/n"); } return 0; }