OpenGL初探:二维卡通人物交互设计
2017-05-21 23:17
501 查看
使用OpenGL实现基于鼠标交互的卡通人物和其它环境物体的设计与绘制。使用颜色填充与反走样技术对卡通人物外貌以及衣着进行绘制。实现对卡通人物或物体轮廓的交互控制,点击鼠标左键可以对人物或者物体进行拖拽移动调整。按“↑”按键能够实现卡通人物绕坐标原点(或指定点)进行旋转,按“z”键可实现对选中的人物或者物体进行放缩。选中其中的一个多边形区域,点击鼠标右键,弹出一个菜单,可以对该区域进行不同颜色的选择。
首先设计图形,选择简单的卡通人物可以简化设计过程、加快渲染速度。这里选择机器猫作为绘制的对象,并对其形象进行简化,通过绘制圆、半圆、椭圆、线等简单图形,组合成一个整体的人物形象:
以上是主体人物的绘制,接下来绘制一些小物件:
进行简单的图形绘制后(绘制方法可能比较笨,但是比较好理解),为了增加交互功能,使用户可以使用鼠标进行一系列的交互操作,需要增加图元识别的功能:
首先设置缓存,存放鼠标点击时鼠标所在点的图元数据,包括当前击中图元的数量,各个图元中各个顶点中深度最大和深度最小的顶点的深度;当两个或多个图元重叠时,根据逻辑应将上层图元作为选中图元,因此根据深度数据,选择深度最小的图元,即为浮在最上面的图元,将其状态改为选择状态,表示当前鼠标点击选中该图元,以便进行后续操作。(OpenGL超级宝典第4版P298有相关介绍)
接下来编写各类回调函数:
首先添加窗口大小改变时的回调函数ChangeSize,当用户改变窗口的大小时,为了使图形仍然保持正确的比例,需要对窗口进行重新裁剪:
接下来编写鼠标的回调函数:
当用户使用鼠标点击时,调用之前关于图元识别的函数,确定用户是否选中某图元,并对图元的状态进行修改,以便后续绘制时进行相应的变换。
以帽子为例,如果选中的图元是帽子(即HAT_STATE变量为TRUE),则将HAT_WEARING变量设置为TRUE,表示它被穿着在人物身上,在绘制时,可以随着人物一起转动。这里的判定是为了将帽子摆正,当用户的鼠标在一定范围内松开时,将帽子自动对齐到中央位置:
接下来是鼠标移动的回调函数,当鼠标移动时触发。
因为系统返回的是鼠标的窗口坐标,并不是世界坐标,因此为了使图像和鼠标同步运动,需要进行坐标的转换:
接下来是键盘响应函数,当用户点击键盘时,触发对应的功能:
当用户点击z时,要实现缩放的功能,因此设置一个scale变量,它的值表示缩放的倍数,并且在1、2、3之间变换,每当用户点击时,就进行切换,而scale变量在绘制图形的函数中作为glScalef函数的参数,实现图形缩放的功能;当用户点击↑键时,则更改变量theta的值,它代表旋转的角度,同样在绘制函数中作为glRotatef函数的参数使用,完成图形旋转功能,前文中提到图元可以设置是否穿戴,只有穿戴在卡通人物身上的图元会随着人物旋转,而旁边没有穿戴的图元则不进行旋转:
菜单功能的实现:
使用glutCreateMenu()函数创建菜单,使用glutAttachMenu(GLUT_RIGHT_BUTTON)函数将菜单绑定到鼠标右键上,这样当用户点击右键时,菜单即可呼出,使用glutAddMenuEntry()函数设置菜单项,第一个参数是菜单项显示的文字,第二项是传递给判断函数menu的值:
其中判断函数menu的功能是根据传入的参数,进行相应的操作:
程序运行结果:
选中部件可以进行拖动:
穿戴在人物身上的物件可以随人物一同旋转,而未穿戴的物件不旋转:
对选中部件进行缩放(旁边的绿色旗子,因为背景和旗杆都是黑色所以可能看不出来是个旗子):
右键可以将旗子换色:
首先设计图形,选择简单的卡通人物可以简化设计过程、加快渲染速度。这里选择机器猫作为绘制的对象,并对其形象进行简化,通过绘制圆、半圆、椭圆、线等简单图形,组合成一个整体的人物形象:
//绘制身体 void DrawBody(int x, int y) { /* glEnable(GL_BLEND);//启用颜色混合 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//接受源颜色并将这个颜色(RGB)与alpha值相乘, //然后把这个结果加上目标颜色乘“1减去源颜色的alpha值”的结果 */ //启用防走样 glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST); glEnable(GL_POINT_SMOOTH); glEnable(GL_LINE_SMOOTH); glEnable(GL_POLYGON_SMOOTH); //左胳膊 { glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(-38.0, 10.0, 0.0); glRotatef(45.0, 0.0, 0.0, 1.0); glColor3f(ColorChoose[BODY_COLOR][0], ColorChoose[BODY_COLOR][1], ColorChoose[BODY_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = 20 * cos(2 * PI*i / N); tempY = 10 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); //描边 glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][1], ColorChoose[LINE_COLOR][2]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i < N; i++) { tempX = 20 * cos(2 * PI*i / N); tempY = 10 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //左手 { glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(-50.0, 0.0, 0.0); glColor3f(ColorChoose[HAND_COLOR][0], ColorChoose[HAND_COLOR][1], ColorChoose[HAND_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = 8 * cos(2 * PI*i / N); tempY = 8 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][1], ColorChoose[LINE_COLOR][2]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i < N; i++) { tempX = 8 * cos(2 * PI*i / N); tempY = 8 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //右胳膊 { glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(38.0, 10.0, 0.0); glRotatef(-45.0, 0.0, 0.0, 1.0); glColor3f(ColorChoose[BODY_COLOR][0], ColorChoose[BODY_COLOR][1], ColorChoose[BODY_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = 20 * cos(2 * PI*i / N); tempY = 10 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][1], ColorChoose[LINE_COLOR][2]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i < N; i++) { tempX = 20 * cos(2 * PI*i / N); tempY = 10 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //右手 { glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(50.0, 0.0, 0.0); glColor3f(ColorChoose[HAND_COLOR][0], ColorChoose[HAND_COLOR][1], ColorChoose[HAND_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = 8 * cos(2 * PI*i / N); tempY = 8 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][1], ColorChoose[LINE_COLOR][2]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i < N; i++) { tempX = 8 * cos(2 * PI*i / N); tempY = 8 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //身体 { glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glColor3f(ColorChoose[BODY_COLOR][0], ColorChoose[BODY_COLOR][1], ColorChoose[BODY_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i <= N / 8; i++) { tempX = 40 * cos(2 * PI*i / N); tempY = 42 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } for (int i = 3 * N / 8; i <= 5 * N / 8; i++) { tempX = 40 * cos(2 * PI*i / N); tempY = 42 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } for (int i = 7 * N / 8; i < N; i++) { tempX = 40 * cos(2 * PI*i / N); tempY = 42 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); //描边 glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i <= N / 8; i++) { tempX = 40 * cos(2 * PI*i / N); tempY = 42 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } for (int i = 3 * N / 8; i <= 5 * N / 8; i++) { tempX = 40 * cos(2 * PI*i / N); tempY = 42 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } for (int i = 7 * N / 8; i < N; i++) { tempX = 40 * cos(2 * PI*i / N); tempY = 42 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //项圈 { glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glColor3f(ColorChoose[NECKLACE_COLOR][0], ColorChoose[NECKLACE_COLOR][1], ColorChoose[NECKLACE_COLOR][2]); glBegin(GL_POLYGON); tempX = 40 * cos(2 * PI * 5 / N); tempY = 42 * sin(2 * PI * 5 / N); glVertex2f(tempX, tempY); tempX = 40 * cos(2 * PI * 15 / N); //tempY= 40 * sin(2 * PI*15 / N); glVertex2f(tempX, tempY); tempY = 40 * sin(2 * PI * 15 / N) + 5; glVertex2f(tempX, tempY); tempX = 40 * cos(2 * PI * 5 / N); glVertex2f(tempX, tempY); glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); tempX = 40 * cos(2 * PI * 5 / N); tempY = 42 * sin(2 * PI * 5 / N); glVertex2f(tempX, tempY); tempX = 40 * cos(2 * PI * 15 / N); //tempY= 40 * sin(2 * PI*15 / N); glVertex2f(tempX, tempY); tempY = 40 * sin(2 * PI * 15 / N) + 5; glVertex2f(tempX, tempY); tempX = 40 * cos(2 * PI * 5 / N); glVertex2f(tempX, tempY); glEnd(); } //头部 { translate = 42 * sin(2 * PI * 5 / N) + 40 * sin(2 * PI * 5 / N) + 4; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(0.0, translate, 0.0); glColor3f(ColorChoose[BODY_COLOR][0], ColorChoose[BODY_COLOR][1], ColorChoose[BODY_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i <= 5 * N / 8; i++) { tempX = 40 * cos(2 * PI*i / N); tempY = 40 * sin(2 * PI*i / N) ; glVertex2f(tempX, tempY); } for (int i = 7 * N / 8; i < N; i++) { tempX = 40 * cos(2 * PI*i / N) ; tempY = 40 * sin(2 * PI*i / N) ; glVertex2f(tempX, tempY); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i <= 5 * N / 8; i++) { tempX = 40 * cos(2 * PI*i / N); tempY = 40 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } for (int i = 7 * N / 8; i < N; i++) { tempX = 40 * cos(2 * PI*i / N); tempY = 40 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //脸 { tempR = 40 * cos(2 * PI * 5 / N) / cos(35.0*PI/180.0);//脸的半径 translate = 42 * sin(2 * PI * 5 / N) + tempR*sin(35.0*PI / 180.0) + 5; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(0.0, translate, 0.0); glColor3f(ColorChoose[FACE_COLOR][0], ColorChoose[FACE_COLOR][1], ColorChoose[FACE_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < 7 * N / 12 + 2; i++) { tempX = tempR*cos(2 * PI*i / N); tempY = tempR*sin(2 * PI*i / N); glVertex2f(tempX, tempY); } for (int i = 11 * N / 12; i < N; i++) { tempX = tempR*cos(2 * PI*i / N); tempY = tempR*sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //肚子 { tempR = 40 * cos(2 * PI * 5 / N) / cos(35.0*PI / 180.0)-5;//肚子的半径 translate = 42 * sin(2 * PI * 5 / N) - tempR*sin(35.0*PI / 180.0)-1; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(0.0, translate, 0.0); glColor3f(ColorChoose[FACE_COLOR][0], ColorChoose[FACE_COLOR][1], ColorChoose[FACE_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N / 12 + 2; i++) { tempX = tempR*cos(2 * PI*i / N); tempY = tempR*sin(2 * PI*i / N); glVertex2f(tempX, tempY); } for (int i = 5 * N / 12; i < N; i++) { tempX = tempR*cos(2 * PI*i / N); tempY = tempR*sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); //口袋 tempR = 18; glBegin(GL_POLYGON); for (int i = N / 2; i <= N; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); e812 glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = N / 2; i <= N; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //左眼 { tempR = 40 * cos(2 * PI * 5 / N) / cos(35.0*PI / 180.0);//脸的半径 translate = 42 * sin(2 * PI * 5 / N) + tempR*sin(35.0*PI / 180.0) + 5 + tempR; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(-6.0, translate, 0.0); glColor3f(ColorChoose[EYE_OUT_COLOR][0], ColorChoose[EYE_OUT_COLOR][1], ColorChoose[EYE_OUT_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = 6*cos(2 * PI*i / N); tempY = 8*sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i < N; i++) { tempX = 6*cos(2 * PI*i / N); tempY = 8*sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //左眼眼珠 { tempR = 40 * cos(2 * PI * 5 / N) / cos(35.0*PI / 180.0);//脸的半径 translate = 42 * sin(2 * PI * 5 / N) + tempR*sin(35.0*PI / 180.0) + 5 + tempR; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(-3.0, translate, 0.0); glColor3f(ColorChoose[EYE_IN_COLOR][0], ColorChoose[EYE_IN_COLOR][1], ColorChoose[EYE_IN_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = 3 * cos(2 * PI*i / N); tempY = 3 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //右眼 { tempR = 40 * cos(2 * PI * 5 / N) / cos(35.0*PI / 180.0);//脸的半径 translate = 42 * sin(2 * PI * 5 / N) + tempR*sin(35.0*PI / 180.0) + 5 + tempR; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(6.0, translate, 0.0); glColor3f(ColorChoose[EYE_OUT_COLOR][0], ColorChoose[EYE_OUT_COLOR][1], ColorChoose[EYE_OUT_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = 6 * cos(2 * PI*i / N); tempY = 8 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i < N; i++) { tempX = 6 * cos(2 * PI*i / N); tempY = 8 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //右眼眼珠 { tempR = 40 * cos(2 * PI * 5 / N) / cos(35.0*PI / 180.0);//脸的半径 translate = 42 * sin(2 * PI * 5 / N) + tempR*sin(35.0*PI / 180.0) + 5 + tempR; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(3.0, translate, 0.0); glColor3f(ColorChoose[EYE_IN_COLOR][0], ColorChoose[EYE_IN_COLOR][1], ColorChoose[EYE_IN_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = 3 * cos(2 * PI*i / N); tempY = 3 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //鼻子 { tempR = 40 * cos(2 * PI * 5 / N) / cos(35.0*PI / 180.0);//脸的半径 translate = 42 * sin(2 * PI * 5 / N) + tempR*sin(35.0*PI / 180.0) + tempR-5; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(0.0, translate, 0.0); glColor3f(ColorChoose[NOSE_COLOR][0], ColorChoose[NOSE_COLOR][1], ColorChoose[NOSE_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = 3 * cos(2 * PI*i / N); tempY = 3 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //嘴 { tempR = 40 * cos(2 * PI * 5 / N) / cos(35.0*PI / 180.0);//脸的半径 translate = 42 * sin(2 * PI * 5 / N) + tempR*sin(35.0*PI / 180.0) + tempR - 25; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(0.0, translate, 0.0); glColor3f(ColorChoose[NOSE_COLOR][0], ColorChoose[NOSE_COLOR][1], ColorChoose[NOSE_COLOR][2]); glBegin(GL_POLYGON); for (int i = N/2; i <= N; i++) { tempX = 20 * cos(2 * PI*i / N); tempY = 20 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = N / 2 ; i <= N; i++) { tempX = 20 * cos(2 * PI*i / N); tempY = 20 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); glBegin(GL_LINES); glVertex2f(0.0f, 0.0f); glVertex2f(0.0f, 17.0f); glEnd(); } //胡须 { tempR = 40 * cos(2 * PI * 5 / N) / cos(35.0*PI / 180.0);//脸的半径 translate = 42 * sin(2 * PI * 5 / N) + tempR*sin(35.0*PI / 180.0) + tempR - 15; glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(0.0, translate, 0.0); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][1], ColorChoose[LINE_COLOR][2]); glBegin(GL_LINES); glVertex2f(-25.0f, 0.0f); glVertex2f(-5.0f, 0.0f); glVertex2f(25.0f, 0.0f); glVertex2f(5.0f, 0.0f); glVertex2f(-23.0f, 8.0f); glVertex2f(-5.0f, 4.0f); glVertex2f(23.0f, 8.0f); glVertex2f(5.0f, 4.0f); glVertex2f(-23.0f, -8.0f); glVertex2f(-5.0f, -4.0f); glVertex2f(23.0f, -8.0f); glVertex2f(5.0f, -4.0f); glEnd(); } //左脚 { tempR = 20 * cos(2 * PI * 5 / N); translate = 42 * sin(2 * PI * 5 / N); glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(-tempR, -translate, 0.0); glColor3f(ColorChoose[FOOT_COLOR][0], ColorChoose[FOOT_COLOR][1], ColorChoose[FOOT_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * 0.5 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][0]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i < N; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * 0.5 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } //右脚 { tempR = 20 * cos(2 * PI * 5 / N); translate = 42 * sin(2 * PI * 5 / N); glLoadIdentity(); glRotatef(theta, 0.0, 0.0, 1.0); glTranslatef(tempR, -translate, 0.0); glColor3f(ColorChoose[FOOT_COLOR][0], ColorChoose[FOOT_COLOR][1], ColorChoose[FOOT_COLOR][2]); glBegin(GL_POLYGON); for (int i = 0; i < N; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * 0.5 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][1], ColorChoose[LINE_COLOR][2]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i < N; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * 0.5 * sin(2 * PI*i / N); glVertex2f(tempX, tempY); } glEnd(); } }
以上是主体人物的绘制,接下来绘制一些小物件:
//绘制帽子 void DrawHat(int x, int y) { glLoadIdentity(); //若是穿在身上,则一起旋转 if (HAT_WEARING == TRUE) glRotatef(theta, 0.0, 0.0, 1.0); //若是选择缩放的状态,将图形移动到原点,缩放后再移动到原位置 if (SCALE_STATE == TRUE && SelectPart == HAT) { glTranslatef(x, y, 0.0); glScalef(scale, scale, 1); glTranslatef(-x, -y, 0.0); } else { } glColor3f(ColorChoose[HAT_COLOR][0], ColorChoose[HAT_COLOR][1], ColorChoose[HAT_COLOR][2]); //将物体z坐标置为1,表示层级在身体之上 tempR = 18; glBegin(GL_POLYGON); for (int i = 0; i <= N/2; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * sin(2 * PI*i / N); glVertex3f(tempX+x, tempY+y,1.5); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][1], ColorChoose[LINE_COLOR][2]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i <= N/2; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * sin(2 * PI*i / N); glVertex3f(tempX+x, tempY+y,1.5); } glEnd(); glColor3f(ColorChoose[HAT_COLOR][0], ColorChoose[HAT_COLOR][1], ColorChoose[HAT_COLOR][2]); tempR = 5; glBegin(GL_POLYGON); for (int i = 0; i <= N; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * sin(2 * PI*i / N) + 23; glVertex3f(tempX + x, tempY + y, 1.5); } glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][1], ColorChoose[LINE_COLOR][2]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); for (int i = 0; i <= N; i++) { tempX = tempR * cos(2 * PI*i / N); tempY = tempR * sin(2 * PI*i / N) + 23; glVertex3f(tempX + x, tempY + y, 1.5); } glEnd(); glColor3f(ColorChoose[HAT_COLOR][0], ColorChoose[HAT_COLOR][1], ColorChoose[HAT_COLOR][2]); glBegin(GL_POLYGON); glVertex3f(-18.0 + x, 0.0+ y , 1.5); glVertex3f(-18.0 + x, -5.0 + y, 1.5); glVertex3f(18.0 + x, -5.0 + y, 1.5); glVertex3f(18.0 + x, 0.0 + y, 1.5); glEnd(); glColor3f(ColorChoose[LINE_COLOR][0], ColorChoose[LINE_COLOR][1], ColorChoose[LINE_COLOR][2]); glLineWidth(1.0);//线条粗细设置 glBegin(GL_LINE_LOOP); glVertex3f(-18.0 + x, 0.0 + y, 1.5); glVertex3f(-18.0 + x, -5.0 + y, 1.5); glVertex3f(18.0 + x, -5.0 + y, 1.5); glVertex3f(18.0 + x, 0.0 + y, 1.5); glEnd(); }
进行简单的图形绘制后(绘制方法可能比较笨,但是比较好理解),为了增加交互功能,使用户可以使用鼠标进行一系列的交互操作,需要增加图元识别的功能:
首先设置缓存,存放鼠标点击时鼠标所在点的图元数据,包括当前击中图元的数量,各个图元中各个顶点中深度最大和深度最小的顶点的深度;当两个或多个图元重叠时,根据逻辑应将上层图元作为选中图元,因此根据深度数据,选择深度最小的图元,即为浮在最上面的图元,将其状态改为选择状态,表示当前鼠标点击选中该图元,以便进行后续操作。(OpenGL超级宝典第4版P298有相关介绍)
//处理选择,鼠标左键单击时触发 #define BUFFER_LENGTH 64//定义缓冲区大小 void ChooseProcess(int x, int y) { GLfloat Times; //点击计数器和视口存储 GLint hits, viewport[4]; //选择缓冲区空间 static GLuint SelectBuff[BUFFER_LENGTH]; //设置选择缓冲区 glSelectBuffer(BUFFER_LENGTH, SelectBuff); //获得视口(0,0,800,600)起始点坐标和视口大小 glGetIntegerv(GL_VIEWPORT, viewport); /*for (int i = 0; i < 4; i++) { printf("%d \n", viewport[i]); }*/ //切换到投影模式,并保存矩阵 glMatrixMode(GL_PROJECTION); glPushMatrix(); //修改渲染模式,改为选择模式 glRenderMode(GL_SELECT); //围绕鼠标光标点(x,y)建立新的单位立方体裁剪区域,并在垂直和水平方向扩展2个像素 glLoadIdentity(); gluPickMatrix(x, viewport[3] - y, 2, 2, viewport); //根据视口大小调整裁切区域的大小 Times = (float)viewport[2] / (float)viewport[3]; if (viewport[2] <= viewport[3]) { glOrtho(-VIEWSIZE, VIEWSIZE, -VIEWSIZE * Times, VIEWSIZE * Times, -VIEWSIZE, VIEWSIZE); } else { glOrtho(-VIEWSIZE * Times, VIEWSIZE * Times, -VIEWSIZE, VIEWSIZE, -VIEWSIZE, VIEWSIZE); } //绘制场景 myDisplay(); //收集点击记录 hits = glRenderMode(GL_RENDER); //printf("1> Hits is %d\n", hits); //缓存器中的数据4个为一组,分别记录着 //名字堆栈的数量, //可视区域内包含的所有顶点的最小和最大的z坐标,范围是[0-1],但会被扩充到无符号整数的最大长度 //名字堆栈的底部 if (hits > 0) { int Choose = SelectBuff[3]; int depth = SelectBuff[1]; //printf("2> SelectBuffer[3] is %d\n", Choose); //printf("2> SelectBuffer[1] is %d\n", depth); //遍历缓存中的数据,选择深度较小的图元 for (int loop = 1; loop < hits; loop++) { if (SelectBuff[loop * 4 + 1] < GLuint(depth)) { Choose = SelectBuff[loop * 4 + 3]; depth = SelectBuff[loop * 4 + 1]; } //printf(">3 Choose is %d depth is %d \n", Choose, depth); } //printf(">3 Hits is %d Choose is %d \n", hits, Choose); ChooseState(GLuint(Choose));//根据选择的图元,改变它的状态 } //点击数为0,说明击中的是空白部分,重置所有状态 if (hits == 0) { ChooseState(0); } /*printf("\n show buffer\n"); for (int i = 0; i < BUFFER_LENGTH; i++) { printf("SelectBuffer[%d] is %d ", i, SelectBuff[i]); if (i % 5 == 0) printf("\n"); } printf("\n");*/ //恢复投影矩阵 glMatrixMode(GL_PROJECTION); glPopMatrix(); //恢复到模型视图 glMatrixMode(GL_MODELVIEW); }
接下来编写各类回调函数:
首先添加窗口大小改变时的回调函数ChangeSize,当用户改变窗口的大小时,为了使图形仍然保持正确的比例,需要对窗口进行重新裁剪:
//窗口大小改变时调用的函数 void ChangeSize(int w, int h) { GLfloat Times; if (h == 0) h = 1; glViewport(0, 0, w, h); Times = (GLfloat)w / (GLfloat)h; glMatrixMode(GL_PROJECTION); glLoadIdentity(); //根据窗口大小调整裁切区域的大小 if (w <= h) { glOrtho(-VIEWSIZE, VIEWSIZE, -VIEWSIZE / Times, VIEWSIZE / Times, -VIEWSIZE, VIEWSIZE); } else { glOrtho(-VIEWSIZE * Times, VIEWSIZE * Times, -VIEWSIZE, VIEWSIZE, -VIEWSIZE, VIEWSIZE); } printf("WindowSize changed , now VIEWSIZE is %d \n", VIEWSIZE); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glHint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST); glEnable(GL_POLYGON_SMOOTH); glEnable(GL_POINT_SMOOTH); glEnable(GL_LINE_SMOOTH); }
接下来编写鼠标的回调函数:
当用户使用鼠标点击时,调用之前关于图元识别的函数,确定用户是否选中某图元,并对图元的状态进行修改,以便后续绘制时进行相应的变换。
以帽子为例,如果选中的图元是帽子(即HAT_STATE变量为TRUE),则将HAT_WEARING变量设置为TRUE,表示它被穿着在人物身上,在绘制时,可以随着人物一起转动。这里的判定是为了将帽子摆正,当用户的鼠标在一定范围内松开时,将帽子自动对齐到中央位置:
//帽子位置判断 if (button == GLUT_LEFT_BUTTON&&state == GLUT_UP&&HAT_STATE == TRUE) { //printf("Loop1 success!"); if (xnow > -15 && xnow < 15 && ynow>103 && ynow < 115) { HAT_WEARING = TRUE; } else { HAT_WEARING = FALSE; } glutPostRedisplay(); myDisplay(); }
接下来是鼠标移动的回调函数,当鼠标移动时触发。
因为系统返回的是鼠标的窗口坐标,并不是世界坐标,因此为了使图像和鼠标同步运动,需要进行坐标的转换:
if (HAT_STATE == TRUE) { if (viewport[2] <= viewport[3]) { Hat[0] = -VIEWSIZE + 2 * VIEWSIZE * x / viewport[2]; Hat[1] = -VIEWSIZE * Times + (viewport[3] - y) * 2 * VIEWSIZE * Times / viewport[3]; } else { Hat[0] = -VIEWSIZE * Times + 2 * VIEWSIZE * Times*x / viewport[2]; Hat[1] = -VIEWSIZE + (viewport[3] - y) * 2 * VIEWSIZE / viewport[3]; } //printf("Now Mouse is on ( %d , %d ) \n", Crown[0], Crown[1]); //移动到指定位置,改变状态为wearing if (Hat[0] > -15 && Hat[0] < 15 && Hat[1]>103 && Hat[1] < 115) { HAT_WEARING = TRUE; } else { HAT_WEARING = FALSE; } myDisplay(); }
接下来是键盘响应函数,当用户点击键盘时,触发对应的功能:
当用户点击z时,要实现缩放的功能,因此设置一个scale变量,它的值表示缩放的倍数,并且在1、2、3之间变换,每当用户点击时,就进行切换,而scale变量在绘制图形的函数中作为glScalef函数的参数,实现图形缩放的功能;当用户点击↑键时,则更改变量theta的值,它代表旋转的角度,同样在绘制函数中作为glRotatef函数的参数使用,完成图形旋转功能,前文中提到图元可以设置是否穿戴,只有穿戴在卡通人物身上的图元会随着人物旋转,而旁边没有穿戴的图元则不进行旋转:
//键盘响应函数 void myKeyboard(unsigned char key, int x, int y) { switch (key) { case 'z': SCALE_STATE = TRUE; scale += 1; if (scale == 4) scale = 1; glutPostRedisplay(); myDisplay(); //SCALE_STATE = FALSE; break; default: break; } } //键盘方向键响应,根据按下的方向键进行操作 void mySpecial(int key, int x, int y) { switch (key) { case GLUT_KEY_UP: theta = (theta + 10); glutPostRedisplay(); myDisplay(); break; case GLUT_KEY_DOWN: theta = (theta - 10); glutPostRedisplay(); myDisplay(); break; default: break; } }
菜单功能的实现:
使用glutCreateMenu()函数创建菜单,使用glutAttachMenu(GLUT_RIGHT_BUTTON)函数将菜单绑定到鼠标右键上,这样当用户点击右键时,菜单即可呼出,使用glutAddMenuEntry()函数设置菜单项,第一个参数是菜单项显示的文字,第二项是传递给判断函数menu的值:
//创建右键菜单 glutCreateMenu(myMenu); glutAddMenuEntry("balck", 0);//第一个参数是菜单项显示的文本,第二个参数是传递给menu函数的值 glutAddMenuEntry("red", 1); glutAddMenuEntry("green", 2); glutAddMenuEntry("blue", 3); glutAddMenuEntry("cyan", 4); glutAddMenuEntry("purple", 5); glutAddMenuEntry("yellow", 6); glutAddMenuEntry("white", 7); glutAttachMenu(GLUT_RIGHT_BUTTON);
其中判断函数menu的功能是根据传入的参数,进行相应的操作:
//菜单 void myMenu(int index) { //根据选项设定当前选中图元的颜色 switch (SelectPart) { case CROWN: CROWN_COLOR = index; break; case HAT: HAT_COLOR = index; break; case FLAG: FLAG_COLOR = index; break; default: break; } //二次绘制防闪烁 glutPostRedisplay(); myDisplay(); }
程序运行结果:
选中部件可以进行拖动:
穿戴在人物身上的物件可以随人物一同旋转,而未穿戴的物件不旋转:
对选中部件进行缩放(旁边的绿色旗子,因为背景和旗杆都是黑色所以可能看不出来是个旗子):
右键可以将旗子换色:
相关文章推荐
- [OpenGL]图形学课程设计:二维卡通人脸交互设计与控制
- 计算机图形学——二维卡通人物交互设计
- 交互设计:触屏网页设计初探(一)
- [OpenGL]图形学课程设计:二维射击游戏
- 软件产品基础设计初探(界面、用户交互与体验、基础功能、业务功能)
- PS设计可爱的卡通剪纸人物场景
- 5.21 使用波纹效果命令给卡通人物设计波浪发型 [Illustrator CC教程]
- 图形学卡通人物绘制以及交互操作
- 图形学卡通人物绘制以及交互操作
- WEB交互界面易用性设计和验收的指导性原则
- 游戏交互设计顶尖大师Chris Crawford最新力作《Chris Crawford on Interactive Storytelling》
- 如何解决大量字段的录入交互界面的设计呢?
- 网站制作流程及界面交互设计
- 交互设计与状态模式,兼谈几个失败的设计
- 卡通人物语录
- 网站制作流程及界面交互设计研究探讨
- WEB交互界面易用性设计和验收的指导性原则
- 21世纪——"交互设计"
- WEB交互界面易用性设计和验收的指导性原则