/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------

Copyright (c) 2006-2018, assimp team



All rights reserved.

Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following
conditions are met:

* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.

* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.

* Neither the name of the assimp team, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of the assimp team.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
#include "glview.hpp"

// Header files, Qt.
#include <QTime>

// Header files, OpenGL.
#if defined(__APPLE__)
# include <OpenGL/glu.h>
#else
# include <GL/glu.h>
#endif

// Header files, DevIL.

// Header files, Assimp.
#include <assimp/DefaultLogger.hpp>

#define STB_IMAGE_IMPLEMENTATION
#include "contrib/stb_image/stb_image.h"

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() {
	delete [] Index_Point;
	delete [] Index_Line;
	delete [] Index_Triangle;
}

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);
}

static void 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;
}

static void 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;
}

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;

	///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
	ai_real 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& scenePath) {
    auto LoadTexture = [&](const QString& pFileName) -> bool ///TODO: IME texture mode, operation.
    {
        GLuint id_ogl_texture;// OpenGL texture ID.

	    if(!pFileName.startsWith(AI_EMBEDDED_TEXNAME_PREFIX))
	    {
		    QString basepath = scenePath.left(scenePath.lastIndexOf('/') + 1);// path with '/' at the end.
		    QString fileloc = (basepath + pFileName);

		    fileloc.replace('\\', "/");
            int x, y, n;
            unsigned char *data = stbi_load(fileloc.toLocal8Bit(), &x, &y, &n, STBI_rgb_alpha );
            if ( nullptr == data ) {
			    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.

		    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, n, x, y, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, data );// Texture specification.
            stbi_image_free(data);
            // Cleanup
	    }
	    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;
	}

	//
	// 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.empty()) {
		LogInfo("No textures for import.");
	}
}

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 ai_real x = pVertices[idx_vert].x;
		const ai_real y = pVertices[idx_vert].y;
		const ai_real 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);
	}
}

void CGLView::LogInfo(const QString& pMessage) {
	Assimp::DefaultLogger::get()->info(pMessage.toStdString());
}

void CGLView::LogError(const QString& pMessage) {
	Assimp::DefaultLogger::get()->error(pMessage.toStdString());
}

void CGLView::Draw_Node(const aiNode* pNode) {
    aiMatrix4x4 mat_node = pNode->mTransformation;

	// Apply node transformation matrix.
	mat_node.Transpose();
	glPushMatrix();
#ifdef ASSIMP_DOUBLE_PRECISION
	glMultMatrixd((GLdouble*)mat_node[0]);
#else
	glMultMatrixf((GLfloat*)&mat_node);
#endif // ASSIMP_DOUBLE_PRECISION

	// 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);
#if ASSIMP_DOUBLE_PRECISION
	glVertexPointer(3, GL_DOUBLE, 0, mesh_cur.mVertices);
#else
	glVertexPointer(3, GL_FLOAT, 0, mesh_cur.mVertices);
#endif // ASSIMP_DOUBLE_PRECISION

	if(mesh_cur.HasVertexColors(0))
	{
		glEnable(GL_COLOR_MATERIAL);///TODO: cache
		glEnableClientState(GL_COLOR_ARRAY);
#ifdef ASSIMP_DOUBLE_PRECISION
		glColorPointer(4, GL_DOUBLE, 0, mesh_cur.mColors[0]);
#else
		glColorPointer(4, GL_FLOAT, 0, mesh_cur.mColors[0]);
#endif // ASSIMP_DOUBLE_PRECISION
	}

	//
	// Texture coordinates array
	//
	if(mesh_cur.HasTextureCoords(0))
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
#ifdef ASSIMP_DOUBLE_PRECISION
		glTexCoordPointer(2, GL_DOUBLE, sizeof(aiVector3D), mesh_cur.mTextureCoords[0]);
#else
		glTexCoordPointer(2, GL_FLOAT, sizeof(aiVector3D), mesh_cur.mTextureCoords[0]);
#endif // ASSIMP_DOUBLE_PRECISION
	}

	//
	// Normals array
	//
	if(mesh_cur.HasNormals())
	{
		glEnableClientState(GL_NORMAL_ARRAY);
#ifdef ASSIMP_DOUBLE_PRECISION
		glNormalPointer(GL_DOUBLE, 0, mesh_cur.mNormals);
#else
		glNormalPointer(GL_FLOAT, 0, mesh_cur.mNormals);
#endif // ASSIMP_DOUBLE_PRECISION
	}

	//
	// 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);
	const QColor c_w(Qt::white);

	glColor3f(c_w.redF(), c_w.greenF(), c_w.blueF());

	glBegin(GL_LINE_STRIP);
#	ifdef ASSIMP_DOUBLE_PRECISION
		glVertex3dv(&vertex[0][0]), glVertex3dv(&vertex[1][0]), glVertex3dv(&vertex[2][0]), glVertex3dv(&vertex[3][0]), glVertex3dv(&vertex[0][0]);// "Minimum" side.
		glVertex3dv(&vertex[4][0]), glVertex3dv(&vertex[5][0]), glVertex3dv(&vertex[6][0]), glVertex3dv(&vertex[7][0]), glVertex3dv(&vertex[4][0]);// Edge and "maximum" side.
#	else
		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.
#	endif // ASSIMP_DOUBLE_PRECISION
	glEnd();

	glBegin(GL_LINES);
#	ifdef ASSIMP_DOUBLE_PRECISION
		glVertex3dv(&vertex[1][0]), glVertex3dv(&vertex[5][0]);
		glVertex3dv(&vertex[2][0]), glVertex3dv(&vertex[6][0]);
		glVertex3dv(&vertex[3][0]), glVertex3dv(&vertex[7][0]);
#	else
		glVertex3fv(&vertex[1][0]), glVertex3fv(&vertex[5][0]);
		glVertex3fv(&vertex[2][0]), glVertex3fv(&vertex[6][0]);
		glVertex3fv(&vertex[3][0]), glVertex3fv(&vertex[7][0]);
#	endif // ASSIMP_DOUBLE_PRECISION
	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);
	}
}

void CGLView::initializeGL() {
	mGLContext_Current = true;
	initializeOpenGLFunctions();
	glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
	glShadeModel(GL_SMOOTH);

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_NORMALIZE);
	glEnable(GL_TEXTURE_2D);
    glEnable( GL_MULTISAMPLE );

	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 width, int height) {
	mCamera_Viewport_AspectRatio = (GLdouble)width / height;
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(mCamera_FOVY, mCamera_Viewport_AspectRatio, 1.0, 100000.0);///TODO: znear/zfar depend on scene size.
}

void CGLView::drawCoordSystem() {
	// Disable lighting. Colors must be bright and colorful)
	if ( mLightingEnabled ) glDisable( GL_LIGHTING );///TODO: display list

	// For same reason - disable textures.
	glBindTexture(GL_TEXTURE_1D, 0);
    glBindTexture(GL_TEXTURE_2D, 0);
    glBindTexture(GL_TEXTURE_3D, 0);
    glEnable(GL_COLOR_MATERIAL);
    glBegin(GL_LINES);

    // X, -X
	glColor3f(1.0f, 0.0f, 0.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(100000.0, 0.0, 0.0);
	glColor3f(0.5f, 0.5f, 1.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(-100000.0, 0.0, 0.0);
	// Y, -Y
	glColor3f(0.0f, 1.0f, 0.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 100000.0, 0.0);
	glColor3f(1.0f, 0.0f, 1.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, -100000.0, 0.0);
	// Z, -Z
	glColor3f(0.0f, 0.0f, 1.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 0.0, 100000.0);
	glColor3f(1.0f, 1.0f, 0.0f), glVertex3f(0.0, 0.0, 0.0), glVertex3f(0.0, 0.0, -100000.0);
	glColor3f(1.0f, 1.0f, 1.0f);

    glEnd();
	// Restore previous state of lighting.
    if (mLightingEnabled) {
        glEnable( GL_LIGHTING );
    }
}

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.
#if ASSIMP_DOUBLE_PRECISION
	glMultMatrixd((GLdouble*)&mHelper_Camera.Rotation_AroundCamera);
	glTranslated(-mHelper_Camera.Translation_ToScene.x, -mHelper_Camera.Translation_ToScene.y, -mHelper_Camera.Translation_ToScene.z);
	glMultMatrixd((GLdouble*)&mHelper_Camera.Rotation_Scene);
#else
	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);
#endif // ASSIMP_DOUBLE_PRECISION

	// Coordinate system
	if ( mScene_AxesEnabled ) {
        drawCoordSystem();
    }

	glDisable(GL_COLOR_MATERIAL);
	
    // 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());
}


CGLView::CGLView( QWidget *pParent )
: QOpenGLWidget( pParent )
, mGLContext_Current( false ) {
    // set initial view
    mHelper_CameraDefault.SetDefault();
    Camera_Set( 0 );
}

CGLView::~CGLView() {
	FreeScene();
}

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 through 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 splitted 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 through 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
            // Use index if name is empty.
            if (name.isEmpty()) {
                name += QString( "%1" ).arg( idx_light );
            }

			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
}

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;

	// Ambient color
    farr[0] = pLightParameters.Ambient.r;
    farr[1] = pLightParameters.Ambient.g;
    farr[2] = pLightParameters.Ambient.b;
    farr[3] = pLightParameters.Ambient.a;
	glLightfv(light_num, GL_AMBIENT, farr);

    // Diffuse color
    farr[0] = pLightParameters.Diffuse.r;
    farr[1] = pLightParameters.Diffuse.g;
    farr[2] = pLightParameters.Diffuse.b;
    farr[3] = pLightParameters.Diffuse.a;
	glLightfv(light_num, GL_DIFFUSE, farr);

    // Specular color
    farr[0] = pLightParameters.Specular.r;
    farr[1] = pLightParameters.Specular.g;
    farr[2] = pLightParameters.Specular.b;
    farr[3] = pLightParameters.Specular.a;
	glLightfv(light_num, GL_SPECULAR, farr);

    // Other parameters
	switch(pLightParameters.Type)
	{
		case aiLightSource_DIRECTIONAL:
			// Direction
			farr[0] = pLightParameters.For.Directional.Direction.x, farr[1] = 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[1] = 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[1] = 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[1] = 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[1] = 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);
}

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, const aiMatrix4x4* pMatrix_Rotation_Initial) {
    auto deg2rad = [](const GLfloat pDegree) -> GLfloat { 
        return pDegree * AI_MATH_PI / 180.0f;
    };

	aiMatrix4x4 mat_rot;

	mat_rot.FromEulerAnglesXYZ(deg2rad(pAngle_X), deg2rad(pAngle_Y), deg2rad(pAngle_Z));
	if(pMatrix_Rotation_Initial != nullptr)
		mHelper_Camera.Rotation_Scene = *pMatrix_Rotation_Initial * mat_rot;
	else
		mHelper_Camera.Rotation_Scene *= mat_rot;
}

void CGLView::Camera_Rotate(const GLfloat pAngle_X, const GLfloat pAngle_Y, const GLfloat pAngle_Z, const aiMatrix4x4* pMatrix_Rotation_Initial)
{
    auto deg2rad = [](const GLfloat pDegree) -> GLfloat { return pDegree * AI_MATH_PI / 180.0; };

	aiMatrix4x4 mat_rot;

	mat_rot.FromEulerAnglesXYZ(deg2rad(pAngle_X), deg2rad(pAngle_Y), deg2rad(pAngle_Z));
	if(pMatrix_Rotation_Initial != nullptr)
		mHelper_Camera.Rotation_AroundCamera = *pMatrix_Rotation_Initial * mat_rot;
	else
		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;
}

void CGLView::Camera_Matrix(aiMatrix4x4& pRotation_Camera, aiMatrix4x4& pRotation_Scene, aiVector3D& pTranslation_Camera)
{
	pRotation_Camera = mHelper_Camera.Rotation_AroundCamera;
	pRotation_Scene = mHelper_Camera.Rotation_Scene;
	pTranslation_Camera = mHelper_Camera.Translation_ToScene;
}
