text: ﻿/*
  ---------------------------------------------------------------------------
  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 "mainwindow.hpp"
  #include "ui_mainwindow.h"
  
  // Header files, Assimp.
  #include <assimp/Exporter.hpp>
  #include <assimp/postprocess.h>
  
  #ifndef __unused
  	#define __unused	__attribute__((unused))
  #endif // __unused
  
  using namespace Assimp;
  
  
  void MainWindow::ImportFile(const QString &pFileName) {
      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::number(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::number(mScene->mNumCameras));
  		// Lights
  		ui->lblLightCount->setText(QString::number(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::number(mScene->mNumMeshes));
  		ui->lblFaceCount->setText(QString::number(qty_face));
  		ui->lblVertexCount->setText(QString::number(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.");
  #if ASSIMP_QT4_VIEWER
  		mGLView->updateGL();
  #else
  		mGLView->update();
  #endif // ASSIMP_QT4_VIEWER
  	}
  	else
  	{
  		ResetSceneInfos();
  
  		QString errorMessage = QString("Error parsing \'%1\' : \'%2\'").arg(pFileName).arg(mImporter.GetErrorString());
  		QMessageBox::critical(this, "Import error", errorMessage);
  		LogError(errorMessage);
  	}// if(mScene != nullptr)
  }
  
  void MainWindow::ResetSceneInfos()
  {
  	ui->lblLoadTime->clear();
  	ui->leFileName->clear();
  	ui->lblMeshCount->setText("0");
  	ui->lblFaceCount->setText("0");
  	ui->lblVertexCount->setText("0");
  	ui->lblCameraCount->setText("0");
  	ui->lblLightCount->setText("0");
  	ui->lblHasAnimation->setText("no");
  }
  
  /********************************************************************/
  /************************ 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());
  }
  
  /********************************************************************/
  /*********************** Override functions ************************/
  /********************************************************************/
  
  void MainWindow::mousePressEvent(QMouseEvent* pEvent)
  {
      const QPoint ms_pt = pEvent->pos();
      aiVector3D temp_v3;
  
  	// Check if GLView is pointed.
  	if(childAt(ms_pt) == mGLView)
  	{
  		if(!mMouse_Transformation.Position_Pressed_Valid)
  		{
  			mMouse_Transformation.Position_Pressed_Valid = true;// set flag
  			// Store current transformation matrices.
  			mGLView->Camera_Matrix(mMouse_Transformation.Rotation_AroundCamera, mMouse_Transformation.Rotation_Scene, temp_v3);
  		}
  
  		if(pEvent->button() & Qt::LeftButton)
  			mMouse_Transformation.Position_Pressed_LMB = ms_pt;
  		else if(pEvent->button() & Qt::RightButton)
  			mMouse_Transformation.Position_Pressed_RMB = ms_pt;
  	}
  	else
  	{
  		mMouse_Transformation.Position_Pressed_Valid = false;
  	}
  }
  
  void MainWindow::mouseReleaseEvent(QMouseEvent *pEvent)
  {
  	if(pEvent->buttons() == 0) mMouse_Transformation.Position_Pressed_Valid = false;
  
  }
  
  void MainWindow::mouseMoveEvent(QMouseEvent* pEvent)
  {
  	if(mMouse_Transformation.Position_Pressed_Valid)
  	{
  		if(pEvent->buttons() & Qt::LeftButton)
  		{
  			GLfloat dx = 180 * GLfloat(pEvent->x() - mMouse_Transformation.Position_Pressed_LMB.x()) / mGLView->width();
  			GLfloat dy = 180 * GLfloat(pEvent->y() - mMouse_Transformation.Position_Pressed_LMB.y()) / mGLView->height();
  
  			if(pEvent->modifiers() & Qt::ShiftModifier)
  				mGLView->Camera_RotateScene(dy, 0, dx, &mMouse_Transformation.Rotation_Scene);// Rotate around oX and oZ axises.
  			else
  				mGLView->Camera_RotateScene(dy, dx, 0, &mMouse_Transformation.Rotation_Scene);// Rotate around oX and oY axises.
  
  	#if ASSIMP_QT4_VIEWER
  			mGLView->updateGL();
  	#else
  			mGLView->update();
  	#endif // ASSIMP_QT4_VIEWER
  		}
  
  		if(pEvent->buttons() & Qt::RightButton)
  		{
  			GLfloat dx = 180 * GLfloat(pEvent->x() - mMouse_Transformation.Position_Pressed_RMB.x()) / mGLView->width();
  			GLfloat dy = 180 * GLfloat(pEvent->y() - mMouse_Transformation.Position_Pressed_RMB.y()) / mGLView->height();
  
  			if(pEvent->modifiers() & Qt::ShiftModifier)
  				mGLView->Camera_Rotate(dy, 0, dx, &mMouse_Transformation.Rotation_AroundCamera);// Rotate around oX and oZ axises.
  			else
  				mGLView->Camera_Rotate(dy, dx, 0, &mMouse_Transformation.Rotation_AroundCamera);// Rotate around oX and oY axises.
  
  	#if ASSIMP_QT4_VIEWER
  			mGLView->updateGL();
  	#else
  			mGLView->update();
  	#endif // ASSIMP_QT4_VIEWER
  		}
  	}
  }
  
  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);
  
  #if ASSIMP_QT4_VIEWER
  	mGLView->updateGL();
  #else
  	mGLView->update();
  #endif // ASSIMP_QT4_VIEWER
  }
  
  /********************************************************************/
  /********************** Constructor/Destructor **********************/
  /********************************************************************/
  
  MainWindow::MainWindow(QWidget *parent)
  	: QMainWindow(parent), ui(new Ui::MainWindow),
  		mScene(nullptr)
  {
  
  	// other variables
  	mMouse_Transformation.Position_Pressed_Valid = false;
  
  	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);
  
  	ResetSceneInfos();
  }
  
  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::number(pPaintTime_ms));
  	ui->lblDistance->setText(QString::number(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;
      mImporter.GetExtensionList( filter_temp );
  
      QString filename, filter;
      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;
  
  #ifndef ASSIMP_BUILD_NO_EXPORT
      QString filename, filter, format_id;
      Exporter exporter;
      QTime time_begin;
      aiReturn rv;
      QStringList exportersList;
      QMap<QString, const aiExportFormatDesc*> exportersMap;
  
  
  	if(mScene == nullptr)
  	{
  		QMessageBox::critical(this, "Export error", "Scene is empty");
  
  		return;
  	}
  
  	for (size_t i = 0; i < exporter.GetExportFormatCount(); ++i)
  	{
  		const aiExportFormatDesc* desc = exporter.GetExportFormatDescription(i);
  		exportersList.push_back(desc->id + QString(": ") + desc->description);
  		exportersMap.insert(desc->id, desc);
  	}
  
  	// get an exporter
  	bool dialogSelectExporterOk;
  	QString selectedExporter = QInputDialog::getItem(this, "Export format", "Select the exporter : ", exportersList, 0, false, &dialogSelectExporterOk);
  	if (!dialogSelectExporterOk)
  		return;
  
  	// build the filter
  	QString selectedId = selectedExporter.left(selectedExporter.indexOf(':'));
  	filter = QString("*.") + exportersMap[selectedId]->fileExtension;
  
  	// get file path
  	filename = QFileDialog::getSaveFileName(this, "Set file name", "", filter);
  	// if it's canceled
  	if (filename == "")
  		return;
  
  	// begin export
  	time_begin = QTime::currentTime();
  	rv = exporter.Export(mScene, selectedId.toLocal8Bit(), filename.toLocal8Bit(), aiProcess_FlipUVs);
  	ui->lblExportTime->setText(QString::number(time_begin.secsTo(QTime::currentTime())));
  	if(rv == aiReturn_SUCCESS)
  		LogInfo("Export done: " + filename);
  	else
  	{
  		QString errorMessage = QString("Export failed: ") + filename;
  		LogError(errorMessage);
  		QMessageBox::critical(this, "Export error", errorMessage);
  	}
  #endif
  }
  
  void MainWindow::on_cbxLighting_clicked(bool pChecked)
  {
  	if(pChecked)
  		mGLView->Lighting_Enable();
  	else
  		mGLView->Lighting_Disable();
  
  	mGLView->update();
  }
  
  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());
  
  #if ASSIMP_QT4_VIEWER
  	mGLView->updateGL();
  #else
  	mGLView->update();
  #endif // ASSIMP_QT4_VIEWER
  }
  
  void MainWindow::on_lstCamera_clicked( const QModelIndex &)
  {
  	mGLView->Camera_Set(ui->lstLight->currentRow());
  #if ASSIMP_QT4_VIEWER
  	mGLView->updateGL();
  #else
  	mGLView->update();
  #endif // ASSIMP_QT4_VIEWER
  }
  
  void MainWindow::on_cbxBBox_clicked(bool checked)
  {
  	mGLView->Enable_SceneBBox(checked);
  #if ASSIMP_QT4_VIEWER
  	mGLView->updateGL();
  #else
  	mGLView->update();
  #endif // ASSIMP_QT4_VIEWER
  }
  
  void MainWindow::on_cbxDrawAxes_clicked(bool checked)
  {
  	mGLView->Enable_Axes(checked);
  #if ASSIMP_QT4_VIEWER
  	mGLView->updateGL();
  #else
  	mGLView->update();
  #endif // ASSIMP_QT4_VIEWER
  }
  
  void MainWindow::on_cbxTextures_clicked(bool checked)
  {
  	mGLView->Enable_Textures(checked);
  	mGLView->update();
  }
  