qtopengl_obj_model.cpp
Go to the documentation of this file.
1 
9 #include "qtopengl_obj_model.h"
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 
19 namespace 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 
62  void CQTOpenGLObjModel::Draw() const {
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 }
#define THROW_ARGOSEXCEPTION(message)
This macro throws an ARGoS exception with the passed message.
signed int SInt32
32-bit signed integer.
Definition: datatypes.h:93
The namespace containing all the ARGoS related code.
Definition: ci_actuator.h:12
CVisualization & GetVisualization()
Returns a reference to the visualization.
Definition: simulator.h:157
static CSimulator & GetInstance()
Returns the instance to the CSimulator class.
Definition: simulator.cpp:78
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()