/**
© Florian Joncour, 2013-2018 florian@zetta-sys.com

Ce logiciel est un programme informatique faisant interface à la bibliothèque
Perspective3D, un outil de modélisation 3D à partir de vues orthographiques 2D.

Ce logiciel est régi par la licence CeCILL-C soumise au droit français et
respectant les principes de diffusion des logiciels libres. Vous pouvez
utiliser, modifier et/ou redistribuer ce programme sous les conditions
de la licence CeCILL-C telle que diffusée par le CEA, le CNRS et l'INRIA
sur le site "http://www.cecill.info".

En contrepartie de l'accessibilité au code source et des droits de copie,
de modification et de redistribution accordés par cette licence, il n'est
offert aux utilisateurs qu'une garantie limitée.  Pour les mêmes raisons,
seule une responsabilité restreinte pèse sur l'auteur du programme,  le
titulaire des droits patrimoniaux et les concédants successifs.

A cet égard  l'attention de l'utilisateur est attirée sur les risques
associés au chargement,  à l'utilisation,  à la modification et/ou au
développement et à la reproduction du logiciel par l'utilisateur étant
donné sa spécificité de logiciel libre, qui peut le rendre complexe à
manipuler et qui le réserve donc à des développeurs et des professionnels
avertis possédant  des  connaissances  informatiques approfondies.  Les
utilisateurs sont donc invités à charger  et  tester  l'adéquation  du
logiciel à leurs besoins dans des conditions permettant d'assurer la
sécurité de leurs systèmes et ou de leurs données et, plus généralement,
à l'utiliser et l'exploiter dans les mêmes conditions de sécurité.

Le fait que vous puissiez accéder à cet en-tête signifie que vous avez
pris connaissance de la licence CeCILL-C, et que vous en avez accepté les
termes.
**/

/* Objet: Affichage des modèles 3D avec OpenGL et l'aide de Perspective_GL. */

#ifndef VUEMODELE3D_H
#define VUEMODELE3D_H

#include <QtGlobal>

/* QOpenGLWidget est en principe plus "moderne", mais bouffe 2x plus de mémoire et est buggé (appel de glViewport() après painGL(), faisant foirer la projection ortho). */
#if ( QT_VERSION >= QT_VERSION_CHECK(5,9,1) ) // Qt >= 5.9.1
#define UTILISE_QOPENGLWIDGET
#endif // QT_VERSION

//#define ACTIVE_QPAINTER_POST_VUE3D

#ifdef UTILISE_QOPENGLWIDGET
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#else
#include <QGLWidget>
#endif // UTILISE_QOPENGLWIDGET

#include <QMutex>
#include <QMenu>

#ifdef ACTIVE_QPAINTER_POST_VUE3D
#include <QPainter>
#endif // ACTIVE_QPAINTER_POST_VUE3D

#include "animeQt.h"
#include "perspective_types.h"
#include "GL/perspective_gl.h"

struct  ElementsSuppr_t
{
        ElementsSuppr_t(): nSommets(0), nSegments(0), nSurfaces(0) {}
        ElementsSuppr_t(unsigned short nsommets, unsigned short nsegments, unsigned short nsurfaces, unsigned short nsolides) :
            nSommets(nsommets), nSegments(nsegments), nSurfaces(nsurfaces), nSolides(nsolides) { ; }
        unsigned short nSommets, nSegments, nSurfaces, nSolides;
};
typedef std::vector<ElementsSuppr_t> VectElementsSuppr;

class VueModele3D :
        #ifdef UTILISE_QOPENGLWIDGET
        public QOpenGLWidget, protected QOpenGLFunctions
        #else
        public QGLWidget
        #endif // UTILISE_QOPENGLWIDGET
{
    Q_OBJECT

    public:
        VueModele3D(const AnimationQt &anim, const PGL::Params_GL &params_gl, const PGL::Params_Perspective_GL &params_pgl, const VectElementsSuppr &vect_suppr, bool outils_menu_contextuel=true, bool affichage_points_accroche=false, Perspective3D::vues2D_t vue_reference=Perspective3D::vues2D_t::VUEMULT, QWidget *parent = 0);
        ~VueModele3D();

        inline bool TermineInitialisation() const
        {
            return termine_initialise;
        }

        inline bool ValidePerspGL() const
        {
            return PerspGL!=nullptr;
        }

        inline bool defEtatShaders(bool etat)
        {
            if (ValidePerspGL())
            {
                return PerspGL->defEtatShaders(etat);
            }
            return false;
        }

        const std::string& InitialisationShadersModele(const char *source_vertex_shader, const char *source_pixel_shader, const unsigned int contexte=PGL::CONTEXTE_MODELE);

        inline QImage CaptureEcran()
        {
#ifdef UTILISE_QOPENGLWIDGET
            return grabFramebuffer();
#else
            return grabFrameBuffer();
#endif // UTILISE_QOPENGLWIDGET
        }

        void ReinitPerspGL(const Perspective3D::Perspective *perspective); /* Réinitialisation compilète du moteur d'affichage ! */
        void ReinitPerspGL(const Perspective3D::PScene3D *scene3d); /* Réinitialisation compilète du moteur d'affichage ! */

        void MiseAJourAffichage(bool force=false);
        void MiseaJourModele(bool sommets, bool filaire, bool triangles);

        void defImprimante3D(bool active, float x, float y, float z); /* Définition d'une imprimante 3D (pour afficher le plateau et la zone imprimable) */

        void TransfertModele(const Perspective3D::Perspective *perspective); /* Demande le transfert du modèle Perspective3D -> Perspective_GL. */
        void TransfertModele(const Perspective3D::PScene3D *scene3d); /* Demande le transfert du modèle Perspective3D -> Perspective_GL. */
        void xSuppr();
        void defAffichage(int id, bool updategl=false); /* Change le mode d'affichage */
        void defOpacite(int val, bool animation=true); /* Assigne le niveau de transparence, met à jour l'affichage si demandé */
        void defCouleurFond(QRgb couleur, bool animation=false); /* Assigne la couleur du fond, met à jour l'affichage si demandé */
        void defCouleursScene(QRgb couleur_sommets, QRgb couleur_segments, QRgb couleur_imprimante, QRgb couleur_reperes_sommets, QRgb couleur_reperes_segments, QRgb couleur_reperes_faces);

        bool InitAnimationSolide(int force_delai_chrono=-1);
        bool AnimationContour(const Perspective3D::PStdVectSegments3D &contour);

        void defEclairageDynamique(bool etat);
        void defAffichageAccroche(bool etat);

        inline void SelectionCouleur(PGL::PCouleur_GL &c, const Perspective3D::Pvec3 &norm) const
        /* Assigne dans c la couleur suivant le RAL. Si il n'est pas défini, on génère une couleur en fonction du vecteur normal. */
        {
            if (ValidePerspGL())
            {
                PerspGL->SelectionCouleur(c, PGL::PVertex_GL(norm));
            }
        }

        inline void AffichageCentreSurface(int id) const
        {
            if (ValidePerspGL())
            {
                PerspGL->AffichageCentreSurface(id);
            }
        }

        inline void AffichageCentreSegment(int id) const
        {
            if (ValidePerspGL())
            {
                PerspGL->AffichageCentreSegment(id);
            }
        }

        inline void AffichagePoint(int id) const
        {
            if (ValidePerspGL())
            {
                PerspGL->AffichagePoint(id);
            }
        }

        void defModeAffichage(bool ortho, bool force_redim);
        void defLissageAffichage3D(bool etat);
        inline bool Mode2D() const { return paramsPGL.mode_2d; }

        int IdCouleurScene() const;
        void defIdCouleurScene(int val, bool updategl=false); /* Met à jour la couleur du solide. */
        void ReInitEchelle(bool anim=true);
        void ReinitSelection();
        void CentreSur(const Perspective3D::Pvec3 &p, bool rotation_axe=false, bool change_echelle=true, const pfloat anglex=0., const pfloat angley=0., const pfloat anglez=0.);  /* Centre la vue sur un point particulier du modèle. */

        void AssigneVue(const Perspective3D::Pvec3 &angle, const Perspective3D::Pvec3 &position);
        /* Positionne la vue directement, sans animation et met à jour l'affichage. */

        void VuePerspective();
        void VuePerspectiveInv();
        void VueFace();
        void VueArriere();
        void VueCote();
        void VueCoteGauche();
        void VueHaut();
        void VueDessous();
        void ForceVue(Perspective3D::vues2D_t vue);
        void AnimeRotationScene(); /* Rotation 360° de la scène. La fonction est bloquante, elle ne termine qu'une fois l'animation finie. */
        void ArretAnimations();

#ifdef ACTIVE_QPAINTER_POST_VUE3D
        QPainter &PainterWGL();
#endif // ACTIVE_QPAINTER_POST_VUE3D

        inline PGL::PVectCouleur_GL &CouleursTriangles() const
        {
            if (ValidePerspGL())
            {
                return PerspGL->CouleursTriangles();
            }
            static PGL::PVectCouleur_GL vect;
            return vect;
        }

        inline PGL::PVectCouleur_GL &CouleursLignes() const
        {
            if (ValidePerspGL())
            {
                return PerspGL->CouleursLignes();
            }
            static PGL::PVectCouleur_GL vect;
            return vect;
        }

        inline PGL::PVectCouleur_GL &CouleursSommets() const
        {
            if (ValidePerspGL())
            {
                return PerspGL->CouleursSommets();
            }
            static PGL::PVectCouleur_GL vect;
            return vect;
        }

        inline const PGL::PCouleur_GL &CouleurReferenceSegments() const
        {
            if (ValidePerspGL())
            {
                return PerspGL->CouleurReferenceSegments();
            }
            static PGL::PCouleur_GL c;
            return c;
        }

        inline const PGL::PCouleur_GL &CouleurFond() const
        {
            if (ValidePerspGL())
            {
                return PerspGL->CouleurFond();
            }
            static PGL::PCouleur_GL c;
            return c;
        }

        inline const PGL::PCouleur_GL &CouleurReferenceSommets() const
        {
            if (ValidePerspGL())
            {
                return PerspGL->CouleurReferenceSommets();
            }
            static PGL::PCouleur_GL c;
            return c;
        }

        void RedimAuto();

        bool TermineShaderPost();
        bool ExecShaderPost();

        void defTempoShaderPost(int t); /* Tempo de rafraichissement des shaders de post-traitement en milli-secondes (reçoit une valeur en FPS) */

    protected:
        void GenPerspGL();
        void BloqueAnimationSolide();
        void TermineAnimationSolide();
        bool GereAnimationSolide();
        bool GereAnimationContour();

        void Redim(int x, int y);

        void resizeGL(int largeur, int hauteur);
        void paintGL();
#ifdef UTILISE_QOPENGLWIDGET
        void paintEvent(QPaintEvent *e);
#endif // UTILISE_QOPENGLWIDGET

        void initializeGL();

        void contextMenuEvent(QContextMenuEvent *ev);
        void mouseDoubleClickEvent(QMouseEvent *ev);
        void mousePressEvent(QMouseEvent *ev);
        void mouseMoveEvent(QMouseEvent *ev);
        void mouseReleaseEvent(QMouseEvent *ev);
        void wheelEvent(QWheelEvent *ev);
        const Perspective3D::PScene3D *Scene;

        void BloqueExecAnimation(int tempo_supplementaire=0);

        void CalCAxeRotation(QPoint &pos);

    private:
        const AnimationQt &animation;
        PGL::Params_GL paramsGL;
        PGL::Params_Perspective_GL paramsPGL;
        PGL::Perspective_GL *PerspGL;
        Perspective3D::vues2D_t vue_reference_affichage;
        const VectElementsSuppr &VectSuppr;
        QMutex mutex_anim;

#ifdef ACTIVE_QPAINTER_POST_VUE3D
        QPainter painter_glw;
#endif // ACTIVE_QPAINTER_POST_VUE3D

        bool termine_initialise;
        bool termine_perspective;

        int def_opacite_solide;

        GLcoord def_couleur_fond[3];

        pfloat def_m_Echelle;

        PGL::PVertex_GL sav_DeplacementCam;
        PGL::PVertex_GL def_DeplacementCam;

        Perspective3D::Ppoint2D sav_PositionSouris_orig;
        Perspective3D::Ppoint2D PositionSouris_orig;
        PGL::PVertex_GL def_axeRotationCam;
        PGL::PVertex_GL sav_axeRotationCam;

        int id_timer_animation;
        int id_timer_animation_2;
        int id_timer_exec;
        int temps_timer_exec;

        unsigned int etat_maj_modeles;

        unsigned int iter_segment_contour_anim;
        unsigned int id_sommet_contour;
//        Perspective3D::PStdVectInts ids_contour_anim;
        Perspective3D::PStdVectPoint3D_min ids_contour_anim;

        bool affiche_outils_3d;
        QMenu MenuContextuel;

        int SelectionPoint;
        int SelectionSegment;
        int SelectionSurface;

        unsigned int DelaiChrono_animations;

        bool affiche_accroche;
        bool affiche_accroche_sommets;

    private slots:
#ifndef UTILISE_QOPENGLWIDGET
        void resizeEvent(QResizeEvent *ev);
#endif // UTILISE_QOPENGLWIDGET
        void timerEvent(QTimerEvent *ev);
        void SupprimeSurface();
        void SupprimePoint();
        void VueFace_M();
        void VueCote_M();
        void VueHaut_M();
        void VueOblique_M();
        void Animation_M();
        void ExportSolide();
        void ExportScript();
        void ExportImage();
        void ConfigAffichage_0();
        void ConfigAffichage_1();
        void ConfigAffichage_2();
        void ChangeTypeAffichage_0();
        void ChangeTypeAffichage_1();
        void ChangeTypeAffichage_2();
        void ChangeTypeAffichage_3();

#ifdef SUPPORT_GIF
        void ExportAnimation();
#endif // SUPPORT_GIF
        void Imprimer();
        void ProprietesSolide();
        void Restaure();

        void closeEvent(QCloseEvent *ev);

    signals:
#ifdef ACTIVE_QPAINTER_POST_VUE3D
        void PaintGLPost();
#endif // ACTIVE_QPAINTER_POST_VUE3D

        void SelectionePoint(int id, bool affiche_infos, bool centre_point);
        void SelectioneSegment(int id, bool affiche_infos, bool centre_segment);
        void SelectioneSurface(int id, bool affiche_infos, bool centre_surface, bool reinit_surfaces);
        void SupprimeSurface_S(int id);
        void SupprimePoint_S(int id);
        void ExportSolide_S();
        void ExportScript_S();
        void ExportImage_S();
        void ConfigAffichage_S(int);
        void TypeAffichage_S(int);

#ifdef SUPPORT_GIF
        void ExportAnimation_S();
#endif // SUPPORT_GIF
        void Imprimer_S();
        void ProprietesSolide_S();
        void Restaure_S();
};

#endif // VUEMODELE3D_H
