0%

OpenGL绘图实例七之鼠标设色取点绘图

综述

在上一节我们学习了鼠标监听事件,在这里我们就利用它来做一个实例,对鼠标监听事件进行一个综合的应用。

要求

1. 绘制如下的机器人,并在此基础上进行创作 图片1 2. 对象创建:支持用户利用鼠标指定各个顶点位置,补充完整机器人的腿部。 3. 对象删除:支持用户选择一个腿部的多边形(与你的多边形保存的数据结构有关)并删除。 4. 对象存储:设计一种数据结构存储每个多边形的顶点与边,支持文件存盘,并支持读盘复原。 5. 支持用户选择多边形的颜色,支持用户移动多边形

成果

在这里为了动态演示,将我的成果以视频形式演示如下

[embed]http://cdn.cuiqingcai.com/robot.mp4[/embed]

好,欣赏完视频之后是不是非常想知道是怎样实现的呢?下面我们来一步一步进行讲解。

绘制半机器人

在前面的几篇中,已经详细描述了绘制机器人的方法,在这里我们简单贴一下代码来绘制一个半机器人。 直接上代码吧,不多说了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include <glut.h>
#include <math.h>
#define PI 3.1415926

/* 画矩形,传入的是左下角XY坐标和右上角XY坐标 */
void glRect( int leftX, int leftY, int rightX, int rightY )
{
/* 画封闭曲线 */
glBegin( GL_LINE_LOOP );
/* 左下角 */
glVertex2d( leftX, leftY );
/* 右下角 */
glVertex2d( rightX, leftY );
/* 右上角 */
glVertex2d( rightX, rightY );
/* 左上角 */
glVertex2d( leftX, rightY );
/* 结束画线 */
glEnd();
}


/* 画圆角矩形,传入矩形宽高,角半径,矩形中心点坐标 */
void glRoundRec( int centerX, int centerY, int width, int height, float cirR )
{
/* 二分之PI,一个象限的角度 */
float PI_HALF = PI / 2;
/* 划分程度,值越大画得越精细 */
float divide = 20.0;
/* 圆角矩形的坐标 */
float tx, ty;
/* 画封闭曲线 */
glBegin( GL_LINE_LOOP );
/* 四个象限不同的操作符 */
int opX[4] = { 1, -1, -1, 1 };
int opY[4] = { 1, 1, -1, -1 };
/* 用来计数,从第一象限到第四象限 */
float x = 0;
/* x自增时加的值 */
float part = 1 / divide;
/* 计算内矩形宽高一半的数值 */
int w = width / 2 - cirR;
int h = height / 2 - cirR;
/* 循环画线 */
for ( x = 0; x < 4; x += part )
{
/* 求出弧度 */
float rad = PI_HALF * x;
/* 计算坐标值 */
tx = cirR * cos( rad ) + opX[(int) x] * w + centerX;
ty = cirR * sin( rad ) + opY[(int) x] * h + centerY;
/*传入坐标画线 */
glVertex2f( tx, ty );
}
/* 结束画线 */
glEnd();
}


/* 画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径 */
void glArc( double x, double y, double start_angle, double end_angle, double radius )
{
/* 开始绘制曲线 */
glBegin( GL_LINE_STRIP );
/* 每次画增加的弧度 */
double delta_angle = PI / 180;
/* 画圆弧 */
for ( double i = start_angle; i <= end_angle; i += delta_angle )
{
/* 绝对定位加三角函数值 */
double vx = x + radius * cos( i );
double vy = y + radius*sin( i );
glVertex2d( vx, vy );
}
/* 结束绘画 */
glEnd();
}


/* 画圆 */
void glCircle( double x, double y, double radius )
{
/* 画全圆 */
glArc( x, y, 0, 2 * PI, radius );
}


/* 画三角形,传入三个点的坐标 */
void glTri( int x1, int y1, int x2, int y2, int x3, int y3 )
{
/* 画封闭线 */
glBegin( GL_LINE_LOOP );
/* 一点 */
glVertex2d( x1, y1 );
/* 二点 */
glVertex2d( x2, y2 );
/*三点 */
glVertex2d( x3, y3 );
/* 结束画线 */
glEnd();
}


/* 画线,传入两点坐标 */
void glLine( int x1, int y1, int x2, int y2 )
{
/* 画封闭线 */
glBegin( GL_LINE_STRIP );
/* 一点 */
glVertex2d( x1, y1 );
/* 二点 */
glVertex2d( x2, y2 );
/* 结束画线 */
glEnd();
}


/* 函数用来画图 */
void display( void )
{
/* GL_COLOR_BUFFER_BIT表示清除颜色 */
glClear( GL_COLOR_BUFFER_BIT );
/* 设置画线颜色 */
glColor3f( 0.5, 0.5, 0.5 );
/* 画点大小 */
glPointSize( 2 );
/* 画圆角矩形,大肚子 */
glRoundRec( 0, 0, 146, 120, 15 );
/* 画圆,中间小圈 */
glCircle( 0, 0, 10 );
/* 画矩形,脖子 */
glRect( -25, 60, 25, 76 );
/* 画圆角矩形,大脸 */
glRoundRec( 0, 113, 128, 74, 10 );
/* 两个眼睛 */
glCircle( -30, 111, 10 );
glCircle( 30, 111, 10 );
/* 两条天线 */
glLine( -35, 150, -35, 173 );
glLine( 35, 150, 35, 173 );
/* 圆角矩形,两个耳朵 */
glRoundRec( 81, 115, 20, 34, 5 );
glRoundRec( -81, 115, 20, 34, 5 );
/* 圆弧,画嘴 */
glArc( 0, 133, 11 * PI / 8, 13 * PI / 8, 45 );
/* 画三角,肚子里的三角 */
glTri( -30, -15, 30, -15, 0, 28 );
/* 画矩形,胳膊连接处 */
glRect( -81, 43, -73, 25 );
glRect( 81, 43, 73, 25 );
/* 画矩形,上臂 */
glRect( -108, 45, -81, 0 );
glRect( 108, 45, 81, 0 );
/* 画矩形,中臂 */
glRect( -101, 0, -88, -4 );
glRect( 101, 0, 88, -4 );
/* 画矩形,下臂 */
glRect( -108, -4, -81, -37 );
glRect( 108, -4, 81, -37 );
/* 画圆形,手掌 */
glCircle( -95, -47, 10 );
glCircle( 95, -47, 10 );
/* 保证前面的OpenGL命令立即执行,而不是让它们在缓冲区中等待 */
glFlush();
}


/* 窗口大小变化时调用的函数 */
void ChangeSize( GLsizei w, GLsizei h )
{
/* 避免高度为0 */
if ( h == 0 )
{
h = 1;
}
/* 定义视口大小,宽高一致 */
glViewport( 0, 0, w, h );
int half = 300;
/* 重置坐标系统,使投影变换复位 */
glMatrixMode( GL_PROJECTION );
/* 将当前的用户坐标系的原点移到了屏幕中心 */
glLoadIdentity();
/* 定义正交视域体 */
if ( w < h )
{
/* 如果高度大于宽度,则将高度视角扩大,图形显示居中 */
glOrtho( -half, half, -half * h / w, half * h / w, -half, half );
} else {
/* 如果宽度大于高度,则将宽度视角扩大,图形显示居中 */
glOrtho( -half * w / h, half * w / h, -half, half, -half, half );
}
}


/*程序入口 */
int main( int argc, char *argv[] )
{
/* 对GLUT进行初始化,并处理所有的命令行参数 */
glutInit( &argc, argv );
/* 指定RGB颜色模式和单缓冲窗口 */
glutInitDisplayMode( GLUT_RGB | GLUT_SINGLE );
/* 定义窗口的位置 */
glutInitWindowPosition( 100, 100 );
/* 定义窗口的大小 */
glutInitWindowSize( 600, 600 );
/* 创建窗口,同时为之命名 */
glutCreateWindow( "OpenGL" );
/* 设置窗口清除颜色为白色 */
glClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
/* 参数为一个函数,绘图时这个函数就会被调用 */
glutDisplayFunc( &display );
/* 参数为一个函数,当窗口大小改变时会被调用 */
glutReshapeFunc( ChangeSize );
/* 该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止 */
glutMainLoop();
/*程序返回 */
return(0);
}

运行结果如下 20150511002257 这一步非常非常简单,如果有不懂的小伙伴请参见 此文章

对象创建

通过上面的一步,我们已经可以轻松地绘制出一个半身机器人,接下来我们就要通过监听鼠标事件来实现动态多边形的绘制了。 因为实验要求中要求了多边形的颜色绘制,所以颜色怎么选取呢?我是这么想的,在面板的左上角绘制几个色块,通过鼠标点击这几个色块,读取出 RGB 值,然后相应地设置当前绘图的颜色即可。 所以首先我在左上角定义了几个颜色色块,同时监听鼠标事件。 首先定义几个颜色的 RGB 值

1
2
3
4
5
6
7
8
9
/* 各种颜色 */
GLubyte border[3] = {0, 0, 0};
GLubyte grey[3] = {195, 195, 195};
GLubyte yellow[3] = {255, 243, 0};
GLubyte red[3] = {237, 28, 36};
GLubyte darkGrey[3] = {126, 126, 126};
GLubyte white[3] = {255, 255, 255};
/* 当前颜色 */
GLubyte nowColor[3] = {0, 0, 0};

然后定义一个绘制填充色的圆形的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 绘制填充的圆形 */
void glColorCircle(int x, int y, int R, GLubyte color[3])
{
/* 开始绘制曲线 */
glBegin(GL_POLYGON);
/* 设置颜色 */
glColor3ub(color[0], color[1], color[2]);
/* 每次画增加的弧度 */
double delta_angle = PI / 180;
/* 画圆弧 */
for (double i = 0; i <= 2 * PI; i += delta_angle)
{
/* 绝对定位加三角函数值 */
double vx = x + R * cos(i);
double vy = y + R*sin(i);
glVertex2d(vx, vy);
}
/* 结束绘画 */
glEnd();
glFlush();
}

那么在 display 函数中,我们就可以调用下面的方法来绘制三个取色盘拾取颜色,其中最后一个取色盘保存了当前的颜色。

1
2
3
4
5
/* 绘制取色盘 */
glColorCircle(-280, 280, 10, red);
glColorCircle(-250, 280, 10, yellow);
glColorCircle(-220, 280, 10, grey);
glColorCircle(-180, 280, 10, nowColor);

这样光有了取色盘可不行,需要有鼠标点击事动态地改变取色的色值。现在我们可以加入鼠标监听方法如下 通过上一节的说明,我们定义一个 mouseClick 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {

}else{

}
}
}

另外加入监听方法

1
2
/*鼠标点击事件,鼠标点击或者松开时调用 */
glutMouseFunc(mouseClick);

在这里有几个地方需要说明一下。

1.选项卡的分界范围是什么?

其实我是把整个画面分割成了三部分,其中最上面 40 像素的一个长条就是选项卡,选项卡又分为了两部分。左边是取色板,右边是绘制的选项,比如取点,绘制,删除等等的操作。所以,optionHeight 就指的是上面的 40 像素的分割线,optionWidth 是中间的分割线,即上方调色板和绘制选项点的分割线。 通过鼠标点击判断是在哪个范围内从而响应不同的事件。 从而鼠标监听事件就是下面的结构了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {

/* 如果点击的是下方的绘图页面 */
}else{

}

其中第四个色板就是显示了当前的颜色,在-180,280 的位置绘制了当前的颜色色盘。并同时颜色值保存在了 nowColor 中。

2. halfHeight 和 halfWidth

这里定义了 halfHeight 和 halfWdith,在后面的绘图中会用到,同时在取色时也会用到,实际就是整个窗口宽高的一半,定义为全局变量即可。如当前的窗口是 600x600,所以现在二者就都是 300,300. 到现在,如果你按照流程走下来,代码是可以正常运行的,现在可以通过点击左上角的色盘进行取色,同时第四个色盘显示了当前的颜色。运行结果截图如下 20150511005807 好,接下来我们就进行多边形的定义了,多边形的定义我们利用结构体的形式,假设我们绘制的边数最大是 10,可以定义如下的多边形结构体,结构体中包含了顶点坐标(数组形式),顶点数量,颜色(RGB)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 每个图形最多的顶点数 */
#define MAX_VERTEX 10
/* 画的图形最多的个数 */
#define MAX_PLOY 10
/* 多边形结构体 */
struct polygon {
/* 顶点坐标 */
int x[MAX_VERTEX];
int y[MAX_VERTEX];
/* 定义第几个顶点 */
int verNum;
GLubyte color[3];
};
/* 声明多边形数组 */
polygon polygons[MAX_PLOY];

为了便于记录,我们定义一个 con 变量来记录当前绘制到了哪个多边形

1
2
/* 记录画了几个多边形 */
int con = 0;

好,定义好结构体之后,我们就可以先选取点,然后绘制多边形了。所以在这里我们定义两个按钮,取点和绘制。 定义两个颜色

1
2
GLubyte startBtn[3] = {10, 10, 10};
GLubyte endBtn[3] = {20, 20, 20};

我们可以用颜色来区分不同的按钮,现在原来的 display 函数中画上这俩按钮。

1
2
glColorCircle(250, 280, 10, startBtn);
glColorCircle(280, 280, 10, endBtn);

如图所示,当我们要取点的时候就可以点击一下左边的按钮,取完点后就可以点击右边的按钮完成绘制。 QQ截图20150511010802 在鼠标监听事件中,当点击了左边的按钮,便开始拾取几个顶点,然后点击右边的按钮之后,会将图形绘制出来。所以为了区分当前是在拾取点还是完成绘制,我们定义一个状态变量叫 drawStatus

1
2
/* 绘制多边形的起始标志,0是开始绘制,1是结束绘制,初始为-1 */
int drawStatus = -1;

在原来的 mouseClick 函数中,对于判断点击右上方选项卡的响应就可以改写如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {
//取当前的点
GLubyte color[3];
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color);
printf("r:%d,g:%d,b:%d\n", color[0], color[1], color[2]);
/* 如果点击了开始绘制的按钮 */
if (sameColor(color, startBtn)) {
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
printf("drawStatus:%d\n", drawStatus);
/* 如果点击了结束绘制的按钮 */
} else if (sameColor(color, endBtn)) {
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
printf("drawStatus:%d\n", drawStatus);
}
/* 如果点击的是下方的绘图页面 */
}else{

}
}
}

其中 sameColor 方法就是来判断当前点击的位置是否和按钮颜色相等,在这里为了避免误差,我们像往常一样设置一个容差。

1
2
3
4
5
6
7
8
9
10
/* 判断两个颜色是否相等 */
bool sameColor(GLubyte color1[3], GLubyte color2[3])
{
if (color1[0] - color2[0] < 5 && color1[1] - color2[1] < 5 && color1[2] - color2[1] < 5)
{
return(true);
} else {
return(false);
}
}

好,当点击了第一个黑色按钮(取点的按钮)时,我们便可以在下方拾取点来保存到数组中了。 在最后的 else 代码块中加入如下的方法,用来取点。 这里的取点是取到了点击的坐标值,当前的 RGB 值,每取一个点,响应的 verNum(顶点数目)就加一。 而且当前保存到的多边形是 polygons[con],con 初始值为 0,也就是保存到第一个多边形结构体中。 当我们点击了开始绘制按钮时,con 会加 1,并且将保存的多边形绘制出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 如果当前是正在取点状态 */
if (drawStatus == 0)
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints(x, y);
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
printf("polyColor%d%d%d\n", polygons[con].color[0],polygons[con].color[1],polygons[con].color[2]);
polygons[con].verNum++;
printf("con:%d,verNum:%d\n", con, polygons[con].verNum);
}

到现在上面的 mouseClick 方法就变成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {
//取当前的点
GLubyte color[3];
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color);
printf("r:%d,g:%d,b:%d\n", color[0], color[1], color[2]);
/* 如果点击了开始绘制的按钮 */
if (sameColor(color, startBtn)) {
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
printf("drawStatus:%d\n", drawStatus);
/* 如果点击了结束绘制的按钮 */
} else if (sameColor(color, endBtn)) {
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
printf("drawStatus:%d\n", drawStatus);
}
/* 如果点击的是下方的绘图页面 */
}else{
/* 如果当前是正在取点状态 */
if (drawStatus == 0)
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints(x, y);
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
printf("polyColor%d%d%d\n", polygons[con].color[0],polygons[con].color[1],polygons[con].color[2]);
polygons[con].verNum++;
printf("con:%d,verNum:%d\n", con, polygons[con].verNum);
}
}
}
}

在这里有几点说明如下

1.glPoints 方法

即画点方法,为了直观地显示用户选取了哪个点,我们设置了这个画点的方法,每点一个点,就在屏幕上显示出来。 方法定义如下

1
2
3
4
5
6
7
8
9
/* 画点 */
void glPoints(int x, int y) {
glBegin(GL_POINTS);
/* 点直接设置为黑色 */
glColor3ub(0, 0, 0);
glVertex2d(x - halfWidth, halfHeight - y);
glEnd();
glFlush();
}

在这里,glVertex2d 方法的参数为什么不是 x,y 呢?很简单,因为我们绘画时的坐标原点是窗口的中心点,而鼠标点击的坐标原点是左上角,为了做一个映射变换,x 需要减去 halfWidth,y 需要取反并加 halfWidth。总之定义这两个 half 变量还是用处多多的。

2.glutPostRedisplay

这个方法就是重新绘制的方法,我们看到在结束绘制的地方调用了这个方法,其实就是重新调用了 display 方法,所以为了让我们绘制的图形正常显示,我们需要在 display 方法中来绘制我们已经存储好的多边形。我们可以把绘制多边形定义为一个方法,然后在 display 中调用即可。 绘制多边形的方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 绘制多边形 */
void glPolygons()
{
if (con>=0) {
for (int i = 0; i <= con; i++) {
/* 取到这个多边形 */
polygon poly = polygons[i];
/* 画封闭线 */
glBegin(GL_LINE_LOOP);
int num = poly.verNum;
printf("num:%d\n",num);
for (int j = 0; j < num; j++)
{
glColor3ub(poly.color[0], poly.color[1], poly.color[2]);
glVertex2d(poly.x[j] - halfWidth, halfHeight - poly.y[j]);
printf("polyx:%d,polyy:%d",poly.x[j],poly.y[j]);
}
/* 结束画线 */
glEnd();
/* 刷新 */
glFlush();
}
}
}

在 display 函数中调用一下即可,方法的最后面调用一下。

1
2
/* 绘制多边形 */
glPolygons();

好,现在我们点击第一个黑色圆点,然后在屏幕上取点,取完点之后,然后点击右上角的黑色圆点,即可完成绘制 运行结果如下 QQ截图20150511013259 在此先告一段落,下一篇我们接着这里来实现图形的删除,移动以及存盘,读盘等操作。

本节代码

到这里,提供如下代码,代码运行结果即为上图所示,由于代码太长,不完全展开显示,双击查看全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
#include <glut.h>
#include <math.h>
#include <stdio.h>

#define PI 3.1415926
/* 每个图形最多的顶点数 */
#define MAX_VERTEX 10
/* 画的图形最多的个数 */
#define MAX_PLOY 10
/* 窗口长宽的一半 */
int halfWidth, halfHeight;
/* 绘制多边形的起始标志,0是开始绘制,1是结束绘制,初始为-1 */
int drawStatus = -1;
/* 多边形结构体 */
struct polygon {
/* 顶点坐标 */
int x[MAX_VERTEX];
int y[MAX_VERTEX];
/* 定义第几个顶点 */
int verNum;
GLubyte color[3];
};
/* 各种颜色 */
GLubyte border[3] = {0, 0, 0};
GLubyte grey[3] = {195, 195, 195};
GLubyte yellow[3] = {255, 243, 0};
GLubyte red[3] = {237, 28, 36};
GLubyte darkGrey[3] = {126, 126, 126};
GLubyte white[3] = {255, 255, 255};
GLubyte startBtn[3] = {10, 10, 10};
GLubyte endBtn[3] = {20, 20, 20};
/* 当前颜色 */
GLubyte nowColor[3] = {0, 0, 0};
/* 声明多边形数组 */
polygon polygons[MAX_PLOY];
/* 记录画了几个多边形 */
int con = 0;

/* 绘制多边形 */
void glPolygons()
{
if (con>=0) {
for (int i = 0; i <= con; i++) {
/* 取到这个多边形 */
polygon poly = polygons[i];
/* 画封闭线 */
glBegin(GL_LINE_LOOP);
int num = poly.verNum;
printf("num:%d\n",num);
for (int j = 0; j < num; j++)
{
glColor3ub(poly.color[0], poly.color[1], poly.color[2]);
glVertex2d(poly.x[j] - halfWidth, halfHeight - poly.y[j]);
printf("polyx:%d,polyy:%d",poly.x[j],poly.y[j]);
}
/* 结束画线 */
glEnd();
/* 刷新 */
glFlush();
}
}
}

/* 绘制填充的圆形 */
void glColorCircle(int x, int y, int R, GLubyte color[3])
{
/* 开始绘制曲线 */
glBegin(GL_POLYGON);
/* 设置颜色 */
glColor3ub(color[0], color[1], color[2]);
/* 每次画增加的弧度 */
double delta_angle = PI / 180;
/* 画圆弧 */
for (double i = 0; i <= 2 * PI; i += delta_angle)
{
/* 绝对定位加三角函数值 */
double vx = x + R * cos(i);
double vy = y + R*sin(i);
glVertex2d(vx, vy);
}
/* 结束绘画 */
glEnd();
glFlush();
}

//画矩形,传入的是左下角XY坐标和右上角XY坐标
void glRect(int leftX,int leftY,int rightX,int rightY){
//画封闭曲线
glBegin(GL_LINE_LOOP);
//左下角
glVertex2d(leftX,leftY);
//右下角
glVertex2d(rightX,leftY);
//右上角
glVertex2d(rightX,rightY);
//左上角
glVertex2d(leftX,rightY);
//结束画线
glEnd();
}

//画圆角矩形,传入矩形宽高,角半径,矩形中心点坐标
void glRoundRec(int centerX,int centerY,int width,int height,float cirR){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(GL_LINE_LOOP);
//四个象限不同的操作符
int opX[4]={1,-1,-1,1};
int opY[4]={1,1,-1,-1};
//用来计数,从第一象限到第四象限
float x=0;
//x自增时加的值
float part=1/divide;
//计算内矩形宽高一半的数值
int w=width/2-cirR;
int h=height/2-cirR;
//循环画线
for(x=0;x<4;x+=part){
//求出弧度
float rad = PI_HALF*x;
//计算坐标值
tx=cirR*cos(rad)+opX[(int)x]*w+centerX;
ty=cirR*sin(rad)+opY[(int)x]*h+centerY;
//传入坐标画线
glVertex2f(tx,ty);
}
//结束画线
glEnd();
}

//画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径
void glArc(double x,double y,double start_angle,double end_angle,double radius)
{
//开始绘制曲线
glBegin(GL_LINE_STRIP);
//每次画增加的弧度
double delta_angle=PI/180;
//画圆弧
for (double i=start_angle;i<=end_angle;i+=delta_angle)
{
//绝对定位加三角函数值
double vx=x+radius * cos(i);
double vy=y+radius*sin(i);
glVertex2d(vx,vy);
}
//结束绘画
glEnd();
}


//画圆
void glCircle(double x, double y, double radius)
{
//画全圆
glArc(x,y,0,2*PI,radius);
}

//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3){
//画封闭线
glBegin(GL_LINE_LOOP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

//画线,传入两点坐标
void glLine(int x1,int y1,int x2,int y2){
//画封闭线
glBegin(GL_LINE_STRIP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//结束画线
glEnd();
}

//函数用来画图
void display(void)
{
//GL_COLOR_BUFFER_BIT表示清除颜色
glClear(GL_COLOR_BUFFER_BIT);
//设置画线颜色
glColor3f(0.5,0.5,0.5);
//画点大小
glPointSize(2);
//画圆角矩形,大肚子
glRoundRec(0,0,146,120,15);
//画圆,中间小圈
glCircle(0,0,10);
//画矩形,脖子
glRect(-25,60,25,76);
//画圆角矩形,大脸
glRoundRec(0,113,128,74,10);
//两个眼睛
glCircle(-30,111,10);
glCircle(30,111,10);
//两条天线
glLine(-35,150,-35,173);
glLine(35,150,35,173);
//圆角矩形,两个耳朵
glRoundRec(81,115,20,34,5);
glRoundRec(-81,115,20,34,5);
//圆弧,画嘴
glArc(0,133,11*PI/8,13*PI/8,45);
//画三角,肚子里的三角
glTri(-30,-15,30,-15,0,28);
//画矩形,胳膊连接处
glRect(-81,43,-73,25);
glRect(81,43,73,25);
//画矩形,上臂
glRect(-108,45,-81,0);
glRect(108,45,81,0);
//画矩形,中臂
glRect(-101,0,-88,-4);
glRect(101,0,88,-4);
//画矩形,下臂
glRect(-108,-4,-81,-37);
glRect(108,-4,81,-37);
//画圆形,手掌
glCircle(-95,-47,10);
glCircle(95,-47,10);

/* 绘制取色盘 */
glColorCircle(-280, 280, 10, red);
glColorCircle(-250, 280, 10, yellow);
glColorCircle(-220, 280, 10, grey);
glColorCircle(-180, 280, 10, nowColor);
glColorCircle(250, 280, 10, startBtn);
glColorCircle(280, 280, 10, endBtn);
//保证前面的OpenGL命令立即执行,而不是让它们在缓冲区中等待
/* 绘制多边形 */
glPolygons();
glFlush();
}


//窗口大小变化时调用的函数
void ChangeSize(GLsizei w,GLsizei h)
{
//避免高度为0
if(h==0) {
h=1;
}
//定义视口大小,宽高一致
glViewport(0,0,w,h);
int half = 300;
/* 定义宽高的一半 */
halfHeight = 300;
halfWidth = 300;
//重置坐标系统,使投影变换复位
glMatrixMode(GL_PROJECTION);
//将当前的用户坐标系的原点移到了屏幕中心
glLoadIdentity();
//定义正交视域体
if(w<h) {
//如果高度大于宽度,则将高度视角扩大,图形显示居中
glOrtho(-half,half,-half*h/w,half*h/w,-half,half);
} else {
//如果宽度大于高度,则将宽度视角扩大,图形显示居中
glOrtho(-half*w/h,half*w/h,-half,half,-half,half);
}

}
/* 判断两个颜色是否相等 */
bool sameColor(GLubyte color1[3], GLubyte color2[3])
{
if (color1[0] - color2[0] < 5 && color1[1] - color2[1] < 5 && color1[2] - color2[1] < 5)
{
return(true);
} else {
return(false);
}
}

/* 画点 */
void glPoints(int x, int y) {
glBegin(GL_POINTS);
/* 点直接设置为黑色 */
glColor3ub(0, 0, 0);
glVertex2d(x - halfWidth, halfHeight - y);
glEnd();
glFlush();
}

/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 选项卡的分界范围 */
int optionHeight = 40;
int optionWidth = 250;
if (btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
/* 如果在颜色选项卡中点击 */
if (y < optionHeight && x < optionWidth)
{
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &nowColor);
printf("r:%d,g:%d,b:%d\n", nowColor[0], nowColor[1], nowColor[2]);
/* 设置绘图颜色并显示当前取色板颜色 */
glColorCircle(-180, 280, 10, nowColor);
/* 如果点击的是右侧选项按钮 */
}else if (y < optionHeight && x > optionWidth) {
//取当前的点
GLubyte color[3];
glReadPixels(x, 2 * halfHeight - y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &color);
printf("r:%d,g:%d,b:%d\n", color[0], color[1], color[2]);
/* 如果点击了开始绘制的按钮 */
if (sameColor(color, startBtn)) {
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
printf("drawStatus:%d\n", drawStatus);
/* 如果点击了结束绘制的按钮 */
} else if (sameColor(color, endBtn)) {
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
printf("drawStatus:%d\n", drawStatus);
}
/* 如果点击的是下方的绘图页面 */
}else{
/* 如果当前是正在取点状态 */
if (drawStatus == 0)
{
/* 保存每个点,然后该图形顶点个数加一 */
polygons[con].x[polygons[con].verNum] = x;
polygons[con].y[polygons[con].verNum] = y;
/* 画点 */
glPoints(x, y);
/* 设置当前颜色RGB,如果取色有变动,以最后的取色为准 */
polygons[con].color[0] = nowColor[0];
polygons[con].color[1] = nowColor[1];
polygons[con].color[2] = nowColor[2];
printf("polyColor%d%d%d\n", polygons[con].color[0],polygons[con].color[1],polygons[con].color[2]);
polygons[con].verNum++;
printf("con:%d,verNum:%d\n", con, polygons[con].verNum);
}
}
}
}




//程序入口
int main(int argc, char *argv[]){
//对GLUT进行初始化,并处理所有的命令行参数
glutInit(&argc, argv);
//指定RGB颜色模式和单缓冲窗口
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
//定义窗口的位置
glutInitWindowPosition(100, 100);
//定义窗口的大小
glutInitWindowSize(600, 600);
//创建窗口,同时为之命名
glutCreateWindow("OpenGL");
//设置窗口清除颜色为白色
glClearColor(1.0f,1.0f,1.0f,1.0f);
//参数为一个函数,绘图时这个函数就会被调用
glutDisplayFunc(&display);
//参数为一个函数,当窗口大小改变时会被调用
glutReshapeFunc(ChangeSize);
/*鼠标点击事件,鼠标点击或者松开时调用 */
glutMouseFunc(mouseClick);
//该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止
glutMainLoop();
//程序返回
return 0;
}

小伙伴们可以运行试试看。

综述

本节我们主要利用了鼠标时间来实现了取色,设置颜色,并成功通过取点绘制出了各种形状的多边形。