ARGoS 3
A parallel, multi-engine simulator for swarm robotics
qtopengl_obj_model.cpp
Go to the documentation of this file.
1
10
11#include <argos3/core/simulator/visualization/visualization.h>
12#include <argos3/plugins/simulator/visualizations/qt-opengl/qtopengl_render.h>
13#include <argos3/plugins/simulator/visualizations/qt-opengl/qtopengl_main_window.h>
14
15#include <QFile>
16#include <QTextStream>
17#include <QRegularExpression>
18
19namespace argos {
20
21 /****************************************/
22 /****************************************/
23
24 CQTOpenGLObjModel::CQTOpenGLObjModel(const std::string& str_obj_file) {
25 /* create a default material */
26 m_mapMaterials["__default_material"];
27 /* load the model */
28 QFile cGeometryFile(GetModelDir() + QString::fromStdString(str_obj_file));
29 if(!cGeometryFile.open(QIODevice::ReadOnly)) {
30 THROW_ARGOSEXCEPTION("Error loading model \"" <<
31 cGeometryFile.fileName().toStdString() << "\": " <<
32 cGeometryFile.errorString().toStdString());
33 }
34 QTextStream cGeometryStream(&cGeometryFile);
35 ImportGeometry(cGeometryStream);
36 /* build the meshes */
37 BuildMeshes();
38 /* check if normals have been provided */
39 if (m_vecNormals.empty()) {
40 THROW_ARGOSEXCEPTION("Error loading model \"" <<
41 cGeometryFile.fileName().toStdString() <<
42 "\": model does not specify normals.")
43 }
44 /* generate the OpenGL vectors */
45 GenerateOpenGLVectors();
46 }
47
48 /****************************************/
49 /****************************************/
50
52 auto tIter = m_mapMaterials.find(str_material);
53 if(tIter == std::end(m_mapMaterials)) {
54 THROW_ARGOSEXCEPTION("Material \"" << str_material << "\" is undefined");
55 }
56 return tIter->second;
57 }
58
59 /****************************************/
60 /****************************************/
61
63 for(const CQTOpenGLObjModel::SMesh& s_mesh : m_vecMeshes) {
64 /* set material properties for the mesh */
65 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, s_mesh.Material->second.Ambient.data());
66 glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, s_mesh.Material->second.Diffuse.data());
67 glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, s_mesh.Material->second.Emission.data());
68 glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, s_mesh.Material->second.Shininess.data());
69 glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, s_mesh.Material->second.Specular.data());
70 /* enable blending for transparent materials */
71 if(s_mesh.Material->second.EnableTransparency) {
72 glEnable(GL_BLEND);
73 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
74 }
75 /* enable client states, set pointers */
76 if (!m_vecOpenGLPositions.empty()) {
77 glEnableClientState(GL_VERTEX_ARRAY);
78 glVertexPointer(3, GL_DOUBLE, sizeof(GLdouble) * 3, m_vecOpenGLPositions.data());
79 }
80 if (!m_vecOpenGLNormals.empty()) {
81 glEnableClientState(GL_NORMAL_ARRAY);
82 glNormalPointer(GL_DOUBLE, sizeof(GLdouble) * 3, m_vecOpenGLNormals.data());
83 }
84 /* draw the triangles */
85 glDrawElements(GL_TRIANGLES, s_mesh.Triangles.size(), GL_UNSIGNED_INT, s_mesh.Triangles.data());
86 /* disable client states */
87 if (!m_vecOpenGLNormals.empty()) {
88 glDisableClientState(GL_NORMAL_ARRAY);
89 }
90 if (!m_vecOpenGLPositions.empty()) {
91 glDisableClientState(GL_VERTEX_ARRAY);
92 }
93 /* disable blending for transparent materials */
94 if(s_mesh.Material->second.EnableTransparency) {
95 glDisable(GL_BLEND);
96 }
97 }
98 }
99
100 /****************************************/
101 /****************************************/
102
103 const QString& CQTOpenGLObjModel::GetModelDir() const {
104 /* get a reference to the visualization */
106 auto& cQtOpenGLRender = dynamic_cast<CQTOpenGLRender&>(cVisualization);
107 /* return the model directory */
108 return cQtOpenGLRender.GetMainWindow().GetModelDir();
109 }
110
111 /****************************************/
112 /****************************************/
113
114 void CQTOpenGLObjModel::ImportGeometry(QTextStream& c_text_stream) {
115 /* select the default material */
116 auto itSelectedMaterial =
117 m_mapMaterials.find("__default_material");
118 /* define a functor for adjusting negative indices found in the obj model */
119 struct {
120 void operator()(SInt32& n_index, SInt32 un_count) {
121 n_index = (n_index < 0) ? (n_index + un_count) : (n_index - 1);
122 }
123 } AdjustIndex;
124 /* create a buffer for read the file */
125 QString strLineBuffer;
126 /* loop over the lines from the text stream */
127 while(c_text_stream.readLineInto(&strLineBuffer)) {
128 const QStringList& cLineBufferSplit =
129#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
130 strLineBuffer.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
131#else
132 strLineBuffer.split(QRegularExpression("\\s+"), QString::SkipEmptyParts);
133#endif
134 if(cLineBufferSplit.empty()) {
135 /* skip blank lines */
136 continue;
137 }
138 else if(cLineBufferSplit[0] == "mtllib") {
139 QFile cMaterialsFile(GetModelDir() + cLineBufferSplit.value(1));
140 cMaterialsFile.open(QIODevice::ReadOnly);
141 QTextStream cMaterialsStream(&cMaterialsFile);
142 ImportMaterials(cMaterialsStream);
143 }
144 else if(cLineBufferSplit[0] == "f") {
145 /* detect the coordinate format by splitting on the '/' character */
146 const QStringList& cCoordinateParts = cLineBufferSplit.value(1).split('/');
147 /* f v v v ... */
148 if(cCoordinateParts.count() == 1) {
149 /* v0 */
150 SInt32 nVertex0 = cLineBufferSplit.value(1).toInt();
151 AdjustIndex(nVertex0, m_vecVertexCoords.size());
152 /* v1 */
153 SInt32 nVertex1 = cLineBufferSplit.value(2).toInt();
154 AdjustIndex(nVertex1, m_vecVertexCoords.size());
155 /* v2 ... vn */
156 for(SInt32 n_index = 3; n_index < cLineBufferSplit.count(); n_index++) {
157 SInt32 nVertex2 = cLineBufferSplit.value(n_index).toInt();
158 AdjustIndex(nVertex2, m_vecVertexCoords.size());
159 /* add triangle */
160 m_vecTriangles.emplace_back(itSelectedMaterial, std::array<GLuint, 3> {
161 AddVertex(nVertex0, m_vecVertexCoords[nVertex0], CVector3::ZERO, CVector2::ZERO),
162 AddVertex(nVertex1, m_vecVertexCoords[nVertex1], CVector3::ZERO, CVector2::ZERO),
163 AddVertex(nVertex2, m_vecVertexCoords[nVertex2], CVector3::ZERO, CVector2::ZERO),
164 });
165 nVertex1 = nVertex2;
166 }
167 }
168 /* f v/vt v/vt v/vt ... */
169 else if(cCoordinateParts.count() == 2) {
170 /* v0/vt0 */
171 const QStringList& cVertex0 = cLineBufferSplit.value(1).split('/');
172 SInt32 nVertex0 = cVertex0.value(0).toInt();
173 AdjustIndex(nVertex0, m_vecVertexCoords.size());
174 SInt32 nTexture0 = cVertex0.value(1).toInt();
175 AdjustIndex(nTexture0, m_vecTextureCoords.size());
176 /* v1/vt1 */
177 const QStringList& cVertex1 = cLineBufferSplit.value(2).split('/');
178 SInt32 nVertex1 = cVertex1.value(0).toInt();
179 AdjustIndex(nVertex1, m_vecVertexCoords.size());
180 SInt32 nTexture1 = cVertex1.value(1).toInt();
181 AdjustIndex(nTexture1, m_vecTextureCoords.size());
182 /* v2/vt2 ... vn/vtn */
183 for(SInt32 n_index = 3; n_index < cLineBufferSplit.count(); n_index++) {
184 const QStringList& cVertex2 = cLineBufferSplit.value(n_index).split('/');
185 SInt32 nVertex2 = cVertex2.value(0).toInt();
186 AdjustIndex(nVertex2, m_vecVertexCoords.size());
187 SInt32 nTexture2 = cVertex2.value(1).toInt();
188 AdjustIndex(nTexture2, m_vecTextureCoords.size());
189 /* add triangle */
190 m_vecTriangles.emplace_back(itSelectedMaterial, std::array<GLuint, 3> {
191 AddVertex(nVertex0, m_vecVertexCoords[nVertex0], CVector3::ZERO, m_vecTextureCoords[nTexture0]),
192 AddVertex(nVertex1, m_vecVertexCoords[nVertex1], CVector3::ZERO, m_vecTextureCoords[nTexture1]),
193 AddVertex(nVertex2, m_vecVertexCoords[nVertex2], CVector3::ZERO, m_vecTextureCoords[nTexture2]),
194 });
195 /* prepare for next face */
196 nVertex1 = nVertex2;
197 nTexture1 = nTexture2;
198 }
199 }
200 /* f v//vn v//vn v//vn ... */
201 else if(cCoordinateParts.count() == 3 && cCoordinateParts[1].isEmpty()) {
202 /* v0//vn0 */
203 const QStringList& cVertex0 = cLineBufferSplit.value(1).split('/');
204 SInt32 nVertex0 = cVertex0.value(0).toInt();
205 AdjustIndex(nVertex0, m_vecVertexCoords.size());
206 SInt32 nNormal0 = cVertex0.value(2).toInt();
207 AdjustIndex(nNormal0, m_vecNormals.size());
208 /* v1//vn1 */
209 const QStringList& cVertex1 = cLineBufferSplit.value(2).split('/');
210 SInt32 nVertex1 = cVertex1.value(0).toInt();
211 AdjustIndex(nVertex1, m_vecVertexCoords.size());
212 SInt32 nNormal1 = cVertex1.value(2).toInt();
213 AdjustIndex(nNormal1, m_vecNormals.size());
214 /* v2//vn2 ... vn//vnn */
215 for(SInt32 n_index = 3; n_index < cLineBufferSplit.count(); n_index++) {
216 const QStringList& cVertex2 = cLineBufferSplit.value(n_index).split('/');
217 SInt32 nVertex2 = cVertex2.value(0).toInt();
218 AdjustIndex(nVertex2, m_vecVertexCoords.size());
219 SInt32 nNormal2 = cVertex2.value(2).toInt();
220 AdjustIndex(nNormal2, m_vecNormals.size());
221 /* add triangle */
222 m_vecTriangles.emplace_back(itSelectedMaterial, std::array<GLuint, 3> {
223 AddVertex(nVertex0, m_vecVertexCoords[nVertex0], m_vecNormals[nNormal0], CVector2::ZERO),
224 AddVertex(nVertex1, m_vecVertexCoords[nVertex1], m_vecNormals[nNormal1], CVector2::ZERO),
225 AddVertex(nVertex2, m_vecVertexCoords[nVertex2], m_vecNormals[nNormal2], CVector2::ZERO),
226 });
227 /* prepare for next face */
228 nVertex1 = nVertex2;
229 nNormal1 = nNormal2;
230 }
231 }
232 /* f v/vt/vn v/vt/vn v/vt/vn ... */
233 else if(cCoordinateParts.count() == 3) {
234 /* v0/vt0/vn0 */
235 const QStringList& cVertex0 = cLineBufferSplit.value(1).split('/');
236 SInt32 nVertex0 = cVertex0.value(0).toInt();
237 AdjustIndex(nVertex0, m_vecVertexCoords.size());
238 SInt32 nTexture0 = cVertex0.value(1).toInt();
239 AdjustIndex(nTexture0, m_vecTextureCoords.size());
240 SInt32 nNormal0 = cVertex0.value(2).toInt();
241 AdjustIndex(nNormal0, m_vecNormals.size());
242 /* v1/vt1/vn1 */
243 const QStringList& cVertex1 = cLineBufferSplit.value(2).split('/');
244 SInt32 nVertex1 = cVertex1.value(0).toInt();
245 AdjustIndex(nVertex1, m_vecVertexCoords.size());
246 SInt32 nTexture1 = cVertex1.value(1).toInt();
247 AdjustIndex(nTexture1, m_vecTextureCoords.size());
248 SInt32 nNormal1 = cVertex1.value(2).toInt();
249 AdjustIndex(nNormal1, m_vecNormals.size());
250 /* v2/vt2/vn2 ... vn/vtn/vnn */
251 for(SInt32 n_index = 3; n_index < cLineBufferSplit.count(); n_index++) {
252 const QStringList& cVertex2 = cLineBufferSplit.value(n_index).split('/');
253 SInt32 nVertex2 = cVertex2.value(0).toInt();
254 AdjustIndex(nVertex2, m_vecVertexCoords.size());
255 SInt32 nTexture2 = cVertex2.value(1).toInt();
256 AdjustIndex(nTexture2, m_vecTextureCoords.size());
257 SInt32 nNormal2 = cVertex2.value(2).toInt();
258 AdjustIndex(nNormal2, m_vecNormals.size());
259 /* add triangle */
260 m_vecTriangles.emplace_back(itSelectedMaterial, std::array<GLuint, 3> {
261 AddVertex(nVertex0, m_vecVertexCoords[nVertex0], m_vecNormals[nNormal0], m_vecTextureCoords[nTexture0]),
262 AddVertex(nVertex1, m_vecVertexCoords[nVertex1], m_vecNormals[nNormal1], m_vecTextureCoords[nTexture1]),
263 AddVertex(nVertex2, m_vecVertexCoords[nVertex2], m_vecNormals[nNormal2], m_vecTextureCoords[nTexture2]),
264 });
265 /* prepare for next face */
266 nVertex1 = nVertex2;
267 nNormal1 = nNormal2;
268 nTexture1 = nTexture2;
269 }
270 }
271 }
272 else if(cLineBufferSplit[0] == "usemtl") {
273 std::string strName = cLineBufferSplit.value(1).toStdString();
274 itSelectedMaterial = m_mapMaterials.find(strName);
275 /* select the default material if the requested material is missing */
276 if(itSelectedMaterial == std::end(m_mapMaterials)) {
277 itSelectedMaterial = m_mapMaterials.find("__default_material");
278 }
279 }
280 else if(cLineBufferSplit[0] == "v") {
281#ifdef ARGOS_USE_DOUBLE
282 m_vecVertexCoords.emplace_back(cLineBufferSplit.value(1).toDouble(),
283 cLineBufferSplit.value(2).toDouble(),
284 cLineBufferSplit.value(3).toDouble());
285#else
286 m_vecVertexCoords.emplace_back(cLineBufferSplit.value(1).toFloat(),
287 cLineBufferSplit.value(2).toFloat(),
288 cLineBufferSplit.value(3).toFloat());
289#endif
290 }
291 else if(cLineBufferSplit[0] == "vn") {
292#ifdef ARGOS_USE_DOUBLE
293 m_vecNormals.emplace_back(cLineBufferSplit.value(1).toDouble(),
294 cLineBufferSplit.value(2).toDouble(),
295 cLineBufferSplit.value(3).toDouble());
296#else
297 m_vecNormals.emplace_back(cLineBufferSplit.value(1).toFloat(),
298 cLineBufferSplit.value(2).toFloat(),
299 cLineBufferSplit.value(3).toFloat());
300#endif
301 }
302 else if(cLineBufferSplit[0] == "vt") {
303#ifdef ARGOS_USE_DOUBLE
304 m_vecTextureCoords.emplace_back(cLineBufferSplit.value(1).toDouble(),
305 cLineBufferSplit.value(2).toDouble());
306#else
307 m_vecTextureCoords.emplace_back(cLineBufferSplit.value(1).toFloat(),
308 cLineBufferSplit.value(2).toFloat());
309#endif
310 }
311 }
312 }
313
314 /****************************************/
315 /****************************************/
316
317 void CQTOpenGLObjModel::ImportMaterials(QTextStream& c_text_stream) {
318 /* select the default material */
319 auto itMaterial =
320 m_mapMaterials.find("__default_material");
321 /* create a buffer for parsing the input */
322 QString strLineBuffer;
323 /* load the materials in the MTL file */
324 while(c_text_stream.readLineInto(&strLineBuffer)) {
325 const QStringList& cLineBufferSplit =
326#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
327 strLineBuffer.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
328#else
329 strLineBuffer.split(QRegularExpression("\\s+"), QString::SkipEmptyParts);
330#endif
331 if(cLineBufferSplit.empty()) {
332 /* skip blank lines */
333 continue;
334 }
335 else if(cLineBufferSplit[0] == "newmtl") {
336 const QString& strMaterialName = cLineBufferSplit.value(1);
337 itMaterial = m_mapMaterials.find(strMaterialName.toStdString());
338 if(itMaterial != std::end(m_mapMaterials)) {
339 THROW_ARGOSEXCEPTION("Material \"" << strMaterialName.toStdString() <<
340 "\" already defined");
341 }
342 itMaterial = m_mapMaterials.emplace(strMaterialName.toStdString(), SMaterial()).first;
343 }
344 else if(cLineBufferSplit[0] == "Ka") {
345 itMaterial->second.Ambient[0] = cLineBufferSplit.value(1).toFloat();
346 itMaterial->second.Ambient[1] = cLineBufferSplit.value(2).toFloat();
347 itMaterial->second.Ambient[2] = cLineBufferSplit.value(3).toFloat();
348 }
349 else if(cLineBufferSplit[0] == "Kd") {
350 itMaterial->second.Diffuse[0] = cLineBufferSplit.value(1).toFloat();
351 itMaterial->second.Diffuse[1] = cLineBufferSplit.value(2).toFloat();
352 itMaterial->second.Diffuse[2] = cLineBufferSplit.value(3).toFloat();
353 }
354 else if(cLineBufferSplit[0] == "Ks") {
355 itMaterial->second.Specular[0] = cLineBufferSplit.value(1).toFloat();
356 itMaterial->second.Specular[1] = cLineBufferSplit.value(2).toFloat();
357 itMaterial->second.Specular[2] = cLineBufferSplit.value(3).toFloat();
358 }
359 else if(cLineBufferSplit[0] == "Tr") {
360 GLfloat fAlpha = 1.0f - cLineBufferSplit.value(1).toFloat();
361 if(fAlpha < 1.0f) {
362 itMaterial->second.EnableTransparency = true;
363 itMaterial->second.Ambient[3] = fAlpha;
364 itMaterial->second.Diffuse[3] = fAlpha;
365 itMaterial->second.Specular[3] = fAlpha;
366 }
367 itMaterial->second.Alpha = fAlpha;
368 }
369 else if(cLineBufferSplit[0] == "d") {
370 GLfloat fAlpha = cLineBufferSplit.value(1).toFloat();
371 if(fAlpha < 1.0f) {
372 itMaterial->second.EnableTransparency = true;
373 itMaterial->second.Ambient[3] = fAlpha;
374 itMaterial->second.Diffuse[3] = fAlpha;
375 itMaterial->second.Specular[3] = fAlpha;
376 }
377 itMaterial->second.Alpha = fAlpha;
378 }
379 else if(cLineBufferSplit[0] == "i" && cLineBufferSplit.value(1) == "1") {
380 itMaterial->second.Specular = {
381 0.0f, 0.0f, 0.0f, 1.0f
382 };
383 }
384 }
385 }
386
387 /****************************************/
388 /****************************************/
389
390 GLuint CQTOpenGLObjModel::AddVertex(SInt32 n_key,
391 const CVector3& c_position,
392 const CVector3& c_normal,
393 const CVector2& c_texture) {
394 /* for readability */
395 typedef std::multimap<SInt32, GLuint>::iterator TCacheIterator;
396 typedef std::multimap<SInt32, GLuint>::value_type TCacheEntry;
397 /* find cache entries matching the key */
398 std::pair<TCacheIterator, TCacheIterator> cRange =
399 m_mapVertexCache.equal_range(n_key);
400 /* check if the vertex already exists in the cache */
401 auto itVertex =
402 std::find_if(cRange.first,
403 cRange.second,
404 [this, &c_position, &c_normal, &c_texture] (const TCacheEntry& c_entry) {
405 return (m_vecVertices[c_entry.second].Position == c_position &&
406 m_vecVertices[c_entry.second].Normal == c_normal &&
407 m_vecVertices[c_entry.second].Texture == c_texture);
408 });
409 /* if the vertex doesn't exist create it and put it in the cache */
410 if(itVertex == cRange.second) {
411 m_vecVertices.emplace_back(c_position, c_normal, c_texture);
412 itVertex = m_mapVertexCache.emplace(n_key, m_vecVertices.size() - 1);
413 }
414 return itVertex->second;
415 }
416
417 /****************************************/
418 /****************************************/
419
420 void CQTOpenGLObjModel::BuildMeshes() {
421 auto itCurrentMaterial = std::end(m_mapMaterials);
422 for(const STriangle& s_triangle : m_vecTriangles) {
423 if(s_triangle.Material != itCurrentMaterial) {
424 /* select the current material */
425 itCurrentMaterial = s_triangle.Material;
426 /* create a new mesh */
427 m_vecMeshes.emplace_back(itCurrentMaterial);
428 }
429 m_vecMeshes.back().Triangles.push_back(s_triangle.Indices[0]);
430 m_vecMeshes.back().Triangles.push_back(s_triangle.Indices[1]);
431 m_vecMeshes.back().Triangles.push_back(s_triangle.Indices[2]);
432 }
433 /* sort the meshes based on their material's alpha. Fully opaque meshes towards the
434 front and fully transparent towards the back. */
435 std::sort(m_vecMeshes.begin(),
436 m_vecMeshes.end(),
437 [] (const SMesh& s_left,
438 const SMesh& s_right) {
439 return (s_left.Material->second.Alpha > s_right.Material->second.Alpha);
440 });
441 }
442
443 /****************************************/
444 /****************************************/
445
446 void CQTOpenGLObjModel::GenerateOpenGLVectors() {
447 /* reserve memory */
448 m_vecOpenGLPositions.reserve(m_vecVertices.size() * 3);
449 m_vecOpenGLNormals.reserve(m_vecVertices.size() * 3);
450 /* copy data */
451 for(const SVertex& s_vertex : m_vecVertices) {
452 /* positions */
453 m_vecOpenGLPositions.push_back(s_vertex.Position.GetX());
454 m_vecOpenGLPositions.push_back(s_vertex.Position.GetY());
455 m_vecOpenGLPositions.push_back(s_vertex.Position.GetZ());
456 /* normals */
457 m_vecOpenGLNormals.push_back(s_vertex.Normal.GetX());
458 m_vecOpenGLNormals.push_back(s_vertex.Normal.GetY());
459 m_vecOpenGLNormals.push_back(s_vertex.Normal.GetZ());
460 }
461 }
462
463 /****************************************/
464 /****************************************/
465
466
467}
signed int SInt32
32-bit signed integer.
Definition datatypes.h:93
#define THROW_ARGOSEXCEPTION(message)
This macro throws an ARGoS exception with the passed message.
The namespace containing all the ARGoS related code.
Definition ci_actuator.h:12
static CSimulator & GetInstance()
Returns the instance to the CSimulator class.
Definition simulator.cpp:78
CVisualization & GetVisualization()
Returns a reference to the visualization.
Definition simulator.h:157
static const CVector2 ZERO
The zero vector (0,0)
Definition vector2.h:42
static const CVector3 ZERO
The zero vector (0,0,0)
Definition vector3.h:45
const QString & GetModelDir() const
CQTOpenGLObjModel(const std::string &str_model)
SMaterial & GetMaterial(const std::string &str_material)
CQTOpenGLMainWindow & GetMainWindow()