0%

OpenGL绘图实例八之图形的移动删除存盘

综述

在上一篇文章中我们已经实现了图形的取点绘制,接下来我们还要实现的功能有图形的删除、移动以及存盘读盘功能。 概述如下

  • 鼠标点击某个绘制的图形,将其删除
  • 鼠标拖动某个图形,可以对其随意拖拽改变位置
  • 点击存盘,将图形的位置及颜色保存到文件
  • 点击读盘,将图形的位置颜色从文件中读取出来并重新绘制

好,接下来我们在上一篇的基础上进行一步步的说明。

图形删除

首先像之前一样,我们定义一个删除的按钮,指定好颜色。

1
GLubyte delBtn[3] = {30, 30, 30};

在display函数中绘制一个删除的按钮

1
glColorCircle(220, 280, 10, delBtn);

顶部变变成了这个样子,其中最左边的那个按钮便是删除按钮 QQ截图20150511124225 接下来我们想一下删除的实现。 首先我们点击删除按钮之后要设置一下drawStatus,比如设置为2代表删除。 然后鼠标点击下方任意位置时,如果当前drawStatus为2,如果鼠标处在一个多边形内部,我们就把它删除掉。 那么就会出现两个问题了,怎样判断点击的点处在多边形的内部?怎样执行删除操作? 下面我们分别来研究一下

1. 判断点在多边形内部

思路:求解通过该点的水平线与多边形各边的交点 ,如果交点为奇数,那么就判定为在多边形内部,否则在外部。 因此,我的算法实现如下,传入的参数一个是ploygon结构体,结构体中包含了顶点坐标,另外就是这个点的x,y坐标。 思路参考来源: 判断点在多边形内的多种写法 经过我的改写,变成了如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 判断一个点是否在多边形内部 */
bool pointInPoly(polygon poly, int x, int y){
int nCross = 0;
for (int i = 0; i < poly.verNum; i++) {
int p1x = poly.x[i];
int p1y = poly.y[i];
int p2x = poly.x[(i + 1) % poly.verNum];
int p2y = poly.y[(i + 1) % poly.verNum];
/* 平行或在延长线上,忽略 */
if (p1y == p2y || y < min(p1y, p2y) || y > max(p1y, p2y)) {
continue;
}
/* 计算交点横坐标 */
double m = (double)(y - p1y) * (double)(p2x - p1x) / (double)(p2y - p1y) + p1x;
if ( m > x ) nCross++;
}
return (nCross % 2 == 1);
}

以上便是判断在多边形内部的算法。

2.删除图形实现

好了,有了上面的算法,我们就依次查找我们声明的 polygons 数组,找到第一个符合要求的多边形结构体 polygon 返回它的索引。 这个方法就是传入了点击点的位置x,y坐标,然后判断是在哪个多边形内,然后移除之。如果没有找到,则不执行删除操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 删除多边形 */
void deletePoly(int x, int y) {
int delenum = -1;
for (int i = 0; i< con; i++) {
if (pointInPoly(polygons[i], x, y)) {
delenum = i;
}
}
if (delenum != -1) {
removePoly(delenum);
}
/* 重新绘制 */
glutPostRedisplay();
}

其中 removePoly(delenum) 就是通过传入这个结构体的索引,然后将它从polygons里面删除即可,方法实现如下

1
2
3
4
5
6
7
8
9
10
11
/* 删除多边形 */
void removePoly(int index)
{
if (index>con) {
printf("Out Of Index\n");
}
for (int i = index; i < con; i++) {
polygons[i] = polygons[i+1];
}
con--;
}

其实就是相当于从数组中移除一个元素,然后后面的元素依次前移。 恩好了,准备工作都做好了,我们只需要在点击了删除按钮之后,调用一下deletePoly函数即可。 把原来的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
56
57
58
59
60
61
62
63
/*鼠标点击事件 */
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 (sameColor(color, delBtn)) {
/* 删除时设置drawStatus为2 */
drawStatus = 2;
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);
}else if (drawStatus == 2) {
/* 删除图形 */
deletePoly(x, y);
}
}
}
}

经过上面的步骤,我们便可以很方便地实现图形的删除了。运行结果如下

[embed]http://qiniu.cuiqingcai.com/robot2.mp4[/embed]

嗯,就是这样,现在删除就大功告成了。

移动图形

像之前一样,定义一个移动图形的按钮。在此不再赘述

1
GLubyte moveBtn[3] = {40, 40, 40};
1
glColorCircle(190, 280, 10, moveBtn);

那么图形移动怎么实现?思路如下 首先利用判断点击的点是否在多边形内部的算法,找到需要移动的多边形。然后在监听鼠标移动的事件中设置这个多边形各个顶点的相对移动距离等于鼠标相对移动距离即可。 首先,移动的drawSatus我们设置为3,在mouseClick方法中加入如下部分,在哪加你应该已经知道了。

1
2
3
4
5
else if (sameColor(color, moveBtn)) {
/* 移动时设置drawStatus为3 */
drawStatus = 3;
printf("drawStatus:%d\n", drawStatus);
}

在mouseMove方法中(另一个监听鼠标移动的事件)中首先判断当前的drawStatus如果为3,那么就执行移动操作。 加入如下的方法即可。

1
2
3
4
5
6
7
8
/* 鼠标移动事件 */
void mouseMove(int x, int y)
{
if(drawStatus == 3 && moveNum != -1){
printf("move x:%d,y:%dmoveNum:%d\n", x, y,moveNum);
movePoly(x, y);
}
}

这里两点需要说明,moveNum就是我们需要移动的图形的索引,定义为了全局变量。在鼠标点击时,首先判定点击点在哪个图形中,然而因为有两个鼠标事件(一个是点击,一个是移动),所以就把这个变量定义为全局变量,来标记该移动哪个图形。moveNum的初始值为-1,如果为-1,那么就代表没有选中任何图形,不移动。如果不是-1,就进行移动。 其次,movePloy方法实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 移动多边形 */
void movePoly(int x, int y) {
/* 取到要移动的图形 */
int verNum = polygons[moveNum].verNum;
printf("verNum%d,x%d,y%d,clickX%d,clickY%d\n",verNum,x,y,clickX,clickY);
for (int i = 0; i < verNum; i++) {
polygons[moveNum].x[i] += (x - clickX);
polygons[moveNum].y[i] += (y - clickY);
printf("polyx%d\n",polygons[moveNum].x[i]);
}
/* 移动完毕之后重新记录当前的点 */
clickX = x;
clickY = y;
/* 重新绘制 */
glutPostRedisplay();
}

这个方法的实现有一部分需要说明,首先是clickX,clickY。 因为在移动时移动的是相对距离。所以,鼠标每移动一个像素,我们就要计算下一步鼠标移动的位置和上一个位置的差值,所以每次移动我们都需要标记一下点击的位置。所以上面的方法中才有了

1
2
3
/* 移动完毕之后重新记录当前的点 */
clickX = x;
clickY = y;

然后,在图形移动时,我们需要移动的是相对位置。所以我们将一个图形的所有顶点进行遍历,加上鼠标移动的相对位置即可。就是如下的实现

1
2
3
4
for (int i = 0; i < verNum; i++) {
polygons[moveNum].x[i] += (x - clickX);
polygons[moveNum].y[i] += (y - clickY);
}

另外,mouseClick方法中我们需要调用一下即可。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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*鼠标点击事件 */
void mouseClick(int btn, int state, int x, int y)
{
/* 赋值全局变量 */
clickX = x;
clickY = 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 (sameColor(color, delBtn)) {
/* 删除时设置drawStatus为2 */
drawStatus = 2;
printf("drawStatus:%d\n", drawStatus);
} else if (sameColor(color, moveBtn)) {
/* 移动时设置drawStatus为3 */
drawStatus = 3;
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);
}else if (drawStatus == 2) {
/* 删除图形 */
deletePoly(x, y);
} else if(drawStatus == 3) {
moveNum = -1;
for (int i = 0; i< con; i++) {
if (pointInPoly(polygons[i], x, y)) {
moveNum = i;
}
}
}
}
}
}

好,现在就大功告成了,看一下运行结果吧

[embed]http://qiniu.cuiqingcai.com/robot3.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
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
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
#include <glut.h>
#include <math.h>
#include <stdio.h>
#include <iostream>

using namespace std;

#define PI 3.1415926
/* 每个图形最多的顶点数 */
#define MAX_VERTEX 10
/* 画的图形最多的个数 */
#define MAX_PLOY 10
/* 窗口长宽的一半 */
int halfWidth, halfHeight;
/* 绘制多边形的起始标志,0是开始绘制,1是结束绘制,初始为-1 */
int drawStatus = -1;
int moveNum = -1;
/* 定义点击点的x,y*/
int clickX, clickY;
/* 多边形结构体 */
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 delBtn[3] = { 30, 30, 30 };
GLubyte moveBtn[3] = { 40, 40, 40 };
/* 当前颜色 */
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 );
glColorCircle( 220, 280, 10, delBtn );
glColorCircle( 190, 280, 10, moveBtn );
/* 保证前面的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();
}


/* 判断一个点是否在多边形内部 */
bool pointInPoly( polygon poly, int x, int y )
{
int nCross = 0;
for ( int i = 0; i < poly.verNum; i++ )
{
int p1x = poly.x[i];
int p1y = poly.y[i];
int p2x = poly.x[(i + 1) % poly.verNum];
int p2y = poly.y[(i + 1) % poly.verNum];
/* 平行或在延长线上,忽略 */
if ( p1y == p2y || y < min( p1y, p2y ) || y > max( p1y, p2y ) )
{
continue;
}
/* 计算交点横坐标 */
double m = (double) (y - p1y) * (double) (p2x - p1x) / (double) (p2y - p1y) + p1x;
if ( m > x )
nCross++;
}
return(nCross % 2 == 1);
}


/* 移动多边形 */
void movePoly( int x, int y )
{
/* 取到要移动的图形 */
int verNum = polygons[moveNum].verNum;
printf( "verNum%d,x%d,y%d,clickX%d,clickY%d\n", verNum, x, y, clickX, clickY );
for ( int i = 0; i < verNum; i++ )
{
polygons[moveNum].x[i] += (x - clickX);
polygons[moveNum].y[i] += (y - clickY);
printf( "polyx%d\n", polygons[moveNum].x[i] );
}
/* 移动完毕之后重新记录当前的点 */
clickX = x;
clickY = y;
/* 重新绘制 */
glutPostRedisplay();
}


/* 删除多边形 */
void removePoly( int index )
{
if ( index > con )
{
printf( "Out Of Index\n" );
}
for ( int i = index; i < con; i++ )
{
polygons[i] = polygons[i + 1];
}
con--;
}


/* 删除多边形 */
void deletePoly( int x, int y )
{
int delenum = -1;
for ( int i = 0; i < con; i++ )
{
if ( pointInPoly( polygons[i], x, y ) )
{
delenum = i;
}
}
if ( delenum != -1 )
{
removePoly( delenum );
}
/* 重新绘制 */
glutPostRedisplay();
}


/*鼠标点击事件 */
void mouseClick( int btn, int state, int x, int y )
{
/* 赋值全局变量 */
clickX = x;
clickY = 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 ( sameColor( color, delBtn ) )
{
/* 删除时设置drawStatus为2 */
drawStatus = 2;
printf( "drawStatus:%d\n", drawStatus );
} else if ( sameColor( color, moveBtn ) )
{
/* 移动时设置drawStatus为3 */
drawStatus = 3;
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 );
}else if ( drawStatus == 2 )
{
/* 删除图形 */
deletePoly( x, y );
} else if ( drawStatus == 3 )
{
moveNum = -1;
for ( int i = 0; i < con; i++ )
{
if ( pointInPoly( polygons[i], x, y ) )
{
moveNum = i;
}
}
}
}
}
}


/* 鼠标移动事件 */
void mouseMove( int x, int y )
{
if ( drawStatus == 3 && moveNum != -1 )
{
printf( "move x:%d,y:%dmoveNum:%d\n", x, y, moveNum );
movePoly( x, y );
}
}


/*程序入口 */
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 );
/*鼠标移动事件,鼠标按下并移动时调用 */
glutMotionFunc( mouseMove );
/* 该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止 */
glutMainLoop();
/*程序返回 */
return(0);
}

以上,就可以实现图形的移动和删除了。

存盘和读盘

这个很简单,存盘我们只需要把所有图形的位置以及颜色输出到一个文本中,然后在读盘时,把文本中所有内容读入并存储到多边形结构体数组中,然后重新绘制即可。 代码的讲解直接写在注释中,比较好理解

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
/* 保存所有的图形 */
void savePolygons()
{
FILE *fp;
int i;
if ((fp = fopen("shapes.txt", "w")) == NULL)
{
printf("不能打开文件!");
return;
}
/* 保存一共多少个图形 */
fprintf(fp, "%d", con);
fprintf(fp, "\n");
for (i = 0; i < con; i++){
/* 保存颜色值 */
fprintf(fp, "%d %d %d ", polygons[i].color[0], polygons[i].color[1], polygons[i].color[2]);
/* 保存顶点数量 */
fprintf(fp, "%d ", polygons[i].verNum);
/* 保存顶点坐标 */
for (int j = 0; j < polygons[i].verNum; j++)
fprintf(fp, "%d ", polygons[i].x[j]);
for (int j = 0; j < polygons[i].verNum; j++)
fprintf(fp, "%d ", polygons[i].y[j]);
fprintf(fp, "\n");
}
fclose(fp);
printf("保存成功。\n");
}

/* 读取存档 */
void readPolygons()
{
FILE *fp;
int i;
if ((fp = fopen("shapes.txt", "r")) == NULL)
{
printf("不能打开文件!");
return;
}
fscanf(fp, "%d", &con);
printf("count%d",con);
fscanf(fp, "\n");
for (i = 0; i < con; i++){
/* 读取颜色值 */
fscanf(fp, "%d %d %d ", &polygons[i].color[0], &polygons[i].color[1], &polygons[i].color[2]);
/* 读取顶点数量 */
fscanf(fp, "%d ", &polygons[i].verNum);
/* 读取顶点坐标 */
for (int j = 0; j < polygons[i].verNum; j++)
fscanf(fp, "%d ", &polygons[i].x[j]);
for (int j = 0; j < polygons[i].verNum; j++)
fscanf(fp, "%d ", &polygons[i].y[j]);
fscanf(fp, "\n");
}
fclose(fp);
printf("读取成功。\n");

}

我们只需要再定义两个按钮,再display中画上这俩按钮

1
2
GLubyte storeBtn[3] = {50, 50, 50};
GLubyte loadBtn[3] = {55, 55, 55};

然后把mouseClick方法改写一下就好了。最终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
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
/* 鼠标点击事件 */
void mouseClick( int btn, int state, int x, int y )
{
/* 赋值全局变量 */
clickX = x;
clickY = 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 );
/* 设置绘图颜色并显示当前取色板颜色 */
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 );
/* 如果点击了开始绘制的按钮 */
if ( sameColor( color, startBtn ) )
{
drawStatus = 0;
/* 开始画一个图形,顶点个数置零 */
polygons[con].verNum = 0;
/* 如果点击了结束绘制的按钮 */
} else if ( sameColor( color, endBtn ) )
{
glutPostRedisplay();
/* 画的图形个数加一 */
con++;
} else if ( sameColor( color, delBtn ) )
{
/* 删除时设置drawStatus为2 */
drawStatus = 2;
} else if ( sameColor( color, moveBtn ) )
{
/* 移动时设置drawStatus为3 */
drawStatus = 3;
} else if ( sameColor( color, storeBtn ) )
{
/* 存盘时设置drawStatus为4 */
drawStatus = 4;
glutPostRedisplay();
savePolygons();
} else if ( sameColor( color, loadBtn ) )
{
/* 读盘时设置drawStatus为5 */
drawStatus = 5;
readPolygons();
glutPostRedisplay();
}

/* 如果点击的是下方的绘图页面 */
}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];
polygons[con].verNum++;
}else if ( drawStatus == 2 )
{
/* 删除图形 */
deletePoly( x, y );
} else if ( drawStatus == 3 )
{
moveNum = -1;
for ( int i = 0; i < con; i++ )
{
if ( pointInPoly( polygons[i], x, y ) )
{
moveNum = i;
}
}
}
}
}
}

大功告成!现在只要你存盘成功,不管对图形进行怎样的移动,删除操作。只要再点击读盘,就可以恢复到存盘时的状态,是不是非常酷炫。 大家可以尝试一下!

总结

本篇文章说明了图形的删除,移动以及存盘读盘的功能。代码实现仅仅是我个人的思路,当然实现方式不止这一种,欢迎大家创新。 部分思路如果大家有更好的解决方法,欢迎同我交流,非常感谢。 总之希望对大家有帮助!小伙伴们加油!