From 886037b8745f3038b1d71736dbea3cce0c158c8a Mon Sep 17 00:00:00 2001 From: Michael Allwright Date: Fri, 15 Mar 2019 16:23:43 +0100 Subject: [PATCH] Add support for rendering OBJ models inside the Qt-OpenGL visualization. This commit introduces the following changes: 1. Moves the basic Qt-OpenGL models (box, cylinder, light) into the parent directory 2. Updates the CMakeLists to reflect the changes in #1 and to install any files in the src/plugins/simulator/visualizations/qt-opengl/models/ directory following the same scheme as the icons and textures. 3. Updates the main window code to parse and write back settings related to the model directory following the same scheme as icons and textures 4. Adds the class CQtOpenGLObjModel whose constructor takes the filename of an OBJ model stored in the model directory. The MTL (materials file) is assumed to be in the same directory 5. Updates README.asciidoc to document the settings for the models directory --- README.asciidoc | 6 +- .../visualizations/qt-opengl/CMakeLists.txt | 22 +- .../qt-opengl/{models => }/qtopengl_box.cpp | 0 .../qt-opengl/{models => }/qtopengl_box.h | 0 .../{models => }/qtopengl_cylinder.cpp | 0 .../{models => }/qtopengl_cylinder.h | 0 .../qt-opengl/{models => }/qtopengl_light.cpp | 0 .../qt-opengl/{models => }/qtopengl_light.h | 0 .../qt-opengl/qtopengl_main_window.cpp | 11 + .../qt-opengl/qtopengl_main_window.h | 5 + .../qt-opengl/qtopengl_obj_model.cpp | 441 ++++++++++++++++++ .../qt-opengl/qtopengl_obj_model.h | 131 ++++++ 12 files changed, 602 insertions(+), 14 deletions(-) rename src/plugins/simulator/visualizations/qt-opengl/{models => }/qtopengl_box.cpp (100%) rename src/plugins/simulator/visualizations/qt-opengl/{models => }/qtopengl_box.h (100%) rename src/plugins/simulator/visualizations/qt-opengl/{models => }/qtopengl_cylinder.cpp (100%) rename src/plugins/simulator/visualizations/qt-opengl/{models => }/qtopengl_cylinder.h (100%) rename src/plugins/simulator/visualizations/qt-opengl/{models => }/qtopengl_light.cpp (100%) rename src/plugins/simulator/visualizations/qt-opengl/{models => }/qtopengl_light.h (100%) create mode 100644 src/plugins/simulator/visualizations/qt-opengl/qtopengl_obj_model.cpp create mode 100644 src/plugins/simulator/visualizations/qt-opengl/qtopengl_obj_model.h diff --git a/README.asciidoc b/README.asciidoc index 4812cb85..cd928bdc 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -233,8 +233,8 @@ run ARGoS: $ ./argos3 -q all # this shows all the plugins recognized by ARGoS If you execute ARGoS with the graphical visualization, you'll notice that -icons and textures are missing. This is normal, as ARGoS by default looks -for them in the default install location. To fix this, you need to edit +icons, models, and textures are missing. This is normal, as ARGoS by default +looks for them in the default install location. To fix this, you need to edit the default settings of the GUI. On Linux, edit the file +$HOME/.config/Iridia-ULB/ARGoS.conf+ as follows: @@ -245,6 +245,7 @@ On Linux, edit the file +$HOME/.config/Iridia-ULB/ARGoS.conf+ as follows: # icon_dir=/PATH/TO/argos3/src/plugins/simulator/visualizations/qt-opengl/icons/ texture_dir=/PATH/TO/argos3/src/plugins/simulator/visualizations/qt-opengl/textures/ + model_dir=/PATH/TO/argos3/src/plugins/simulator/visualizations/qt-opengl/models/ # # more stuff # @@ -252,6 +253,7 @@ On Linux, edit the file +$HOME/.config/Iridia-ULB/ARGoS.conf+ as follows: On Mac, write the following commands on the terminal window to fix the paths manually: $ defaults write info.argos-sim.ARGoS MainWindow.texture_dir -string "/PATH/TO/argos3/src/plugins/simulator/visualizations/qt-opengl/textures/" + $ defaults write info.argos-sim.ARGoS MainWindow.model_dir -string "/PATH/TO/argos3/src/plugins/simulator/visualizations/qt-opengl/models/" $ defaults write info.argos-sim.ARGoS MainWindow.icon_dir -string "/PATH/TO/argos3/src/plugins/simulator/visualizations/qt-opengl/icons/" $ killall -u YOURUSERNAME cfprefsd diff --git a/src/plugins/simulator/visualizations/qt-opengl/CMakeLists.txt b/src/plugins/simulator/visualizations/qt-opengl/CMakeLists.txt index 56033393..8585b864 100644 --- a/src/plugins/simulator/visualizations/qt-opengl/CMakeLists.txt +++ b/src/plugins/simulator/visualizations/qt-opengl/CMakeLists.txt @@ -1,17 +1,16 @@ # # Headers # -# argos3/plugins/simulator/visualizations/qt-opengl/models -set(ARGOS3_HEADERS_PLUGINS_SIMULATOR_VISUALIZATIONS_QTOPENGL_MODELS - models/qtopengl_box.h - models/qtopengl_cylinder.h - models/qtopengl_light.h) # argos3/plugins/simulator/visualizations/qt-opengl set(ARGOS3_HEADERS_PLUGINS_SIMULATOR_VISUALIZATIONS_QTOPENGL qtopengl_application.h + qtopengl_box.h qtopengl_camera.h + qtopengl_cylinder.h + qtopengl_light.h qtopengl_log_stream.h qtopengl_main_window.h + qtopengl_obj_model.h qtopengl_render.h qtopengl_user_functions.h qtopengl_widget.h) @@ -32,13 +31,13 @@ endif(ARGOS_WITH_LUA) # Common for all builds set(ARGOS3_SOURCES_PLUGINS_SIMULATOR_VISUALIZATIONS_QTOPENGL ${ARGOS3_HEADERS_PLUGINS_SIMULATOR_VISUALIZATIONS_QTOPENGL} - ${ARGOS3_HEADERS_PLUGINS_SIMULATOR_VISUALIZATIONS_QTOPENGL_MODELS} - models/qtopengl_box.cpp - models/qtopengl_cylinder.cpp - models/qtopengl_light.cpp qtopengl_application.cpp + qtopengl_box.cpp qtopengl_camera.cpp + qtopengl_cylinder.cpp + qtopengl_light.cpp qtopengl_main_window.cpp + qtopengl_obj_model.cpp qtopengl_render.cpp qtopengl_user_functions.cpp qtopengl_widget.cpp) @@ -64,9 +63,8 @@ target_link_libraries(argos3plugin_${ARGOS_BUILD_FOR}_qtopengl argos3plugin_${AR # target_link_libraries(argos3plugin_${ARGOS_BUILD_FOR}_qtopengl ${SDL_LIBRARY}) # endif(SDL_FOUND) -install(DIRECTORY icons textures DESTINATION include/argos3/plugins/simulator/visualizations/qt-opengl) -install(FILES ${ARGOS3_HEADERS_PLUGINS_SIMULATOR_VISUALIZATIONS_QTOPENGL_MODELS} DESTINATION include/argos3/plugins/simulator/visualizations/qt-opengl/models) -install(FILES ${ARGOS3_HEADERS_PLUGINS_SIMULATOR_VISUALIZATIONS_QTOPENGL} DESTINATION include/argos3/plugins/simulator/visualizations/qt-opengl) +install(DIRECTORY icons textures models DESTINATION include/argos3/plugins/simulator/visualizations/qt-opengl) +install(FILES ${ARGOS3_HEADERS_PLUGINS_SIMULATOR_VISUALIZATIONS_QTOPENGL} DESTINATION include/argos3/plugins/simulator/visualizations/qt-opengl) install(TARGETS argos3plugin_${ARGOS_BUILD_FOR}_qtopengl RUNTIME DESTINATION bin LIBRARY DESTINATION lib/argos3 diff --git a/src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_box.cpp b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_box.cpp similarity index 100% rename from src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_box.cpp rename to src/plugins/simulator/visualizations/qt-opengl/qtopengl_box.cpp diff --git a/src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_box.h b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_box.h similarity index 100% rename from src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_box.h rename to src/plugins/simulator/visualizations/qt-opengl/qtopengl_box.h diff --git a/src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_cylinder.cpp b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_cylinder.cpp similarity index 100% rename from src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_cylinder.cpp rename to src/plugins/simulator/visualizations/qt-opengl/qtopengl_cylinder.cpp diff --git a/src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_cylinder.h b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_cylinder.h similarity index 100% rename from src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_cylinder.h rename to src/plugins/simulator/visualizations/qt-opengl/qtopengl_cylinder.h diff --git a/src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_light.cpp b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_light.cpp similarity index 100% rename from src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_light.cpp rename to src/plugins/simulator/visualizations/qt-opengl/qtopengl_light.cpp diff --git a/src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_light.h b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_light.h similarity index 100% rename from src/plugins/simulator/visualizations/qt-opengl/models/qtopengl_light.h rename to src/plugins/simulator/visualizations/qt-opengl/qtopengl_light.h diff --git a/src/plugins/simulator/visualizations/qt-opengl/qtopengl_main_window.cpp b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_main_window.cpp index 7f5c6404..65a77563 100644 --- a/src/plugins/simulator/visualizations/qt-opengl/qtopengl_main_window.cpp +++ b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_main_window.cpp @@ -209,6 +209,16 @@ namespace argos { m_strTextureDir = QString::fromStdString(CSimulator::GetInstance().GetInstallationDirectory()); m_strTextureDir += "/include/argos3/plugins/simulator/visualizations/qt-opengl/textures/"; } + if(cSettings.contains("model_dir")) { + m_strModelDir = cSettings.value("model_dir").toString(); + if(m_strModelDir.at(m_strModelDir.length()-1) != '/') { + m_strModelDir.append("/"); + } + } + else { + m_strModelDir = QString::fromStdString(CSimulator::GetInstance().GetInstallationDirectory()); + m_strModelDir += "/include/argos3/plugins/simulator/visualizations/qt-opengl/models/"; + } cSettings.endGroup(); } @@ -233,6 +243,7 @@ namespace argos { cSettings.setValue("position", pos()); cSettings.setValue("icon_dir", m_strIconDir); cSettings.setValue("texture_dir", m_strTextureDir); + cSettings.setValue("model_dir", m_strModelDir); cSettings.endGroup(); } diff --git a/src/plugins/simulator/visualizations/qt-opengl/qtopengl_main_window.h b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_main_window.h index 7754f79c..56e4e10b 100644 --- a/src/plugins/simulator/visualizations/qt-opengl/qtopengl_main_window.h +++ b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_main_window.h @@ -66,6 +66,10 @@ namespace argos { return m_strTextureDir; } + inline const QString& GetModelDir() const { + return m_strModelDir; + } + private: void ReadSettingsPreCreation(); @@ -225,6 +229,7 @@ namespace argos { CQTOpenGLWidget* m_pcOpenGLWidget; QString m_strIconDir; QString m_strTextureDir; + QString m_strModelDir; EExperimentState m_eExperimentState; diff --git a/src/plugins/simulator/visualizations/qt-opengl/qtopengl_obj_model.cpp b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_obj_model.cpp new file mode 100644 index 00000000..1aca24b0 --- /dev/null +++ b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_obj_model.cpp @@ -0,0 +1,441 @@ +/** + * @file + * + * @author Majd Kassawat - + * @author Michael Allwright - + * + */ + +#include "qtopengl_obj_model.h" + +#include +#include +#include + +#include +#include + +namespace argos { + + /****************************************/ + /****************************************/ + + CQTOpenGLObjModel::CQTOpenGLObjModel(const std::string& str_obj_file) { + /* create a default material */ + m_mapMaterials["__default_material"]; + /* load the model */ + QFile cGeometryFile(GetModelDir() + QString::fromStdString(str_obj_file)); + if(!cGeometryFile.open(QIODevice::ReadOnly)) { + THROW_ARGOSEXCEPTION("Error loading model \"" << + cGeometryFile.fileName().toStdString() << "\": " << + cGeometryFile.errorString().toStdString()); + } + QTextStream cGeometryStream(&cGeometryFile); + ImportGeometry(cGeometryStream); + /* build the meshes */ + BuildMeshes(); + /* check if normals have been provided */ + if (m_vecNormals.empty()) { + THROW_ARGOSEXCEPTION("Error loading model \"" << + cGeometryFile.fileName().toStdString() << + "\": model does not specify normals.") + } + /* generate the OpenGL vectors */ + GenerateOpenGLVectors(); + } + + /****************************************/ + /****************************************/ + + CQTOpenGLObjModel::SMaterial& CQTOpenGLObjModel::GetMaterial(const std::string& str_material) { + SMaterial::TMapIterator tIter = m_mapMaterials.find(str_material); + if(tIter == std::end(m_mapMaterials)) { + THROW_ARGOSEXCEPTION("Material \"" << str_material << "\" is undefined"); + } + return tIter->second; + } + + /****************************************/ + /****************************************/ + + void CQTOpenGLObjModel::Draw() const { + for(const CQTOpenGLObjModel::SMesh& s_mesh : m_vecMeshes) { + /* set material properties for the mesh */ + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, s_mesh.Material->second.Ambient.data()); + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, s_mesh.Material->second.Diffuse.data()); + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, s_mesh.Material->second.Emission.data()); + glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, s_mesh.Material->second.Shininess.data()); + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, s_mesh.Material->second.Specular.data()); + /* enable blending for transparent materials */ + if(s_mesh.Material->second.EnableTransparency) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + /* enable client states, set pointers */ + if (!m_vecOpenGLPositions.empty()) { + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, sizeof(GLfloat) * 3, m_vecOpenGLPositions.data()); + } + if (!m_vecOpenGLNormals.empty()) { + glEnableClientState(GL_NORMAL_ARRAY); + glNormalPointer(GL_FLOAT, sizeof(GLfloat) * 3, m_vecOpenGLNormals.data()); + } + /* draw the triangles */ + glDrawElements(GL_TRIANGLES, s_mesh.Triangles.size(), GL_UNSIGNED_INT, s_mesh.Triangles.data()); + /* disable client states */ + if (!m_vecOpenGLNormals.empty()) { + glDisableClientState(GL_NORMAL_ARRAY); + } + if (!m_vecOpenGLPositions.empty()) { + glDisableClientState(GL_VERTEX_ARRAY); + } + /* disable blending for transparent materials */ + if(s_mesh.Material->second.EnableTransparency) { + glDisable(GL_BLEND); + } + } + } + + /****************************************/ + /****************************************/ + + const QString& CQTOpenGLObjModel::GetModelDir() const { + /* get a reference to the visualization */ + CVisualization& cVisualization = CSimulator::GetInstance().GetVisualization(); + CQTOpenGLRender& cQtOpenGLRender = dynamic_cast(cVisualization); + /* return the model directory */ + return cQtOpenGLRender.GetMainWindow().GetModelDir(); + } + + /****************************************/ + /****************************************/ + + void CQTOpenGLObjModel::ImportGeometry(QTextStream& c_text_stream) { + /* select the default material */ + SMaterial::TMapIterator itSelectedMaterial = + m_mapMaterials.find("__default_material"); + /* define a functor for adjusting negative indices found in the obj model */ + struct { + void operator()(SInt32& n_index, SInt32 un_count) { + n_index = (n_index < 0) ? (n_index + un_count) : (n_index - 1); + } + } AdjustIndex; + /* create a buffer for read the file */ + QString strLineBuffer; + /* loop over the lines from the text stream */ + while(c_text_stream.readLineInto(&strLineBuffer)) { + const QStringList& cLineBufferSplit = + strLineBuffer.split(QRegExp("\\s+"), QString::SkipEmptyParts); + if(cLineBufferSplit.empty()) { + /* skip blank lines */ + continue; + } + else if(cLineBufferSplit[0] == "mtllib") { + QFile cMaterialsFile(GetModelDir() + cLineBufferSplit.value(1)); + cMaterialsFile.open(QIODevice::ReadOnly); + QTextStream cMaterialsStream(&cMaterialsFile); + ImportMaterials(cMaterialsStream); + } + else if(cLineBufferSplit[0] == "f") { + /* detect the coordinate format by splitting on the '/' character */ + const QStringList& cCoordinateParts = cLineBufferSplit.value(1).split('/'); + /* f v v v ... */ + if(cCoordinateParts.count() == 1) { + /* v0 */ + SInt32 nVertex0 = cLineBufferSplit.value(1).toInt(); + AdjustIndex(nVertex0, m_vecVertexCoords.size()); + /* v1 */ + SInt32 nVertex1 = cLineBufferSplit.value(2).toInt(); + AdjustIndex(nVertex1, m_vecVertexCoords.size()); + /* v2 ... vn */ + for(SInt32 n_index = 3; n_index < cLineBufferSplit.count(); n_index++) { + SInt32 nVertex2 = cLineBufferSplit.value(n_index).toInt(); + AdjustIndex(nVertex2, m_vecVertexCoords.size()); + /* add triangle */ + m_vecTriangles.emplace_back(itSelectedMaterial, std::array { + AddVertex(nVertex0, m_vecVertexCoords[nVertex0], CVector3::ZERO, CVector2::ZERO), + AddVertex(nVertex1, m_vecVertexCoords[nVertex1], CVector3::ZERO, CVector2::ZERO), + AddVertex(nVertex2, m_vecVertexCoords[nVertex2], CVector3::ZERO, CVector2::ZERO), + }); + nVertex1 = nVertex2; + } + } + /* f v/vt v/vt v/vt ... */ + else if(cCoordinateParts.count() == 2) { + /* v0/vt0 */ + const QStringList& cVertex0 = cLineBufferSplit.value(1).split('/'); + SInt32 nVertex0 = cVertex0.value(0).toInt(); + AdjustIndex(nVertex0, m_vecVertexCoords.size()); + SInt32 nTexture0 = cVertex0.value(1).toInt(); + AdjustIndex(nTexture0, m_vecTextureCoords.size()); + /* v1/vt1 */ + const QStringList& cVertex1 = cLineBufferSplit.value(2).split('/'); + SInt32 nVertex1 = cVertex1.value(0).toInt(); + AdjustIndex(nVertex1, m_vecVertexCoords.size()); + SInt32 nTexture1 = cVertex1.value(1).toInt(); + AdjustIndex(nTexture1, m_vecTextureCoords.size()); + /* v2/vt2 ... vn/vtn */ + for(SInt32 n_index = 3; n_index < cLineBufferSplit.count(); n_index++) { + const QStringList& cVertex2 = cLineBufferSplit.value(n_index).split('/'); + SInt32 nVertex2 = cVertex2.value(0).toInt(); + AdjustIndex(nVertex2, m_vecVertexCoords.size()); + SInt32 nTexture2 = cVertex2.value(1).toInt(); + AdjustIndex(nTexture2, m_vecTextureCoords.size()); + /* add triangle */ + m_vecTriangles.emplace_back(itSelectedMaterial, std::array { + AddVertex(nVertex0, m_vecVertexCoords[nVertex0], CVector3::ZERO, m_vecTextureCoords[nTexture0]), + AddVertex(nVertex1, m_vecVertexCoords[nVertex1], CVector3::ZERO, m_vecTextureCoords[nTexture1]), + AddVertex(nVertex2, m_vecVertexCoords[nVertex2], CVector3::ZERO, m_vecTextureCoords[nTexture2]), + }); + /* prepare for next face */ + nVertex1 = nVertex2; + nTexture1 = nTexture2; + } + } + /* f v//vn v//vn v//vn ... */ + else if(cCoordinateParts.count() == 3 && cCoordinateParts[1].isEmpty()) { + /* v0//vn0 */ + const QStringList& cVertex0 = cLineBufferSplit.value(1).split('/'); + SInt32 nVertex0 = cVertex0.value(0).toInt(); + AdjustIndex(nVertex0, m_vecVertexCoords.size()); + SInt32 nNormal0 = cVertex0.value(2).toInt(); + AdjustIndex(nNormal0, m_vecNormals.size()); + /* v1//vn1 */ + const QStringList& cVertex1 = cLineBufferSplit.value(2).split('/'); + SInt32 nVertex1 = cVertex1.value(0).toInt(); + AdjustIndex(nVertex1, m_vecVertexCoords.size()); + SInt32 nNormal1 = cVertex1.value(2).toInt(); + AdjustIndex(nNormal1, m_vecNormals.size()); + /* v2//vn2 ... vn//vnn */ + for(SInt32 n_index = 3; n_index < cLineBufferSplit.count(); n_index++) { + const QStringList& cVertex2 = cLineBufferSplit.value(n_index).split('/'); + SInt32 nVertex2 = cVertex2.value(0).toInt(); + AdjustIndex(nVertex2, m_vecVertexCoords.size()); + SInt32 nNormal2 = cVertex2.value(2).toInt(); + AdjustIndex(nNormal2, m_vecNormals.size()); + /* add triangle */ + m_vecTriangles.emplace_back(itSelectedMaterial, std::array { + AddVertex(nVertex0, m_vecVertexCoords[nVertex0], m_vecNormals[nNormal0], CVector2::ZERO), + AddVertex(nVertex1, m_vecVertexCoords[nVertex1], m_vecNormals[nNormal1], CVector2::ZERO), + AddVertex(nVertex2, m_vecVertexCoords[nVertex2], m_vecNormals[nNormal2], CVector2::ZERO), + }); + /* prepare for next face */ + nVertex1 = nVertex2; + nNormal1 = nNormal2; + } + } + /* f v/vt/vn v/vt/vn v/vt/vn ... */ + else if(cCoordinateParts.count() == 3) { + /* v0/vt0/vn0 */ + const QStringList& cVertex0 = cLineBufferSplit.value(1).split('/'); + SInt32 nVertex0 = cVertex0.value(0).toInt(); + AdjustIndex(nVertex0, m_vecVertexCoords.size()); + SInt32 nTexture0 = cVertex0.value(1).toInt(); + AdjustIndex(nTexture0, m_vecTextureCoords.size()); + SInt32 nNormal0 = cVertex0.value(2).toInt(); + AdjustIndex(nNormal0, m_vecNormals.size()); + /* v1/vt1/vn1 */ + const QStringList& cVertex1 = cLineBufferSplit.value(2).split('/'); + SInt32 nVertex1 = cVertex1.value(0).toInt(); + AdjustIndex(nVertex1, m_vecVertexCoords.size()); + SInt32 nTexture1 = cVertex1.value(1).toInt(); + AdjustIndex(nTexture1, m_vecTextureCoords.size()); + SInt32 nNormal1 = cVertex1.value(2).toInt(); + AdjustIndex(nNormal1, m_vecNormals.size()); + /* v2/vt2/vn2 ... vn/vtn/vnn */ + for(SInt32 n_index = 3; n_index < cLineBufferSplit.count(); n_index++) { + const QStringList& cVertex2 = cLineBufferSplit.value(n_index).split('/'); + SInt32 nVertex2 = cVertex2.value(0).toInt(); + AdjustIndex(nVertex2, m_vecVertexCoords.size()); + SInt32 nTexture2 = cVertex2.value(1).toInt(); + AdjustIndex(nTexture2, m_vecTextureCoords.size()); + SInt32 nNormal2 = cVertex2.value(2).toInt(); + AdjustIndex(nNormal2, m_vecNormals.size()); + /* add triangle */ + m_vecTriangles.emplace_back(itSelectedMaterial, std::array { + AddVertex(nVertex0, m_vecVertexCoords[nVertex0], m_vecNormals[nNormal0], m_vecTextureCoords[nTexture0]), + AddVertex(nVertex1, m_vecVertexCoords[nVertex1], m_vecNormals[nNormal1], m_vecTextureCoords[nTexture1]), + AddVertex(nVertex2, m_vecVertexCoords[nVertex2], m_vecNormals[nNormal2], m_vecTextureCoords[nTexture2]), + }); + /* prepare for next face */ + nVertex1 = nVertex2; + nNormal1 = nNormal2; + nTexture1 = nTexture2; + } + } + } + else if(cLineBufferSplit[0] == "usemtl") { + std::string strName = cLineBufferSplit.value(1).toStdString(); + itSelectedMaterial = m_mapMaterials.find(strName); + /* select the default material if the requested material is missing */ + if(itSelectedMaterial == std::end(m_mapMaterials)) { + itSelectedMaterial = m_mapMaterials.find("__default_material"); + } + } + else if(cLineBufferSplit[0] == "v") { + m_vecVertexCoords.emplace_back(cLineBufferSplit.value(1).toFloat(), + cLineBufferSplit.value(2).toFloat(), + cLineBufferSplit.value(3).toFloat()); + } + else if(cLineBufferSplit[0] == "vn") { + m_vecNormals.emplace_back(cLineBufferSplit.value(1).toFloat(), + cLineBufferSplit.value(2).toFloat(), + cLineBufferSplit.value(3).toFloat()); + } + else if(cLineBufferSplit[0] == "vt") { + m_vecTextureCoords.emplace_back(cLineBufferSplit.value(1).toFloat(), + cLineBufferSplit.value(2).toFloat()); + } + } + } + + /****************************************/ + /****************************************/ + + void CQTOpenGLObjModel::ImportMaterials(QTextStream& c_text_stream) { + /* select the default material */ + SMaterial::TMapIterator itMaterial = + m_mapMaterials.find("__default_material"); + /* create a buffer for parsing the input */ + QString strLineBuffer; + /* load the materials in the MTL file */ + while(c_text_stream.readLineInto(&strLineBuffer)) { + const QStringList& cLineBufferSplit = + strLineBuffer.split(QRegExp("\\s+"), QString::SkipEmptyParts); + if(cLineBufferSplit.empty()) { + /* skip blank lines */ + continue; + } + else if(cLineBufferSplit[0] == "newmtl") { + const QString& strMaterialName = cLineBufferSplit.value(1); + itMaterial = m_mapMaterials.find(strMaterialName.toStdString()); + if(itMaterial != std::end(m_mapMaterials)) { + THROW_ARGOSEXCEPTION("Material \"" << strMaterialName.toStdString() << + "\" already defined"); + } + itMaterial = m_mapMaterials.emplace(strMaterialName.toStdString(), SMaterial()).first; + } + else if(cLineBufferSplit[0] == "Ka") { + itMaterial->second.Ambient[0] = cLineBufferSplit.value(1).toFloat(); + itMaterial->second.Ambient[1] = cLineBufferSplit.value(2).toFloat(); + itMaterial->second.Ambient[2] = cLineBufferSplit.value(3).toFloat(); + } + else if(cLineBufferSplit[0] == "Kd") { + itMaterial->second.Diffuse[0] = cLineBufferSplit.value(1).toFloat(); + itMaterial->second.Diffuse[1] = cLineBufferSplit.value(2).toFloat(); + itMaterial->second.Diffuse[2] = cLineBufferSplit.value(3).toFloat(); + } + else if(cLineBufferSplit[0] == "Ks") { + itMaterial->second.Specular[0] = cLineBufferSplit.value(1).toFloat(); + itMaterial->second.Specular[1] = cLineBufferSplit.value(2).toFloat(); + itMaterial->second.Specular[2] = cLineBufferSplit.value(3).toFloat(); + } + else if(cLineBufferSplit[0] == "Tr") { + GLfloat fAlpha = 1.0f - cLineBufferSplit.value(1).toFloat(); + if(fAlpha < 1.0f) { + itMaterial->second.EnableTransparency = true; + itMaterial->second.Ambient[3] = fAlpha; + itMaterial->second.Diffuse[3] = fAlpha; + itMaterial->second.Specular[3] = fAlpha; + } + itMaterial->second.Alpha = fAlpha; + } + else if(cLineBufferSplit[0] == "d") { + GLfloat fAlpha = cLineBufferSplit.value(1).toFloat(); + if(fAlpha < 1.0f) { + itMaterial->second.EnableTransparency = true; + itMaterial->second.Ambient[3] = fAlpha; + itMaterial->second.Diffuse[3] = fAlpha; + itMaterial->second.Specular[3] = fAlpha; + } + itMaterial->second.Alpha = fAlpha; + } + else if(cLineBufferSplit[0] == "i" && cLineBufferSplit.value(1) == "1") { + itMaterial->second.Specular = { + 0.0f, 0.0f, 0.0f, 1.0f + }; + } + } + } + + /****************************************/ + /****************************************/ + + GLuint CQTOpenGLObjModel::AddVertex(SInt32 n_key, + const CVector3& c_position, + const CVector3& c_normal, + const CVector2& c_texture) { + /* for readability */ + typedef std::multimap::iterator TCacheIterator; + typedef std::multimap::value_type TCacheEntry; + /* find cache entries matching the key */ + std::pair cRange = + m_mapVertexCache.equal_range(n_key); + /* check if the vertex already exists in the cache */ + TCacheIterator itVertex = + std::find_if(cRange.first, + cRange.second, + [this, &c_position, &c_normal, &c_texture] (const TCacheEntry& c_entry) { + return (m_vecVertices[c_entry.second].Position == c_position && + m_vecVertices[c_entry.second].Normal == c_normal && + m_vecVertices[c_entry.second].Texture == c_texture); + }); + /* if the vertex doesn't exist create it and put it in the cache */ + if(itVertex == cRange.second) { + m_vecVertices.emplace_back(c_position, c_normal, c_texture); + itVertex = m_mapVertexCache.emplace(n_key, m_vecVertices.size() - 1); + } + return itVertex->second; + } + + /****************************************/ + /****************************************/ + + void CQTOpenGLObjModel::BuildMeshes() { + SMaterial::TMapIterator itCurrentMaterial = std::end(m_mapMaterials); + for(const STriangle& s_triangle : m_vecTriangles) { + if(s_triangle.Material != itCurrentMaterial) { + /* select the current material */ + itCurrentMaterial = s_triangle.Material; + /* create a new mesh */ + m_vecMeshes.emplace_back(itCurrentMaterial); + } + m_vecMeshes.back().Triangles.push_back(s_triangle.Indices[0]); + m_vecMeshes.back().Triangles.push_back(s_triangle.Indices[1]); + m_vecMeshes.back().Triangles.push_back(s_triangle.Indices[2]); + } + /* sort the meshes based on their material's alpha. Fully opaque meshes towards the + front and fully transparent towards the back. */ + std::sort(m_vecMeshes.begin(), + m_vecMeshes.end(), + [] (const SMesh& s_left, + const SMesh& s_right) { + return (s_left.Material->second.Alpha > s_right.Material->second.Alpha); + }); + } + + /****************************************/ + /****************************************/ + + void CQTOpenGLObjModel::GenerateOpenGLVectors() { + /* reserve memory */ + m_vecOpenGLPositions.reserve(m_vecVertices.size() * 3); + m_vecOpenGLNormals.reserve(m_vecVertices.size() * 3); + /* copy data */ + for(const SVertex& s_vertex : m_vecVertices) { + /* positions */ + m_vecOpenGLPositions.push_back(s_vertex.Position.GetX()); + m_vecOpenGLPositions.push_back(s_vertex.Position.GetY()); + m_vecOpenGLPositions.push_back(s_vertex.Position.GetZ()); + /* normals */ + m_vecOpenGLNormals.push_back(s_vertex.Normal.GetX()); + m_vecOpenGLNormals.push_back(s_vertex.Normal.GetY()); + m_vecOpenGLNormals.push_back(s_vertex.Normal.GetZ()); + } + } + + /****************************************/ + /****************************************/ + + +} diff --git a/src/plugins/simulator/visualizations/qt-opengl/qtopengl_obj_model.h b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_obj_model.h new file mode 100644 index 00000000..3d9701d8 --- /dev/null +++ b/src/plugins/simulator/visualizations/qt-opengl/qtopengl_obj_model.h @@ -0,0 +1,131 @@ +/** + * @file + * + * @author Majd Kassawat - + * @author Michael Allwright - + * + * This class was inspired by the OpenGL OBJ Viewer Demo from dhpoware. Details + * can be found on the following page: http://www.dhpoware.com/demos/glObjViewer.html + */ + +#ifndef QTOPENGL_OBJ_MODEL_H +#define QTOPENGL_OBJ_MODEL_H + +class QTextStream; +class QString; + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#else +#include +#endif + +namespace argos { + + class CQTOpenGLObjModel { + + public: + + struct SMaterial { + /* OpenGL material arrays */ + std::array Ambient = {0.0f, 0.0f, 0.0f, 1.0f}; + std::array Diffuse = {0.0f, 0.0f, 0.0f, 1.0f}; + std::array Emission = {0.0f, 0.0f, 0.0f, 1.0f}; + std::array Specular = {0.0f, 0.0f, 0.0f, 1.0f}; + std::array Shininess = {0.0f}; + /* material transparency */ + GLfloat Alpha = 1.0f; + /* enable the transparency effect for this material? */ + bool EnableTransparency = false; + /* convenience typedef for an iterator over a std::map of SMaterial */ + typedef std::map::iterator TMapIterator; + }; + + public: + + CQTOpenGLObjModel(const std::string& str_model); + + SMaterial& GetMaterial(const std::string& str_material); + + void Draw() const; + + private: + + struct SVertex { + SVertex(const CVector3& c_position = CVector3::ZERO, + const CVector3& c_normal = CVector3::ZERO, + const CVector2& c_texture = CVector2::ZERO) : + Position(c_position), + Normal(c_normal), + Texture(c_texture) {} + + CVector3 Position; + CVector3 Normal; + CVector2 Texture; + }; + + struct STriangle { + STriangle(const SMaterial::TMapIterator& it_material, + const std::array& arr_indices) : + Material(it_material), + Indices(arr_indices) {} + + SMaterial::TMapIterator Material; + std::array Indices; + }; + + struct SMesh { + SMesh(SMaterial::TMapIterator t_material) : + Material(t_material) {} + + std::vector Triangles; + SMaterial::TMapIterator Material; + }; + + private: + + const QString& GetModelDir() const; + + void ImportGeometry(QTextStream& c_text_stream); + + void ImportMaterials(QTextStream& c_text_stream); + + GLuint AddVertex(SInt32 n_key, + const CVector3& c_position, + const CVector3& c_normal, + const CVector2& c_texture); + + void BuildMeshes(); + + void GenerateOpenGLVectors(); + + private: + /* parsed data */ + std::vector m_vecVertexCoords; + std::vector m_vecTextureCoords; + std::vector m_vecNormals; + /* high-level primitives */ + std::map m_mapMaterials; + std::vector m_vecVertices; + std::vector m_vecTriangles; + std::vector m_vecMeshes; + std::multimap m_mapVertexCache; + /* low-level primitives */ + std::vector m_vecOpenGLPositions; + std::vector m_vecOpenGLNormals; + }; + +} + +#endif +