OpenGL中如何实现通过鼠标点击选取对象(正交投影)
最近在用OpenGL写一个魔方程序,考虑到需要选取其中的小方块,于是我研究了半天,发现了这个方法
这种方法暂时只能在正交投影下使用,不需要OpenGL自带的什么glpick之类的方法,说实话,我现在并不想学习那种方法
接下来,我要介绍方法的大概思路:
该方法的核心是对凸包算法的使用以及OpenGL里的矩阵运算()即仿射变换)但是凸包算法已经是一个经典算法,大家尝试自己解决,如果实在不行,我下面会发一下我的土包代码。
首先建立cube对象,cube指的是魔方之中每个小的立方体方块。每个cube对象都维护:
一个相对矩阵(MATRIX[16]),
一个绝对矩阵(ABSOLUTE_MATRIX[16]),
一个初始位置数组origion_position[3],固定不变
一个绝对当前位置数组cur_abs_position[3],可以改变,值由origon_position与MATRIX相乘得出
这组数据中实际上用到的只是z坐标值(因此尚有改进之处),用于判断深度,如果忽略深度条件,鼠标点击可能会选取到多个对象,但是一般来说我们想选的是我们可以看到的,即离我们最近的哪一个对象,因此需要进行深度比较
(其实还有一个相对的当前位置current_position[3],但是这与今天要讲的选取没有关系,所以姑且不多提),
八个顶点的初始位置origion_vertex[24],固定不变
八个顶点的当前位置urrent_vertex[24],可以改变,值由origon_vertex与ABSOLUTE_MATRIX相乘得出
这八组数据主要用到每组数据的x坐标与y坐标,相当于八个二维点,通过这八个点来求出他们的凸包,进而当鼠标点击窗口某个位置时用于判断在哪些凸包之内
因为实际上只需要16个信息,因此尚有改进之处
整个程序还要维护一个矩阵M[16],用于进行整个魔方的旋转与通过与MATRIX结合来求ABSOLUTE_MATRIX;
接下来下介绍各种矩阵的获取方法:
MATRIX的获取方法:
因为魔方有6个面可以先把每个面作为一个组,然后每次旋转魔方的一个面时,对各个组的成员进行重新分配,对每个小方块(cube)的MATRIX重新获取
代码如下:
void rotate(int right, int angle) { //当time为9时,说明旋转了90度,此刻才会形成完整的一步旋转 for (int i = 0;i < 9;i++) { glPushMatrix(); //每做一次小的旋转,都要对小方块的当前位置更新一下 glLoadIdentity(); //当coord为零时,说明group所在的面与x轴垂直,则绕x轴旋转 // 为1时, y y // 2 z z if (coord == 0) glRotatef(angle, right, 0, 0); else if (coord == 1) glRotatef(angle, 0, right, 0); else if (coord == 2) glRotatef(angle, 0, 0, right); glMultMatrixf(cubes[i]->MATRIX); glGetFloatv(GL_MODELVIEW_MATRIX, cubes[i]->MATRIX); glPopMatrix(); //每次小的旋转都要改变当前位置 //这样才可以正确画出方块 cubes[i]->changePosition(); mc->firstNine[i] = cubes[i]->index; cubes[i]->changeAbsData(M); }
这个不是今天的重点,详情请研究原代码及注释
通过该代码主要了解到,MATRIX的获取是对每个小方块进行如下操作(伪代码):
for(i=0:n)
第一步,保存当前矩阵: glpushMatrix();
第二步,当前矩阵变为单位阵: glloadIdentity();
第三步: 调用一系列变换函数;
第四步,右乘当前cube的MATRIX: glmultMatrix(cube[i]->MATRIX);
第五步,获取新的MATRIX: glGetFloat(GL_MODEL_MATRIX,cube[i]->MATRIX);
第六步:还原之前保存的矩阵: glPopMatrix();
M的获取方法(例如要对图像进行总体的旋转),与MAYTRIX的获取有些类似,可以采用如下代码,改代码时glMotionFunc的一个回掉函数:
void motion(int x, int y) { //变换一下坐标系 y = 600 - y; float dx = -stx +x; float dy = -sty + y; glLoadIdentity(); glRotatef(2, -dy, dx, 0); glMultMatrixf(M); glGetFloatv(GL_MODELVIEW_MATRIX, M); glutPostRedisplay(); stx = x; sty = y; }
其中:
stx,sty是鼠标按下左键时的坐标(已经经过变换,现在以左下角原点),
x,y是鼠标拖动的坐标,以左上角为原点,需要变换使其以左下角为原点
dx,dy是鼠标拖动的位移,通过他的方向来确定物体的旋转方向
有时可能会出现旋转方向与鼠标拖动方向下给你返的情况,则把一下两行代码的正负号变换一下:
float dx = -stx +x; float dy = -sty + y;
改为:
float dx = stx -x; float dy = sty - y;
尤其注意,每当拖动一次鼠标之后,在函数最后要更新stx,sty的位置
ABSOLUTE_MATRIX的方法很简单,调用一下下列代码段就可以:
glPushMatrix();//保存当前矩阵 glLoadIdentity();//是当前矩阵变为单位阵 glMultMatrixf(M);//右乘M glMultMatrixf(MATRIX);//右乘MATRIX glGetFloatv(GL_MODELVIEW_MATRIX, ABSOLUTE_MATRIX);//获得当前矩阵,即ABSOLUTE_MATRIX glPopMatrix();//恢复之前保存的矩阵
获得了各种矩阵,就可以求各种绝对位置,相对位置,求取方法就是简单向量与矩阵相称的原理,采用一种简单的函数就可以轻而易举地实现,下面是一种函数:
void changeVector(float *MAT, float* o_v, float *n_v) { float tm[16]; //矩阵转置 for (int i = 0;i < 4;i++) { for (int j = 0;j < 4;j++) { tm[i * 4 + j] = MAT[j * 4 + i]; // PRintf("%f ", tm[i * 4 + j]); } // printf("\n"); } for (int i = 0;i < 3;i++) { n_v[i] = 0; for (int j = 0;j < 3;j++) { n_v[i] += tm[i * 4 + j] * o_v[j]; } } }
函数中,MAT表示要乘的矩阵,o_v表示变换之前的向量,n_v表示变换之后的向量
这样,我们就可以当每次相改变一次图形各部分位置时(例如,我的模仿可以旋转)就可以通过一下步骤来对各个顶点的信息进行更改:
第一步:获取M
第二步:获取每个cube对象的MATRIX
第三步:获取每个cube对象的ABSLOOLUTE_MATRIX
第四步:调用changeVector方法获取新的current_vertex和cur_abs_position
第五步:改变各个cube对象所维护的凸包的信息
这样,这一次图像变换完成(以上五步记作Change)
接下来又是一个新周期
一个变换周期可以如下概括:
一、鼠标选中{
检查那个对象被选中
}
二、判断是否改变(即是否拖动鼠标或旋转魔方){
若改变,则调用Change;
否则,不调用Change,即跳过
}
三、画图
当然,这并不是绝对的严格遵守的三步,有可能会直接从第二步开始,但是只有通过鼠标才能表现出对对象的选取,详细请见程序
我写的这段程序,有一些扩展功能,当然,还有一些没有实现的功能,这只是实验产品,希望大家有兴趣的加以完善
#pragma once #ifndef CONVEX_ROC #define CONVEX_ROC #include<stack> using namespace std; template<class T> class Convex { private: struct point2 { int x, y; double ATAN; point2() {}; ~point2() {} point2(const point2&p) { x = p.x; y = p.y; ATAN = p.ATAN; } point2& Operator=(const point2&p) { x = p.x; y = p.y; ATAN = p.ATAN; return *this; } point2(int _x, int _y) { x = _x; y = _y; }; int &operator[](int i) { if (i == 0)return x; if (i == 1)return y; return x; }; }; //交换两个点的数据 void Swap(point2& p1, point2& p2) { swap(p1.x, p2.x); swap(p1.y, p2.y); swap(p1.ATAN, p2.ATAN); } //检测是否比子树的值大,如果大于子树则交换 //比较顺序,先与左子树比较,再与右子树比较 //先按y值比较,再按x值比较 void check_xchange(const int& a, point2*ps, const int & allPointNum) { if (ps[a].y > ps[a * 2].y) { Swap(ps[a], ps[a * 2]); } else if (ps[a].y == ps[a * 2].y) { if (ps[a].x > ps[a * 2].x) { Swap(ps[a], ps[a * 2]); } } if (a * 2 + 1 <= allPointNum) if (ps[a].y > ps[a * 2 + 1].y) { Swap(ps[a], ps[a * 2 + 1]); } else if (ps[a].y == ps[a * 2 + 1].y) { if (ps[a].x > ps[a * 2 + 1].x) { Swap(ps[a], ps[a * 2 + 1]); } } } //使用堆排序算法,求出y最小的点.当有多个y最小的点时,再从中选取x最小的 void HEAPresort(point2*ps, const int& point_num) { for (int i = point_num / 2;i > 0;i--) { check_xchange(i, ps, point_num); } } //获得每个点的极角, //通过反三角函数acos来确定角的大小 //改进后通过函数的单调性来确定ATAN的大小 void getJiJiao(point2*points, const int& point_num) { for (int i = 2;i <= point_num;i++) { if (points[i].x == points[1].x) { points[i].ATAN = 0; continue; } double t = points[i].x - points[1].x; //if (points[i].x != points[1].x) points[i].ATAN = -(t) / pow((pow(t, 2) + pow(points[i].y - points[1].y, 2)), 0.5); //else points[i].ATAN = PI / 2; } } //按照极角的大小,有小到大排列,从第二个开始排 void MERGESORT(point2*points, const int &point_num) { for (int j = point_num - 1;j > 0;j--) for (int i = 2;i <= j;i++) { if (points[i].ATAN > points[i + 1].ATAN) { Swap(points[i], points[i + 1]); } else if (points[i].ATAN == points[i + 1].ATAN) { if (points[i].y > points[i + 1].y) { Swap(points[i], points[i + 1]); } else if (points[i].x > points[i + 1].x) { Swap(points[i], points[i + 1]); } } } } //当返回值小于0时 说明是向左转(即p3在p1->p2左面),等于零则三点共线 int LeftTurn_CHA(point2 &p1, point2 &p2, point2 &p3) { return (p1.x - p2.x)*(p3.y - p2.y) - (p1.y - p2.y)*(p3.x - p2.x); } bool inPolygon(point2 &p, point2*ps, int p_size) { if (p_size < 3)return 0; else if (p_size == 3) { return (LeftTurn_CHA(ps[0], ps[1], p)) <= 0 && (LeftTurn_CHA(ps[1], ps[2], p)) <= 0 && (LeftTurn_CHA(ps[2], ps[0], p)) <= 0; } else { int t = LeftTurn_CHA(ps[0], ps[p_size / 2], p); if (t == 0) { return LeftTurn_CHA(ps[p_size / 2], ps[p_size / 2 + 1], p) <= 0; } else if (t > 0) { return inPolygon(p, ps, p_size / 2 + 1); } else { point2 *tps = new point2[p_size - p_size / 2 + 1]; tps[0] = ps[0]; for (int i = 1;i < p_size - p_size / 2 + 1;i++) tps[i] = (ps + (p_size / 2))[i - 1]; bool in = inPolygon(p, tps, p_size - p_size / 2 + 1); delete[]tps; return in; } } } point2*vertex = 0;//顶点数组 int level = 1;//精确级别,精确度越高级别越大,都是10的整数幂 int tp_size = 0;//顶点数目 public: Convex() {} ~Convex() { if (vertex) delete[]vertex; } //T为一维数组,可以用作二维数组 bool creatHull(T *data, const int& point_num, const int& lev) { level = lev; point2*points = new point2[point_num + 1]; for (int i = 1;i < point_num + 1;i++) { points[i] = point2(data[i * 2 - 2] * level, data[i * 2 - 1] * level); } HEAPresort(points, point_num); getJiJiao(points, point_num); MERGESORT(points, point_num); //取得顶点 stack<point2>tp; tp.push(points[1]); tp.push(points[2]); for (int i = 3;i <= point_num;i++) { point2 *p2 = &tp.top(); tp.pop(); point2 *p1 = &tp.top(); tp.pop(); int t; while ((t = LeftTurn_CHA(*p1, *p2, points[i])) >= 0) { if (tp.size() == 0) { p2 = &points[i]; break; } p2 = p1; p1 = &tp.top(); tp.pop(); } tp.push(*p1); tp.push(*p2); tp.push(points[i]); } //将栈中的数据转移到数组中来 vertex = new point2[(tp_size = tp.size())]; for (int i = tp_size - 1;i >= 0;i--) { vertex[i] = tp.top(); tp.pop(); //printf("TP:%d,%d %f\n", vertex[i].x, vertex[i].y, vertex[i].ATAN); } delete[]points; return true; } bool testIn(const T *point) { point2 p(point[0] * level, point[1] * level); return inPolygon(p, vertex, tp_size); } //需要用户自行释放 T* getNewVertx()const { T*v = new T[tp_size * 2]; for (int i = 0;i < tp_size;i++) { v[i * 2] = (T)(vertex[i].x) / level; v[i * 2 + 1] = (T)(vertex[i].y) / level; } return v; } int getVertexSize() const { return tp_size; } }; #endif // !CONVEX_ROC #include<iostream> #include<vector> #include<gl\glut.h> using namespace std; Convex<float> c; float PROJECT_MATRIX[16]; float LOOKAT_MATRIX[16]; /** 结构体steps用于记录模仿的拧动历史,便于还原模仿 */ //根据矩阵mat,将o_position转换为n_position void setPosition(float*mat, float*o_position, float *n_position) { float tm[16]; for (int i = 0;i < 4;i++) { for (int j = 0;j < 4;j++) { tm[i * 4 + j] = mat[j * 4 + i]; //printf("%f ", tm[i * 4 + j]); } //printf("\n"); } //printf("\n"); for (int i = 0;i < 3;i++) { n_position[i] = 0; //printf("np[%d]=", i); for (int j = 0;j < 3;j++) { n_position[i] += tm[i * 4 + j] * o_position[j]; // printf("%d*%d+", (int)tm[i * 4 + j], p[j]); } // printf(" = %d\n", np[i]); } //cout << np[0] << " " << np[1] << " " << np[2] << endl; } struct step { int drawPlane;//旋转的面的序号 int rotate_direction;//旋转的方向 //在控制台输出魔方的历史数据 void PrintOut() { cout << "PLANE: " << drawPlane << " DER: " << rotate_direction; } }; //创建step向量,每当旋转一次魔方,都会产生一个新的step对象并存入steps vector<step>steps; //魔方各个面的颜色,可随意更该 float color[7][3] = { 1,1,0, 0,1,0, 1,0.5,0, 0,0,1, 1,0,1, 0,1,1, 0.5,0.5,0.5 }; //画小方块cube的一个面,在cube方法中调用,即可将一个小方格画出 void drawPlane(int a) { glColor3fv(color[a]); glBegin(GL_QUADS); glVertex3f(1, 1, 0); glVertex3f(-1, 1, 0); glVertex3f(-1, -1, 0); glVertex3f(1, -1, 0); glEnd(); } float CM[16] = { 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1 }; float p[3] = { 1,1,1 }; float np[3]; int absulueP[3]; float AM[16]; float o_range[18] = { 1,1,0.5, 1,1,1.5, 0.5,1,1, 1.5,1,1, 1,0.5,1, 1,1.5,1 }; float c_range[18]; //画一个小方块,边长为2,中心在原点 void cube() { glPushMatrix(); //glTranslatef(1, 1, 0); for (int i = 0;i < 3;i++) { glPushMatrix(); if (i == 0) glRotatef(90, 1, 0, 0); if (i == 1) glRotatef(90, 0, 1, 0); glTranslatef(0, 0, 1); drawPlane(i * 2); glTranslatef(0, 0, -2); drawPlane(1 + 2 * i); glPopMatrix(); } glPopMatrix(); } void bigcube() { //glTranslatef(2, 2, 0); glPushMatrix(); glScalef(0.2, 0.2, 0.2); glRotatef(90, 0, 1, 0); cube(); glPopMatrix(); } //八个顶点,最初的位置 float ori_poinVer[24] = { 0.5,0.5,0.5, 0.5,0.5,1.5, 0.5,1.5,1.5, 0.5,1.5,0.5, 1.5,0.5,0.5, 1.5,0.5,1.5, 1.5,1.5,1.5, 1.5,1.5,0.5, }; //八个顶点当前位置 float cur_poinVer[24]; float *convexVertex;//图报的顶点 int conVertNum;//凸包的顶点数 //求出每个顶点的当前坐标 void changeVertex() { float ver[16]; for (int i = 0;i < 8;i++) { setPosition(AM, ori_poinVer + i * 3, cur_poinVer + i * 3); //得到二维坐标,去掉深度 ver[i * 2] = cur_poinVer[i * 3]; ver[i * 2 + 1] = cur_poinVer[i * 3 + 1]; } //求八个顶点的凸包 c.creatHull(ver, 8, 1000); conVertNum = c.getVertexSize(); convexVertex = c.getNewVertx(); for (int i = 0;i < conVertNum;i++) { //printf("%f,%f\n", convexVertex[i * 2], convexVertex[i * 2 + 1]); } delete[]convexVertex; } void smallcube() { glPushMatrix(); glScalef(0.1, 0.1, 0.1); glTranslatef(2, 2, 2); cube(); glPopMatrix(); setPosition(CM, p, np); } void changeCM() { glPushMatrix(); glLoadIdentity(); glRotatef(90, 1, 0, 0); glMultMatrixf(CM); glGetFloatv(GL_MODELVIEW_MATRIX, CM); glPopMatrix(); } float M[16] = { 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1 }; void changeAM() { glPushMatrix(); glLoadIdentity(); glMultMatrixf(M); glMultMatrixf(CM); glGetFloatv(GL_MODELVIEW_MATRIX, AM); glPopMatrix(); //printf("M:\n"); for (int i = 0;i < 4;i++) { for (int j = 0;j < 4;j++) { // printf("%f ", M[j * 4 + i]); } // printf("\n"); } //printf("CM:\n"); for (int i = 0;i < 4;i++) { for (int j = 0;j < 4;j++) { // printf("%f ", CM[j * 4 + i]); } // printf("\n"); } //printf("AM:\n"); for (int i = 0;i < 4;i++) { for (int j = 0;j < 4;j++) { // printf("%f ", AM[j * 4 + i]); } // printf("\n"); } setPosition(AM, p, np); // printf("np:\n"); for (int i = 0;i < 3;i++) { // printf("%f ", np[i]); } // printf("\n"); // printf("6面:\n"); for (int i = 0;i < 6;i++) { setPosition(AM, o_range + i * 3, c_range + i * 3); for (int j = 0;j < 3;j++) { // printf("%f ", (c_range+i*3)[j]); } // printf("\n"); } // printf("\n"); // printf("CONVEX:\n"); } //检查是否被选中 bool picked(int x, int y) { float p[2]; p[0] = (float)x / 60 - 5; p[1] = (float)y / 60 - 5; printf("%f %f\nIn: %d\n", p[0], p[1]); return c.testIn(p); } bool rotae = 1; #define CUBE_SCALE_TIME 0.96 #define MAGIC_CUBE_SCALE_TIME 0.2 template<class T> T whatangle(const T *a, const T *b, const T *c) { T t = 0; for (int i = 0;i < 3;i++) { t += (a[i] - b[i])*(c[i] - b[i]); } return t; } //改变向量,通过对应矩阵改变相应点的坐标,通法 void changeVector(float *MAT, float* o_v, float *n_v) { float tm[16]; //矩阵转置 for (int i = 0;i < 4;i++) { for (int j = 0;j < 4;j++) { tm[i * 4 + j] = MAT[j * 4 + i]; // printf("%f ", tm[i * 4 + j]); } // printf("\n"); } //printf("\n"); for (int i = 0;i < 3;i++) { n_v[i] = 0; //printf("np[%d]=", i); for (int j = 0;j < 3;j++) { n_v[i] += tm[i * 4 + j] * o_v[j]; // printf("%d*%d+", (int)tm[i * 4 + j], p[j]); } // printf(" = %d\n", np[i]); } } /* 小方块,一共需要27个,算上中间的 */ struct Cube { //画一个小方块,边长为2,中心在原点 int validcolor[3] = { -1,-1,-1 }; int validColorNum; void setValideColor() { validColorNum = 0; int a[3]; for (int i = 0;i < 6;i++) { for (int j = 0;j < 3;j++) a[j] = (int)origion_position[j]; switch (i) { case 0: a[1] -= 1; break; case 1: a[1] += 1; break; case 2: a[0] += 1; break; case 3: a[0] -= 1; break; case 4: a[2] += 1; break; case 5: a[2] -= 1; break; } int p[3] = { 0,0,0 }; int d[3]; for (int j = 0;j < 3;j++) { d[j] = (int)origion_position[j]; } if (whatangle<int>(p, d, a) < 0) { validcolor[validColorNum++] = i; } } } void drawcube() { glPushMatrix(); //glTranslatef(1, 1, 0); int k = 0; int able = validcolor[k++]; for (int i = 0;i < 3;i++) { glPushMatrix(); if (i == 0) glRotatef(90, 1, 0, 0); if (i == 1) glRotatef(90, 0, 1, 0); glTranslatef(0, 0, 1); if (i * 2 == able) { able = validcolor[k++]; drawPlane(i * 2); } else drawPlane(6); //glFlush(); glTranslatef(0, 0, -2); if (i * 2 + 1 == able) { able = validcolor[k++]; drawPlane(1 + 2 * i); } else drawPlane(6); // glFlush(); glPopMatrix(); } glPopMatrix(); } bool ispicked = 0;;//是否被选中 int index;//序号 bool drew = 0;//是否已经画过 float origion_position[3];//原始位置 float current_position[3];//旋转之后的当前位置,相对位置 float cur_abs_position[3];//绝对位置,由绝对矩阵与原始位置相乘而得 //原始顶点位置 float origion_vertex[24]; //当前顶点位置,绝对位置,由原始顶点位置与绝对矩阵相乘得出 float current_vertex[24]; Convex<float> c;//维护一个凸包,用于选择 //旋转之后的当前仿射变换矩阵,相对矩阵 float MATRIX[16] = { 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1 }; //绝对矩阵,由相对矩阵与总体变化的矩阵相乘而得 float ABSOLUTE_MATRIX[16] = { 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1 }; //改变8个顶点坐标,绝对改变 void changeVertix() { for (int i = 0;i < 8;i++) { changeVector(ABSOLUTE_MATRIX, origion_vertex + 3 * i, current_vertex + 3 * i); } } //更新凸包 void refreshConvex() { float v[16]; for (int i = 0;i < 8;i++) { //setPosition(AM, ori_poinVer + i * 3, cur_poinVer + i * 3); //得到二维坐标,去掉深度 v[i * 2] = current_vertex[i * 3]; v[i * 2 + 1] = current_vertex[i * 3 + 1]; } //求八个顶点的凸包 c.creatHull(v, 8, 1000); } bool picked(const float x, const float y) { float p[2] = { x,y }; return c.testIn(p); } //改变当前的位置current_position,改变相对位置 void changePosition() { changeVector(MATRIX, origion_position, current_position); } void changeAbsPosition() { changeVector(ABSOLUTE_MATRIX, origion_position, cur_abs_position); } //画出小方块 void draw() { changeAbsData(M); if (!drew) { glPushMatrix(); //右乘当前矩阵 glMultMatrixf(MATRIX); glPushMatrix(); //平移小方块,使各个小方块按照序号放在相应的位置 glTranslatef(index % 3 * 2 - 2, index / 3 % 3 * 2 - 2, index / 9 % 3 * 2 - 2); //缩小一下,使得各个方块之间有缝隙 glScalef(CUBE_SCALE_TIME, CUBE_SCALE_TIME, CUBE_SCALE_TIME); drawcube(); glPopMatrix(); glPopMatrix(); drew = 1; if (ispicked) { glColor3f(1, 1, 1); glLineWidth(3); glPushMatrix(); glLoadIdentity(); glScalef(MAGIC_CUBE_SCALE_TIME, MAGIC_CUBE_SCALE_TIME, MAGIC_CUBE_SCALE_TIME); //glMultMatrixf(M); glTranslatef(0, 0, -5); glPushMatrix(); glBegin(GL_LINE_LOOP); for (int i = 0;i < 4;i++) glVertex2f((current_vertex + 3 * i)[0], (current_vertex + 3 * i)[1]); for (int i = 7;i >= 4;i--) glVertex2f((current_vertex + 3 * i)[0], (current_vertex + 3 * i)[1]); glEnd(); glBegin(GL_LINES); glVertex2f((current_vertex)[0], (current_vertex)[1]); glVertex2f((current_vertex + 9)[0], (current_vertex + 9)[1]); glVertex2f((current_vertex + 3)[0], (current_vertex + 3)[1]); glVertex2f((current_vertex + 15)[0], (current_vertex + 15)[1]); glVertex2f((current_vertex + 6)[0], (current_vertex + 6)[1]); glVertex2f((current_vertex + 18)[0], (current_vertex + 18)[1]); glVertex2f((current_vertex + 12)[0], (current_vertex + 12)[1]); glVertex2f((current_vertex + 21)[0], (current_vertex + 21)[1]); glEnd(); glPopMatrix(); glPopMatrix(); } } } void changeAbsData(float*M) { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); // glMultMatrixf(PROJECT_MATRIX); //glMultMatrixf(LOOKAT_MATRIX); glMultMatrixf(M); glMultMatrixf(MATRIX); glGetFloatv(GL_MODELVIEW_MATRIX, ABSOLUTE_MATRIX); glPopMatrix(); refreshConvex(); changeVertix(); changeAbsPosition(); } }; float verts[24] = { -1,-1,-1, -1,1,-1, -1,1,1, -1,-1,1, 1,-1,-1, 1,1,-1, 1,1,1, 1,-1,1 }; //魔方 struct MagicCube { //每次画的时候,首先画的9个方块的序号,初始化如下 int firstNine[9] = { 0,1,2,3,4,5,6,7,8 }; //27的小方块 Cube cubes[27]; MagicCube() { //初始化位置信息 for (int i = 0;i < 27;i++) { cubes[i].index = i; cubes[i].current_position[0] = cubes[i].origion_position[0] = i % 3 - 1; cubes[i].current_position[1] = cubes[i].origion_position[1] = i / 3 % 3 - 1; cubes[i].current_position[2] = cubes[i].origion_position[2] = i / 9 % 3 - 1; for (int j = 0;j < 8;j++) { cubes[i].current_vertex[j * 3] = cubes[i].origion_vertex[j * 3] = cubes[i].origion_position[0] * 2 + verts[j * 3] * CUBE_SCALE_TIME; cubes[i].current_vertex[j * 3 + 1] = cubes[i].origion_vertex[j * 3 + 1] = cubes[i].origion_position[1] * 2 + verts[j * 3 + 1] * CUBE_SCALE_TIME; cubes[i].current_vertex[j * 3 + 2] = cubes[i].origion_vertex[j * 3 + 2] = cubes[i].origion_position[2] * 2 + verts[j * 3 + 2] * CUBE_SCALE_TIME; } cubes[i].setValideColor(); //cubes[i].changeAbsData(M); // printf("%d :%d %d %d\n", i, cubes[i].current_position[0], cubes[i].current_position[1], cubes[i].current_position[2]); } } //画出整个魔方 void draw() { //令每个小方块变为未画的状态 for (int i = 0;i < 27;i++) { cubes[i].drew = 0; } //先画前九个方块 for (int i = 0;i < 9;i++) { glPushMatrix(); //glMultMatrixf(cubes[firstNine[i]].MATRIX); cubes[firstNine[i]].draw(); glPopMatrix(); } //再画后18个方块,因为不能确定那几个先画,因此用27次循环, //若遇到之前已经画过的,因为drew为true所以直接跳过 for (int i = 0;i < 27;i++) { //cubes[i].drew = 1; glPushMatrix(); //glMultMatrixf(cubes[i].MATRIX); cubes[i].draw(); glPopMatrix(); } } }; #define POSITIVE 1 #define NEGTIVE -1 #define COORD_X 0 #define COORD_Y 1; #define COORD_Z 2 //每次旋转时,旋转了第几次, //当time为0时,没有旋转, //当time为9时,本次大的旋转结束,time归零 //每次大的旋转包含9次小的旋转, //每次小的旋转会旋转10度,每次大的旋转旋转90度 int time = 0; //方块的组.有9个成员,每次旋转一次魔方,成员有可能需要变 struct group { int coord;//坐标不为零的坐标轴x:0,y:1,z:2 float center;//中心的坐标,只有1与-1两种可能 Cube *cubes[9];//成员的指针数组 MagicCube *mc;//魔方对象的指针 group() {} group(MagicCube*m_c, int cen, int coor) { mc = m_c; center = cen; coord = coor; } //初始化成员, //或者 //每当有任何一个group旋转一次,需要改变其他group的成员信息,调用该方法即可 //该方法的作用是获得或改变该group的成员对象,使得满足一定要求的cube属于该group void getMember() { int k = 0; for (int i = 0;i < 27;i++) { //当cneter与当前方块的currentposition相差小于0.1时, //说明该方块属于该group if (abs(center - mc->cubes[i].current_position[coord]) < 0.1) { cubes[k] = &mc->cubes[i]; // printf("%d ", cubes[k]->index); mc->firstNine[k++] = i; } } //printf("\n"); } //对group进行一次小的旋转,连续调用9次则构成一次大的旋转 //连续调用的目的是增强动画效果 void rotate(int right, int angle) { //当time为9时,说明旋转了90度,此刻才会形成完整的一步旋转 for (int i = 0;i < 9;i++) { glPushMatrix(); //每做一次小的旋转,都要对小方块的当前位置更新一下 glLoadIdentity(); //当coord为零时,说明group所在的面与x轴垂直,则绕x轴旋转 // 为1时, y y // 2 z z if (coord == 0) glRotatef(angle, right, 0, 0); else if (coord == 1) glRotatef(angle, 0, right, 0); else if (coord == 2) glRotatef(angle, 0, 0, right); glMultMatrixf(cubes[i]->MATRIX); glGetFloatv(GL_MODELVIEW_MATRIX, cubes[i]->MATRIX); glPopMatrix(); //每次小的旋转都要改变当前位置 //这样才可以正确画出方块 cubes[i]->changePosition(); mc->firstNine[i] = cubes[i]->index; cubes[i]->changeAbsData(M); } } }; MagicCube mc; group gc[6]; bool rotaing = 0;//是否正在转动 int plane_rotating = -1;//正在转动的group序号,没有转动时一直为-1 int rotat_der = -1;//转动的方向,顺时针or逆时针 int rotate_angle = 10;//每一次小旋转的角度,四种为10,修改该值的同时也要修改time的值 //计时器,用于实现转动时的动画效果 void timer(int); int planeToDraw = -1; struct plane { bool ispicked = 0; Cube* of; int color; float vertex[8]; float original_center_time[3]; float current_center_time[3]; float *cur_vertex = 0; int hulVerNum = 0; Convex <float>c; plane() {} ~plane() { if (cur_vertex) { delete[]cur_vertex; cur_vertex = 0; }; } plane(const plane&p) { of = p.of; color = p.color; for (int i = 0;i < 3;i++) { original_center_time[i] = p.original_center_time[i]; } for (int i = 0;i < 8;i++) { vertex[i] = p.vertex[i]; } //changeVector(of->ABSOLUTE_MATRIX, vertex, new_vertex); refrushHull(); } void refrushCurrentCenter() { changeVector(of->ABSOLUTE_MATRIX, original_center_time, current_center_time); } plane& operator =(const plane& p) { of = p.of; color = p.color; for (int i = 0;i < 3;i++) { original_center_time[i] = p.original_center_time[i]; } //changeVector(of->ABSOLUTE_MATRIX, vertex, new_vertex); refrushHull(); return *this; } plane(Cube* o, int c) { of = o; color = c; for (int i = 0;i < 3;i++) { original_center_time[i] = of->origion_position[i]; } switch (c) { case 0: original_center_time[1] -= 1; break; case 1: original_center_time[1] += 1; break; case 2: original_center_time[0] += 1; break; case 3: original_center_time[0] -= 1; break; case 4: original_center_time[2] += 1; break; case 5: original_center_time[2] -= 1; break; } refrushHull(); /** case 0: a[1] -= 1; break; case 1: a[1] += 1; break; case 2: a[0] += 1; break; case 3: a[0] -= 1; break; case 4: a[2] += 1; break; case 5: a[2] -= 1; */ } void refrushHull() { refrushCurrentCenter(); switch (color) { case 3: for (int i = 0;i < 4;i++) { vertex[i * 2] = of->current_vertex[i * 3]; vertex[i * 2 + 1] = of->current_vertex[i * 3 + 1]; } break; case 2: for (int i = 0;i < 4;i++) { vertex[i * 2] = of->current_vertex[(i + 4) * 3]; vertex[i * 2 + 1] = of->current_vertex[(i + 4) * 3 + 1]; } break; case 0: for (int i = 0;i < 2;i++) { vertex[0 + i] = of->current_vertex[i]; vertex[2 + i] = of->current_vertex[9 + i]; vertex[4 + i] = of->current_vertex[12 + i]; vertex[6 + i] = of->current_vertex[21 + i]; } break; case 1: for (int i = 0;i < 2;i++) { vertex[0 + i] = of->current_vertex[3 + i]; vertex[2 + i] = of->current_vertex[6 + i]; vertex[4 + i] = of->current_vertex[15 + i]; vertex[6 + i] = of->current_vertex[18 + i]; } break; case 4: for (int i = 0;i < 2;i++) { vertex[0 + i] = of->current_vertex[9 + i]; vertex[2 + i] = of->current_vertex[6 + i]; vertex[4 + i] = of->current_vertex[21 + i]; vertex[6 + i] = of->current_vertex[18 + i]; } break; case 5: for (int i = 0;i < 2;i++) { vertex[0 + i] = of->current_vertex[3 + i]; vertex[2 + i] = of->current_vertex[12 + i]; vertex[4 + i] = of->current_vertex[15 + i]; vertex[6 + i] = of->current_vertex[i]; } break; } c.creatHull(vertex, 4, 1000); if (cur_vertex) { delete[]cur_vertex; //cur_vertex = 0; } cur_vertex = c.getNewVertx(); hulVerNum = c.getVertexSize(); } bool picked(float x, float y) { refrushHull(); bool t; float p[2] = { x,y }; return t = c.testIn(p); } void drawLine() { glPushMatrix(); glScalef(MAGIC_CUBE_SCALE_TIME, MAGIC_CUBE_SCALE_TIME, MAGIC_CUBE_SCALE_TIME); glTranslatef(0, 0, -5); //glMultMatrixf(M); // glMultMatrixf(of->MATRIX); glLineWidth(10); glColor3f(1, 0, 0); glBegin(GL_LINE_LOOP); for (int i = 0;i < hulVerNum;i++) glVertex2fv(cur_vertex + 2 * i); glEnd(); glPopMatrix(); } }; plane planes[54]; void display() { for (int i = 0;i < 54;i++) { planes[i].refrushHull(); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); //glEnable(GL_POLYGON_SMOOTH); glLoadIdentity(); //glScalef(30, 1, 1, 0); glScalef(MAGIC_CUBE_SCALE_TIME, MAGIC_CUBE_SCALE_TIME, MAGIC_CUBE_SCALE_TIME); //gluLookAt(0, 0,30, 0, 0, 0, 0, 1, 0); glPushMatrix(); glMultMatrixf(M); /* //glPolygonMode() //bigcube(); glPopMatrix(); glMultMatrixf(M); glPushMatrix(); glMultMatrixf(CM); smallcube(); */ mc.draw(); glPopMatrix(); glPushMatrix(); glLoadIdentity(); if (planeToDraw >= 0) { //for (int i = 0;i < 54;i++) {{ planes[planeToDraw].drawLine(); } glPopMatrix(); //} glFlush(); //changeAM(); } float stx, sty; void mouse(int b, int s, int x, int y) { if (b == GLUT_LEFT_BUTTON&&s == GLUT_DOWN) { stx = x; sty = 600 - y; } if (b == GLUT_RIGHT_BUTTON&&s == GLUT_DOWN) { y = 600 - y; float X = x, Y = y; X = (X - 300) / 300 / MAGIC_CUBE_SCALE_TIME; Y = (Y - 300) / 300 / MAGIC_CUBE_SCALE_TIME; //printf("\n%f %f : ", X, Y); int theNrearest; bool first = 1; for (int i = 0;i < 27;i++) { mc.cubes[i].ispicked = 0; if (mc.cubes[i].picked(X, Y)) { //planes[i].refrushHull(); // printf("%d\n", i); if (first) { mc.cubes[i].ispicked = 1; theNrearest = i; first = 0; } else { if (mc.cubes[i].cur_abs_position[2] < mc.cubes[theNrearest].cur_abs_position[2]) { mc.cubes[i].ispicked = 1; mc.cubes[theNrearest].ispicked = 0; theNrearest = i; }; } } else mc.cubes[i].ispicked = 0; } bool firstPlane = 1; planeToDraw = -1; for (int i = 0;i < 54;i++) { //planes[i].refrushHull(); if (planes[i].picked(X, Y)) { if (firstPlane) { planeToDraw = i; firstPlane = 0; } else { if (planes[i].picked(X, Y) && planes[i].current_center_time[2] < planes[planeToDraw].current_center_time[2]) { planeToDraw = i; }; } } } glutPostRedisplay(); //printf("\n"); } } void getPlanes() { int k = 0; for (int i = 0;i < 27;i++) { for (int j = 0;j < mc.cubes[i].validColorNum;j++) { int t = mc.cubes[i].validcolor[j]; switch (t) { case 3: planes[k++] = plane(&mc.cubes[i], mc.cubes[i].validcolor[j]); break; case 2: planes[k++] = plane(&mc.cubes[i], mc.cubes[i].validcolor[j]); break; case 4: planes[k++] = plane(&mc.cubes[i], mc.cubes[i].validcolor[j]); break; case 5: planes[k++] = plane(&mc.cubes[i], mc.cubes[i].validcolor[j]); break; case 0: planes[k++] = plane(&mc.cubes[i], mc.cubes[i].validcolor[j]); break; case 1: planes[k++] = plane(&mc.cubes[i], mc.cubes[i].validcolor[j]); break; } } } } void motion(int x, int y) { //变换一下坐标系 y = 600 - y; float dx = stx -x; float dy = sty - y; glLoadIdentity(); glRotatef(2, -dy, dx, 0); glMultMatrixf(M); glGetFloatv(GL_MODELVIEW_MATRIX, M); glutPostRedisplay(); stx = x; sty = y; } int cotl = 0; //一次大的旋转,输入group的序号,与旋转的方向即可 void bigRotate(int groupIndex, int degree_direction) { plane_rotating = groupIndex; rotat_der = degree_direction; glutTimerFunc(20, timer, 0); } int stepIndex; int step_index; bool recover_started = 0; void recover_timer(int t) { if (!recover_started) { recover_started = 1; step_index = steps.size(); } if (step_index > 0) { step_index--; bigRotate(steps[step_index].drawPlane, -steps[step_index].rotate_direction); glutTimerFunc(1000, recover_timer, 0); } else { steps.clear(); recover_started = 0; } } void key(int k, int x, int y) { y = 600 - y; if (plane_rotating == -1) switch (k) { case GLUT_KEY_F4: glutTimerFunc(200,recover_timer, 0); return; case GLUT_KEY_UP: { step s; s.drawPlane = 0; s.rotate_direction = -1; steps.push_back(s); } bigRotate(0, -1); break; case GLUT_KEY_DOWN: //printf("%d\n", picked(x, y)); break; case GLUT_KEY_LEFT: bigRotate(1, -1); { step s; s.drawPlane = 1; s.rotate_direction = -1; steps.push_back(s); } break; case GLUT_KEY_RIGHT: bigRotate(1, 1); { step s; s.drawPlane = 1; s.rotate_direction = 1; steps.push_back(s); } break; case GLUT_KEY_F1: bigRotate(2, -1); break; case GLUT_KEY_F2: bigRotate(2, 1); break; } //glutPostRedisplay(); } void timer(int n) { time++;//小旋转次数+1,加到9归零 //调用小旋转 gc[plane_rotating].rotate(rotat_der, rotate_angle); glutPostRedisplay(); //当time为9时,表示已经完成一次大的旋转,这停止旋转,而且要time归零 //而且要调整旋转之后各个group的成员改变 if (time == 9) { plane_rotating = -1; time = 0; //改变各个group的成员cube for (int i = 0;i < 6;i++) { gc[i].getMember(); } //不再注册计时器,即停止旋转,直接返回 return; } glutTimerFunc(50, timer, 0); } void passMotion(int x, int y) { y = 600 - y; } void main() { //初始化group for (int i = 0;i < 6;i++) { gc[i].mc = &mc; gc[i].coord = i % 3; if (i < 3)gc[i].center = -1; else gc[i].center = 1; gc[i].getMember(); } getPlanes(); glutInitWindowSize(600, 600); glutCreateWindow("TESTCUBE"); glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH); glClearColor(0, 0, 0, 1); glutDisplayFunc(display); glutMouseFunc(mouse); glutMotionFunc(motion); //glutPassiveMotionFunc(passMotion); glutSpecialFunc(key); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glGetFloatv(GL_PROJECTION_MATRIX, PROJECT_MATRIX); // glOrtho(-1, 1, -1, 1, 100, -100); //gluPerspective(30, 1, 1, 100); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); //gluLookAt(0, 0, 30, 0, 0, 0, 0, 1, 0); glGetFloatv(GL_MODELVIEW_MATRIX, LOOKAT_MATRIX); //glLoadIdentity(); //glScalef(MAGIC_CUBE_SCALE_TIME, MAGIC_CUBE_SCALE_TIME, MAGIC_CUBE_SCALE_TIME); //glGetFloatv(GL_MODELVIEW_MATRIX, M); glutMainLoop(); }感谢阅览,若有什么好的建议,指教,想法,请留言,谢谢!