diff --git a/CMakeLists.txt b/CMakeLists.txt index 67fd67f94..0a9b45fdf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -306,6 +306,32 @@ IF ( ASSIMP_BUILD_ASSIMP_TOOLS ) ENDIF ( WIN32 ) ADD_SUBDIRECTORY( tools/assimp_cmd/ ) + + # Check dependencies for assimp_qt_viewer. + # Why here? Maybe user do not want Qt viewer and have no Qt. + # Why assimp_qt_viewer/CMakeLists.txt still contain similar check? + # Because viewer can be build independently of Assimp. + FIND_PACKAGE(Qt4 QUIET) + FIND_PACKAGE(DevIL QUIET) + FIND_PACKAGE(OpenGL QUIET) + IF ( Qt4_FOUND AND IL_FOUND AND OPENGL_FOUND) + ADD_SUBDIRECTORY( tools/assimp_qt_viewer/ ) + ELSE() + SET ( ASSIMP_QT_VIEWER_DEPENDENCIES "") + IF (NOT Qt4_FOUND) + SET ( ASSIMP_QT_VIEWER_DEPENDENCIES "${ASSIMP_QT_VIEWER_DEPENDENCIES} Qt4") + ENDIF (NOT Qt4_FOUND) + + IF (NOT IL_FOUND) + SET ( ASSIMP_QT_VIEWER_DEPENDENCIES "${ASSIMP_QT_VIEWER_DEPENDENCIES} DevIL") + ENDIF (NOT IL_FOUND) + + IF (NOT OPENGL_FOUND) + SET ( ASSIMP_QT_VIEWER_DEPENDENCIES "${ASSIMP_QT_VIEWER_DEPENDENCIES} OpengGL") + ENDIF (NOT OPENGL_FOUND) + + MESSAGE (WARNING "Build of assimp_qt_viewer is disabled. Unsatisfied dendencies: ${ASSIMP_QT_VIEWER_DEPENDENCIES}") + ENDIF ( Qt4_FOUND AND IL_FOUND AND OPENGL_FOUND) ENDIF ( ASSIMP_BUILD_ASSIMP_TOOLS ) option ( ASSIMP_BUILD_SAMPLES diff --git a/tools/assimp_qt_viewer/CMakeLists.txt b/tools/assimp_qt_viewer/CMakeLists.txt new file mode 100644 index 000000000..26f43c156 --- /dev/null +++ b/tools/assimp_qt_viewer/CMakeLists.txt @@ -0,0 +1,44 @@ +project(assimp_qt_viewer) +set(PROJECT_VERSION "") + +cmake_minimum_required(VERSION 2.6) + +find_package(Qt4 REQUIRED) +find_package(DevIL REQUIRED) +find_package(OpenGL REQUIRED) + +include_directories( + ${QT_INCLUDES} + ${Assimp_SOURCE_DIR}/include + ${Assimp_SOURCE_DIR}/code + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR} + ${OPENGL_INCLUDE_DIR} + ${IL_INCLUDE_DIR} +) + +link_directories(${Assimp_BINARY_DIR}) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pedantic -Wall") + +set(assimp_qt_viewer_SRCS main.cpp loggerview.cpp glview.cpp mainwindow.cpp) +qt4_wrap_ui(UISrcs mainwindow.ui) +qt4_wrap_cpp(MOCrcs mainwindow.hpp glview.hpp) + +add_executable(${PROJECT_NAME} ${assimp_qt_viewer_SRCS} ${UISrcs} ${MOCrcs}) +target_link_libraries(${PROJECT_NAME} ${QT_LIBRARIES} ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTOPENGL_LIBRARY} ${IL_LIBRARIES} ${OPENGL_LIBRARIES} assimp) + +if(WIN32) # Check if we are on Windows + if(MSVC) # Check if we are using the Visual Studio compiler + set_target_properties(TestProject PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") + elseif(CMAKE_COMPILER_IS_GNUCXX) + # SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mwindows") # Not tested + else() + message(SEND_ERROR "You are using an unsupported Windows compiler! (Not MSVC or GCC)") + endif() +elseif(UNIX) + # Nothing special required +else() + message(SEND_ERROR "You are on an unsupported platform! (Not Win32 or Unix)") +endif() + +set_property(TARGET ${PROJECT_NAME} PROPERTY DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) diff --git a/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (en).odt b/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (en).odt new file mode 100644 index 000000000..48317305f Binary files /dev/null and b/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (en).odt differ diff --git a/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (ru).odt b/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (ru).odt new file mode 100644 index 000000000..44f6444a1 Binary files /dev/null and b/tools/assimp_qt_viewer/doc/Assimp_qt_viewer. Manual (ru).odt differ diff --git a/tools/assimp_qt_viewer/glview.cpp b/tools/assimp_qt_viewer/glview.cpp new file mode 100644 index 000000000..e4a2d7662 --- /dev/null +++ b/tools/assimp_qt_viewer/glview.cpp @@ -0,0 +1,1043 @@ +/// \file glview.cpp +/// \brief OpenGL visualisation. Implementation file. +/// \author smal.root@gmail.com +/// \date 2016 + +#include "glview.hpp" + +// Header files, OpenGL. +#include + +// Header files, DevIL. +#include + +// Header files, Assimp. +#include + +#ifndef __unused + #define __unused __attribute__((unused)) +#endif // __unused + +/**********************************/ +/********** SHelper_Mesh **********/ +/**********************************/ + +CGLView::SHelper_Mesh::SHelper_Mesh(const size_t pQuantity_Point, const size_t pQuantity_Line, const size_t pQuantity_Triangle, const SBBox& pBBox) + : Quantity_Point(pQuantity_Point), Quantity_Line(pQuantity_Line), Quantity_Triangle(pQuantity_Triangle), BBox(pBBox) +{ + Index_Point = pQuantity_Point ? new GLuint[pQuantity_Point * 1] : nullptr; + Index_Line = pQuantity_Line ? new GLuint[pQuantity_Line * 2] : nullptr; + Index_Triangle = pQuantity_Triangle ? new GLuint[pQuantity_Triangle * 3] : nullptr; +} + +CGLView::SHelper_Mesh::~SHelper_Mesh() +{ + if(Index_Point != nullptr) delete [] Index_Point; + if(Index_Line != nullptr) delete [] Index_Line; + if(Index_Triangle != nullptr) delete [] Index_Triangle; +} + +/**********************************/ +/********** SHelper_Mesh **********/ +/**********************************/ + +void CGLView::SHelper_Camera::SetDefault() +{ + Position.Set(0, 0, 0); + Target.Set(0, 0, -1); + Rotation_AroundCamera = aiMatrix4x4(); + Rotation_Scene = aiMatrix4x4(); + Translation_ToScene.Set(0, 0, 2); +} + +/**********************************/ +/************ CGLView *************/ +/**********************************/ + +void CGLView::Material_Apply(const aiMaterial* pMaterial) +{ +GLfloat tcol[4]; +aiColor4D taicol; +unsigned int max; +int ret1, ret2; +int texture_index = 0; +aiString texture_path; + +auto set_float4 = [](float f[4], float a, float b, float c, float d) { f[0] = a, f[1] = b, f[2] = c, f[3] = d; }; +auto color4_to_float4 = [](const aiColor4D *c, float f[4]) { f[0] = c->r, f[1] = c->g, f[2] = c->b, f[3] = c->a; }; + + ///TODO: cache materials + // Disable color material because glMaterial is used. + glDisable(GL_COLOR_MATERIAL);///TODO: cache + // Set texture. If assigned. + if(AI_SUCCESS == pMaterial->GetTexture(aiTextureType_DIFFUSE, texture_index, &texture_path)) + { + //bind texture + unsigned int texture_ID = mTexture_IDMap.value(texture_path.data, 0); + + glBindTexture(GL_TEXTURE_2D, texture_ID); + } + // + // Set material parameters from scene or default values. + // + // Diffuse + set_float4(tcol, 0.8f, 0.8f, 0.8f, 1.0f); + if(AI_SUCCESS == aiGetMaterialColor(pMaterial, AI_MATKEY_COLOR_DIFFUSE, &taicol)) color4_to_float4(&taicol, tcol); + + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, tcol); + // Specular + set_float4(tcol, 0.0f, 0.0f, 0.0f, 1.0f); + if(AI_SUCCESS == aiGetMaterialColor(pMaterial, AI_MATKEY_COLOR_SPECULAR, &taicol)) color4_to_float4(&taicol, tcol); + + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, tcol); + // Ambient + set_float4(tcol, 0.2f, 0.2f, 0.2f, 1.0f); + if(AI_SUCCESS == aiGetMaterialColor(pMaterial, AI_MATKEY_COLOR_AMBIENT, &taicol)) color4_to_float4(&taicol, tcol); + + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, tcol); + // Emission + set_float4(tcol, 0.0f, 0.0f, 0.0f, 1.0f); + if(AI_SUCCESS == aiGetMaterialColor(pMaterial, AI_MATKEY_COLOR_EMISSIVE, &taicol)) color4_to_float4(&taicol, tcol); + + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, tcol); + // Shininess + float shininess, strength; + + max = 1; + ret1 = aiGetMaterialFloatArray(pMaterial, AI_MATKEY_SHININESS, &shininess, &max); + // Shininess strength + max = 1; + ret2 = aiGetMaterialFloatArray(pMaterial, AI_MATKEY_SHININESS_STRENGTH, &strength, &max); + if((ret1 == AI_SUCCESS) && (ret2 == AI_SUCCESS)) + { + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess * strength);///TODO: cache + } + else + { + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 0.0f);///TODO: cache + set_float4(tcol, 0.0f, 0.0f, 0.0f, 0.0f); + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, tcol); + } + + // Fill mode + GLenum fill_mode; + int wireframe; + + max = 1; + if(AI_SUCCESS == aiGetMaterialIntegerArray(pMaterial, AI_MATKEY_ENABLE_WIREFRAME, &wireframe, &max)) + fill_mode = wireframe ? GL_LINE : GL_FILL; + else + fill_mode = GL_FILL; + + glPolygonMode(GL_FRONT_AND_BACK, fill_mode);///TODO: cache + // Fill side + int two_sided; + + max = 1; + if((AI_SUCCESS == aiGetMaterialIntegerArray(pMaterial, AI_MATKEY_TWOSIDED, &two_sided, &max)) && two_sided)///TODO: cache + glDisable(GL_CULL_FACE); + else + glEnable(GL_CULL_FACE); +} + +void CGLView::Matrix_NodeToRoot(const aiNode* pNode, aiMatrix4x4& pOutMatrix) +{ +const aiNode* node_cur; +std::list mat_list; + + pOutMatrix = aiMatrix4x4(); + // starting walk from current element to root + node_cur = pNode; + if(node_cur != nullptr) + { + do + { + // if cur_node is group then store group transformation matrix in list. + mat_list.push_back(node_cur->mTransformation); + node_cur = node_cur->mParent; + } while(node_cur != nullptr); + } + + // multiplicate all matrices in reverse order + for(std::list::reverse_iterator rit = mat_list.rbegin(); rit != mat_list.rend(); rit++) pOutMatrix = pOutMatrix * (*rit); +} + +void CGLView::ImportTextures(const QString& pScenePath) +{ +ILboolean success; + + if(mScene == nullptr) + { + LogError("Trying to load textures for empty scene."); + + return; + } + + // Before calling ilInit() version should be checked. + if(ilGetInteger(IL_VERSION_NUM) < IL_VERSION) + { + LogError("Wrong DevIL version."); + + return; + } + + ilInit();// Initialization of DevIL. + // + // Load embedded textures + // + if(mScene->HasTextures()) LogError("Support for meshes with embedded textures is not implemented."); + + // + // Load textures from external files. + // + // Get textures file names and number of textures. + for(size_t idx_material = 0; idx_material < mScene->mNumMaterials; idx_material++) + { + int idx_texture = 0; + aiString path; + + do + { + if(mScene->mMaterials[idx_material]->GetTexture(aiTextureType_DIFFUSE, idx_texture, &path) != AI_SUCCESS) break; + + mTexture_IDMap[path.data] = 0;// Fill map with invalid ID's. + idx_texture++; + } while(true); + }// for(size_t idx_mat = 0; idx_mat < scene->mNumMaterials; idx_mat++) + + // Textures list is empty, exit. + if(mTexture_IDMap.size() == 0) + { + LogInfo("No textures for import."); + + return; + } + + size_t num_textures = mTexture_IDMap.size(); + + + ILuint* id_images = nullptr;// Array with DevIL image ID's. + GLuint* id_textures = nullptr;// Array with OpenGL textures ID's. + + // Generate DevIL image IDs. + id_images = new ILuint[num_textures]; + ilGenImages(num_textures, id_images);// Generation of 'num_textures' image names. + // Create and fill array with OpenGL texture ID's. + id_textures = new GLuint[num_textures]; + ///TODO: if can not load textures then will stay orphande texture ID's in OpenGL. Generate OpenGL ID's after successfull loading of image. + glGenTextures(num_textures, id_textures);// Texture ID's generation. + + QMap::iterator map_it = mTexture_IDMap.begin();// Get iterator + QString basepath = pScenePath.left(pScenePath.lastIndexOf('/') + 1);// path with '/' at the end. + + for(size_t idx_texture = 0; idx_texture < num_textures; idx_texture++) + { + //save IL image ID + QString filename = map_it.key();// get filename + + mTexture_IDMap[filename] = id_textures[idx_texture];// save texture ID for filename in map + map_it++;// next texture + ilBindImage(id_images[idx_texture]);// Binding of DevIL image name. + + QString fileloc = basepath + filename; /* Loading of image */ + + fileloc.replace('\\', "/"); + success = ilLoadImage(fileloc.toLocal8Bit()); + if(!success) + { + LogError(QString("Couldn't load Image: %1").arg(fileloc)); + goto it_for_err; + } + + // Convert every colour component into unsigned byte. If your image contains alpha channel you can replace IL_RGB with IL_RGBA. + success = ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE); + if(!success) + { + LogError("Couldn't convert image."); + goto it_for_err; + } + + glBindTexture(GL_TEXTURE_2D, id_textures[idx_texture]);// Binding of texture ID. + // Redefine standard texture values + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// We will use linear interpolation for magnification filter. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);// We will use linear interpolation for minifying filter. + glTexImage2D(GL_TEXTURE_2D, 0, ilGetInteger(IL_IMAGE_BPP), ilGetInteger(IL_IMAGE_WIDTH), ilGetInteger(IL_IMAGE_HEIGHT), 0, + ilGetInteger(IL_IMAGE_FORMAT), GL_UNSIGNED_BYTE, ilGetData());// Texture specification. + continue; + +it_for_err: + + LogError(QString("DevIL error: %1, [%2]").arg(ilGetError()).arg(ilGetString(ilGetError()))); + mTexture_IDMap.remove(filename); + }// for(size_t idx_texture = 0; idx_texture < num_textures; i++) + + // Because we have already copied image data into texture data we can release memory used by image. + ilDeleteImages(num_textures, id_images); + + //Cleanup + delete [] id_images; + delete [] id_textures; +} + +void CGLView::BBox_GetForNode(const aiNode& pNode, const aiMatrix4x4& pParent_TransformationMatrix, SBBox& pNodeBBox, bool& pFirstAssign) +{ +aiMatrix4x4 mat_trans = pParent_TransformationMatrix * pNode.mTransformation; + + // Check if node has meshes + for(size_t idx_idx_mesh = 0; idx_idx_mesh < pNode.mNumMeshes; idx_idx_mesh++) + { + size_t idx_mesh; + SBBox bbox_local; + aiVector3D bbox_vertices[8]; + + idx_mesh = pNode.mMeshes[idx_idx_mesh]; + // Get vertices of mesh BBox + BBox_GetVertices(mHelper_Mesh[idx_mesh]->BBox, bbox_vertices); + // Transform vertices + for(size_t idx_vert = 0; idx_vert < 8; idx_vert++) bbox_vertices[idx_vert] *= mat_trans; + + // And create BBox for transformed mesh + BBox_GetFromVertices(bbox_vertices, 8, bbox_local); + + if(!pFirstAssign) + { + BBox_Extend(bbox_local, pNodeBBox); + } + else + { + pFirstAssign = false; + pNodeBBox = bbox_local; + } + }// for(size_t idx_idx_mesh = 0; idx_idx_mesh < pNode.mNumMeshes; idx_idx_mesh++) + + for(size_t idx_node = 0; idx_node < pNode.mNumChildren; idx_node++) + { + BBox_GetForNode(*pNode.mChildren[idx_node], mat_trans, pNodeBBox, pFirstAssign); + } +} + +void CGLView::BBox_Extend(const SBBox& pChild, SBBox& pParent) +{ + // search minimal... + AssignIfLesser(&pParent.Minimum.x, pChild.Minimum.x); + AssignIfLesser(&pParent.Minimum.y, pChild.Minimum.y); + AssignIfLesser(&pParent.Minimum.z, pChild.Minimum.z); + // and maximal values + AssignIfGreater(&pParent.Maximum.x, pChild.Maximum.x); + AssignIfGreater(&pParent.Maximum.y, pChild.Maximum.y); + AssignIfGreater(&pParent.Maximum.z, pChild.Maximum.z); +} + +void CGLView::BBox_GetVertices(const SBBox& pBBox, aiVector3D pVertex[8]) +{ + pVertex[0] = pBBox.Minimum; + pVertex[1].Set(pBBox.Minimum.x, pBBox.Minimum.y, pBBox.Maximum.z); + pVertex[2].Set(pBBox.Minimum.x, pBBox.Maximum.y, pBBox.Maximum.z); + pVertex[3].Set(pBBox.Minimum.x, pBBox.Maximum.y, pBBox.Minimum.z); + + pVertex[4].Set(pBBox.Maximum.x, pBBox.Minimum.y, pBBox.Minimum.z); + pVertex[5].Set(pBBox.Maximum.x, pBBox.Minimum.y, pBBox.Maximum.z); + pVertex[6] = pBBox.Maximum; + pVertex[7].Set(pBBox.Maximum.x, pBBox.Maximum.y, pBBox.Minimum.z); + +} + +void CGLView::BBox_GetFromVertices(const aiVector3D* pVertices, const size_t pVerticesQuantity, SBBox& pBBox) +{ + if(pVerticesQuantity == 0) + { + pBBox.Maximum.Set(0, 0, 0); + pBBox.Minimum.Set(0, 0, 0); + + return; + } + + // Assign first values. + pBBox.Minimum = pVertices[0]; + pBBox.Maximum = pVertices[0]; + + for(size_t idx_vert = 1; idx_vert < pVerticesQuantity; idx_vert++) + { + const GLfloat x = pVertices[idx_vert].x; + const GLfloat y = pVertices[idx_vert].y; + const GLfloat z = pVertices[idx_vert].z; + + // search minimal... + AssignIfLesser(&pBBox.Minimum.x, x); + AssignIfLesser(&pBBox.Minimum.y, y); + AssignIfLesser(&pBBox.Minimum.z, z); + // and maximal values + AssignIfGreater(&pBBox.Maximum.x, x); + AssignIfGreater(&pBBox.Maximum.y, y); + AssignIfGreater(&pBBox.Maximum.z, z); + } +} + +/********************************************************************/ +/************************ Logging functions *************************/ +/********************************************************************/ + +void CGLView::LogInfo(const QString& pMessage) +{ + Assimp::DefaultLogger::get()->info(pMessage.toStdString()); +} + +void CGLView::LogError(const QString& pMessage) +{ + Assimp::DefaultLogger::get()->error(pMessage.toStdString()); +} + +/********************************************************************/ +/************************** Draw functions **************************/ +/********************************************************************/ + +void CGLView::Draw_Node(const aiNode* pNode) +{ +aiMatrix4x4 mat_node = pNode->mTransformation; + + // Apply node transformation matrix. + mat_node.Transpose(); + glPushMatrix(); + glMultMatrixf((GLfloat*)&mat_node); + // Draw all meshes assigned to this node + for(size_t idx_mesh_arr = 0; idx_mesh_arr < pNode->mNumMeshes; idx_mesh_arr++) Draw_Mesh(pNode->mMeshes[idx_mesh_arr]); + + // Draw all children nodes + for(size_t idx_node = 0; idx_node < pNode->mNumChildren; idx_node++) Draw_Node(pNode->mChildren[idx_node]); + + // Restore transformation matrix. + glPopMatrix(); +} + +void CGLView::Draw_Mesh(const size_t pMesh_Index) +{ + // Check argument + if(pMesh_Index >= mHelper_Mesh_Quantity) return; + + aiMesh& mesh_cur = *mScene->mMeshes[pMesh_Index]; + + if(!mesh_cur.HasPositions()) return;// Nothing to draw. + + // If mesh use material then apply it + if(mScene->HasMaterials()) Material_Apply(mScene->mMaterials[mesh_cur.mMaterialIndex]); + + // + // Vertices array + // + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, mesh_cur.mVertices); + + if(mesh_cur.HasVertexColors(0)) + { + glEnable(GL_COLOR_MATERIAL);///TODO: cache + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_FLOAT, 0, mesh_cur.mColors[0]); + } + + // + // Texture coordinates array + // + if(mesh_cur.HasTextureCoords(0)) + { + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(aiVector3D), mesh_cur.mTextureCoords[0]); + } + + // + // Normals array + // + if(mesh_cur.HasNormals()) + { + glEnableClientState(GL_NORMAL_ARRAY); + glNormalPointer(GL_FLOAT, 0, mesh_cur.mNormals); + } + + // + // Draw arrays + // + SHelper_Mesh& helper_cur = *mHelper_Mesh[pMesh_Index]; + + if(helper_cur.Quantity_Triangle > 0) glDrawElements(GL_TRIANGLES, helper_cur.Quantity_Triangle * 3, GL_UNSIGNED_INT, helper_cur.Index_Triangle); + if(helper_cur.Quantity_Line > 0) glDrawElements(GL_LINES,helper_cur.Quantity_Line * 2, GL_UNSIGNED_INT, helper_cur.Index_Line); + if(helper_cur.Quantity_Point > 0) glDrawElements(GL_POINTS, helper_cur.Quantity_Point, GL_UNSIGNED_INT, helper_cur.Index_Point); + + // + // Clean up + // + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); +} + +void CGLView::Draw_BBox(const SBBox& pBBox) +{ +aiVector3D vertex[8]; + + BBox_GetVertices(pBBox, vertex); + // Draw + if(mLightingEnabled) glDisable(GL_LIGHTING);///TODO: display list + + glEnable(GL_COLOR_MATERIAL); + glBindTexture(GL_TEXTURE_1D, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_3D, 0); + qglColor(QColor(Qt::white)); + glBegin(GL_LINE_STRIP); + glVertex3fv(&vertex[0][0]), glVertex3fv(&vertex[1][0]), glVertex3fv(&vertex[2][0]), glVertex3fv(&vertex[3][0]), glVertex3fv(&vertex[0][0]);// "Minimum" side. + glVertex3fv(&vertex[4][0]), glVertex3fv(&vertex[5][0]), glVertex3fv(&vertex[6][0]), glVertex3fv(&vertex[7][0]), glVertex3fv(&vertex[4][0]);// Edge and "maximum" side. + glEnd(); + glBegin(GL_LINES); + glVertex3fv(&vertex[1][0]), glVertex3fv(&vertex[5][0]); + glVertex3fv(&vertex[2][0]), glVertex3fv(&vertex[6][0]); + glVertex3fv(&vertex[3][0]), glVertex3fv(&vertex[7][0]); + glEnd(); + glDisable(GL_COLOR_MATERIAL); + if(mLightingEnabled) glEnable(GL_LIGHTING); +} + +void CGLView::Enable_Textures(const bool pEnable) +{ + if(pEnable) + { + glEnable(GL_TEXTURE_1D); + glEnable(GL_TEXTURE_2D); + glEnable(GL_TEXTURE_3D); + } + else + { + glDisable(GL_TEXTURE_1D); + glDisable(GL_TEXTURE_2D); + glDisable(GL_TEXTURE_3D); + } +} + +/********************************************************************/ +/*********************** Overrided functions ************************/ +/********************************************************************/ + +void CGLView::initializeGL() +{ + qglClearColor(Qt::gray); + glShadeModel(GL_SMOOTH); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_NORMALIZE); + glEnable(GL_TEXTURE_2D); + + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT); + glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE); + glDisable(GL_COLOR_MATERIAL); + + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + glFrontFace(GL_CCW); +} + +void CGLView::resizeGL(int pWidth, int pHeight) +{ + mCamera_Viewport_AspectRatio = (GLdouble)pWidth / pHeight; + glViewport(0, 0, pWidth, pHeight); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(mCamera_FOVY, mCamera_Viewport_AspectRatio, 1.0, 100000.0);///TODO: znear/zfar depend on scene size. +} + +void CGLView::paintGL() +{ +QTime time_paintbegin; + + time_paintbegin = QTime::currentTime(); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + // Apply current camera transformations. + glMultMatrixf((GLfloat*)&mHelper_Camera.Rotation_AroundCamera); + glTranslatef(-mHelper_Camera.Translation_ToScene.x, -mHelper_Camera.Translation_ToScene.y, -mHelper_Camera.Translation_ToScene.z); + glMultMatrixf((GLfloat*)&mHelper_Camera.Rotation_Scene); + // Coordinate system + if(mLightingEnabled) glDisable(GL_LIGHTING);///TODO: display list + + glBindTexture(GL_TEXTURE_1D, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_3D, 0); + glEnable(GL_COLOR_MATERIAL); + glBegin(GL_LINES); + // X, -X + qglColor(QColor(Qt::red)), glVertex3f(0.0, 0.0, 0.0), glVertex3f(100000.0, 0.0, 0.0); + qglColor(QColor(Qt::cyan)), glVertex3f(0.0, 0.0, 0.0), glVertex3f(-100000.0, 0.0, 0.0); + // Y, -Y + qglColor(QColor(Qt::green)), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 100000.0, 0.0); + qglColor(QColor(Qt::magenta)), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, -100000.0, 0.0); + // Z, -Z + qglColor(QColor(Qt::blue)), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 0.0, 100000.0); + qglColor(QColor(Qt::yellow)), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 0.0, -100000.0); + glEnd(); + glDisable(GL_COLOR_MATERIAL); + if(mLightingEnabled) glEnable(GL_LIGHTING); + + // Scene + if(mScene != nullptr) + { + Draw_Node(mScene->mRootNode); + // Scene BBox + if(mScene_DrawBBox) Draw_BBox(mScene_BBox); + } + + emit Paint_Finished((size_t)time_paintbegin.msecsTo(QTime::currentTime()), mHelper_Camera.Translation_ToScene.Length()); +} + +/********************************************************************/ +/********************** Constructor/Destructor **********************/ +/********************************************************************/ + +CGLView::CGLView(QWidget *pParent) + : QGLWidget(QGLFormat(QGL::DoubleBuffer | QGL::DepthBuffer), pParent) +{ + static_assert(sizeof(GLfloat) == sizeof(ai_real), "ai_real in Assimp must be equal to GLfloat/float.");///TODO: may be templates can be used. + + // set initial view + mHelper_CameraDefault.SetDefault(); + Camera_Set(0); +} + +CGLView::~CGLView() +{ + FreeScene(); +} + +/********************************************************************/ +/********************* Scene control functions **********************/ +/********************************************************************/ + +void CGLView::FreeScene() +{ + // Set scene to null and after that \ref paintGL will not try to render it. + mScene = nullptr; + // Clean helper objects. + if(mHelper_Mesh != nullptr) + { + for(size_t idx_mesh = 0; idx_mesh < mHelper_Mesh_Quantity; idx_mesh++) delete mHelper_Mesh[idx_mesh]; + + delete [] mHelper_Mesh; + mHelper_Mesh = nullptr; + } + + mHelper_Mesh_Quantity = 0; + // Delete textures + const int id_tex_size = mTexture_IDMap.size(); + + if(id_tex_size) + { + GLuint* id_tex = new GLuint[id_tex_size]; + QMap::iterator it = mTexture_IDMap.begin(); + + for(int idx = 0; idx < id_tex_size; idx++, it++) + { + id_tex[idx] = it.value(); + } + + glDeleteTextures(id_tex_size, id_tex); + mTexture_IDMap.clear(); + delete [] id_tex; + } +} + +void CGLView::SetScene(const aiScene *pScene, const QString& pScenePath) +{ + FreeScene();// Clear old data + // Why checking here, not at begin of function. Because old scene may not exist at know. So, need cleanup. + if(pScene == nullptr) return; + + mScene = pScene;// Copy pointer of new scene. + + // + // Meshes + // + // Create helper objects for meshes. This allow to render meshes as OpenGL arrays. + if(mScene->HasMeshes()) + { + // Create mesh helpers array. + mHelper_Mesh_Quantity = mScene->mNumMeshes; + mHelper_Mesh = new SHelper_Mesh*[mScene->mNumMeshes]; + + // Walk thru the meshes and extract needed data and, also calculate BBox. + for(size_t idx_mesh = 0; idx_mesh < mScene->mNumMeshes; idx_mesh++) + { + aiMesh& mesh_cur = *mScene->mMeshes[idx_mesh]; + + // + // Calculate BBox + // + SBBox mesh_bbox; + + BBox_GetFromVertices(mesh_cur.mVertices, mesh_cur.mNumVertices, mesh_bbox); + // + // Create vertices indices arrays splited by primitive type. + // + size_t indcnt_p = 0;// points quantity + size_t indcnt_l = 0;// lines quantity + size_t indcnt_t = 0;// triangles quantity + + if(mesh_cur.HasFaces()) + { + // Usual way: all faces are triangles + if(mesh_cur.mPrimitiveTypes == aiPrimitiveType_TRIANGLE) + { + indcnt_t = mesh_cur.mNumFaces; + } + else + { + // Calculate count of primitives by types. + for(size_t idx_face = 0; idx_face < mesh_cur.mNumFaces; idx_face++) + { + if(mesh_cur.mFaces[idx_face].mNumIndices == 3) + indcnt_t++; + else if(mesh_cur.mFaces[idx_face].mNumIndices == 2) + indcnt_l++; + else if(mesh_cur.mFaces[idx_face].mNumIndices == 1) + indcnt_p++; + } + }// if(mesh_cur.mPrimitiveTypes == aiPrimitiveType_TRIANGLE) else + + // Create helper + mHelper_Mesh[idx_mesh] = new SHelper_Mesh(indcnt_p, indcnt_l, indcnt_t, mesh_bbox); + // Fill indices arrays + indcnt_p = 0, indcnt_l = 0, indcnt_t = 0;// Reuse variables as indices + for(size_t idx_face = 0; idx_face < mesh_cur.mNumFaces; idx_face++) + { + if(mesh_cur.mFaces[idx_face].mNumIndices == 3) + { + mHelper_Mesh[idx_mesh]->Index_Triangle[indcnt_t++] = mesh_cur.mFaces[idx_face].mIndices[0]; + mHelper_Mesh[idx_mesh]->Index_Triangle[indcnt_t++] = mesh_cur.mFaces[idx_face].mIndices[1]; + mHelper_Mesh[idx_mesh]->Index_Triangle[indcnt_t++] = mesh_cur.mFaces[idx_face].mIndices[2]; + } + else if(mesh_cur.mFaces[idx_face].mNumIndices == 2) + { + mHelper_Mesh[idx_mesh]->Index_Line[indcnt_l++] = mesh_cur.mFaces[idx_face].mIndices[0]; + mHelper_Mesh[idx_mesh]->Index_Line[indcnt_l++] = mesh_cur.mFaces[idx_face].mIndices[1]; + } + else if(mesh_cur.mFaces[idx_face].mNumIndices == 1) + { + mHelper_Mesh[idx_mesh]->Index_Point[indcnt_p++] = mesh_cur.mFaces[idx_face].mIndices[0]; + } + }// for(size_t idx_face = 0; idx_face < mesh_cur.mNumFaces; idx_face++) + }// if(mesh_cur.HasFaces()) + else + { + // If mesh has no faces then vertices can be just points set. + indcnt_p = mesh_cur.mNumVertices; + // Create helper + mHelper_Mesh[idx_mesh] = new SHelper_Mesh(indcnt_p, 0, 0, mesh_bbox); + // Fill indices arrays + for(size_t idx = 0; idx < indcnt_p; idx++) mHelper_Mesh[idx_mesh]->Index_Point[idx] = idx; + + }// if(mesh_cur.HasFaces()) else + }// for(size_t idx_mesh = 0; idx_mesh < mScene->mNumMeshes; idx_mesh++) + }// if(mScene->HasMeshes()) + + // + // Scene BBox + // + // For calculating right BBox we must walk thru all nodes and apply transformation to meshes BBoxes + if(mHelper_Mesh_Quantity > 0) + { + bool first_assign = true; + aiMatrix4x4 mat_root; + + BBox_GetForNode(*mScene->mRootNode, mat_root, mScene_BBox, first_assign); + mScene_Center = mScene_BBox.Maximum + mScene_BBox.Minimum; + mScene_Center /= 2; + } + else + { + mScene_BBox = {{0, 0, 0}, {0, 0, 0}}; + mScene_Center = {0, 0, 0}; + }// if(mHelper_Mesh_Count > 0) else + + // + // Textures + // + ImportTextures(pScenePath); + + // + // Light sources + // + Lighting_Enable(); + // If scene has no lights then enable default + if(!mScene->HasLights()) + { + const GLfloat col_amb[4] = { 0.2, 0.2, 0.2, 1.0 }; + SLightParameters lp; + + lp.Type = aiLightSource_POINT; + lp.Ambient.r = col_amb[0], lp.Ambient.g = col_amb[1], lp.Ambient.b = col_amb[2], lp.Ambient.a = col_amb[3]; + lp.Diffuse = { 1.0, 1.0, 1.0, 1.0 }; + lp.Specular = lp.Diffuse; + lp.For.Point.Position = mScene_Center; + lp.For.Point.Attenuation_Constant = 1; + lp.For.Point.Attenuation_Linear = 0; + lp.For.Point.Attenuation_Quadratic = 0; + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, col_amb); + Lighting_EditSource(0, lp); + emit SceneObject_LightSource("_default");// Light source will be enabled in signal handler. + } + else + { + for(size_t idx_light = 0; idx_light < mScene->mNumLights; idx_light++) + { + SLightParameters lp; + QString name; + const aiLight& light_cur = *mScene->mLights[idx_light]; + + auto col3_to_col4 = [](const aiColor3D& pCol3) -> aiColor4D { return aiColor4D(pCol3.r, pCol3.g, pCol3.b, 1.0); }; + + ///TODO: find light source node and apply all transformations + // General properties + name = light_cur.mName.C_Str(); + lp.Ambient = col3_to_col4(light_cur.mColorAmbient); + lp.Diffuse = col3_to_col4(light_cur.mColorDiffuse); + lp.Specular = col3_to_col4(light_cur.mColorSpecular); + lp.Type = light_cur.mType; + // Depend on type properties + switch(light_cur.mType) + { + case aiLightSource_DIRECTIONAL: + lp.For.Directional.Direction = light_cur.mDirection; + break; + case aiLightSource_POINT: + lp.For.Point.Position = light_cur.mPosition; + lp.For.Point.Attenuation_Constant = light_cur.mAttenuationConstant; + lp.For.Point.Attenuation_Linear = light_cur.mAttenuationLinear; + lp.For.Point.Attenuation_Quadratic = light_cur.mAttenuationQuadratic; + break; + case aiLightSource_SPOT: + lp.For.Spot.Position = light_cur.mPosition; + lp.For.Spot.Direction = light_cur.mDirection; + lp.For.Spot.Attenuation_Constant = light_cur.mAttenuationConstant; + lp.For.Spot.Attenuation_Linear = light_cur.mAttenuationLinear; + lp.For.Spot.Attenuation_Quadratic = light_cur.mAttenuationQuadratic; + lp.For.Spot.CutOff = light_cur.mAngleOuterCone; + break; + case aiLightSource_AMBIENT: + lp.For.Point.Position = light_cur.mPosition, lp.For.Point.Attenuation_Constant = 1, lp.For.Point.Attenuation_Linear = 0, lp.For.Point.Attenuation_Quadratic = 0; + name.append("_unsup_ambient"); + break; + case aiLightSource_AREA: + lp.For.Point.Position = light_cur.mPosition, lp.For.Point.Attenuation_Constant = 1, lp.For.Point.Attenuation_Linear = 0, lp.For.Point.Attenuation_Quadratic = 0; + name.append("_unsup_area"); + break; + case aiLightSource_UNDEFINED: + lp.For.Point.Position = light_cur.mPosition, lp.For.Point.Attenuation_Constant = 1, lp.For.Point.Attenuation_Linear = 0, lp.For.Point.Attenuation_Quadratic = 0; + name.append("_unsup_undefined"); + break; + default: + lp.For.Point.Position = light_cur.mPosition, lp.For.Point.Attenuation_Constant = 1, lp.For.Point.Attenuation_Linear = 0, lp.For.Point.Attenuation_Quadratic = 0; + name.append("_unsupported_invalid"); + break; + }// switch(light_cur.mType) + + // Add light source + if(name.isEmpty()) name += QString("%1").arg(idx_light);// Use index if name is empty. + + Lighting_EditSource(idx_light, lp); + emit SceneObject_LightSource(name);// Light source will be enabled in signal handler. + }// for(size_t idx_light = 0; idx_light < mScene->mNumLights; idx_light++) + }// if(!mScene->HasLights()) else + + // + // Cameras + // + if(!mScene->HasCameras()) + { + mCamera_DefaultAdded = true; + mHelper_CameraDefault.SetDefault(); + // Calculate distance from camera to scene. Distance must be enoguh for that viewport contain whole scene. + const GLfloat tg_angle = tan(mCamera_FOVY / 2); + + GLfloat val_x = ((mScene_BBox.Maximum.x - mScene_BBox.Minimum.x) / 2) / (mCamera_Viewport_AspectRatio * tg_angle); + GLfloat val_y = ((mScene_BBox.Maximum.y - mScene_BBox.Minimum.y) / 2) / tg_angle; + GLfloat val_step = val_x; + + AssignIfGreater(val_step, val_y); + mHelper_CameraDefault.Translation_ToScene.Set(mScene_Center.x, mScene_Center.y, val_step + mScene_BBox.Maximum.z); + emit SceneObject_Camera("_default"); + } + else + { + mCamera_DefaultAdded = false; + for(size_t idx_cam = 0; idx_cam < mScene->mNumCameras; idx_cam++) + { + emit SceneObject_Camera(mScene->mCameras[idx_cam]->mName.C_Str()); + } + }// if(!mScene->HasCameras()) else +} + +/********************************************************************/ +/******************** Lighting control functions ********************/ +/********************************************************************/ + +void CGLView::Lighting_Enable() +{ + mLightingEnabled = true; + glEnable(GL_LIGHTING); +} + +void CGLView::Lighting_Disable() +{ + glDisable(GL_LIGHTING); + mLightingEnabled = false; +} + +void CGLView::Lighting_EditSource(const size_t pLightNumber, const SLightParameters& pLightParameters) +{ +const size_t light_num = GL_LIGHT0 + pLightNumber; + +GLfloat farr[4]; + + if(pLightNumber >= GL_MAX_LIGHTS) return;///TODO: return value; + + glLightfv(light_num, GL_AMBIENT, &pLightParameters.Ambient.r);// Ambient color + glLightfv(light_num, GL_DIFFUSE, &pLightParameters.Diffuse.r);// Diffuse color + glLightfv(light_num, GL_SPECULAR, &pLightParameters.Specular.r);// Specular color + // Other parameters + switch(pLightParameters.Type) + { + case aiLightSource_DIRECTIONAL: + // Direction + farr[0] = pLightParameters.For.Directional.Direction.x, farr[2] = pLightParameters.For.Directional.Direction.y; + farr[2] = pLightParameters.For.Directional.Direction.z; farr[3] = 0; + glLightfv(light_num, GL_POSITION, farr); + break; + case aiLightSource_POINT: + // Position + farr[0] = pLightParameters.For.Point.Position.x, farr[2] = pLightParameters.For.Point.Position.y; + farr[2] = pLightParameters.For.Point.Position.z; farr[3] = 1; + glLightfv(light_num, GL_POSITION, farr); + // Attenuation + glLightf(light_num, GL_CONSTANT_ATTENUATION, pLightParameters.For.Point.Attenuation_Constant); + glLightf(light_num, GL_LINEAR_ATTENUATION, pLightParameters.For.Point.Attenuation_Linear); + glLightf(light_num, GL_QUADRATIC_ATTENUATION, pLightParameters.For.Point.Attenuation_Quadratic); + glLightf(light_num, GL_SPOT_CUTOFF, 180.0); + break; + case aiLightSource_SPOT: + // Position + farr[0] = pLightParameters.For.Spot.Position.x, farr[2] = pLightParameters.For.Spot.Position.y, farr[2] = pLightParameters.For.Spot.Position.z; farr[3] = 1; + glLightfv(light_num, GL_POSITION, farr); + // Attenuation + glLightf(light_num, GL_CONSTANT_ATTENUATION, pLightParameters.For.Spot.Attenuation_Constant); + glLightf(light_num, GL_LINEAR_ATTENUATION, pLightParameters.For.Spot.Attenuation_Linear); + glLightf(light_num, GL_QUADRATIC_ATTENUATION, pLightParameters.For.Spot.Attenuation_Quadratic); + // Spot specific + farr[0] = pLightParameters.For.Spot.Direction.x, farr[2] = pLightParameters.For.Spot.Direction.y, farr[2] = pLightParameters.For.Spot.Direction.z; farr[3] = 0; + glLightfv(light_num, GL_SPOT_DIRECTION, farr); + glLightf(light_num, GL_SPOT_CUTOFF, pLightParameters.For.Spot.CutOff); + break; + default:// For unknown light source types use point source. + // Position + farr[0] = pLightParameters.For.Point.Position.x, farr[2] = pLightParameters.For.Point.Position.y; + farr[2] = pLightParameters.For.Point.Position.z; farr[3] = 1; + glLightfv(light_num, GL_POSITION, farr); + // Attenuation + glLightf(light_num, GL_CONSTANT_ATTENUATION, 1); + glLightf(light_num, GL_LINEAR_ATTENUATION, 0); + glLightf(light_num, GL_QUADRATIC_ATTENUATION, 0); + glLightf(light_num, GL_SPOT_CUTOFF, 180.0); + break; + }// switch(pLightParameters.Type) +} + +void CGLView::Lighting_EnableSource(const size_t pLightNumber) +{ + if(pLightNumber >= GL_MAX_LIGHTS) return;///TODO: return value; + + glEnable(GL_LIGHT0 + pLightNumber); +} + +void CGLView::Lighting_DisableSource(const size_t pLightNumber) +{ + if(pLightNumber >= GL_MAX_LIGHTS) return;///TODO: return value; + + glDisable(GL_LIGHT0 + pLightNumber); +} + +/********************************************************************/ +/******************** Cameras control functions *********************/ +/********************************************************************/ + +void CGLView::Camera_Set(const size_t pCameraNumber) +{ +SHelper_Camera& hcam = mHelper_Camera;// reference with short name for conveniance. +aiVector3D up; + + if(mCamera_DefaultAdded || (pCameraNumber >= mScene->mNumCameras))// If default camera used then 'pCameraNumber' doesn't matter. + { + // Transformation parameters + hcam = mHelper_CameraDefault; + up.Set(0, 1, 0); + } + else + { + const aiCamera& camera_cur = *mScene->mCameras[pCameraNumber]; + const aiNode* camera_node; + + aiMatrix4x4 camera_mat; + aiQuaternion camera_quat_rot; + aiVector3D camera_tr; + + up = camera_cur.mUp; + // + // Try to get real coordinates of the camera. + // + // Find node + camera_node = mScene->mRootNode->FindNode(camera_cur.mName); + if(camera_node != nullptr) Matrix_NodeToRoot(camera_node, camera_mat); + + hcam.Position = camera_cur.mLookAt; + hcam.Target = camera_cur.mPosition; + hcam.Rotation_AroundCamera = aiMatrix4x4(camera_quat_rot.GetMatrix()); + hcam.Rotation_AroundCamera.Transpose(); + // get components of transformation matrix. + camera_mat.DecomposeNoScaling(camera_quat_rot, camera_tr); + hcam.Rotation_Scene = aiMatrix4x4(); + hcam.Translation_ToScene = camera_tr; + } + + // Load identity matrix - travel to world begin. + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + // Set camera and update picture + gluLookAt(hcam.Position.x, hcam.Position.y, hcam.Position.z, hcam.Target.x, hcam.Target.y, hcam.Target.z, up.x, up.y, up.z); +} + +void CGLView::Camera_RotateScene(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z) +{ +auto deg2rad = [](const GLfloat pDegree) -> GLfloat { return pDegree * M_PI / 180.0; }; + + aiMatrix4x4 mat_rot; + + mat_rot.FromEulerAnglesXYZ(deg2rad(pAngle_X), deg2rad(pAngle_Y), deg2rad(pAngle_Z)); + mHelper_Camera.Rotation_Scene *= mat_rot; +} + +void CGLView::Camera_Rotate(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z) +{ +auto deg2rad = [](const GLfloat pDegree) -> GLfloat { return pDegree * M_PI / 180.0; }; + + aiMatrix4x4 mat_rot; + + mat_rot.FromEulerAnglesXYZ(deg2rad(pAngle_X), deg2rad(pAngle_Y), deg2rad(pAngle_Z)); + mHelper_Camera.Rotation_AroundCamera *= mat_rot; +} + +void CGLView::Camera_Translate(const GLfloat pTranslate_X, const GLfloat pTranslate_Y, const GLfloat pTranslate_Z) +{ +aiVector3D vect_tr(pTranslate_X, pTranslate_Y, pTranslate_Z); + + vect_tr *= mHelper_Camera.Rotation_AroundCamera; + mHelper_Camera.Translation_ToScene += vect_tr; +} diff --git a/tools/assimp_qt_viewer/glview.hpp b/tools/assimp_qt_viewer/glview.hpp new file mode 100644 index 000000000..25c340565 --- /dev/null +++ b/tools/assimp_qt_viewer/glview.hpp @@ -0,0 +1,382 @@ +/// \file glview.hpp +/// \brief OpenGL visualisation. +/// \author smal.root@gmail.com +/// \date 2016 + +#pragma once + +// Header files, Qt. +#include + +// Header files Assimp +#include + +/// \class CGLView +/// Class which hold and render scene. +class CGLView : public QGLWidget +{ + Q_OBJECT + + /**********************************/ + /************* Types **************/ + /**********************************/ + +private: + + /// \struct SBBox + /// Bounding box for object. + struct SBBox + { + aiVector3D Minimum;///< Minimum values of coordinates. + aiVector3D Maximum;///< Maximum values of coordinates. + }; + + /// \struct SHelper_Mesh + /// Helper object for fast rendering of mesh (\ref aiMesh). + struct SHelper_Mesh + { + const size_t Quantity_Point;///< Quantity of points. + const size_t Quantity_Line;///< Quantity of lines. + const size_t Quantity_Triangle;///< Quantity of triangles. + GLuint* Index_Point;///< Array of indices for drawing points. + GLuint* Index_Line;///< Array of indices for drawing lines. + GLuint* Index_Triangle;///< Array of indices for drawing triangles. + + const SBBox BBox;///< BBox of mesh. + + /// \fn explicit SHelper_Mesh(const size_t pQuantity_Point, const size_t pQuantity_Line, const size_t pQuantity_Triangle, const SBBox& pBBox = {{0, 0, 0}, {0, 0, 0}}) + /// Constructor. + /// \param [in] pQuantity_Point - quantity of points. + /// \param [in] pQuantity_Line - quantity of lines. + /// \param [in] pQuantity_Triangle - quantity of triangles. + /// \param [in] pBBox - BBox of mesh. + explicit SHelper_Mesh(const size_t pQuantity_Point, const size_t pQuantity_Line, const size_t pQuantity_Triangle, const SBBox& pBBox = {{0, 0, 0}, {0, 0, 0}}); + + /// \fn ~SHelper_Mesh() + /// Destructor. + ~SHelper_Mesh(); + }; + + /// \struct SHelper_Camera + /// Information about position of the camera in space. + struct SHelper_Camera + { + aiVector3D Position;///< Coordinates of the camera. + aiVector3D Target;///< Target point of the camera. + // Transformation path: + // set Camera -> Rotation_AroundCamera -> Translation_ToScene -> Rotation_Scene -> draw Scene + aiMatrix4x4 Rotation_AroundCamera;///< Rotation matrix which set rotation angles of the scene around camera. + aiMatrix4x4 Rotation_Scene;///< Rotation matrix which set rotation angles of the scene around own center. + aiVector3D Translation_ToScene;///< Translation vector from camera to the scene. + + /// \fn void SetDefault() + /// Set default parameters of camera. + void SetDefault(); + }; + +public: + + /// \enum ELightType + /// Type of light source. + enum class ELightType { Directional, Point, Spot }; + + /// \struct SLightParameters + /// Parameters of light source. + struct SLightParameters + { + aiLightSourceType Type;///< Type of light source. + + aiColor4D Ambient;///< Ambient RGBA intensity of the light. + aiColor4D Diffuse;///< Diffuse RGBA intensity of the light. + aiColor4D Specular;///< Specular RGBA intensity of the light. + + union UFor + { + /// \struct SDirectional + /// Parameters of directional light source. + struct SDirectional + { + aiVector3D Direction; + + SDirectional() {} + } Directional; + + /// \struct SPoint + /// Parameters of point light source. + struct SPoint + { + aiVector3D Position; + GLfloat Attenuation_Constant; + GLfloat Attenuation_Linear; + GLfloat Attenuation_Quadratic; + + SPoint() {} + } Point; + + /// \struct SSpot + /// Parameters of spot light source. + struct SSpot + { + aiVector3D Position; + GLfloat Attenuation_Constant; + GLfloat Attenuation_Linear; + GLfloat Attenuation_Quadratic; + aiVector3D Direction; + GLfloat CutOff; + + SSpot() {} + } Spot; + + UFor() {} + } For; + + SLightParameters() {} + }; + + /**********************************/ + /************ Variables ***********/ + /**********************************/ + +private: + + // Scene + const aiScene* mScene = nullptr;///< Copy of pointer to scene (\ref aiScene). + SBBox mScene_BBox;///< Bounding box of scene. + aiVector3D mScene_Center;///< Coordinates of center of the scene. + bool mScene_DrawBBox = false;///< Flag which control drawing scene BBox. + // Meshes + size_t mHelper_Mesh_Quantity = 0;///< Quantity of meshes in scene. + SHelper_Mesh** mHelper_Mesh = nullptr;///< Array of pointers to helper objects for drawing mesh. Sequence of meshes are equivalent to \ref aiScene::mMeshes. + // Cameras + SHelper_Camera mHelper_Camera;///< Information about current camera placing in space. + SHelper_Camera mHelper_CameraDefault;///< Information about default camera initial placing in space. + bool mCamera_DefaultAdded = true;///< If true then scene has no defined cameras and default was added, if false - scene has defined cameras. + GLdouble mCamera_FOVY = 45.0;///< Specifies the field of view angle, in degrees, in the y direction. + GLdouble mCamera_Viewport_AspectRatio;///< Specifies the aspect ratio that determines the field of view in the x direction. The aspect ratio is the ratio of x (width) to y (height). + // Lighting + bool mLightingEnabled = false;///< If true then OpenGL lighting is enabled (glEnable(GL_LIGHTING)), if false - disabled. + // Textures + QMap mTexture_IDMap;///< Map image filenames to textures ID's. + + /**********************************/ + /************ Functions ***********/ + /**********************************/ + +private: + + // Why in some cases pointers are used? Because: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36566 + template void AssignIfLesser(TArg* pBaseValue, const TArg pTestValue) { if(pTestValue < *pBaseValue) *pBaseValue = pTestValue; } + template void AssignIfGreater(TArg* pBaseValue, const TArg pTestValue) { if(pTestValue > *pBaseValue) *pBaseValue = pTestValue; } + + template void AssignIfLesser(TArg& pBaseValue, const TArg pTestValue) { if(pTestValue < pBaseValue) pBaseValue = pTestValue; } + template void AssignIfGreater(TArg& pBaseValue, const TArg pTestValue) { if(pTestValue > pBaseValue) pBaseValue = pTestValue; } + + /// \fn void Material_Apply(const aiMaterial* pMaterial) + /// Enable pointed material. + /// \param [in] pMaterial - pointer to material which must be used. + void Material_Apply(const aiMaterial* pMaterial); + + /// \fn void Matrix_NodeToRoot(const aiNode* pNode, aiMatrix4x4& pOutMatrix) + /// Calculate matrix for transforming coordinates from pointed node to root node (read as "global coordinate system"). + /// \param [in] pNode - pointer initial node from which relative coordintaes will be taken, + /// \param [out] pOutMatrix - matrix for transform relative coordinates in \ref pNode to coordinates in root node (\ref aiScene::mRootNode). + void Matrix_NodeToRoot(const aiNode* pNode, aiMatrix4x4& pOutMatrix); + + /// \fn void ImportTextures() + /// Import textures. + /// \param [in] pScenePath - path to the file of the scene. + void ImportTextures(const QString& pScenePath); + + /// \fn void BBox_GetForNode(const aiNode& pNode, const aiMatrix4x4& pParentNode_TransformationMatrix, SBBox& pNodeBBox, bool& pFirstAssign) + /// Calculate BBox for pointed node. Function walk thru child nodes and apply all transformations. + /// \param [in] pNode - reference to node for which needed BBox. + /// \param [in] pParent_TransformationMatrix - reference to parent (parent for pNode) transformation matrix. + /// \param [in,out] pNodeBBox - reference to where pNode BBox will be placed. It will expanded by child nodes BBoxes. + /// \param [in] pFirstAssign - means that pNodeBBox not contain valid BBox at now and assign ('=') will used for setting new value, If + /// false then \ref BBox_Extend will be used for setting new BBox. + void BBox_GetForNode(const aiNode& pNode, const aiMatrix4x4& pParent_TransformationMatrix, SBBox& pNodeBBox, bool& pFirstAssign); + + /// \fn void BBox_Extend(const SBBox& pChild, SBBox& pParent) + /// Check and if need - extend current node BBox with BBox of child node. + /// \param [in] pChild - reference to BBox which used for extend parent BBox. + /// \param [in.out] pParent - BBox which will be extended using child BBox. + void BBox_Extend(const SBBox& pChild, SBBox& pParent); + + /// \fn void BBox_GetVertices(const SBBox& pBBox, aiVector3D pVertices[8]) + /// Get vertices of a parallelepiped which is described by BBox. + /// \param [in] pBBox - input BBox. + /// \param [out] pVertices - array of vertices. + void BBox_GetVertices(const SBBox& pBBox, aiVector3D pVertices[8]); + + /// \fn void BBox_GetFromVertices(const aiVector3D* pVertices, const size_t pVerticesQuantity, SBBox& pBBox) + /// Calculate BBox for vertices array. + /// \param [in] pVertices - vertices array. + /// \param [in] pVerticesQuantity - quantity of vertices in array. If 0 then pBBox will be assigned with {{0, 0, 0}, {0, 0, 0}}. + /// \param [out] pBBox - calculated BBox. + void BBox_GetFromVertices(const aiVector3D* pVertices, const size_t pVerticesQuantity, SBBox& pBBox); + + /********************************************************************/ + /************************ Logging functions *************************/ + /********************************************************************/ + + /// \fn void LogInfo(const QString& pMessage) + /// Add message with severity "Warning" to log. + void LogInfo(const QString& pMessage); + + /// \fn void LogError(const QString& pMessage) + /// Add message with severity "Error" to log. + void LogError(const QString& pMessage); + + /********************************************************************/ + /************************** Draw functions **************************/ + /********************************************************************/ + + /// \fn void Draw_Node(const aiNode* pNode) + /// Apply node transformation and draw meshes assigned to this node. + /// \param [in] pNode - pointer to node for drawing (\ref aiNode). + void Draw_Node(const aiNode* pNode); + + /// \fn void Draw_Mesh(const size_t pMesh_Index) + /// Draw mesh. + /// \param [in] pMesh_Index - index of mesh which must be drawn. Index point to mesh in \ref mHelper_Mesh. + void Draw_Mesh(const size_t pMesh_Index); + + /// \fn void Draw_BBox(const SBBox& pBBox) + /// Draw bounding box using lines. + /// \param [in] pBBox - bounding box for drawing. + void Draw_BBox(const SBBox& pBBox); + + /********************************************************************/ + /*********************** Overrided functions ************************/ + /********************************************************************/ + +protected: + + /// \fn void initializeGL() override + /// Overrided function for initialise OpenGL. + void initializeGL() override; + + /// \fn void resizeGL(int pWidth, int pHeight) override + /// \param [in] pWidth - new width of viewport. + /// \param [in] pHeight - new height of viewport. + void resizeGL(int pWidth, int pHeight) override; + + /// \fn void paintGL() override + /// Overrided function for rendering. + void paintGL() override; + +public: + + /********************************************************************/ + /********************** Constructor/Destructor **********************/ + /********************************************************************/ + + /// \fn explicit CGLView(QWidget* pParent) + /// Constructor. + /// \param [in] pParent - parent widget. + explicit CGLView(QWidget* pParent); + + /// \fn virtual ~CGLView() + /// Destructor. + virtual ~CGLView(); + + /********************************************************************/ + /********************* Scene control functions **********************/ + /********************************************************************/ + + /// \fn void FreeScene() + /// Free all helper objects data. + void FreeScene(); + + /// \fn void SetScene(const aiScene* pScene) + /// Set scene for rendering. + /// \param [in] pScene - pointer to scene. + /// \param [in] pScenePath - path to the file of the scene. + void SetScene(const aiScene* pScene, const QString& pScenePath); + + /// \fn void Enable_SceneBBox(const bool pEnable) + /// Enable drawing scene bounding box. + /// \param [in] pEnable - if true then bbox will be drawing, if false - will not be drawing. + void Enable_SceneBBox(const bool pEnable) { mScene_DrawBBox = pEnable; } + + /// \fn void Enable_Textures(const bool pEnable) + /// Control textures drawing. + /// \param [in] pEnable - if true then enable textures, false - disable textures. + void Enable_Textures(const bool pEnable); + + /********************************************************************/ + /******************** Lighting control functions ********************/ + /********************************************************************/ + + /// \fn void Lighting_Enable() + /// Enable OpenGL lighting. + void Lighting_Enable(); + + /// \fn void Lighting_Disable() + /// Disable OpenGL lighting. + void Lighting_Disable(); + + /// \fn void Lighting_EditSource(const size_t pLightNumber, const SLightParameters& pLightParameters) + /// Edit light source properties. + /// \param [in] pLightNumber - light source number. \ref aiScene::mLights. + /// \param [in] pLightParameters - light source parameters. + void Lighting_EditSource(const size_t pLightNumber, const SLightParameters& pLightParameters);///TODO: function set + + /// \fn void Lighting_EnableSource(const size_t pLightNumber) + /// Enable light source. + /// \param [in] pLightNumber - light source number. \ref aiScene::mLights. + void Lighting_EnableSource(const size_t pLightNumber); + + ///void Lighting_DisableSource(const size_t pLightNumber) + /// Disable light source, + /// \param [in] pLightNumber - light source number. \ref aiScene::mLights. + void Lighting_DisableSource(const size_t pLightNumber); + + /********************************************************************/ + /******************** Cameras control functions *********************/ + /********************************************************************/ + + /// \fn void Camera_Set(const size_t pCameraNumber) + /// Set view from pointed camera. + /// \param [in] pCamera_Index - index of the camera (\ref aiScene::mCameras). + void Camera_Set(const size_t pCameraNumber); + + /// \fn void Camera_RotateScene(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z) + /// Rotate scene around axisees. + /// \param [in] pAngle_X - specifies the angle of rotation around axis oX, in degrees. + /// \param [in] pAngle_Y - specifies the angle of rotation around axis oY, in degrees. + /// \param [in] pAngle_Z - specifies the angle of rotation around axis oZ, in degrees. + void Camera_RotateScene(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z); + + /// \fn void Camera_Rotate(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z) + /// Rotate camera around axisees. + /// \param [in] pAngle_X - specifies the angle of rotation around axis oX, in degrees. + /// \param [in] pAngle_Y - specifies the angle of rotation around axis oY, in degrees. + /// \param [in] pAngle_Z - specifies the angle of rotation around axis oZ, in degrees. + void Camera_Rotate(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z); + + /// \fn void Camera_Translate(const size_t pTranslate_X, const size_t pTranslate_Y, const size_t pTranslate_Z) + /// Translate camera along axises. In local coordinates. + /// \param [in] pTranslate_X - specifies the X coordinate of translation vector. + /// \param [in] pTranslate_Y - specifies the Y coordinate of translation vector. + /// \param [in] pTranslate_Z - specifies the Z coordinate of translation vector. + void Camera_Translate(const GLfloat pTranslate_X, const GLfloat pTranslate_Y, const GLfloat pTranslate_Z); + +signals: + + /// \fn void Paint_Finished(const size_t pPaintTime, const GLfloat pDistance) + ///< Signal. Emits when execution of \ref paintGL is end. + /// \param [out] pPaintTime_ms - time spent for rendering, in milliseconds. + /// \param [out] pDistance - distance between current camera and center of the scene. \sa SHelper_Camera::Translation_ToScene. + void Paint_Finished(const size_t pPaintTime_ms, const GLfloat pDistance); + + /// \fn void SceneObject_Camera(const QString& pName) + /// Signal. Emit for every camera found in scene. Also for default camera. + /// \param [out] pName - name of the camera. + void SceneObject_Camera(const QString& pName); + + /// \fn void SceneObject_LightSource(const QString& pName) + /// Signal. Emit for every light source found in scene. Also for default light source. + /// \param [out] pName - name of the light source. + void SceneObject_LightSource(const QString& pName); +};// class CGLView diff --git a/tools/assimp_qt_viewer/loggerview.cpp b/tools/assimp_qt_viewer/loggerview.cpp new file mode 100644 index 000000000..43409852f --- /dev/null +++ b/tools/assimp_qt_viewer/loggerview.cpp @@ -0,0 +1,19 @@ +/// \file loggerview.cpp +/// \brief Stream for Assimp logging subsystem. +/// \author smal.root@gmail.com +/// \date 2016 + +#include "loggerview.hpp" + +// Header files, Qt. +#include + +CLoggerView::CLoggerView(QTextBrowser* pOutputWidget) + : mOutputWidget(pOutputWidget) +{ +} + +void CLoggerView::write(const char *pMessage) +{ + mOutputWidget->insertPlainText(QString("[%1] %2").arg(QTime::currentTime().toString()).arg(pMessage)); +} diff --git a/tools/assimp_qt_viewer/loggerview.hpp b/tools/assimp_qt_viewer/loggerview.hpp new file mode 100644 index 000000000..0540b393d --- /dev/null +++ b/tools/assimp_qt_viewer/loggerview.hpp @@ -0,0 +1,33 @@ +/// \file loggerview.hpp +/// \brief Stream for Assimp logging subsystem. +/// \author smal.root@gmail.com +/// \date 2016 + +#pragma once + +// Header files, Qt. +#include + +// Header files, Assimp. +#include + +/// \class CLoggerView +/// GUI-stream for Assimp logging subsytem. Get data for logging and write it to output widget. +class CLoggerView final : public Assimp::LogStream +{ +private: + + QTextBrowser* mOutputWidget;///< Widget for displaying messages. + +public: + + /// \fn explicit CLoggerView(QTextBrowser* pOutputWidget) + /// Constructor. + /// \param [in] pOutputWidget - pointer to output widget. + explicit CLoggerView(QTextBrowser* pOutputWidget); + + /// \fn virtual void write(const char *pMessage) + /// Write message to output widget. Used by Assimp. + /// \param [in] pMessage - message for displaying. + virtual void write(const char *pMessage); +}; diff --git a/tools/assimp_qt_viewer/main.cpp b/tools/assimp_qt_viewer/main.cpp new file mode 100644 index 000000000..fa6f51b20 --- /dev/null +++ b/tools/assimp_qt_viewer/main.cpp @@ -0,0 +1,21 @@ +/// \file main.cpp +/// \brief Start-up file which contain function "main". +/// \author smal.root@gmail.com +/// \date 2016 +// Thanks to acorn89 for support. + +// Header files, project. +#include "mainwindow.hpp" + +// Header files, Qt. +#include + +int main(int argc, char *argv[]) +{ +QApplication a(argc, argv); +MainWindow w; + + w.show(); + + return a.exec(); +} diff --git a/tools/assimp_qt_viewer/mainwindow.cpp b/tools/assimp_qt_viewer/mainwindow.cpp new file mode 100644 index 000000000..ea4a6efe7 --- /dev/null +++ b/tools/assimp_qt_viewer/mainwindow.cpp @@ -0,0 +1,344 @@ +/// \file mainwindow.hpp +/// \brief Main window and algorhytms. +/// \author smal.root@gmail.com +/// \date 2016 + +#include "mainwindow.hpp" +#include "ui_mainwindow.h" + +// Header files, Assimp. +#include +#include + +#ifndef __unused + #define __unused __attribute__((unused)) +#endif // __unused + +/**********************************/ +/************ Functions ***********/ +/**********************************/ + +/********************************************************************/ +/********************* Import/Export functions **********************/ +/********************************************************************/ + +void MainWindow::ImportFile(const QString &pFileName) +{ +using namespace Assimp; + +QTime time_begin = QTime::currentTime(); + + if(mScene != nullptr) + { + mImporter.FreeScene(); + mGLView->FreeScene(); + } + + // Try to import scene. + mScene = mImporter.ReadFile(pFileName.toStdString(), aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_ValidateDataStructure | \ + aiProcess_GenUVCoords | aiProcess_TransformUVCoords | aiProcess_FlipUVs); + if(mScene != nullptr) + { + ui->lblLoadTime->setText(QString("%1").arg(time_begin.secsTo(QTime::currentTime()))); + LogInfo("Import done: " + pFileName); + // Prepare widgets for new scene. + ui->leFileName->setText(pFileName.right(pFileName.length() - pFileName.lastIndexOf('/') - 1)); + ui->lstLight->clear(); + ui->lstCamera->clear(); + ui->cbxLighting->setChecked(true), mGLView->Lighting_Enable(); + ui->cbxBBox->setChecked(false); mGLView->Enable_SceneBBox(false); + ui->cbxTextures->setChecked(true), mGLView->Enable_Textures(true); + // + // Fill info labels + // + // Cameras + ui->lblCameraCount->setText(QString("%1").arg(mScene->mNumCameras)); + // Lights + ui->lblLightCount->setText(QString("%1").arg(mScene->mNumLights)); + // Meshes, faces, vertices. + size_t qty_face = 0; + size_t qty_vert = 0; + + for(size_t idx_mesh = 0; idx_mesh < mScene->mNumMeshes; idx_mesh++) + { + qty_face += mScene->mMeshes[idx_mesh]->mNumFaces; + qty_vert += mScene->mMeshes[idx_mesh]->mNumVertices; + } + + ui->lblMeshCount->setText(QString("%1").arg(mScene->mNumMeshes)); + ui->lblFaceCount->setText(QString("%1").arg(qty_face)); + ui->lblVertexCount->setText(QString("%1").arg(qty_vert)); + // Animation + if(mScene->mNumAnimations) + ui->lblHasAnimation->setText("yes"); + else + ui->lblHasAnimation->setText("no"); + + // + // Set scene for GL viewer. + // + mGLView->SetScene(mScene, pFileName); + // Select first camera + ui->lstCamera->setCurrentRow(0); + mGLView->Camera_Set(0); + // Scene is loaded, do first rendering. + LogInfo("Scene is ready for rendering."); + mGLView->updateGL(); + } + else + { + ui->lblLoadTime->clear(); + LogError(QString("Error parsing \'%1\' : \'%2\'").arg(pFileName).arg(mImporter.GetErrorString())); + }// if(mScene != nullptr) +} + +/********************************************************************/ +/************************ Logging functions *************************/ +/********************************************************************/ + +void MainWindow::LogInfo(const QString& pMessage) +{ + Assimp::DefaultLogger::get()->info(pMessage.toStdString()); +} + +void MainWindow::LogError(const QString& pMessage) +{ + Assimp::DefaultLogger::get()->error(pMessage.toStdString()); +} + +/********************************************************************/ +/*********************** Overrided functions ************************/ +/********************************************************************/ + +void MainWindow::mousePressEvent(QMouseEvent* pEvent) +{ + if(pEvent->button() & Qt::LeftButton) + mPosition_Pressed_LMB = pEvent->pos(); + else if(pEvent->button() & Qt::RightButton) + mPosition_Pressed_RMB = pEvent->pos(); +} + +void MainWindow::mouseMoveEvent(QMouseEvent* pEvent) +{ + if(pEvent->buttons() & Qt::LeftButton) + { + GLfloat dx = 180 * GLfloat(pEvent->x() - mPosition_Pressed_LMB.x()) / mGLView->width(); + GLfloat dy = 180 * GLfloat(pEvent->y() - mPosition_Pressed_LMB.y()) / mGLView->height(); + + if(pEvent->modifiers() & Qt::ShiftModifier) + mGLView->Camera_RotateScene(dy, 0, dx);// Rotate around oX and oZ axises. + else + mGLView->Camera_RotateScene(dy, dx, 0);// Rotate around oX and oY axises. + + mGLView->updateGL(); + mPosition_Pressed_LMB = pEvent->pos(); + } + + if(pEvent->buttons() & Qt::RightButton) + { + GLfloat dx = 180 * GLfloat(pEvent->x() - mPosition_Pressed_RMB.x()) / mGLView->width(); + GLfloat dy = 180 * GLfloat(pEvent->y() - mPosition_Pressed_RMB.y()) / mGLView->height(); + + if(pEvent->modifiers() & Qt::ShiftModifier) + mGLView->Camera_Rotate(dy, 0, dx);// Rotate around oX and oZ axises. + else + mGLView->Camera_Rotate(dy, dx, 0);// Rotate around oX and oY axises. + + mGLView->updateGL(); + mPosition_Pressed_RMB = pEvent->pos(); + } +} + +void MainWindow::keyPressEvent(QKeyEvent* pEvent) +{ +GLfloat step; + + if(pEvent->modifiers() & Qt::ControlModifier) + step = 10; + else if(pEvent->modifiers() & Qt::AltModifier) + step = 100; + else + step = 1; + + if(pEvent->key() == Qt::Key_A) + mGLView->Camera_Translate(-step, 0, 0); + else if(pEvent->key() == Qt::Key_D) + mGLView->Camera_Translate(step, 0, 0); + else if(pEvent->key() == Qt::Key_W) + mGLView->Camera_Translate(0, step, 0); + else if(pEvent->key() == Qt::Key_S) + mGLView->Camera_Translate(0, -step, 0); + else if(pEvent->key() == Qt::Key_Up) + mGLView->Camera_Translate(0, 0, -step); + else if(pEvent->key() == Qt::Key_Down) + mGLView->Camera_Translate(0, 0, step); + + mGLView->updateGL(); +} + +/********************************************************************/ +/********************** Constructor/Destructor **********************/ +/********************************************************************/ + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow), + mScene(nullptr) +{ +using namespace Assimp; + + ui->setupUi(this); + // Create OpenGL widget + mGLView = new CGLView(this); + mGLView->setMinimumSize(800, 600); + mGLView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); + mGLView->setFocusPolicy(Qt::StrongFocus); + // Connect to GLView signals. + connect(mGLView, SIGNAL(Paint_Finished(size_t, GLfloat)), SLOT(Paint_Finished(size_t, GLfloat))); + connect(mGLView, SIGNAL(SceneObject_Camera(QString)), SLOT(SceneObject_Camera(QString))); + connect(mGLView, SIGNAL(SceneObject_LightSource(QString)), SLOT(SceneObject_LightSource(QString))); + // and add it to layout + ui->hlMainView->insertWidget(0, mGLView, 4); + // Create logger + mLoggerView = new CLoggerView(ui->tbLog); + DefaultLogger::create("", Logger::VERBOSE); + DefaultLogger::get()->attachStream(mLoggerView, DefaultLogger::Debugging | DefaultLogger::Info | DefaultLogger::Err | DefaultLogger::Warn); +} + +MainWindow::~MainWindow() +{ +using namespace Assimp; + + DefaultLogger::get()->detatchStream(mLoggerView, DefaultLogger::Debugging | DefaultLogger::Info | DefaultLogger::Err | DefaultLogger::Warn); + DefaultLogger::kill(); + + if(mScene != nullptr) mImporter.FreeScene(); + if(mLoggerView != nullptr) delete mLoggerView; + if(mGLView != nullptr) delete mGLView; + delete ui; +} + +/********************************************************************/ +/****************************** Slots *******************************/ +/********************************************************************/ + +void MainWindow::Paint_Finished(const size_t pPaintTime_ms, const GLfloat pDistance) +{ + ui->lblRenderTime->setText(QString("%1").arg(pPaintTime_ms)); + ui->lblDistance->setText(QString("%1").arg(pDistance)); +} + +void MainWindow::SceneObject_Camera(const QString& pName) +{ + ui->lstCamera->addItem(pName); +} + +void MainWindow::SceneObject_LightSource(const QString& pName) +{ + ui->lstLight->addItem(pName); + // After item added "currentRow" is still contain old value (even '-1' if first item added). Because "currentRow"/"currentItem" is changed by user interaction, + // not by "addItem". So, "currentRow" must be set manually. + ui->lstLight->setCurrentRow(ui->lstLight->count() - 1); + // And after "selectAll" handler of "signal itemSelectionChanged" will get right "currentItem" and "currentRow" values. + ui->lstLight->selectAll(); +} + +void MainWindow::on_butOpenFile_clicked() +{ +aiString filter_temp; +QString filename, filter; + + mImporter.GetExtensionList(filter_temp); + filter = filter_temp.C_Str(); + filter.replace(';', ' '); + filter.append(" ;; All (*.*)"); + filename = QFileDialog::getOpenFileName(this, "Choose the file", "", filter); + + if(!filename.isEmpty()) ImportFile(filename); +} + + +void MainWindow::on_butExport_clicked() +{ +using namespace Assimp; + +QString filename, filter, format_id; +Exporter exporter; +QTime time_begin; +aiReturn rv; + + if(mScene == nullptr) + { + QMessageBox::critical(this, "Export error", "Scene is empty"); + + return; + } + + // build filter + { + aiString filter_temp; + + mImporter.GetExtensionList(filter_temp); + filter = filter_temp.C_Str(); + filter.replace(';', ' '); + } + + // get file path + filename = QFileDialog::getSaveFileName(this, "Set file name", "", filter); + // extract format ID + format_id = filename.right(filename.length() - filename.lastIndexOf('.') - 1); + if(format_id.isEmpty()) + { + QMessageBox::critical(this, "Export error", "File name must has extension."); + + return; + } + + // begin export + time_begin = QTime::currentTime(); + rv = exporter.Export(mScene, format_id.toLocal8Bit(), filename.toLocal8Bit()); + ui->lblExportTime->setText(QString("%1").arg(time_begin.secsTo(QTime::currentTime()))); + if(rv == aiReturn_SUCCESS) + LogInfo("Export done: " + filename); + else + LogError("Export failed: " + filename); +} + +void MainWindow::on_cbxLighting_clicked(bool pChecked) +{ + if(pChecked) + mGLView->Lighting_Enable(); + else + mGLView->Lighting_Disable(); + + mGLView->updateGL(); +} + +void MainWindow::on_lstLight_itemSelectionChanged() +{ +bool selected = ui->lstLight->isItemSelected(ui->lstLight->currentItem()); + + if(selected) + mGLView->Lighting_EnableSource(ui->lstLight->currentRow()); + else + mGLView->Lighting_DisableSource(ui->lstLight->currentRow()); + + mGLView->updateGL(); +} + +void MainWindow::on_lstCamera_clicked(__unused const QModelIndex &index) +{ + mGLView->Camera_Set(ui->lstLight->currentRow()); + mGLView->updateGL(); +} + +void MainWindow::on_cbxBBox_clicked(bool checked) +{ + mGLView->Enable_SceneBBox(checked); + mGLView->updateGL(); +} + +void MainWindow::on_cbxTextures_clicked(bool checked) +{ + mGLView->Enable_Textures(checked); + mGLView->updateGL(); +} diff --git a/tools/assimp_qt_viewer/mainwindow.hpp b/tools/assimp_qt_viewer/mainwindow.hpp new file mode 100644 index 000000000..f51e023ec --- /dev/null +++ b/tools/assimp_qt_viewer/mainwindow.hpp @@ -0,0 +1,131 @@ +/// \file mainwindow.hpp +/// \brief Main window and algorhytms. +/// \author smal.root@gmail.com +/// \date 2016 + +#pragma once + +// Header files, Qt. +#include + +// Header files, project. +#include "glview.hpp" +#include "loggerview.hpp" + +// Header files, Assimp. +#include +#include + +namespace Ui { class MainWindow; } + +/// \class MainWindow +/// Main window and algorhytms. +class MainWindow : public QMainWindow +{ + Q_OBJECT + + /**********************************/ + /************ Variables ***********/ + /**********************************/ + +private: + + Ui::MainWindow *ui; + + CGLView* mGLView;///< Pointer to OpenGL render. + CLoggerView* mLoggerView;///< Pointer to logging object. + Assimp::Importer mImporter;///< Assimp importer. + const aiScene* mScene;///< Pointer to loaded scene (\ref aiScene). + QPoint mPosition_Pressed_LMB;///< Position where was pressed left mouse button. + QPoint mPosition_Pressed_RMB;///< Position where was pressed right mouse button. + + /**********************************/ + /************ Functions ***********/ + /**********************************/ + + /********************************************************************/ + /********************* Import/Export functions **********************/ + /********************************************************************/ + + /// \fn void ImportFile(const QString& pFileName) + /// Import scene from file. + /// \param [in] pFileName - path and name of the file. + void ImportFile(const QString& pFileName); + + /********************************************************************/ + /************************ Logging functions *************************/ + /********************************************************************/ + + /// \fn void LogInfo(const QString& pMessage) + /// Add message with severity "Warning" to log. + void LogInfo(const QString& pMessage); + + /// \fn void LogError(const QString& pMessage) + /// Add message with severity "Error" to log. + void LogError(const QString& pMessage); + + /********************************************************************/ + /*********************** Overrided functions ************************/ + /********************************************************************/ + +protected: + + /// \fn void mousePressEvent(QMouseEvent* pEvent) override + /// Overrided function which handle mouse event "button pressed". + /// \param [in] pEvent - pointer to event data. + void mousePressEvent(QMouseEvent* pEvent) override; + + /// \fn void mouseMoveEvent(QMouseEvent* pEvent) override + /// Overrided function which handle mouse event "move". + /// \param [in] pEvent - pointer to event data. + void mouseMoveEvent(QMouseEvent* pEvent) override; + + /// \fn void keyPressEvent(QKeyEvent* pEvent) override + /// Overrided function which handle key event "key pressed". + /// \param [in] pEvent - pointer to event data. + void keyPressEvent(QKeyEvent* pEvent) override; + + +public: + + /********************************************************************/ + /********************** Constructor/Destructor **********************/ + /********************************************************************/ + + /// \fn explicit MainWindow(QWidget* pParent = 0) + /// \param [in] pParent - pointer to parent widget. + explicit MainWindow(QWidget* pParent = 0); + + /// \fn ~MainWindow() + /// Destructor. + ~MainWindow(); + + /********************************************************************/ + /****************************** Slots *******************************/ + /********************************************************************/ + +private slots: + + /// \fn void Paint_Finished(const int pPaintTime) + /// Show paint/render time and distance between camera and center of the scene. + /// \param [in] pPaintTime_ms - paint time in milliseconds. + void Paint_Finished(const size_t pPaintTime_ms, const GLfloat pDistance); + + /// \fn void SceneObject_Camera(const QString& pName) + /// Add camera name to list. + /// \param [in] pName - name of the camera. + void SceneObject_Camera(const QString& pName); + + /// \fn void SceneObject_LightSource(const QString& pName) + /// Add lighting source name to list. + /// \param [in] pName - name of the light source, + void SceneObject_LightSource(const QString& pName); + + void on_butOpenFile_clicked(); + void on_butExport_clicked(); + void on_cbxLighting_clicked(bool pChecked); + void on_lstLight_itemSelectionChanged(); + void on_lstCamera_clicked(const QModelIndex &index); + void on_cbxBBox_clicked(bool checked); + void on_cbxTextures_clicked(bool checked); +}; diff --git a/tools/assimp_qt_viewer/mainwindow.ui b/tools/assimp_qt_viewer/mainwindow.ui new file mode 100644 index 000000000..105a470e0 --- /dev/null +++ b/tools/assimp_qt_viewer/mainwindow.ui @@ -0,0 +1,516 @@ + + + MainWindow + + + + 0 + 0 + 641 + 734 + + + + MainWindow + + + + + + + + + QLayout::SetDefaultConstraint + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + File + + + + + + Qt::NoFocus + + + Open file + + + + + + + Qt::NoFocus + + + File name + + + + + + + + 160 + 16777215 + + + + Qt::NoFocus + + + false + + + true + + + + + + + Qt::NoFocus + + + Load time, s + + + + + + + Qt::NoFocus + + + + + + Qt::AlignCenter + + + + + + + Qt::NoFocus + + + Qt::Horizontal + + + + + + + Qt::NoFocus + + + Export + + + + + + + Qt::NoFocus + + + Export time, s + + + + + + + Qt::NoFocus + + + + + + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + Info + + + + + + Qt::NoFocus + + + Render time, ms + + + + + + + Qt::NoFocus + + + + + + Qt::AlignCenter + + + + + + + Qt::NoFocus + + + Meshes + + + + + + + Qt::NoFocus + + + Faces + + + + + + + Qt::NoFocus + + + + + + Qt::AlignCenter + + + + + + + Qt::NoFocus + + + + + + Qt::AlignCenter + + + + + + + Qt::NoFocus + + + Vertices + + + + + + + Qt::NoFocus + + + + + + Qt::AlignCenter + + + + + + + Qt::NoFocus + + + Lights + + + + + + + Qt::NoFocus + + + Cameras + + + + + + + Qt::NoFocus + + + Animation + + + + + + + false + + + Qt::NoFocus + + + Shaders + + + + + + + Qt::NoFocus + + + + + + + + + + Qt::NoFocus + + + + + + + + + + false + + + Qt::NoFocus + + + + + + + + + + Qt::NoFocus + + + + + + + + + + Distance + + + + + + + + + + + + + + + + + false + + + Qt::NoFocus + + + Dynamics + + + + + + Qt::NoFocus + + + Animation start + + + + + + + Qt::NoFocus + + + Animation stop + + + + + + + + + + + + + + Qt::NoFocus + + + 2 + + + + Log + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Qt::NoFocus + + + + + + + + Lights and cameras + + + + + + Qt::NoFocus + + + Light sources of the scene + + + QAbstractItemView::SelectedClicked + + + false + + + QAbstractItemView::MultiSelection + + + + + + + Qt::NoFocus + + + Cameras of the scene + + + QAbstractItemView::NoEditTriggers + + + false + + + + + + + + Control + + + + + + Enable/Disable OpenGL lighting + + + Lighting + + + true + + + + + + + Scene BBox + + + + + + + Textures + + + + + + + + + + + + + + + +