跨平台二维绘图程序

背景

通常而言,C/C++制作二维图形的绘制程序采用GDI或者GDI+已经足够,不论是绘制的效率还是绘制图元的类别。制作跨平台的程序的主要目的是将已有的绘制程序转移到移动端,以安卓为例,可以使用android studio编译出的.so文件,进而做二次开发。

如果是仅仅制作一个apk文件,那推荐使用现有的封装好的平台,如cocos2dx,同样的代码可以在安卓、IOS/MAC、Windows等平台编译出可执行程序。但是这里的需求要把绘制模块独立出来(不能把场景之类的东东带上),数据访问形成jar包供二次开发使用。从cocos2dx中分离绘制 VS 另写一个绘制模块,选择了后者。

既然跨平台,首选当然是openGL,悲催的是,接触后发现移动端不支持很多原生态的函数,诸如glBegin glLineStipple这样的函数都不支持,而不得不使用GLES。以下是一篇介绍从openGL转换到ES的文章:
https://pandorawiki.org/Porting_to_GLES_from_GL
即便是最简单的绘制线,也不得不使用glDrawArrays这样的函数来执行了。

绘图简介

这个系列将用几篇文章来介绍近期GLES学习和使用的心得。绘图程序需要考虑的因素有:
(1)绘制效率,库文件编译出后需要在手机端二次开发使用,因此需要保证绘图效率;
(2)绘制图元,包括点、线、多边形(含不规则多边形)、文字;
(3)绘制顺序,所有的图元有先后顺序,保证叠加的次序和读取的数据一致;
(4)图元特性,例如线宽、线型、符号等的支持;

效果图

通过这一段时间的尝试,得到了如下的展示效果,拥有较高效率且支持主要图元的跨平台程序。

着色器

学习使用GLES,首先要写着色器。

与OpenGL ES1.x渲染管线相比,OpenGL ES 2.0渲染管线中“顶点着色器”取代了OpenGL ES 1.x渲染管线中的“变换和光照”;“片元着色器”取代了OpenGL ES 1.x渲染管线中的“纹理环境和颜色求和”、“雾”以及“Alpha测试”。 这使得开发人员在使用OpenGL
ES 2.0API进行开发时,可以通过编写顶点及片元着色器程序,来完成一些顶点变换和纹理颜色计算工作,实现更加灵活、精细化的计算与渲染。

以上内容转载自:
http://blog.sina.com.cn/s/blog_923fdd9b0102vbe0.html
文章对着色器做了一些介绍,在GLES的使用步骤为:glCreateProgram->glAttachShader->glLinkProgram->glUseProgram。在之后使用纹理之后才知道,一个程序里可能同时存在多个program,在绘制不同对象时需要切换使用glUseProgram。在出现错误的时候错误日志非常关键,调试的时候发现同样的shader代码在windows平台可以绘制,但是在ubuntu中就无法显示,错误日志可能提示语言的版本不支持。这些错误与系统GPU有关系。以下为包含着色器的Program封装,包括常规程序和带纹理的程序。

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

#pragma once

// 矩阵操作
#include <math.h>
#include "glm/mat4x4.hpp"
#include "glm/ext.hpp"

class ShaderId
{
public:
ShaderId()
{
_shaderId = -1;
}
int _shaderId;
};

class ProgramId
{
public:
int _programId;
ShaderId _vertex;
ShaderId _fragment;
public:
ProgramId()
{
_programId = -1;
}
public:
/**
* 加载函数
*/
virtual bool createProgram(const char* vertex, const char* fragment)
{
#ifndef ANDROID
glewInit(); // android need of not?
#endif

bool error = false;
do
{
if (vertex)
{
_vertex._shaderId = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(_vertex._shaderId, 1, &vertex, 0);
glCompileShader(_vertex._shaderId);

GLint compileStatus;
glGetShaderiv(_vertex._shaderId, GL_COMPILE_STATUS, &compileStatus);
error = compileStatus == GL_FALSE;
if (error)
{
GLchar messages[256];
glGetShaderInfoLog(_vertex._shaderId, sizeof(messages), 0, messages);
GLUtil::instance()->outputString(messages);
//assert(messages && 0 != 0);
break;
}
}
if (fragment)
{
_fragment._shaderId = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(_fragment._shaderId, 1, &fragment, 0);
glCompileShader(_fragment._shaderId);

GLint compileStatus;
glGetShaderiv(_fragment._shaderId, GL_COMPILE_STATUS, &compileStatus);
error = compileStatus == GL_FALSE;

if (error)
{
GLchar messages[256];
glGetShaderInfoLog(_fragment._shaderId, sizeof(messages), 0, messages);
GLUtil::instance()->outputString(messages);
//assert(messages && 0 != 0);
break;
}
}
_programId = glCreateProgram();

if (_vertex._shaderId)
{
glAttachShader(_programId, _vertex._shaderId);
}
if (_fragment._shaderId)
{
glAttachShader(_programId, _fragment._shaderId);
}

glLinkProgram(_programId);

GLint linkStatus;
glGetProgramiv(_programId, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE)
{
GLchar messages[256];
glGetProgramInfoLog(_programId, sizeof(messages), 0, messages);
GLUtil::instance()->outputString(messages);
break;
}
glUseProgram(_programId);

} while (false);

if (error)
{
if (_fragment._shaderId)
{
glDeleteShader(_fragment._shaderId);
_fragment._shaderId = 0;
}
if (_vertex._shaderId)
{
glDeleteShader(_vertex._shaderId);
_vertex._shaderId = 0;
}
if (_programId)
{
glDeleteProgram(_programId);
_programId = 0;
}
}
return true;
}

virtual void begin(const glm::mat4& mat)
{
glUseProgram(_programId);

glm::mat4 matTemp = mat;// *m_view/* * module*/;
GLfloat *mvp = (GLfloat*)glm::value_ptr(matTemp);

glUniformMatrix4fv(this->mvp(), 1, GL_FALSE, mvp);

}

virtual void end()
{
glUseProgram(0);
}


virtual bool initialize(){ return false; }

virtual GLint position() { return -1; }

virtual GLint color() { return -1; }

virtual GLint texure() { return -1; }

virtual GLint mvp() { return -1; }

virtual GLint uv() { return -1; }
};

class PROGRAM_P2_AC4 :public ProgramId
{
public:
typedef int attribute;
typedef int uniform;
protected:
attribute _position;
attribute _color;
uniform _MVP;
public:
virtual GLint position() { return _position; }

virtual GLint color() { return _color; }

virtual GLint texure() { return -1; }

virtual GLint mvp() { return _MVP; }

public:
PROGRAM_P2_AC4()
{
_position = -1;
_color = -1;
_MVP = -1;
}
~PROGRAM_P2_AC4()
{
}

/// 初始化函数
virtual bool initialize()
{
/*
const char* vs =
{
"precision lowp float; "
"uniform mat4 _MVP;"
"attribute vec3 _position;"
"attribute vec4 _color;"
"varying vec4 _outColor;"

"void main()"
"{"
" vec4 pos = vec4(_position, 1.);"
" _outColor = _color;"
" gl_Position = _MVP * pos;"
"}"
};
const char* ps =
{
"precision lowp float; "
"varying vec4 _outColor;"
"void main()"
"{"
" gl_FragColor = _outColor;"
"}"
};
*/
#ifdef ANDROID
const char* vs =
{
"attribute vec3 _position;"
"attribute vec4 _color;"
"varying vec4 _outColor;"
"uniform mat4 _MVP;"

"void main()"
"{"
" vec4 pos = vec4(_position, 1.);"
" _outColor = _color;"
" gl_Position = _MVP * pos;"
"}"
};

const char* ps =
{
"precision mediump float;"
"varying vec4 _outColor;"

"void main()"
"{"
" gl_FragColor = _outColor;"
"}"
};
#else
const char* vs =
{
"#version 130\n"
"in vec3 _position;"
"in vec4 _color;"
"out vec4 _outColor;"
"uniform mat4 _MVP;"

"void main()"
"{"
" vec4 pos = vec4(_position, 1.);"
" _outColor = _color;"
" gl_Position = _MVP * pos;"
"}"
};

const char* ps =
{
"#version 130\n"
"in vec4 _outColor;"
"out vec4 FragColor;"
"void main()"
"{"
" FragColor = _outColor;"
"}"
};

#endif

bool res = createProgram(vs, ps);
if (res)
{
_position = glGetAttribLocation(_programId, "_position");
_color = glGetAttribLocation(_programId, "_color");
_MVP = glGetUniformLocation(_programId, "_MVP");
}
return res;
}

/**
* 使用程序
*/
virtual void begin(const glm::mat4& mat)
{
ProgramId::begin(mat);
glEnableVertexAttribArray(_position);
glEnableVertexAttribArray(_color);
}
/**
* 使用完成
*/
virtual void end()
{
glDisableVertexAttribArray(_position);
glDisableVertexAttribArray(_color);
glUseProgram(0);
}
};

class PROGRAM_P2_UV_AC4 :public PROGRAM_P2_AC4
{
public:
typedef int attribute;
typedef int uniform;
protected:
//attribute _position;
//attribute _color;
attribute _uv;
//uniform _MVP;
uniform _texture;
public:
PROGRAM_P2_UV_AC4()
{
_position = -1;
_color = -1;
_uv = -1;
_texture = -1;
_MVP = -1;
}
~PROGRAM_P2_UV_AC4()
{
}

virtual GLint uv() { return _uv; }

virtual GLint texure() { return _texture; }

/// 初始化函数
virtual bool initialize()
{
#ifdef ANDROID
const char* vs =
{
"precision lowp float; "
"uniform mat4 _MVP;"
"attribute vec3 _position;"
"attribute vec2 _uv;"
"attribute vec4 _color;"
"varying vec4 _outColor;"
"varying vec2 _outUV;"

"void main()"
"{"
" vec4 pos = vec4(_position, 1.);"
" _outColor = _color;"
" _outUV = _uv;"
" gl_Position = _MVP * pos;"
"}"
};
const char* ps =
{
"precision lowp float; "
"uniform sampler2D _texture;\n"
"varying vec4 _outColor;\n"
"varying vec2 _outUV;\n"
"void main()"
"{"
" vec4 tColor = texture2D(_texture,_outUV);\n"
" gl_FragColor = tColor * _outColor;\n"
"}"
};
#else
const char* vs =
{
"#version 130\n"
"in vec3 _position;"
"in vec2 _uv;"
"in vec4 _color;"
"out vec4 _outColor;"
"out vec2 _outUV;"
"uniform mat4 _MVP;"

"void main()"
"{"
" vec4 pos = vec4(_position, 1.);"
" _outColor = _color;"
" _outUV = _uv;"
" gl_Position = _MVP * pos;"
"}"
};

const char* ps =
{
"#version 130\n"
"uniform sampler2D _texture;"
"in vec4 _outColor;"
"in vec2 _outUV;"
"out vec4 FragColor;"
"void main()"
"{"
" vec4 tColor = texture2D(_texture,_outUV);\n"
" FragColor = tColor * _outColor;"
"}"
};

#endif

bool res = createProgram(vs, ps);
if (res)
{
_position = glGetAttribLocation(_programId, "_position");
_color = glGetAttribLocation(_programId, "_color");
_uv = glGetAttribLocation(_programId, "_uv");
_texture = glGetUniformLocation(_programId, "_texture");
_MVP = glGetUniformLocation(_programId, "_MVP");
}
return res;
}

/**
* 使用程序
*/

virtual void begin(const glm::mat4& mat)
{
ProgramId::begin(mat);
glEnableVertexAttribArray(_position);
glEnableVertexAttribArray(_uv);
glEnableVertexAttribArray(_color);
}

/**
* 使用完成
*/
virtual void end()
{
glDisableVertexAttribArray(_position);
glDisableVertexAttribArray(_uv);
glDisableVertexAttribArray(_color);
glUseProgram(0);
}
};

代码编写参考了:

http://blog.csdn.net/hb707934728/article/details/71486631?locationNum=4&fps=1
主要做的改动:
(1)着色语言,区分android,区分版本,区分坐标——尤其是z坐标引入保证深度测试能够生效(vec4 pos = vec4(_position, 1.););
(2)纹理程序继承自常规程序而非基础程序类;
(3)引入了变换矩阵(用于旋转缩放平移等运算);

VAO&VBO

随着OpenGL状态和固定管线模式的移除,我们不在用任何glEnable函数调用,而且也不会有glVertex、glColor等函数调用。这就意味着我们需要一种新的方式来将数据传输到图形卡以渲染图形。我们可以采用VBO,或者是在OpenGL3以上版本引入的新的特性,叫做VAO。通过它,我们可以把顶点数据和颜色存储在不同的VBO中,但是在同一个VAO中。对于法线数据或者其他的顶点信息也是一样。

VAO,是这样一种方式:把对象信息直接存储在图形卡中,而不是在当我们需要的时候传输到图形卡。这就是Direct3D所采用得方式,而在OpenGL中只有OpenGL3.X以上的版本中采用。这就意味着我们的应用程序不用将数据传输到图形卡或者是从图形卡输出,这样也就获得了额外的性能提升。

使用VAO并不难。我们不需要大量的glVertex调用,而是把顶点数据存储在数组中,然后放进VBO,最后在VAO中存储相关的状态。记住:VAO中并没有存储顶点的相关属性数据。OpenGL会在后台为我们完成其他的功能。

以上内容引用自:
http://blog.csdn.net/xiajun07061225/article/details/7628146
使用VAO和VBO更重要的原因在于提高绘制效率。在绘制线对象的时候,对现有的GDI和GLES的绘制效率做了对比,同样的4K条折线段绘制时间均在40~60ms。即相比GDI,GL并没有较大的绘制效率提升。

VAO、VBO使用心得

使用VBO和VAO之后,绘制时间基本为0。原因在于:坐标数据已经通过缓冲传入了GPU,通常看图的前提下,坐标数据改动的可能性很小;而原有的绘制在变换图形时,每次都对坐标数据做了转换(实际数据->屏幕数据),继而将转换完成的数据传到GPU,然后渲染。缺少了源源不断不断的坐标数据转换和切换,绘制效率也就提升了。

为了能够继续进行图形的变换(缩放、平移、旋转),引入附加矩阵(additionTransform),用来计算当前视图(currentViewContext)和原始视图(通常是全图时候的上下文:fullViewContext)比例关系。这样当进行缩放时,currentViewContext发生了变化,additionTransform = currentViewContext / fullViewContext,additionTransform传入第二篇中Program的开始函数即可实现变换。而原有的currenViewContext保留用于计算屏幕坐标和实际坐标转换。

坐标数据超过float能表达的范围怎么办?openGL和GLES中传递的数据通常都是float数据,而且有效数据仅6~7个,但是地理坐标数据通常都是需要十几位有效数据,如果在使用时直接将真实坐标数据传入VBO中,绘制出来的线段很可能是锯齿形状。所以在传递数据前,先以地图左下为参考原点做一次偏移再传入VBO,这样基本就能满足数据精度要求了。

代码

以下代码为几何VBO的管理类,负责创建VAO、VBO以及绘制。

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
struct GeoVOBuffer
{
std::vector<PointF> cos; // 坐标缓冲数据
std::vector<ColorF> cols; // 颜色缓冲数据

GLuint vao, coVBO, colVBO; // vao, vbo的Id
GLuint type; // 绘制类型:GL_LINES GL_TRIANGLES GL_POINTS

void init()
{
clear();
}

void clear()
{
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &coVBO);
glDeleteBuffers(1, &colVBO);

cos.clear();
cols.clear();

vao = coVBO = colVBO = 0;
}

GeoVOBuffer()
{
vao = coVBO = colVBO = 0;
type = 0;
}

~GeoVOBuffer()
{
clear();
}

virtual bool draw(ProgramId* progId)
{
int32_t count = cos.size();
if (count == 0)
return false;

if (vao == 0)
{
GLuint vboHandles[2];
glGenBuffers(2, vboHandles);
coVBO = vboHandles[0];
colVBO = vboHandles[1];

glBindBuffer(GL_ARRAY_BUFFER, coVBO);
glBufferData(GL_ARRAY_BUFFER, count * sizeof(PointF), &cos[0], GL_STATIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER, colVBO);
glBufferData(GL_ARRAY_BUFFER, count * sizeof(ColorF), &cols[0], GL_STATIC_DRAW);

GLuint vaoHandle = 0;
glGenVertexArrays(1, &vaoHandle);
glBindVertexArray(vaoHandle);

glEnableVertexAttribArray(0);//顶点坐标
glEnableVertexAttribArray(1);//顶点颜色


//调用glVertexAttribPointer之前需要进行绑定操作
glBindBuffer(GL_ARRAY_BUFFER, coVBO);
glVertexAttribPointer(progId->position(), 3, GL_FLOAT, GL_FALSE, 0, (GLvoid *)NULL);

glBindBuffer(GL_ARRAY_BUFFER, colVBO);
glVertexAttribPointer(progId->color(), 4, GL_FLOAT, GL_FALSE, 0, (GLvoid *)NULL);

glBindVertexArray(0);
vao = vaoHandle;
}

glBindVertexArray(vao);
glDrawArrays(type, 0, count);
glBindVertexArray(0);

return true;
}
};

粗略解释为:我们把所有的线坐标存入了GPU,GPU返回了一个ID(VAO)。在绘制的时候我们绑定这个ID,然后进行绘制,这些线就会被一次性全部画出。

为了管理不同类型的VAO,编写了一个vo的管理器,里面同时记录了一些使用VAO时需要的数据,如左下角偏移、Z深度等。

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

class voMgr
{
public:
voMgr()
{
m_useVo = true;
m_width = 0.;
m_height = 0.;
m_vaoDrawed = false;
s_zIndex = 0;

dotBuffer.type = GL_POINTS;
lineBuffer.type = GL_LINES;
polyBuffer.type = GL_TRIANGLES;
}

~voMgr()
{
clear();
}

bool actived() const
{
return m_useVo;
}

void setExtent(LrDouble w, LrDouble h)
{
m_width = w;
m_height = h;
}

bool hasData()
{
return m_width > 0 || m_height > 0;
}

LrDouble width() const { return m_width; }
LrDouble height() const { return m_height; }

void begin()
{
m_vaoDrawed = false;
}

void clear()
{
dotBuffer.clear();
lineBuffer.clear();
polyBuffer.clear();
}

void init()
{
clear();

m_vaoDrawed = false;
}

void draw(ProgramId* progId, const DrawContext& dc, int32_t flag)
{
if (m_vaoDrawed)
return;

dotBuffer.draw(progId);
lineBuffer.draw(progId);
polyBuffer.draw(progId);

m_vaoDrawed = true;
}

protected:
bool m_useVo;// 是否使用vo
bool m_vaoDrawed; // 是否绘制过,只绘制一次

LrDouble m_width, m_height; // 全屏高宽
public:
GeoVOBuffer dotBuffer, lineBuffer, polyBuffer;
GLfloat s_zIndex;// 绘制顺序
GLdouble s_left, s_bottom; // 左下偏移
};
static voMgr m_vo;

下面是一个线类型坐标转换到VAO的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

ColorF colF(fr, fg, fb, fa);
for (int32_t i = 0; i < count; ++i)
{
PointF cc;
// 偏移
cc.x = pco[i].x - m_vo.s_left;
cc.y = pco[i].y - m_vo.s_bottom;
// 深度信息
cc.z = m_vo.s_zIndex;

m_vo.lineBuffer.cos.push_back(cc);
m_vo.lineBuffer.cols.push_back(colF);

// 补充点用GL_LINES
if (i > 0 && i < count - 1)
{
m_vo.lineBuffer.cos.push_back(cc);
m_vo.lineBuffer.cols.push_back(colF);
}
}

由于要求独个VAO的一次性绘制,不得不使用GL_LINES(GL_LINE_STRIP无法使用)。

多边形绘制

前三篇内容的基础上,可以绘制出如下的图形:

这一篇介绍如何绘制多边形。
(1)GDI通过API函数Polygon或者PolyPolygon就能够绘制不规则的多边形,包括自交带内洞,而GLES只能绘制三角形;
(2)多边形三角化有个开源的triangle.h,这里推荐下:http://compgeom.com/~piyush/scripts/triangle/triangle_8h-source.html
(3)三角化的时候要注意几个问题。多边形点中不可以有重复坐标点,否则会出错;多边形坐标点数不可过少,否则出错。
以下是多边形三角化的一个类:

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

struct Triangle
{
std::vector<Coor> cos;
void set(const std::vector<Coor>& polygon)
{
cos.clear();
cos.insert(cos.end(), polygon.begin(), polygon.end());
}

void set(const Coor& A, const Coor& B, const Coor& C)
{
cos.clear();
cos.push_back(A);
cos.push_back(B);
cos.push_back(C);
}
};

class WithInnerTriangulation
{// 2D Coor
public:
Coor getCenter(const std::vector<Coor>& cos)
{
Coor center;

if (cos.size() == 3)
{
for (size_t j = 0; j < 3; ++j)
{
center.x += cos[j].x;
center.y += cos[j].y;
}
}
else
{
struct triangulateio in, out;
triangulateioinit(&in);
triangulateioinit(&out);

int numPoints = cos.size();
int numSeg = cos.size();

in.numberofpoints = numPoints;
in.numberofpointattributes = 0;
in.pointlist = (REAL*)malloc(sizeof(REAL) * in.numberofpoints * 2);

for (size_t i = 0; i < cos.size(); i++)
{
in.pointlist[i * 2 + 0] = cos[i].x;
in.pointlist[i * 2 + 1] = cos[i].y;
}

in.numberofsegments = numSeg;
in.segmentlist = (int*)malloc(sizeof(int) * in.numberofsegments * 2);

size_t i = 0;
for (i = 0; i < cos.size() - 1; i++)
{
in.segmentlist[i * 2 + 0] = i;
in.segmentlist[i * 2 + 1] = i + 1;
}
in.segmentlist[i * 2 + 0] = i;
in.segmentlist[i * 2 + 1] = 0;

triangulate("pzFQ", &in, &out, NULL);

int count = out.numberoftriangles * 3;
if (count > 3)
count = 3;

for (int i = 0; i < count; i++)
{
int coor_index = out.trianglelist[i];
center.x += out.pointlist[coor_index * 2];
center.y += out.pointlist[coor_index * 2 + 1];
}

triangulateiofree(&out, 0);
triangulateiofree(&in, 1);
}

center.x /= 3;
center.y /= 3;

return center;
}

bool transform(const std::vector< std::vector<Coor> >& polys2,
std::vector<Triangle>& triangles)
{
struct triangulateio in, out;
triangulateioinit(&in);
triangulateioinit(&out);

std::vector< std::vector<Coor> > polys;
for (size_t i = 0; i < polys2.size(); ++i)
{
std::vector<Coor> temp;
GeoAlgorithm::kick_overlay_dot(polys2[i],
temp,
false);

if (temp.size() >= 3)
{
// 点数过少会导致后面的triangulate函数崩溃
polys.push_back(temp);
}
}

if (polys.size() == 0)
{
return false;
}

int numPoints = 0;
int numSeg = 0;
std::vector<Coor> centers;
for (size_t i = 0; i < polys.size(); i++)
{
numSeg += polys[i].size();
Coor center = getCenter(polys[i]);
centers.push_back(center);
}
numPoints = numSeg;

in.numberofpoints = numPoints;
in.numberofpointattributes = 0;
in.pointlist = (REAL*)malloc(sizeof(REAL) * in.numberofpoints * 2);

int pointc = 0;
for (size_t i = 0; i < polys.size(); i++)
{
for (size_t j = 0; j < polys[i].size(); j++, pointc++)
{
in.pointlist[pointc * 2 + 0] = polys[i][j].x;
in.pointlist[pointc * 2 + 1] = polys[i][j].y;
}
}

in.numberofsegments = numSeg;
in.segmentlist = (int*)malloc(sizeof(int) * in.numberofsegments * 2);
pointc = 0;
for (size_t i = 0; i < polys.size(); i++, pointc++)
{
int first = pointc;
for (size_t j = 0; j < polys[i].size() - 1; j++, pointc++)
{
in.segmentlist[pointc * 2 + 0] = pointc;
in.segmentlist[pointc * 2 + 1] = pointc + 1;
}
in.segmentlist[pointc * 2 + 0] = pointc;
in.segmentlist[pointc * 2 + 1] = first;
}

in.numberofregions = 0;
in.numberofholes = centers.size() - 1;
in.holelist = (REAL*)malloc(sizeof(REAL) * in.numberofholes * 2);

for (size_t i = 1; i < centers.size(); i++)
{
in.holelist[(i - 1) * 2 + 0] = centers[i].x;
in.holelist[(i - 1) * 2 + 1] = centers[i].y;
}

triangulate("pzFQ", &in, &out, NULL);
// verify the mesh a bit
if (out.numberofpoints - out.numberofedges + out.numberoftriangles !=
2 - centers.size())
{
cout << endl << "epc wrong" << endl;
}
std::vector<Coor> cos;
int numberofvertex, coor_index;
numberofvertex = out.numberoftriangles * 3;
for (int i = 0; i < numberofvertex; i++)
{
coor_index = out.trianglelist[i];
Coor co;
co.x = out.pointlist[coor_index * 2];
co.y = out.pointlist[coor_index * 2 + 1];

cos.push_back(co);
}

for (int i = 0; i < out.numberoftriangles; ++i)
{
Triangle tri;
tri.set(cos[3 * i], cos[3 * i + 1], cos[3 * i + 2]);
triangles.push_back(tri);
}

triangulateiofree(&out, 0);
triangulateiofree(&in, 1);

return true;
}
};

有了这样的三角化类,就可以将不规则多边形(如凹多边形,带内洞多边形)转换为三角形,然后将三角形传递到VBO中。

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

std::vector< std::vector<Coor> > polys;
int32_t idx = 0;
for (int32_t i = 0; i < pc; ++i)
{
std::vector<Coor> pts;
for (int32_t j = 0; j < ptsc[i]; ++j)
{
Coor cc;
cc.x = pco[idx].x - m_vo.s_left;
cc.y = pco[idx].y - m_vo.s_bottom;
cc.z = 0.f;

pts.push_back(cc);
idx++;
}
polys.push_back(pts);
}

{
WithInnerTriangulation wit;
std::vector<Triangle> triangles;
wit.transform(polys, triangles);
size_t tc = triangles.size();
if (tc)
{
auto tri = triangles.begin();
while (tri != triangles.end())
{
PointF cc;
cc.z = m_vo.s_zIndex;
cc.x = tri->cos[0].x; cc.y = tri->cos[0].y;
m_vo.polyBuffer.cos.push_back(cc);

cc.x = tri->cos[1].x; cc.y = tri->cos[1].y;
m_vo.polyBuffer.cos.push_back(cc);

cc.x = tri->cos[2].x; cc.y = tri->cos[2].y;
m_vo.polyBuffer.cos.push_back(cc);

m_vo.polyBuffer.cols.push_back(colF);
m_vo.polyBuffer.cols.push_back(colF);
m_vo.polyBuffer.cols.push_back(colF);

++tri;
}
}
}

这样多边形就能够渲染出来了!