OpenGL画自行车+菜单设置(附源码) 您所在的位置:网站首页 自行车简易版画画图片 OpenGL画自行车+菜单设置(附源码)

OpenGL画自行车+菜单设置(附源码)

2024-07-13 11:25| 来源: 网络整理| 查看: 265

OpenGL大作业是画一个自行车,难度不高,但是代码量还是比较大的,所以放到博客上展示一下,不具备什么技术性。

全部代码放在最后,可以先看源码再看讲解。

0、准备工作

我们首先各种include

之后定义pi。因为这个程序是我之前画的一个贪吃蛇程序改的,所以画布的定义通过定义格子的数量及大小确定的,这里没有改所以就放在这了。

#define pi 3.1415926535897932 //这里是定义画布的大小,按格子划分画布,一个格子50,一行20个格子 #define gridSize 50 #define gridnum 20

然后为了方便我定义了一个点类,虽然也可以用二维数组或者结构体,不过还是习惯了定义成class。为了方便,全部public,大作业而已要什么严谨,因为OpenGL主要都是float类型,所以这里也用的float而不是double,理论上高精度转低精度问题不大,但是不想管了。

class Point { public: float x; float y; Point(float x, float y) { this->x = x; this->y = y; } };

定义一个点A(1,1)的时候可以直接写

Point A=Point(1,1); 1、Bike类的结构

下面就是这次作业的主要内容了

1.0 成员变量

为了方便,我们需要先定义好这个自行车的特征点。车轮位置、车把位置、旋转过的角度等。

bool Fill = true; //定义是填充还是线框,true是填充 double roAngle = 0; //绘图的初始角度,逐渐改变roAngle可以让图像运动 Point wheel1 = Point(300, 0); //前轮中心的位置 Point wheel2 = Point(-300, 0); //后轮的中心位置 Point Pedal = Point(0, 0); //脚踏板的中心位置 char mode = 'L'; //运行速度可选low mid high 1.1 画圆

我搜索发现OpenGL是没有什么画圆的函数的,所以找到了别人的代码,稍微修改了一下。原理就是利用for循环画多边形,其中accuracy是控制圆的精度的。传入参数Point O是圆心坐标,float r是圆的半径。

没有加控制颜色的内容,所以使用时要先用修改颜色的函数,否则就是跟前面的颜色一样,也行。

void DrawCircular(Point O, float r) { int accuracy = 72; glBegin(GL_POLYGON); for (int i = 0; i < accuracy; ++i) glVertex2f(O.x + r * cos(2 * pi / accuracy * i), O.y + r * sin(2 * pi / accuracy * i)); glEnd(); glFlush(); } 1.2 旋转点

在实现过程中发现画一个斜着的矩形比较麻烦,要输入四个点。而且自行车车轮的辐条也需要利用旋转才可以实现,所以我定义了一个将点绕另一个点旋转的函数。返回的结果是一个Point类型。

实现的功能就是将点p绕点o旋转angle°,这里用的角度制,会比较方便使用。

Point PointRotate(Point o, Point p, double angle) { angle = (angle / 180) * pi; float px = p.x - o.x; float py = p.y - o.y; float tempx = px * cos(angle) - py * sin(angle); float tempy = px * sin(angle) + py * cos(angle); return Point(o.x + tempx, o.y + tempy); } 1.3 绘制斜矩形

OpenGL提供的绘制矩形的函数只能是一个方向的,而自行车绘图里会有很多斜的矩形,所以定义了斜着绘制矩形的函数。

把斜的长方形看成一条线段,那么线段的端点就是p1和p2,width是线宽的一半。

原理就是p1p点在选段上,距离p1点width,然后将这个p1p点绕p1点旋转±90°得到两个点。同样找到p2p点,绕p2点旋转±90°,就可以得到四个点ABCD,最后调用绘制多边形,输入这四个点就可以实现了。

但是最后会出现bug,某条长边的边会出现曲线,原因未知,所以我就画两个三角形,最后看的效果还是一样的。

void DrawLine(Point p1, Point p2, float width) { float lineLength = sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)); float x_p1p = p1.x + (p2.x - p1.x) * width / lineLength; float y_p1p = p1.y + (p2.y - p1.y) * width / lineLength; Point p1p = Point(x_p1p, y_p1p); float x_p2p = p2.x + (p1.x - p2.x) * width / lineLength; float y_p2p = p2.y + (p1.y - p2.y) * width / lineLength; Point p2p = Point(x_p2p, y_p2p); Point A = PointRotate(p1, p1p, 90); Point B = PointRotate(p1, p1p, -90); Point C = PointRotate(p2, p2p, -90); Point D = PointRotate(p2, p2p, 90); glBegin(GL_POLYGON); glVertex2f(D.x, D.y); glVertex2f(C.x, C.y); glVertex2f(B.x, B.y); glEnd(); glBegin(GL_POLYGON); glVertex2f(A.x, A.y); glVertex2f(C.x, C.y); glVertex2f(B.x, B.y); glEnd(); } 1.4 画车架

首先为了方便,把自行车的几个特征点记下来,车轮wheel1和wheel2,车座底端vertex,车座Seat,车头head,车把Handle,脚踏板旋转中心Pedal。

之后要做的就是调用 绘制斜长方形的函数DrawLine,连接这几个点就行了。

void DrawFrame() { Point Seat = Point(-150, 300); Point vertex = Point(-125, 250); glColor3f(0.0, 1.0, 0.0); DrawLine(wheel2, Pedal, 10); DrawLine(wheel2, vertex, 10); DrawLine(Pedal, Seat, 10); DrawLine(Point(Seat.x - 50, Seat.y), Point(Seat.x + 100, Seat.y), 15); Point head(200, 250); Point Handle(170, 350); DrawLine(head, vertex, 10); DrawLine(head, Pedal, 10); DrawLine(Handle, wheel1, 10); DrawLine(Point(Handle.x + 10, Handle.y), Point(Handle.x - 70, Handle.y), 10); DrawCircular(wheel1, 20); DrawCircular(wheel2, 20); DrawCircular(Pedal, 20); } 1.5 画脚踏板

设置脚踏板的旋转中心为Point O

圆环用画圆函数,画一个大圆,一个白色的小同心圆盖住即可。

脚踏杆用DrawLine函数。但是因为设计到旋转,所以这个脚踏杆的初始位置一端就是旋转中心O,另一端就是绕O旋转angle°,由于这个角度需要随时间变化,所以这个在类里设计了一个成员变量roAngle记录当前旋转到的角度就可以了。

void DrawPedal(Point O) { double angle = (roAngle - 60) * pi / 180; DrawCircular(O, 50); glColor3f(1.0, 1.0, 1.0); DrawCircular(O, 30); glColor3f(1.0f, 0.0, 0.0); double length = 120; Point p3 = Point(O.x + length * cos(angle), O.y + length * sin(angle)); glColor3f(0.0f, 1.0, 0.0); DrawLine(O, p3, 10); DrawLine(Point(p3.x - 50, p3.y), Point(p3.x + 50, p3.y), 10); DrawCircular(p3, 10); }

1.6 滑轮子

和脚踏板没啥区别,还是圆和斜线,还是绕roAngle°,所以直接上代码。

轮子的中心Point O

void DrawWheel(Point O) { glColor3f(1.0f, 0.0, 0.0); DrawCircular(O, 200); glColor3f(1.0, 1.0, 1.0); DrawCircular(O, 180); glColor3f(1.0, 0.0, 0.0); glBegin(GL_POLYGON); float weidth = 10; float length = 200; Point p1 = Point(-1 * weidth + O.x, -1 * length + O.y); Point p2 = Point(weidth + O.x, -1 * length + O.y); Point p3 = Point(weidth + O.x, O.y); Point p4 = Point(-1 * weidth + O.x, O.y); double angle = roAngle; for (int i = 0; i < 6; i++) { Point p5 = PointRotate(O, p1, angle); Point p6 = PointRotate(O, p2, angle); Point p7 = PointRotate(O, p3, angle); Point p8 = PointRotate(O, p4, angle); glBegin(GL_POLYGON); glVertex2f(p5.x, p5.y); glVertex2f(p6.x, p6.y); glVertex2f(p7.x, p7.y); glVertex2f(p8.x, p8.y); glEnd(); angle = angle + 60; } } 1.7 画自行车

画元素的函数都编完了,那剩下的就是调用就行了。开始前先clear清除一下画布。

由于有要求说可选填充或者线框,所以做一个判断,是否Fill,决定使用GL_FILL还是GL_LINE。其他的注意遮挡关系,先后画图就好。

最后glFlush刷新到画布上,清空缓存。

void BikeDisplay() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (Fill) { glPolygonMode(GL_FRONT, GL_FILL); glPolygonMode(GL_BACK, GL_FILL); } else { glPolygonMode(GL_FRONT, GL_LINE); glPolygonMode(GL_BACK, GL_LINE); } glFrontFace(GL_CW); DrawWheel(wheel1); DrawWheel(wheel2); DrawPedal(Pedal); DrawFrame(); glFlush(); glutSwapBuffers(); } 1.8 旋转函数

很简单就是让旋转过的角度roAngle增加就行,不过需要根据运行速度低中高,分别设置增量。直接用Switch分支结构就行。

void Rotate() { switch (mode) { case 'L': {roAngle = roAngle - 5; break; } case 'M': {roAngle = roAngle - 10; break; } case 'H': {roAngle = roAngle - 25; break; } default: break; } } 2、子菜单定义

下面的代码一次添加到主函数里就行

先定义子菜单1,速度菜单

glutCreateMenu(speed_menu);是调用speed_menu函数,

传入的参数就是glutAddMenuEntry("低速", 2);中的这个2。每个标签分别对应一个传入参数。

所以点击子菜单中的"低速",相当于运行speed_menu(2)。

int sub_menu1 = glutCreateMenu(speed_menu); glutAddMenuEntry("低速", 2); glutAddMenuEntry("中速", 3); glutAddMenuEntry("高速", 4); 定义子菜单2,线框、填充 int sub_menu2 = glutCreateMenu(mode_menu); glutAddMenuEntry("填充", 5); glutAddMenuEntry("线框", 6); 定义主菜单 //定义主菜单,并将前面的两个子菜单填入 glutCreateMenu(main_menu); glutAddSubMenu("速度", sub_menu1); glutAddSubMenu("模式", sub_menu2); //设置鼠标右键为开启菜单的开关 glutAttachMenu(GLUT_RIGHT_BUTTON); 回调函数

写在main前面

void main_menu(int id) { } void speed_menu(int id) { switch (id) { case 2: bike.mode = 'L'; break; case 3: bike.mode = 'M'; break; case 4: bike.mode = 'H'; break; } } void mode_menu(int id) { switch (id) { case 5:bike.Fill = true; break; case 6:bike.Fill = false; break; } }

3、OpenGL要求使用的函数 初始化函数

确定初始化画布的颜色、大小、窗口位置、显示范围。照抄就行

void init() { glClearColor(1.0, 1.0, 1.0, 1.0); glColor3f(1.0f, 0.0f, 0.0f); gluOrtho2D(0.0, (gridnum + 2) * gridSize / 2, 0.0, (gridnum + 2) * gridSize / 2); } 改变窗口尺寸时的函数 void changSize(GLint w, GLint h) { GLfloat ratio; GLfloat coordinatesize = 1000.0f; if ((w == 0) || (h == 0)) return; glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); ratio = (GLfloat)w / (GLfloat)h; if (w < h) glOrtho(-coordinatesize, coordinatesize, -coordinatesize / ratio, coordinatesize / ratio, -coordinatesize, coordinatesize); else glOrtho(-coordinatesize * ratio, coordinatesize * ratio, -coordinatesize, coordinatesize, -coordinatesize, coordinatesize); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } 初始化一个自行车对象 Bike bike; 显示函数 void MyDisplay() { bike.Rotate(); bike.BikeDisplay(); } 定时函数

负责100ms之后再次显示,里面嵌套一次定时函数,就可以实现一直运动的效果。

void TimerFunc(int value) { glutPostRedisplay(); glutTimerFunc(100, TimerFunc, 1); } 主函数

依次调用前面定义好的函数就行,没说到的代码基本是OpenGL项目都要写的代码。

int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowPosition(200, 50); glutInitWindowSize(gridnum * gridSize, gridnum * gridSize); glutCreateWindow("Point"); init(); glutDisplayFunc(MyDisplay); glutTimerFunc(100, TimerFunc, 1); glutReshapeFunc(changSize); int sub_menu1 = glutCreateMenu(speed_menu); glutAddMenuEntry("低速", 2); glutAddMenuEntry("中速", 3); glutAddMenuEntry("高速", 4); int sub_menu2 = glutCreateMenu(mode_menu); glutAddMenuEntry("填充", 5); glutAddMenuEntry("线框", 6); glutCreateMenu(main_menu); glutAddSubMenu("速度", sub_menu1); glutAddSubMenu("模式", sub_menu2); glutAttachMenu(GLUT_RIGHT_BUTTON); glutMainLoop(); return 0; }

4、全部代码 //这里的include要配置一下不然找不到glut.h,可以参考其他博客 #include #include #include #include #include using namespace std; //这里进行了一些宏定义,pi为圆周率已经定义到double的极限了 #define pi 3.1415926535897932 //这里是定义画布的大小,按格子划分画布,一个格子50,一行20个格子 #define gridSize 50 #define gridnum 20 //定义一个点类,方便后续操作,二维数组也行,不过我习惯了定义成类 class Point { public: float x; float y; Point(float x, float y) { this->x = x; this->y = y; } }; //定义画自行车的类 class Bike { public: bool Fill = true; //定义是填充还是线框,true是填充 double roAngle = 0; //绘图的初始角度,逐渐改变roAngle可以让图像运动 Point wheel1 = Point(300, 0); //前轮中心的位置 Point wheel2 = Point(-300, 0); //后轮的中心位置 Point Pedal = Point(0, 0); //脚踏板的中心位置 char mode = 'L'; //运动速度,L、M、H分别是低中高速 //绘图函数,将整个自行车画出来 void BikeDisplay() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (Fill) { glPolygonMode(GL_FRONT, GL_FILL); glPolygonMode(GL_BACK, GL_FILL); } else { glPolygonMode(GL_FRONT, GL_LINE); glPolygonMode(GL_BACK, GL_LINE); } glFrontFace(GL_CW); DrawWheel(wheel1); DrawWheel(wheel2); DrawPedal(Pedal); DrawFrame(); glFlush(); glutSwapBuffers(); } //逐渐改变绘制图形的初始角度,让自行车轮及脚踏板旋转的函数 void Rotate() { switch (mode) { case 'L': {roAngle = roAngle - 5; break; } case 'M': {roAngle = roAngle - 10; break; } case 'H': {roAngle = roAngle - 25; break; } default: break; } } private: //定义一个画圆的函数,圆心点O,半径为r void DrawCircular(Point O, float r) { int accuracy = 72; glBegin(GL_POLYGON); for (int i = 0; i < accuracy; ++i) glVertex2f(O.x + r * cos(2 * pi / accuracy * i), O.y + r * sin(2 * pi / accuracy * i)); glEnd(); glFlush(); } //将P点绕o点旋转angle度 Point PointRotate(Point o, Point p, double angle) { angle = (angle / 180) * pi; float px = p.x - o.x; float py = p.y - o.y; float tempx = px * cos(angle) - py * sin(angle); float tempy = px * sin(angle) + py * cos(angle); return Point(o.x + tempx, o.y + tempy); } //画一条“线”,是一个斜着的矩形,短边中心点为p1和p2,长方形的宽度为width //直接绘制斜的矩形会出长边变曲线的问题,所以分成两个三角形画 void DrawLine(Point p1, Point p2, float width) { float lineLength = sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)); float x_p1p = p1.x + (p2.x - p1.x) * width / lineLength; float y_p1p = p1.y + (p2.y - p1.y) * width / lineLength; Point p1p = Point(x_p1p, y_p1p); float x_p2p = p2.x + (p1.x - p2.x) * width / lineLength; float y_p2p = p2.y + (p1.y - p2.y) * width / lineLength; Point p2p = Point(x_p2p, y_p2p); Point A = PointRotate(p1, p1p, 90); Point B = PointRotate(p1, p1p, -90); Point C = PointRotate(p2, p2p, -90); Point D = PointRotate(p2, p2p, 90); glBegin(GL_POLYGON); glVertex2f(D.x, D.y); glVertex2f(C.x, C.y); glVertex2f(B.x, B.y); glEnd(); glBegin(GL_POLYGON); glVertex2f(A.x, A.y); glVertex2f(C.x, C.y); glVertex2f(B.x, B.y); glEnd(); } //利用定义好的DrawLine函数,绘制自行车的车架 void DrawFrame() { Point Seat = Point(-150, 300); Point vertex = Point(-125, 250); glColor3f(0.0, 1.0, 0.0); DrawLine(wheel2, Pedal, 10); DrawLine(wheel2, vertex, 10); DrawLine(Pedal, Seat, 10); DrawLine(Point(Seat.x - 50, Seat.y), Point(Seat.x + 100, Seat.y), 15); Point head(200, 250); Point Handle(170, 350); DrawLine(head, vertex, 10); DrawLine(head, Pedal, 10); DrawLine(Handle, wheel1, 10); DrawLine(Point(Handle.x + 10, Handle.y), Point(Handle.x - 70, Handle.y), 10); DrawCircular(wheel1, 20); DrawCircular(wheel2, 20); DrawCircular(Pedal, 20); } //利用DrawCircular和DrawLine函数,绘制脚踏板及轴 void DrawPedal(Point O) { double angle = (roAngle - 60) * pi / 180; DrawCircular(O, 50); glColor3f(1.0, 1.0, 1.0); DrawCircular(O, 30); glColor3f(1.0f, 0.0, 0.0); double length = 120; Point p3 = Point(O.x + length * cos(angle), O.y + length * sin(angle)); glColor3f(0.0f, 1.0, 0.0); DrawLine(O, p3, 10); DrawLine(Point(p3.x - 50, p3.y), Point(p3.x + 50, p3.y), 10); DrawCircular(p3, 10); } //利用DrawCircular、PointRotate函数,绘制车轮 void DrawWheel(Point O) { glColor3f(1.0f, 0.0, 0.0); DrawCircular(O, 200); glColor3f(1.0, 1.0, 1.0); DrawCircular(O, 180); glColor3f(1.0, 0.0, 0.0); glBegin(GL_POLYGON); float weidth = 10; float length = 200; Point p1 = Point(-1 * weidth + O.x, -1 * length + O.y); Point p2 = Point(weidth + O.x, -1 * length + O.y); Point p3 = Point(weidth + O.x, O.y); Point p4 = Point(-1 * weidth + O.x, O.y); double angle = roAngle; for (int i = 0; i < 6; i++) { Point p5 = PointRotate(O, p1, angle); Point p6 = PointRotate(O, p2, angle); Point p7 = PointRotate(O, p3, angle); Point p8 = PointRotate(O, p4, angle); glBegin(GL_POLYGON); glVertex2f(p5.x, p5.y); glVertex2f(p6.x, p6.y); glVertex2f(p7.x, p7.y); glVertex2f(p8.x, p8.y); glEnd(); angle = angle + 60; } } }; //初始化函数 void init() { glClearColor(1.0, 1.0, 1.0, 1.0); glColor3f(1.0f, 0.0f, 0.0f); gluOrtho2D(0.0, (gridnum + 2) * gridSize / 2, 0.0, (gridnum + 2) * gridSize / 2); } //改变尺寸时的函数 void changSize(GLint w, GLint h) { GLfloat ratio; GLfloat coordinatesize = 1000.0f; if ((w == 0) || (h == 0)) return; glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); ratio = (GLfloat)w / (GLfloat)h; if (w < h) glOrtho(-coordinatesize, coordinatesize, -coordinatesize / ratio, coordinatesize / ratio, -coordinatesize, coordinatesize); else glOrtho(-coordinatesize * ratio, coordinatesize * ratio, -coordinatesize, coordinatesize, -coordinatesize, coordinatesize); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } //初始化全局变量bike Bike bike; //主函数的display回调函数,旋转车轮并让bike画图 void MyDisplay() { bike.Rotate(); bike.BikeDisplay(); } //设置定时的回调函数,调用MyDisplay,并再次定时完成循环绘图 void TimerFunc(int value) { glutPostRedisplay(); glutTimerFunc(100, TimerFunc, 1); } //主菜单的回调函数,主菜单没有独立子项,所以什么也不用做,不报错就行 void main_menu(int id) { } //调节速度的子菜单,id:2,3,4分别代表低中高速度 void speed_menu(int id) { switch (id) { case 2: bike.mode = 'L'; break; case 3: bike.mode = 'M'; break; case 4: bike.mode = 'H'; break; } } //调节显示模式的子菜单,id:5,6分别代表填充与线框 void mode_menu(int id) { switch (id) { case 5:bike.Fill = true; break; case 6:bike.Fill = false; break; } } int main(int argc, char** argv) { //常规的初始化、定义窗口大小及位置的步骤 glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowPosition(200, 50); glutInitWindowSize(gridnum * gridSize, gridnum * gridSize); glutCreateWindow("Point"); init(); glutDisplayFunc(MyDisplay); glutTimerFunc(100, TimerFunc, 1); glutReshapeFunc(changSize); //定义速度子菜单及内容 int sub_menu1 = glutCreateMenu(speed_menu); glutAddMenuEntry("低速", 2); glutAddMenuEntry("中速", 3); glutAddMenuEntry("高速", 4); //定义模式子菜单及内容 int sub_menu2 = glutCreateMenu(mode_menu); glutAddMenuEntry("填充", 5); glutAddMenuEntry("线框", 6); //定义主菜单,并将前面的两个子菜单填入 glutCreateMenu(main_menu); glutAddSubMenu("速度", sub_menu1); glutAddSubMenu("模式", sub_menu2); //设置鼠标右键为开启菜单的开关 glutAttachMenu(GLUT_RIGHT_BUTTON); glutMainLoop(); return 0; }



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有