assimp/tools/assimp_qt_viewer/glview.cpp

1088 lines
36 KiB
C++

/// \file glview.cpp
/// \brief OpenGL visualisation. Implementation file.
/// \author smal.root@gmail.com
/// \date 2016
#include "glview.hpp"
// Header files, OpenGL.
#include <GL/glu.h>
// Header files, DevIL.
#include <il.h>
// Header files, Assimp.
#include <assimp/DefaultLogger.hpp>
#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<aiMatrix4x4> 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);
}
// multiply all matrices in reverse order
for ( std::list<aiMatrix4x4>::reverse_iterator rit = mat_list.rbegin(); rit != mat_list.rend(); rit++)
{
pOutMatrix = pOutMatrix * (*rit);
}
}
void CGLView::ImportTextures(const QString& pScenePath)
{
auto LoadTexture = [&](const QString& pFileName) -> bool ///TODO: IME texture mode, operation.
{
ILboolean success;
GLuint id_ogl_texture;// OpenGL texture ID.
if(!pFileName.startsWith(AI_EMBEDDED_TEXNAME_PREFIX))
{
ILuint id_image;// DevIL image ID.
QString basepath = pScenePath.left(pScenePath.lastIndexOf('/') + 1);// path with '/' at the end.
QString fileloc = (basepath + pFileName);
fileloc.replace('\\', "/");
ilGenImages(1, &id_image);// Generate DevIL image ID.
ilBindImage(id_image);
success = ilLoadImage(fileloc.toLocal8Bit());
if(!success)
{
LogError(QString("Couldn't load Image: %1").arg(fileloc));
return false;
}
// 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.");
return false;
}
glGenTextures(1, &id_ogl_texture);// Texture ID generation.
mTexture_IDMap[pFileName] = id_ogl_texture;// save texture ID for filename in map
glBindTexture(GL_TEXTURE_2D, id_ogl_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.
//Cleanup
ilDeleteImages(1, &id_image);// Because we have already copied image data into texture data we can release memory used by image.
}
else
{
struct SPixel_Description
{
const char* FormatHint;
const GLint Image_InternalFormat;
const GLint Pixel_Format;
};
constexpr SPixel_Description Pixel_Description[] = {
{"rgba8880", GL_RGB, GL_RGB},
{"rgba8888", GL_RGBA, GL_RGBA}
};
constexpr size_t Pixel_Description_Count = sizeof(Pixel_Description) / sizeof(SPixel_Description);
size_t idx_description;
// Get texture index.
bool ok;
size_t idx_texture = pFileName.right(strlen(AI_EMBEDDED_TEXNAME_PREFIX)).toULong(&ok);
if(!ok)
{
LogError("Can not get index of the embedded texture from path in material.");
return false;
}
// Create alias for conveniance.
const aiTexture& als = *mScene->mTextures[idx_texture];
if(als.mHeight == 0)// Compressed texture.
{
LogError("IME: compressed embedded textures are not implemented.");
}
else
{
ok = false;
for(size_t idx = 0; idx < Pixel_Description_Count; idx++)
{
if(als.CheckFormat(Pixel_Description[idx].FormatHint))
{
idx_description = idx;
ok = true;
break;
}
}
if(!ok)
{
LogError(QString("Unsupported format hint for embedded texture: [%1]").arg(als.achFormatHint));
return false;
}
glGenTextures(1, &id_ogl_texture);// Texture ID generation.
mTexture_IDMap[pFileName] = id_ogl_texture;// save texture ID for filename in map
glBindTexture(GL_TEXTURE_2D, id_ogl_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.
// Texture specification.
glTexImage2D(GL_TEXTURE_2D, 0, Pixel_Description[idx_description].Image_InternalFormat, als.mWidth, als.mHeight, 0,
Pixel_Description[idx_description].Pixel_Format, GL_UNSIGNED_BYTE, (uint8_t*)als.pcData);
}// if(als.mHeight == 0) else
}// if(!filename.startsWith(AI_EMBEDDED_TEXNAME_PREFIX)) else
return true;
};// auto LoadTexture = [&](const aiString& pPath)
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 textures.
//
// 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;
LoadTexture(QString(path.C_Str()));
idx_texture++;
} while(true);
}// for(size_t idx_material = 0; idx_material < mScene->mNumMaterials; idx_material++)
// Textures list is empty, exit.
if(mTexture_IDMap.size() == 0)
{
LogInfo("No textures for import.");
return;
}
}
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<QString, GLuint>::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;
}