text: /**
   ******************************************************************************
   *
   * @file       modelviewgadgetwidget.cpp
   * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
   * @addtogroup GCSPlugins GCS Plugins
   * @{
   * @addtogroup ModelViewPlugin ModelView Plugin
   * @{
   * @brief A gadget that displays a 3D representation of the UAV
   *****************************************************************************/
  /*
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful, but
   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
   * for more details.
   *
   * You should have received a copy of the GNU General Public License along
   * with this program; if not, see <http://www.gnu.org/licenses/>
   */
  #include "QtDebug"
  #ifdef __APPLE__
  #include "OpenGL/OpenGL.h"
  #endif
  #include "modelviewgadgetwidget.h"
  #include "extensionsystem/pluginmanager.h"
  #include "glc_lib/glc_context.h"
  #include "glc_lib/glc_exception.h"
  #include "glc_lib/glc_openglexception.h"
  #include "glc_lib/viewport/glc_userinput.h"
  
  #include <iostream>
  
  // Model 3d object and background image used when specific one isn't available
  const QString ModelViewGadgetWidget::fallbackAcFilename =
      QString(":/modelview/models/warning_sign.obj");
  const QString ModelViewGadgetWidget::fallbackBgFilename = QString(":/modelview/models/black.jpg");
  
  ModelViewGadgetWidget::ModelViewGadgetWidget(QWidget *parent)
      : QGLWidget(new GLC_Context(QGLFormat(QGL::SampleBuffers)), parent)
      , m_Light()
      , m_World()
      , m_GlView()
      , m_MoverController()
      , m_ModelBoundingBox()
      , m_MotionTimer()
      , acFilename(fallbackAcFilename)
      , bgFilename(fallbackBgFilename)
      , vboEnable(false)
  {
      connect(&m_GlView, &GLC_Viewport::updateOpenGL, this, &QGLWidget::updateGL);
      setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
  
      m_Light.setPosition(4000.0, 40000.0, 80000.0);
      // m_GlView.setBackgroundColor(Qt::white);
      m_Light.setAmbientColor(Qt::lightGray);
  
      m_GlView.cameraHandle()->setDefaultUpVector(glc::Z_AXIS);
      m_GlView.cameraHandle()->setRearView();
  
      QColor repColor;
      repColor.setRgbF(1.0, 0.11372, 0.11372, 0.0);
      m_MoverController = GLC_Factory::instance()->createDefaultMoverController(repColor, &m_GlView);
  
      CreateScene();
      // Get required UAVObjects
      ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
      UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
      attState = AttitudeActual::GetInstance(objManager);
  
      connect(&m_MotionTimer, &QTimer::timeout, this,
              QOverload<>::of(&ModelViewGadgetWidget::updateAttitude));
  }
  
  ModelViewGadgetWidget::~ModelViewGadgetWidget() {}
  
  void ModelViewGadgetWidget::setAcFilename(QString acf)
  {
      if (QFile::exists(acf)) {
          acFilename = acf;
      } else {
          acFilename = acf = fallbackAcFilename;
          m_GlView.cameraHandle()->setFrontView(); // set to front camera to see/read the warning sign
      }
  }
  
  void ModelViewGadgetWidget::setBgFilename(QString bgf)
  {
      if (QFile::exists(bgFilename)) {
          bgFilename = bgf;
      } else {
          qDebug() << "file " << bgf << " doesn't exists";
          bgFilename = fallbackBgFilename; // will put a black background if there's no background
      }
  }
  
  void ModelViewGadgetWidget::setVboEnable(bool eVbo)
  {
      vboEnable = eVbo;
      m_World.collection()->setVboUsage(vboEnable);
  }
  
  //// Public funcitons ////
  void ModelViewGadgetWidget::reloadScene()
  {
      CreateScene();
  }
  
  //// Private functions ////
  void ModelViewGadgetWidget::initializeGL()
  {
  // For VSYNC problem under Mac OS X
  #if defined(Q_OS_MAC)
      const GLint swapInterval = 1;
      CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &swapInterval);
  #endif
  
      // OpenGL initialization
      m_GlView.initGl();
      // Reframe the scene
      m_GlView.reframe(m_ModelBoundingBox);
  
      glEnable(GL_NORMALIZE);
      // Enable antialiasing
      glEnable(GL_MULTISAMPLE);
  
      m_MotionTimer.start(100);
      setFocusPolicy(Qt::StrongFocus); // keyboard capture for camera switching
  }
  
  void ModelViewGadgetWidget::paintGL()
  {
      try {
          // Clear screen
          glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
          // OpenGL error handler
          {
              GLenum error = glGetError();
              if (error != GL_NO_ERROR) {
                  GLC_OpenGlException OpenGlException("ModelViewGadgetWidget::paintGL() ", error);
                  throw(OpenGlException);
              }
          }
  
          // Load identity matrix
          GLC_Context::current()->glcLoadIdentity();
  
          // Enable antialiasing
          glEnable(GL_MULTISAMPLE);
  
          // Calculate camera depth of view
          m_GlView.setDistMinAndMax(m_World.boundingBox());
  
          // define view matrix
          m_GlView.glExecuteCam();
          m_Light.glExecute();
  
          // Display the collection of GLC_Object
          m_World.render(0, glc::ShadingFlag);
          m_World.render(0, glc::TransparentRenderFlag);
  
          // Display UI Info (orbit circle)
          m_MoverController.drawActiveMoverRep();
      } catch (GLC_Exception &e) {
          qDebug() << e.what();
      }
  }
  
  void ModelViewGadgetWidget::resizeGL(int width, int height)
  {
      m_GlView.setWinGLSize(width, height); // Compute window aspect ratio
      // OpenGL error handler
      {
          GLenum error = glGetError();
          if (error != GL_NO_ERROR) {
              GLC_OpenGlException OpenGlException("ModelViewGadgetWidget::resizeGL() ", error);
              throw(OpenGlException);
          }
      }
  }
  
  // Create GLC_Object to display
  void ModelViewGadgetWidget::CreateScene()
  {
      // put a black background if the 3D model is invalid or if the background image is also invalid
      if (acFilename == fallbackAcFilename || !QFile::exists(bgFilename)) {
          bgFilename = fallbackBgFilename;
      }
  
      try {
          m_GlView.loadBackGroundImage(bgFilename);
      } catch (GLC_Exception e) {
          qDebug("ModelView: background image file loading failed.");
      }
  
      try {
          if (QFile::exists(acFilename)) {
              QFile aircraft(acFilename);
              m_World = GLC_Factory::instance()->createWorldFromFile(aircraft);
              m_ModelBoundingBox = m_World.boundingBox();
              m_GlView.reframe(m_ModelBoundingBox); // center 3D model in the scene
          } else {
              qDebug() << "ModelView: aircraft file not found:" << acFilename;
          }
      } catch (GLC_Exception e) {
          qDebug("ModelView: aircraft file loading failed.");
      }
  }
  
  void ModelViewGadgetWidget::wheelEvent(QWheelEvent *e)
  {
      double delta = m_GlView.cameraHandle()->distEyeTarget() - (e->delta() / 4);
  
      m_GlView.cameraHandle()->setDistEyeTarget(delta);
      m_GlView.setDistMinAndMax(m_World.boundingBox());
  }
  
  void ModelViewGadgetWidget::mousePressEvent(QMouseEvent *e)
  {
      GLC_UserInput userInput(e->x(), e->y());
  
      if (m_MoverController.hasActiveMover()) {
          return;
      }
  
      switch (e->button()) {
      case (Qt::LeftButton):
          m_MotionTimer.stop();
          m_MoverController.setActiveMover(GLC_MoverController::TurnTable, userInput);
          updateGL();
          break;
      case (Qt::RightButton):
          printf("VBO enabled: %s, VBO supported: %s, VBO used: %s\n", vboEnable ? "yes" : "no",
                 GLC_State::vboSupported() ? "yes" : "no", GLC_State::vboUsed() ? "yes" : "no");
          printf("Renderer - %s \n", reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
          printf("Extensions - %s\n", reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)));
          break;
      default:
          break;
      }
  }
  
  void ModelViewGadgetWidget::mouseMoveEvent(QMouseEvent *e)
  {
      GLC_UserInput userInput(e->x(), e->y());
  
      if (!m_MoverController.hasActiveMover()) {
          return;
      }
      m_MoverController.move(userInput);
      m_GlView.setDistMinAndMax(m_World.boundingBox());
      updateGL();
  }
  
  void ModelViewGadgetWidget::mouseReleaseEvent(QMouseEvent *)
  {
      if (!m_MoverController.hasActiveMover()) {
          return;
      }
      m_MoverController.setNoMover();
      m_MotionTimer.start();
      updateGL();
  }
  
  void ModelViewGadgetWidget::keyPressEvent(QKeyEvent *e) // switch between camera
  {
      if (e->key() == Qt::Key_1) {
          m_GlView.cameraHandle()->setIsoView();
          updateGL();
      }
      if (e->key() == Qt::Key_2) {
          m_GlView.cameraHandle()->setFrontView();
          updateGL();
      }
      if (e->key() == Qt::Key_3) {
          m_GlView.cameraHandle()->setIsoView();
          m_GlView.cameraHandle()->rotateAroundTarget(glc::Z_AXIS, glc::toRadian(90));
          updateGL();
      }
      if (e->key() == Qt::Key_4) {
          m_GlView.cameraHandle()->setLeftView();
          updateGL();
      }
      if (e->key() == Qt::Key_5) {
          m_GlView.cameraHandle()->setTopView();
          m_GlView.cameraHandle()->rotateAroundTarget(glc::Z_AXIS, glc::toRadian(180));
          updateGL();
      }
      if (e->key() == Qt::Key_6) {
          m_GlView.cameraHandle()->setRightView();
          ;
          updateGL();
      }
      if (e->key() == Qt::Key_7) {
          m_GlView.cameraHandle()->setIsoView();
          m_GlView.cameraHandle()->rotateAroundTarget(glc::Z_AXIS, glc::toRadian(-90));
          updateGL();
      }
      if (e->key() == Qt::Key_8) {
          m_GlView.cameraHandle()->setRearView();
          ;
          updateGL();
      }
      if (e->key() == Qt::Key_9) {
          m_GlView.cameraHandle()->setIsoView();
          m_GlView.cameraHandle()->rotateAroundTarget(glc::Z_AXIS, glc::toRadian(180));
          updateGL();
      }
      if (e->key() == Qt::Key_0) {
          m_GlView.cameraHandle()->setBottomView();
          m_GlView.cameraHandle()->rotateAroundTarget(glc::Z_AXIS, glc::toRadian(180));
          updateGL();
      }
  }
  
  //////////////////////////////////////////////////////////////////////
  // Private slots Functions
  //////////////////////////////////////////////////////////////////////
  void ModelViewGadgetWidget::updateAttitude()
  {
      AttitudeActual::DataFields data = attState->getData(); // get attitude data
      GLC_StructOccurence *rootObject = m_World.rootOccurence(); // get the full 3D model
      double x = data.q3;
      double y = data.q2;
      double z = data.q4;
      double w = data.q1;
  
      if (w == 0.0) {
          w = 1.0;
      }
      // create and gives the product of 2 4x4 matrices to get the rotation of the 3D model's matrix
      QMatrix4x4 m1;
      m1.setRow(0, QVector4D(w, z, -y, x));
      m1.setRow(1, QVector4D(-z, w, x, y));
      m1.setRow(2, QVector4D(y, -x, w, z));
      m1.setRow(3, QVector4D(-x, -y, -z, w));
      QMatrix4x4 m2;
      m2.setRow(0, QVector4D(w, z, -y, -x));
      m2.setRow(1, QVector4D(-z, w, x, -y));
      m2.setRow(2, QVector4D(y, -x, w, -z));
      m2.setRow(3, QVector4D(x, y, z, w));
      QMatrix4x4 m0 = m1 * m2;
      // convert QMatrix4x4 to GLC_Matrix4x4
      GLC_Matrix4x4 rootObjectRotation(m0.data());
      rootObject->structInstance()->setMatrix(rootObjectRotation);
      rootObject->updateChildrenAbsoluteMatrix();
      updateGL();
  }
  