/**
© 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.
**/

#include <QTimerEvent>
#include <QMouseEvent>

#include <cmath>
#include "perspective_api.h"
#include "perspective_types.h"
#include "vue3d.h"
#include "API/Qt/Conversion.h"
#include "API/Qt/Utils.h"

#ifdef DEBUG
#include <QDebug>
#endif /* DEBUG */

inline int ArrdSup(const pfloat x)
/* Renvoi l'arrondi supérieur entier d'un nombre réél. */
{
    if (x < 0.0)
    {
        return static_cast<int>(x-0.5f);
    }
    return static_cast<int>(x+0.5f);
}

void CouleurNormal(const Perspective3D::Pvec3 &n, Perspective3D::PCouleur &c)
/* Génère une couleur depuis le vecteur normal donné en argument */
{
    c.defR(ArrdSup(fabs(n.X())*255.));
    c.defV(ArrdSup(fabs(n.Y())*255.));
    c.defB(ArrdSup(fabs(n.Z())*255.));
}

VueModele3D::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, bool affichage_points_accroche, Perspective3D::vues2D_t vue_reference, QWidget *parent) :
    #ifdef UTILISE_QOPENGLWIDGET
        QOpenGLWidget(parent)
    #else
        QGLWidget(parent)
    #endif // UTILISE_QOPENGLWIDGET
    , animation(anim), paramsGL(params_gl), paramsPGL(params_pgl), PerspGL(nullptr), vue_reference_affichage(vue_reference), VectSuppr(vect_suppr), termine_initialise(false), termine_perspective(false)
{
#ifndef UTILISE_QOPENGLWIDGET
    setAttribute(Qt::WA_PaintOnScreen);
    setAttribute(Qt::WA_NoSystemBackground);
    setAutoBufferSwap(true);
#else
//    initializeOpenGLFunctions();
#endif // UTILISE_QOPENGLWIDGET

//    setAttribute(Qt::WA_TranslucentBackground);
//    setAttribute(Qt::WA_OpaquePaintEvent);
//    setAutoFillBackground(false);

    setMouseTracking(true); // Permet de recevoir les évenements 'mouseEvent' sans clic de la souris.

    SelectionPoint = SelectionSegment = SelectionSurface = IDNUL;
    def_m_Echelle = ECHELLE_DEFAUT_PGL;
    sav_axeRotationCam = def_axeRotationCam = Perspective3D::Pvec3(-16.5, -45, 0.);
    affiche_accroche = affichage_points_accroche;
    affiche_accroche_sommets = true;

    setMinimumSize(300, 250);

    sav_PositionSouris_orig = PositionSouris_orig = Perspective3D::Ppoint2D(0., 0.);

    setContextMenuPolicy(Qt::DefaultContextMenu);
    affiche_outils_3d = outils_menu_contextuel;
    MenuContextuel.setParent(this);
    MenuContextuel.close();

//    defAffichage(1, false);

    id_timer_animation = -1;
    id_timer_animation_2 = -1;
    id_timer_exec = -1;
    temps_timer_exec = 40;

    DelaiChrono_animations = 30; /* 30 pour ~33 FPS lors des transitions. */

    def_couleur_fond[0] = def_couleur_fond[1] = def_couleur_fond[2] = 0;
    def_opacite_solide = params_pgl.opacite_solide;
}

VueModele3D::~VueModele3D()
{
    if (id_timer_animation != -1)
    {
        const int id_timer = id_timer_animation;
        id_timer_animation = -1;
        killTimer(id_timer);
    }

    if (id_timer_animation_2 != -1)
    {
        const int id_timer = id_timer_animation_2;
        id_timer_animation_2 = -1;
        killTimer(id_timer);
    }

    if (ValidePerspGL())
    {
        makeCurrent(); /* On doit être dans un contexte OpenGL valide pour détruire les objets GL. */
        PGL::Perspective_GL *persp_gl = PerspGL;
        PerspGL = nullptr;
        delete persp_gl;
    }
}

void VueModele3D::defAffichage(int id, bool updategl)
/* Change le mode d'affichage */
{
    if (!ValidePerspGL())
    {
        return;
    }

    paramsPGL.type_affichage = id;
    PerspGL->defAffichage(id);
    if (updategl)
    {
        MiseAJourAffichage();
    }
}

void VueModele3D::defOpacite(int val, bool animation)
/* Assigne le niveau de transparence, met à jour l'affichage si demandé */
{
    if (!ValidePerspGL())
    {
        return;
    }
    if (animation)
    {
        def_opacite_solide = val;
    }
    else
    {
        def_opacite_solide = val;
        paramsPGL.opacite_solide = val;
        PerspGL->defOpaciteSolide(val);
        MiseAJourAffichage();
    }
}

void VueModele3D::defCouleurFond(QRgb couleur, bool animation)
/* Assigne le niveau de transparence, met à jour l'affichage si demandé */
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_couleur_fond[0] = PGL_NORM_BYTE_FLOAT(qRed(couleur));
    def_couleur_fond[1] = PGL_NORM_BYTE_FLOAT(qGreen(couleur));
    def_couleur_fond[2] = PGL_NORM_BYTE_FLOAT(qBlue(couleur));

    if (animation)
    {
    }
    else
    {
        PerspGL->defCouleurFond(PGL::PCouleur_GL(qRgbP(couleur)));
        MiseAJourAffichage();
    }
}

void VueModele3D::defCouleursScene(QRgb couleur_sommets, QRgb couleur_segments, QRgb couleur_imprimante, QRgb couleur_reperes_sommets, QRgb couleur_reperes_segments, QRgb couleur_reperes_faces)
{
    if (ValidePerspGL())
    {
        PerspGL->defCouleursScene(PGL::PCouleur_GL(qRgbP(couleur_sommets)),
                                  PGL::PCouleur_GL(qRgbP(couleur_segments)),
                                  PGL::PCouleur_GL(qRgbP(couleur_imprimante)),
                                  PGL::PCouleur_GL(qRgbP(couleur_reperes_sommets)),
                                  PGL::PCouleur_GL(qRgbP(couleur_reperes_segments)),
                                  PGL::PCouleur_GL(qRgbP(couleur_reperes_faces)));
    }
}

int VueModele3D::IdCouleurScene() const
{
    if (!ValidePerspGL())
    {
        return 0;
    }
    return PerspGL->IdCouleurScene();
}

void VueModele3D::defIdCouleurScene(int val, bool updategl)
/* Met à jour la couleur de la scène. */
{
    if (!ValidePerspGL())
    {
        return;
    }

    PerspGL->defIdCouleurScene(val);

    if (updategl)
    {
        MiseAJourAffichage();
    }
}

void VueModele3D::ReInitEchelle(bool anim)
{
    if (!ValidePerspGL())
    {
        return;
    }
    if (anim)
    {
        def_m_Echelle = ECHELLE_DEFAUT_PGL;
    }
    else
    {
        PerspGL->defEchelleAffichage(ECHELLE_DEFAUT_PGL, false);
    }
}

void VueModele3D::ReinitSelection()
{
    if (!ValidePerspGL())
    {
        return;
    }
    SelectionPoint = IDNUL;
    SelectionSegment = IDNUL;
    SelectionSurface = IDNUL;

    if (termine_perspective)
    {
        if (affiche_accroche)
        {
            AffichagePoint(IDNUL);
            AffichageCentreSurface(IDNUL);
        }

        emit SelectioneSurface(0, false, false, true);
        emit SelectionePoint(0, false, false);
    }
}

void VueModele3D::CentreSur(const Perspective3D::Pvec3 &p, bool rotation_axe, bool change_echelle, const pfloat anglex, const pfloat angley, const pfloat anglez)
/* Centre la vue sur un point particulier du modèle. */
{
    if (PISNAN(p.X()) || PISNAN(p.Y()) || PISNAN(p.Z()))
    {
        return;
    }

    if (!Scene || !PerspGL)
    {
        return;
    }

    Perspective3D::Pvec3 pos((p.X()-Scene->Centre().X()), (p.Y()-Scene->Centre().Y()), (p.Z()-Scene->Centre().Z()));
    pos *= -(Scene->Echelle());

    if (change_echelle)
    {
        ReInitEchelle();
    }
    else
    {
        def_m_Echelle = PerspGL->EchelleAffichage();
    }

    def_DeplacementCam.defCoords(pos.X(), pos.Y(), pos.Z());

    if (!paramsGL.mode_ortho)
    {
//        def_DeplacementCam.defZ(def_DeplacementCam.Z()+1.0f);
    }

    if (rotation_axe)
    {
        def_axeRotationCam.defCoords(anglex, angley, anglez);
    }
    else
    {
        if (!paramsGL.mode_ortho)
        {
            def_axeRotationCam.defCoords(-5., 0., 0.);
        }
    }

    InitAnimationSolide();
}

void VueModele3D::AssigneVue(const Perspective3D::Pvec3 &angle, const Perspective3D::Pvec3 &position)
/* Positionne la vue directement, sans animation et met à jour l'affichage. */
{
    if (!ValidePerspGL())
    {
        return;
    }
    PerspGL->defRotationCamera(PGL::PVertex_GL(angle));
    PerspGL->defTranslationCamera(position.X(), position.Y(), false);
    PerspGL->defAvanceCamera(position.Z(), false);
    MiseAJourAffichage(true);
}

void VueModele3D::VuePerspective()
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_axeRotationCam.defCoords(-16.5, 45., 0.);
    ReInitEchelle();
    ReinitSelection();

    if (paramsGL.mode_ortho)
    {
        def_DeplacementCam.defCoords(0., 0., 0.);
    }
    else
    {
        def_DeplacementCam.defCoords(0., 0., 1.5);
    }

    InitAnimationSolide();
}

void VueModele3D::VuePerspectiveInv()
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_axeRotationCam.defCoords(16.5 , -135,  0.);
    ReInitEchelle();
    ReinitSelection();

    if (paramsGL.mode_ortho)
    {
        def_DeplacementCam.defCoords(0., 0., 0.);
    }
    else
    {
        def_DeplacementCam.defCoords(0., 0., 1.5);
    }

    InitAnimationSolide();
}

void VueModele3D::VueFace()
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_axeRotationCam.defCoords(0., 0., 0.);
    ReInitEchelle();
    ReinitSelection();

    if (paramsGL.mode_ortho)
    {
        def_DeplacementCam.defCoords(0., 0., 0.);
    }
    else
    {
        def_DeplacementCam.defCoords(0., 0., 2.);
    }

    InitAnimationSolide();
}

void VueModele3D::VueArriere()
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_axeRotationCam.defCoords(0., 180., 0.);
    ReInitEchelle();
    ReinitSelection();

    if (paramsGL.mode_ortho)
    {
        def_DeplacementCam.defCoords(0., 0., 0.);
    }
    else
    {
        def_DeplacementCam.defCoords(0., 0., 2.);
    }

    InitAnimationSolide();
}

void VueModele3D::VueCote()
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_axeRotationCam.defCoords(0., 90., 0.);
    ReInitEchelle();
    ReinitSelection();

    if (paramsGL.mode_ortho)
    {
        def_DeplacementCam.defCoords(0., 0., 0.);
    }
    else
    {
        def_DeplacementCam.defCoords(0., 0., 2.);
    }

    InitAnimationSolide();
}

void VueModele3D::VueCoteGauche()
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_axeRotationCam.defCoords(0., -90., 0.);
    ReInitEchelle();
    ReinitSelection();

    if (paramsGL.mode_ortho)
    {
        def_DeplacementCam.defCoords(0., 0., 0.);
    }
    else
    {
        def_DeplacementCam.defCoords(0., 0., 2.);
    }

    InitAnimationSolide();
}

void VueModele3D::VueHaut()
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_axeRotationCam.defCoords(-90., 0., 0.);
    ReInitEchelle();
    ReinitSelection();

    if (paramsGL.mode_ortho)
    {
        def_DeplacementCam.defCoords(0., 0., 0.);
    }
    else
    {
        def_DeplacementCam.defCoords(0., 0., 2.);
    }

    InitAnimationSolide();
}

void VueModele3D::VueDessous()
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_axeRotationCam.defCoords(90., 0., 0.);
    ReInitEchelle();
    ReinitSelection();

    if (paramsGL.mode_ortho)
    {
        def_DeplacementCam.defCoords(0., 0., 0.);
    }
    else
    {
        def_DeplacementCam.defCoords(0., 0., 2.);
    }

    InitAnimationSolide();
}


void VueModele3D::ForceVue(Perspective3D::vues2D_t vue)
{
    if (vue == Perspective3D::vues2D_t::VUEFACE)
    {
        VueFace();
    }
    else if (vue == Perspective3D::vues2D_t::VUECOTE)
    {
        VueCote();
    }
    else if (vue == Perspective3D::vues2D_t::VUEHAUT)
    {
        VueHaut();
    }
    else if (vue == Perspective3D::vues2D_t::VUEMULT)
    {
        VuePerspective();
    }
}

#ifdef ACTIVE_QPAINTER_POST_VUE3D
QPainter &VueModele3D::PainterWGL()
{
    return painter_glw;
}
#endif // ACTIVE_QPAINTER_POST_VUE3D

void VueModele3D::ArretAnimations()
{
//    if (!ValidePerspGL()) { return; }

    if (id_timer_animation != -1)
    {
        const int id_timer = id_timer_animation;
        id_timer_animation = -1;
        killTimer(id_timer);
    }
    if (id_timer_animation_2 != -1)
    {
        const int id_timer = id_timer_animation_2;
        id_timer_animation_2 = -1;
        killTimer(id_timer);
    }
}

void VueModele3D::BloqueExecAnimation(int tempo_supplementaire)
/* Bloque l'exécution du programme le temps que l'animation du solide en cours se termine. */
{
//    if (!ValidePerspGL()) { return; }

    if (!animation.Anime())
    {
        return;
    }

    while ((id_timer_animation != -1))
    {
        if (!animation.Sommeil(50))
        {
            break;
        }
    }

    if (tempo_supplementaire)
    {
        animation.Sommeil(tempo_supplementaire);
    }
}

void VueModele3D::AnimeRotationScene()
/* Rotation 360° (bloquante) de la scène */
{
    const unsigned int tempo = animation.TempoReference()*5;

    if (animation.Anime()) /* Sera contrôlé à chaque fois au cas où, si pendant l'exécution des animations l'utilisateur ait fermé la fenêtre. */
    {
        VuePerspective();
        BloqueExecAnimation(tempo);
    }

    if (animation.Anime())
    {
        VueHaut();
        BloqueExecAnimation(tempo);
    }

    if (animation.Anime())
    {
        VuePerspectiveInv();
        BloqueExecAnimation(tempo);
    }

    if (animation.Anime())
    {
        VueFace();
        BloqueExecAnimation(tempo);
    }

    if (animation.Anime())
    {
        VueCote();
        BloqueExecAnimation(tempo);
    }

    if (animation.Anime())
    {
        VuePerspective();
        BloqueExecAnimation(tempo);
    }
}

void VueModele3D::defEclairageDynamique(bool etat)
{
    if (!ValidePerspGL())
    {
        return;
    }
    PerspGL->defEclairageDynamique(etat);
}

void VueModele3D::defAffichageAccroche(bool etat)
{
    if (!ValidePerspGL())
    {
        return;
    }
    affiche_accroche = etat;
    if (!etat)
    {
        AffichageCentreSurface(IDNUL);
        AffichagePoint(IDNUL);
    }
}

const std::string& VueModele3D::InitialisationShadersModele(const char *source_vertex_shader, const char *source_pixel_shader, const unsigned int contexte)
{
    if (ValidePerspGL())
    {
#ifndef UTILISE_QOPENGLWIDGET
        makeCurrent();
#endif // UTILISE_QOPENGLWIDGET

        const std::string &r = PerspGL->InitialisationShaders(source_vertex_shader, source_pixel_shader, contexte);
////#ifndef UTILISE_QOPENGLWIDGET
//        PerspGL->defTailleAffichage(width(), height()); /* Forcera le re-calcul des matrices de projection. */
////#endif // UTILISE_QOPENGLWIDGET

#ifdef DEBUG /* Tests dump mémoire des shaders */
//        if (contexte == PGL::CONTEXTE_FILAIRE)
//        {
//            if (!PerspGL->DumpShaderBin(std::string("shader_filaire.glx"), contexte))
//            {
//                qDebug() << "Erreur lors d'enregistrement du dump du shader filaire...";
//            }
//        }
//        else if (contexte == PGL::CONTEXTE_MODELE)
//        {
//            if (!PerspGL->DumpShaderBin(std::string("shader_modele.glx"), contexte))
//            {
//                qDebug() << "Erreur lors d'enregistrement du dump du shader du modèle...";
//            }
//        }
//        else if (contexte == PGL::CONTEXTE_POST)
//        {
//            if (!PerspGL->DumpShaderBin(std::string("shader_post.glx"), contexte))
//            {
//                qDebug() << "Erreur lors d'enregistrement du dump du shader de post-traitement...";
//            }
//        }
#endif // DEBUG

        return r;
    }
    static const std::string str_nil("(nul)");
    return str_nil;
}

void VueModele3D::ReinitPerspGL(const Perspective3D::Perspective *perspective)
{
    PGL::Perspective_GL *pgl_ptr = PerspGL;
    doneCurrent();
    PerspGL = nullptr; /* Avant la destruction pour bloquer les éventuels appels futurs. */
    if (pgl_ptr)
    {
        delete pgl_ptr;
    }
    ArretAnimations();

//#ifdef DEBUG
//    qDebug() << "\n\nRéinitialisation PerspectiveGL !!!";
//#endif // DEBUG

//    if (context()->create())
//    {
//        qDebug() << "Succès de création d'un nouveau contexte !!!";
//    }
    makeCurrent();
    GenPerspGL();
    PerspGL->defTailleAffichage(width(), height());
    TransfertModele(perspective);
}

void VueModele3D::ReinitPerspGL(const Perspective3D::PScene3D *scene3d)
{
    PGL::Perspective_GL *pgl_ptr = PerspGL;
    doneCurrent();
    PerspGL = 0; /* Avant la destruction pour bloquer les éventuels appels futurs. */
    if (pgl_ptr)
    {
        delete pgl_ptr;
    }
    ArretAnimations();

//#ifdef DEBUG
//    qDebug() << "\n\nRéinitialisation PerspectiveGL !!!";
//#endif // DEBUG

//    if (context()->create())
//    {
//        qDebug() << "Succès de création d'un nouveau contexte !!!";
//    }
    makeCurrent();
    GenPerspGL();
    PerspGL->defTailleAffichage(width(), height());
    TransfertModele(scene3d);
}

void VueModele3D::GenPerspGL()
{
    etat_maj_modeles = PGL::MODELE_NUL;

    PerspGL = new PGL::Perspective_GL(paramsGL, paramsPGL);

    PerspGL->defEchelleAffichage(ECHELLE_DEFAUT_PGL, false);
    defIdCouleurScene(paramsPGL.idcouleur_solide, false);

    def_couleur_fond[0] = paramsGL.couleur_fond.R();
    def_couleur_fond[1] = paramsGL.couleur_fond.V();
    def_couleur_fond[2] = paramsGL.couleur_fond.B();

    PerspGL->InitialisationAffichage();
    PerspGL->defCouleurFond(PGL::PCouleur_GL(0, 0, 0, 1.0));

    if (paramsPGL.mode_2d)
    {
        VueFace();
    }
    else
    {
        ForceVue(vue_reference_affichage);
//        VuePerspective();
    }
}

void VueModele3D::initializeGL()
{
    if (!PGL::Extensions_GL::Init())
    {
#ifdef DEBUG
        qDebug() << "Erreur de chargement d'au moins une extension GL !" << ":" << context();
#endif // DEBUG
    }
    else
    {
#ifdef DEBUG
//        qDebug() << "Initialisation des extensions GL valide !";
#endif // DEBUG
    }

    GenPerspGL();
    termine_initialise = true;
}

void VueModele3D::resizeGL(int largeur, int hauteur)
{
    if (!ValidePerspGL())
    {
        return;
    }

//    qDebug() << "Taille d'affichage : " << largeur << ":" << hauteur;
//    qDebug() << "Taille écran du contexte OpenGL : " << context()->screen()->size();

    PerspGL->defTailleAffichage(largeur, hauteur);
}

void VueModele3D::defModeAffichage(bool ortho, bool force_redim)
{
    if (!ValidePerspGL())
    {
        return;
    }
    if (paramsPGL.mode_2d)
    {
        ortho = true;
    }

//    qDebug() << "defModeAffichage :: ortho=" << ortho << ", " << paramsGL.mode_ortho;


    if (paramsGL.mode_ortho != ortho)
    {
        paramsGL.mode_ortho = ortho;
        PerspGL->defTypeProjection(paramsGL.mode_ortho);
        PerspGL->defTailleAffichage(width(), height());

        PerspGL->IdentiteMatrices();

        PerspGL->defTranslationCamera(0., 0., false);
        PerspGL->defAvanceCamera(0., false);
        PerspGL->defEchelleAffichage(ECHELLE_DEFAUT_PGL);
    }
    else
    {
        if (force_redim)
        {
            PerspGL->defTailleAffichage(width(), height());
            PerspGL->IdentiteMatrices();
        }
    }

//    resizeGL(width(), height());
}

void VueModele3D::defLissageAffichage3D(bool etat)
{
    if (paramsPGL.lissage != etat)
    {
        paramsPGL.lissage = etat;
        PerspGL->defEtatLissage(etat);
//        MiseaJourModele(true, true, true);
    }
}

void VueModele3D::paintGL()
{
//    qDebug() << "Contexte : " << context();
//    qDebug() << "VueModele3D::paintGL()";

//    makeCurrent();

#ifdef ACTIVE_QPAINTER_POST_VUE3D
    painter_glw.begin(this);
    painter_glw.save();

    painter_glw.beginNativePainting();
#endif // ACTIVE_QPAINTER_POST_VUE3D

    if (ValidePerspGL())
    {
        PerspGL->InitialisationEtatAffichage();
        PerspGL->MiseaJourVBO(etat_maj_modeles);
        PerspGL->IdentiteMatrices();
        PerspGL->AfficheScene();

#ifdef UTILISE_QOPENGLWIDGET
        context()->swapBuffers(context()->surface());
#endif // UTILISE_QOPENGLWIDGET
    }

#ifdef ACTIVE_QPAINTER_POST_VUE3D
    painter_glw.endNativePainting();
    painter_glw.restore();

    painter_glw.setRenderHint(QPainter::Antialiasing);
    painter_glw.setRenderHint(QPainter::TextAntialiasing);
    painter_glw.setCompositionMode(QPainter::CompositionMode_SourceOver);

    emit PaintGLPost();

    painter_glw.end();
#endif // ACTIVE_QPAINTER_POST_VUE3D

    etat_maj_modeles = PGL::MODELE_NUL;
}

#ifdef UTILISE_QOPENGLWIDGET
void VueModele3D::paintEvent(QPaintEvent *e)
{
//    qDebug() << "VueModele3D::paintEvent()";
//    makeCurrent();
//    PerspGL->MiseaJourVBO(etat_maj_modeles);
//    doneCurrent();

    QOpenGLWidget::paintEvent(e);
    e->accept();
}
#endif // UTILISE_QOPENGLWIDGET

void VueModele3D::MiseAJourAffichage(bool force)
{
    if (!ValidePerspGL())
    {
        return;
    }
//    qDebug() << "VueModele3D::MiseAJourAffichage(" << force << ")";
    if (!termine_initialise)
    {
        return;
    }
    if (force)
    {
        repaint();
        SommeilTempoQt(20);
        PerspGL->SyncAffichage();
    }
    else
    {
        update();
    }
}

void VueModele3D::MiseaJourModele(bool sommets, bool filaire, bool triangles)
{
    if (!ValidePerspGL())
    {
        return;
    }
    if (!termine_initialise)
    {
        return;
    }

//    qWarning() << "VueModele3D::MiseaJourModele(" << sommets << ", " << filaire << ", " << triangles << ")";

    unsigned int modeles = PGL::MODELE_NUL;
    if (sommets)
    {
        modeles |= PGL::MODELE_SOMMETS;
    }
    if (filaire)
    {
        modeles |= PGL::MODELE_FILAIRE;
    }
    if (triangles)
    {
        modeles |= PGL::MODELE_FACES;
    }

    etat_maj_modeles = modeles;
    MiseAJourAffichage();
}

void VueModele3D::defImprimante3D(bool active, float x, float y, float z)
{
    if (paramsPGL.mode_2d || !PerspGL)
    {
        return;
    }

    paramsPGL.defImprimante3D(active, x, y, z);
    PerspGL->defImprimante3D(active, x, y ,z);
}

void VueModele3D::xSuppr()
/* Vide le dessin. */
{
    if (!ValidePerspGL())
    {
        return;
    }
    Scene = nullptr;
    PerspGL->VideScene();
}

void VueModele3D::TransfertModele(const Perspective3D::Perspective *perspective)
{
    if (!perspective || !PerspGL)
    {
        return;
    }

#ifdef DEBUG
//    qDebug() << "VueModele3D::TransfertModele ( (Perspective)" << ((void*) perspective) << ")";
#endif // DEBUG

    Scene = perspective->SceneConstPtr();
    PerspGL->PrepareScene(Scene);

    if (perspective->FinGeneration())
    {
        termine_perspective = true;
    }

    MiseAJourAffichage();
}

void VueModele3D::TransfertModele(const Perspective3D::PScene3D *scene3d)
{
    if (!scene3d || !PerspGL)
    {
        return;
    }

#ifdef DEBUG
//    qDebug() << "VueModele3D::TransfertModele ( (PScene3D)" << ((void*) scene3d) << ")";
#endif // DEBUG

    Scene = scene3d;
    PerspGL->PrepareScene(scene3d);
    termine_perspective = true;
    MiseAJourAffichage();
}


void VueModele3D::Redim(int x, int y)
{
//    qDebug() << "Redim :: " << x << ":" << y;
    resizeGL(x, y);
}

void VueModele3D::RedimAuto()
{
    Redim(width(), height());
}

#ifndef UTILISE_QOPENGLWIDGET
void VueModele3D::resizeEvent(QResizeEvent *ev)
{
    Redim(ev->size().width(), ev->size().height());
    ev->accept();
}
#endif // UTILISE_QOPENGLWIDGET

void VueModele3D::BloqueAnimationSolide()
/* Sauvegarde l'état actuel pour bloquer l'animation (pour ensuite n'en jouer qu'une par exemple). */
{
    if (!ValidePerspGL())
    {
        return;
    }
    def_axeRotationCam = PerspGL->RotationCamera();

    const PGL::PCouleur_GL &couleur_fond = PerspGL->CouleurFond();
    def_couleur_fond[0] = couleur_fond.R();
    def_couleur_fond[1] = couleur_fond.V();
    def_couleur_fond[2] = couleur_fond.B();

    def_DeplacementCam = sav_DeplacementCam;
    def_m_Echelle = PerspGL->EchelleAffichage();
    def_opacite_solide = PerspGL->OpaciteSolide();
}

void VueModele3D::TermineAnimationSolide()
/* S'assure de la terminaison de l'animation par l'égalité avec les paramètres définis. */
{
    if (!ValidePerspGL())
    {
        return;
    }
    PerspGL->defCouleurFond(PGL::PCouleur_GL(def_couleur_fond[0], def_couleur_fond[1], def_couleur_fond[2]));
    PerspGL->defRotationCamera(def_axeRotationCam);
    sav_DeplacementCam = def_DeplacementCam;
    PerspGL->defEchelleAffichage(def_m_Echelle);
    PerspGL->defOpaciteSolide(def_opacite_solide);
    MiseAJourAffichage(true);
}

bool VueModele3D::InitAnimationSolide(int force_delai_chrono)
/* Démarre l'animation sur le solide. Renvoi true si l'animation a démarrée, sinon false (par exemple si une animation est déjà en cours). */
{
    if (!ValidePerspGL())
    {
        return false;
    }
    if (id_timer_animation == -1)
    {
        id_timer_animation = startTimer((force_delai_chrono != -1) ? force_delai_chrono : DelaiChrono_animations);
        return true;
    }
    return false;
}

bool VueModele3D::AnimationContour(const Perspective3D::PStdVectSegments3D &contour)
/* Anime un contour pour le mettre en évidence. */
{
    if (!ValidePerspGL())
    {
        return false;
    }
    if (id_timer_animation != -1)
    {
        return false;
    }

    if (id_timer_animation_2 != -1)
    {
        killTimer(id_timer_animation_2);
        id_timer_animation_2 = -1;
    }

//    if (id_timer_animation_2 == -1)
    {
        mutex_anim.lock();
        ids_contour_anim.clear();

        unsigned int n = contour.size();
        for(unsigned int i=0; i<n; ++i)
        {
            const Perspective3D::PSegment3D &l = contour[i];
//            ids_contour_anim.push_back(l.P1Const().Id());
            ids_contour_anim.push_back(l.V1());
        }
        mutex_anim.unlock();

        iter_segment_contour_anim = 0;
        id_sommet_contour = 0;
        id_timer_animation_2 = startTimer(15);
        return true;
    }
    return false;
}

bool VueModele3D::GereAnimationContour()
{
    if (!ValidePerspGL())
    {
        return false;
    }
    const unsigned int n_divisions = 25; /* Nombre de divisions pour l'animation le long de la ligne. */

    if (!ids_contour_anim.size() || (id_sommet_contour >= ids_contour_anim.size()))
    {
        PerspGL->SupprimeSommetRepere();
        return false;
    }

    PGL::PVertex_GL p1 = PerspGL->ConvPointGlobal(ids_contour_anim[id_sommet_contour]);
    PGL::PVertex_GL p2;

    if (id_sommet_contour < (ids_contour_anim.size()-1))
    {
        p2 = PerspGL->ConvPointGlobal(ids_contour_anim[id_sommet_contour+1]);
    }
    else
    {
        p2 = PerspGL->ConvPointGlobal(ids_contour_anim[0]);
    }

    const float m = (float) iter_segment_contour_anim / (float) n_divisions;
    const float fsx = p1.X() + ((p2.X() - p1.X()) * m);
    const float fsy = p1.Y() + ((p2.Y() - p1.Y()) * m);
    const float fsz = p1.Z() + ((p2.Z() - p1.Z()) * m);

    if (iter_segment_contour_anim == 0 && id_sommet_contour == 0)
    {
        PerspGL->defSommetRepere(PGL::PVertex_GL(fsx, fsy, fsz), true);
    }
    else
    {
        PerspGL->defSommetRepere(PGL::PVertex_GL(fsx, fsy, fsz), false);
    }

    if (iter_segment_contour_anim >= n_divisions)
    {
        iter_segment_contour_anim = 0;
        ++id_sommet_contour;
    }
    else
    {
        ++iter_segment_contour_anim;
    }

    MiseAJourAffichage();
    return true;
}

bool VueModele3D::GereAnimationSolide()
/* Gère les transitions et animations sur le solide. Renvoi true si l'animation est toujours en cours, sinon false. */
{
    int etat = 0;

    if (!ValidePerspGL())
    {
        return false;
    }

    /* Adapte la vue aux paramètres avec une animation: */

    static const float decalage_echelle = 0.1f; /* Gère l'échelle */
    pfloat diffechelle = def_m_Echelle - PerspGL->EchelleAffichage();

    if (diffechelle > decalage_echelle)
    {
//        qDebug() << "GereAnimationSolide() : +Echelle incomplète !";
        PerspGL->defEchelleAffichage(decalage_echelle, true);
    }
    else if (diffechelle < -decalage_echelle)
    {
//        qDebug() << "GereAnimationSolide() : -Echelle incomplète !";
        PerspGL->defEchelleAffichage(-decalage_echelle, true);
    }
    else
    {
        PerspGL->defEchelleAffichage(def_m_Echelle, false);
        ++etat;
    }

    /* Gère la transition de transparence: */
    const int decalage_opacite = 4;
    int diff_opacite = def_opacite_solide - PerspGL->OpaciteSolide();

    if (diff_opacite > decalage_opacite)
    {
        PerspGL->defOpaciteSolide(def_opacite_solide+decalage_opacite);
//        qDebug() << "GereAnimationSolide() : +Opacité incomplète !";
    }
    else if (diff_opacite < -decalage_opacite)
    {
        PerspGL->defOpaciteSolide(def_opacite_solide-decalage_opacite);
//        qDebug() << "GereAnimationSolide() : -Opacité incomplète !";
    }
    else
    {
        PerspGL->defOpaciteSolide(def_opacite_solide);
        ++etat;
    }

    /* Gère la transition de la couleur du fond */
    const GLcoord decalage_couleur_fond = 0.01175;
    int etat_couleurs = 0;

    const PGL::PCouleur_GL &couleur_gl = PerspGL->CouleurFond();

    GLcoord couleur_fond_tmp[3];
    couleur_fond_tmp[0] = couleur_gl.R();
    couleur_fond_tmp[1] = couleur_gl.V();
    couleur_fond_tmp[2] = couleur_gl.B();

    int diff_couleur_fond_r = def_couleur_fond[0] - couleur_fond_tmp[0];
    int diff_couleur_fond_v = def_couleur_fond[1] - couleur_fond_tmp[1];
    int diff_couleur_fond_b = def_couleur_fond[2] - couleur_fond_tmp[2];

    if (diff_couleur_fond_r > decalage_couleur_fond)
    {
        couleur_fond_tmp[0] += decalage_couleur_fond;
    }
    else if (diff_couleur_fond_r < -(decalage_couleur_fond))
    {
        couleur_fond_tmp[0] -= decalage_couleur_fond;
    }
    else
    {
        couleur_fond_tmp[0] = def_couleur_fond[0];
        ++etat_couleurs;
    }

    if (diff_couleur_fond_v > decalage_couleur_fond)
    {
        couleur_fond_tmp[1] += decalage_couleur_fond;
    }
    else if (diff_couleur_fond_v < -(decalage_couleur_fond))
    {
        couleur_fond_tmp[1] -= decalage_couleur_fond;
    }
    else
    {
        couleur_fond_tmp[1] = def_couleur_fond[1];
        ++etat_couleurs;
    }

    if (diff_couleur_fond_b > decalage_couleur_fond)
    {
        couleur_fond_tmp[2] += decalage_couleur_fond;
    }
    else if (diff_couleur_fond_b < -(decalage_couleur_fond))
    {
        couleur_fond_tmp[2] -= decalage_couleur_fond;
    }
    else
    {
        couleur_fond_tmp[2] = def_couleur_fond[2];
        ++etat_couleurs;
    }

    if (etat_couleurs == 3) // Toutes les couleurs valides.
    {
        ++etat;
    }
    else
    {
//        qDebug() << "GereAnimationSolide() : Couleur imcomplète";
//        qDebug() << "Couleurs : Couleur incomplète : " << couleur_fond_tmp[0] << couleur_fond_tmp[1] << couleur_fond_tmp[2] << " :: " << def_couleur_fond[0] << def_couleur_fond[1] << def_couleur_fond[2];
    }

    PerspGL->defCouleurFond(PGL::PCouleur_GL(couleur_fond_tmp[0], couleur_fond_tmp[1], couleur_fond_tmp[2], 1.0));

    /* Gère le déplacement */
    static const float decalage_deplacement = 0.1f;
    const PGL::PVertex_GL &pos_camera = PerspGL->TranslationCamera();
    Perspective3D::Pvec3 depl = def_DeplacementCam - pos_camera;

    if (depl.X() > decalage_deplacement)
    {
//        qDebug() << "GereAnimationSolide() : Translation X incomplète !";
        PerspGL->defTranslationCamera(decalage_deplacement, 0., true);
    }
    else if (depl.X() < -decalage_deplacement)
    {
//        qDebug() << "GereAnimationSolide() : Translation -X incomplète !";
        PerspGL->defTranslationCamera(-decalage_deplacement, 0., true);
    }
    else
    {
        PerspGL->defTranslationCamera(def_DeplacementCam.X(), PerspGL->TranslationCamera().Y(), false);
        ++etat;
    }

    if (depl.Y() > decalage_deplacement)
    {
//        qDebug() << "GereAnimationSolide() : Translation Y incomplète !";
        PerspGL->defTranslationCamera(0., decalage_deplacement, true);
    }
    else if (depl.Y() < -decalage_deplacement)
    {
//        qDebug() << "GereAnimationSolide() : Translation -Y incomplète !";
        PerspGL->defTranslationCamera(0., -decalage_deplacement, true);
    }
    else
    {
        PerspGL->defTranslationCamera(PerspGL->TranslationCamera().X(), def_DeplacementCam.Y(), false);
        ++etat;
    }

    if (depl.Z() > decalage_deplacement)
    {
//        qDebug() << "GereAnimationSolide() : Translation Z incomplète !";
        PerspGL->defAvanceCamera(decalage_deplacement, true);
    }
    else if (depl.Z() < -decalage_deplacement)
    {
//        qDebug() << "GereAnimationSolide() : Translation -Z incomplète !";
        PerspGL->defAvanceCamera(-decalage_deplacement, true);
    }
    else
    {
        PerspGL->defAvanceCamera(def_DeplacementCam.Z(), false);
        ++etat;
    }

    /* Gère la rotation */
    static const float decalage_angle = 10.f;
    const PGL::PVertex_GL &axeRotationCam = PerspGL->RotationCamera();
    Perspective3D::Pvec3 rot = def_axeRotationCam - axeRotationCam;

//    qDebug() << "Reste à rôter: " << rot.X() << ", " << rot.Y() << ", " << rot.Z() << "(" << def_axeRotationCam.X() << "," << def_axeRotationCam.Y() << ", " << def_axeRotationCam.Z() << " :: " << axeRotationCam.X() << ", " << axeRotationCam.Y() << ", " << axeRotationCam.Z();

    if (rot.X() > decalage_angle)
    {
//        qDebug() << "GereAnimationSolide() : Rotation +X incomplète !";
        PerspGL->defRotationCameraX(axeRotationCam.X() + decalage_angle);
    }
    else if (rot.X() < -decalage_angle)
    {
//        qDebug() << "GereAnimationSolide() : Rotation -X incomplète !";
        PerspGL->defRotationCameraX(axeRotationCam.X() - decalage_angle);
    }
    else
    {
        PerspGL->defRotationCameraX(def_axeRotationCam.X());
        ++etat;
    }

    if (rot.Y() > decalage_angle)
    {
//        qDebug() << "GereAnimationSolide() : Rotation +Y incomplète !";
        PerspGL->defRotationCameraY(axeRotationCam.Y() + decalage_angle);
    }
    else if (rot.Y() < -decalage_angle)
    {
//        qDebug() << "GereAnimationSolide() : Rotation -Y incomplète !";
        PerspGL->defRotationCameraY(axeRotationCam.Y() - decalage_angle);
    }
    else
    {
        PerspGL->defRotationCameraY(def_axeRotationCam.Y());
        ++etat;
    }

    if (rot.Z() > decalage_angle)
    {
//        qDebug() << "GereAnimationSolide() : Rotation +Z incomplète !";
        PerspGL->defRotationCameraZ(axeRotationCam.Z() + decalage_angle);
    }
    else if (rot.Z() < -decalage_angle)
    {
//        qDebug() << "GereAnimationSolide() : Rotation -Z incomplète !";
        PerspGL->defRotationCameraZ(axeRotationCam.Z() - decalage_angle);
    }
    else
    {
        PerspGL->defRotationCameraZ(def_axeRotationCam.Z());
        ++etat;
    }

//    qDebug() << "Animation solide : " << id_timer_animation << " etat : " << etat;

    if (etat >= 9 && id_timer_animation != -1) /* Fin de l'animation. */
    {
        TermineAnimationSolide();
        sav_axeRotationCam = PerspGL->RotationCamera();
        sav_DeplacementCam = def_DeplacementCam;

//        std::cout << "Vue 3D: Fin normale du timer: " << etat << std::endl;
        return false;
    }
    return true; // L'animation doit continuer après.
}

bool VueModele3D::TermineShaderPost()
{
    if (id_timer_exec != -1)
    {
        killTimer(id_timer_exec);
        id_timer_exec = -1;
        return true;
    }
    return false;
}

bool VueModele3D::ExecShaderPost()
{
    if (!paramsGL.active_shaders)
    {
        return false;
    }
    if (id_timer_exec == -1 && temps_timer_exec > 0)
    {
        id_timer_exec = startTimer(temps_timer_exec); /* Timer de mise à jour de l'affichage independamment des animations (appliqué uniquement sur le post-traitement). */
        return true;
    }
    return false;
}

void VueModele3D::defTempoShaderPost(int t)
{
    if (t <= 0)
    {
        temps_timer_exec = 0;
    }
    else
    {
        temps_timer_exec = (int) (((1.0 / ((float) t)) * 1000.0) + 0.5);
    }
    if (TermineShaderPost())
    {
        ExecShaderPost();
    }
}

void VueModele3D::closeEvent(QCloseEvent *ev)
{
    TermineShaderPost();
    ev->accept();
}

void VueModele3D::timerEvent(QTimerEvent *ev)
{
    if (ev->timerId() == id_timer_animation) // Timer animation.
    {
        if (!GereAnimationSolide())
        {
            const int id_timer = id_timer_animation;
            id_timer_animation = -1;
            killTimer(id_timer);
        }
    }
    else if (ev->timerId() == id_timer_animation_2)
    {
        if (!GereAnimationContour())
        {
            const int id_timer = id_timer_animation_2;
            id_timer_animation_2 = -1;
            killTimer(id_timer);
        }
    }
    else if (ev->timerId() == id_timer_exec)
    {
        if (PerspGL && paramsGL.active_shaders)
        {
            PerspGL->ExecShaderPost(false);
        }
    }
    else
    {
    }
    MiseAJourAffichage();
    ev->ignore();
}

void VueModele3D::CalCAxeRotation(QPoint &pos)
{
    if (!ValidePerspGL())
    {
        return;
    }

    if (!paramsPGL.mode_2d) // Pas de rotation en mode 2D
    {
        Perspective3D::Ppoint2D delta = PositionSouris_orig - Perspective3D::Ppoint2D(pos.x(), pos.y());
//        delta *= 0.8; // Sensibilité de la souris.
        delta *= 0.65; // Sensibilité de la souris.
        PGL::PVertex_GL axeRotationCam = (sav_axeRotationCam + PGL::PVertex_GL(delta.Y(), delta.X(), 0.0) * .3333333333333333); // +delta/3

        if (axeRotationCam.X() > 90.)
        {
            axeRotationCam.defX(90.);
        }
        if (axeRotationCam.X() < -90.)
        {
            axeRotationCam.defX(-90.);
        }

        if (axeRotationCam.Y() < -180.0)
        {
            axeRotationCam.defY(axeRotationCam.Y() + 360.);
        }

        if (axeRotationCam.Y() > 180.0)
        {
            axeRotationCam.defY(axeRotationCam.Y() - 360.);
        }
        PerspGL->defRotationCameraX(fmod(axeRotationCam.X(), 360.));
        PerspGL->defRotationCameraY(fmod(axeRotationCam.Y(), 360.));
        PerspGL->defRotationCameraZ(fmod(axeRotationCam.Z(), 360.));
    }
}

void VueModele3D::contextMenuEvent(QContextMenuEvent *ev)
{
    MenuContextuel.clear();
    bool affiche_menu = false;

    if (SelectionSurface != IDNUL)
    {
        MenuContextuel.addAction(QIcon(":/icones/suppr_surface.png"), tr("Masquer la surface") + " " + QString::number(SelectionSurface), this, SLOT(SupprimeSurface()));
        affiche_menu = true;
    }
    else if (SelectionPoint != IDNUL)
    {
        MenuContextuel.addAction(QIcon(":/icones/suppr_point.png"), tr("Masquer le sommet") + " " + QString::number(SelectionPoint), this, SLOT(SupprimePoint()));
        affiche_menu = true;
    }

    if (VectSuppr.size())
    {
        MenuContextuel.addAction(QIcon(":/icones/annuler.png"), tr("Annuler"), this, SLOT(Restaure()));
        affiche_menu = true;
    }


    if (affiche_outils_3d)
    {
        const bool affiche_outils_3d_export = true;

        affiche_menu = true;
        MenuContextuel.addSeparator();
        MenuContextuel.addAction(QIcon(":/icones/cube_face.png"), tr("Vue de face"), this, SLOT(VueFace_M()));
        MenuContextuel.addAction(QIcon(":/icones/cube_cote.png"), tr("Vue de côté"), this, SLOT(VueCote_M()));
        MenuContextuel.addAction(QIcon(":/icones/cube_haut.png"), tr("Vue de dessus"), this, SLOT(VueHaut_M()));
        MenuContextuel.addAction(QIcon(":/icones/cube_perspective.png"), tr("Vue oblique"), this, SLOT(VueOblique_M()));
        MenuContextuel.addAction(QIcon(":/icones/animation.png"), tr("Animer"), this, SLOT(Animation_M()));
        MenuContextuel.addSeparator();
        if (affiche_outils_3d_export)
        {
//            MenuContextuel.addAction(QIcon(":/icones/export.png"), tr("Exporter la scène..."), this, SLOT(ExportSolide()));
//            MenuContextuel.addAction(QIcon(":/icones/export_script.png"), tr("Enregistrer le script.."), this, SLOT(ExportScript()));
            MenuContextuel.addAction(QIcon(":/icones/export_image.png"), tr("Enregistrer l'image..."), this, SLOT(ExportImage()));
#ifdef SUPPORT_GIF
            MenuContextuel.addAction(QIcon(":/icones/export_animation.png"), tr("Enregistsrer animation..."), this, SLOT(ExportAnimation()));
#endif // SUPPORT_GIF
            MenuContextuel.addAction(QIcon(":/icones/imprimer.png"), tr("Imprimer..."), this, SLOT(Imprimer()));
        }

        MenuContextuel.addSeparator();
//        MenuContextuel.addAction(QIcon(":/icones/parametres.png"), tr("Affichage"), this, SLOT(ConfigAffichage(0)));
        QMenu *menu_affichage = MenuContextuel.addMenu(QIcon(":/icones/projections1.png"), "Affichage");
        menu_affichage->addAction(QIcon(":/icones/parametres.png"), tr("Paramètres"), this, SLOT(ConfigAffichage_0()));
        menu_affichage->addAction(QIcon(":/icones/parametres_shaders.png"), tr("Style visuel"), this, SLOT(ConfigAffichage_1()));
        menu_affichage->addAction(QIcon(":/icones/parametres_couleurs.png"), tr("Couleurs"), this, SLOT(ConfigAffichage_2()));
        menu_affichage->addSeparator();
        menu_affichage->addAction(QIcon(":/icones/projections1.png"), tr("Scène"), this, SLOT(ChangeTypeAffichage_0()));
        menu_affichage->addAction(QIcon(":/icones/projections0.png"), tr("Solide"), this, SLOT(ChangeTypeAffichage_1()));
        menu_affichage->addAction(QIcon(":/icones/arretes.png"), tr("Arrêtes"), this, SLOT(ChangeTypeAffichage_2()));
        menu_affichage->addAction(QIcon(":/icones/filaire.png"), tr("Filaire"), this, SLOT(ChangeTypeAffichage_3()));

        MenuContextuel.addAction(QIcon(":/icones/proprietes3d.png"), tr("Proprietés"), this, SLOT(ProprietesSolide()));
    }

    if (affiche_menu)
    {
        QPoint pos_m = ev->pos()-QPoint(15, MenuContextuel.height()/2);
        if (pos_m.x() < 0)
        {
            pos_m.setX(0);
        }
        if (pos_m.y() < 0)
        {
            pos_m.setY(0);
        }
        MenuContextuel.popup(pos_m);
    }
    else
    {
        MenuContextuel.close();
    }

    ev->accept();
}

void VueModele3D::Restaure()
{
    emit(Restaure_S());
    MenuContextuel.close();
}

void VueModele3D::SupprimeSurface()
{
    emit(SupprimeSurface_S(SelectionSurface));
    MenuContextuel.close();
}

void VueModele3D::SupprimePoint()
{
    emit(SupprimePoint_S(SelectionPoint));
    MenuContextuel.close();
}

void VueModele3D::VueFace_M()
{
    MenuContextuel.close();
    VueFace();
}

void VueModele3D::VueCote_M()
{
    MenuContextuel.close();
    VueCote();
}

void VueModele3D::VueHaut_M()
{
    MenuContextuel.close();
    VueHaut();
}

void VueModele3D::VueOblique_M()
{
    MenuContextuel.close();
    VuePerspective();
}

void VueModele3D::Animation_M()
{
    MenuContextuel.close();
    def_axeRotationCam.defCoords(sav_axeRotationCam.X(), sav_axeRotationCam.Y()+360., sav_axeRotationCam.Z());
    InitAnimationSolide(75);
}

void VueModele3D::ExportSolide()
{
    MenuContextuel.close();
    emit ExportSolide_S();
}

void VueModele3D::ExportScript()
{
    MenuContextuel.close();
    emit ExportScript_S();
}

void VueModele3D::ExportImage()
{
    MenuContextuel.close();
    emit ExportImage_S();
}

void VueModele3D::ConfigAffichage_0()
{
    MenuContextuel.close();
    emit ConfigAffichage_S(0);
}

void VueModele3D::ConfigAffichage_1()
{
    MenuContextuel.close();
    emit ConfigAffichage_S(1);
}

void VueModele3D::ConfigAffichage_2()
{
    MenuContextuel.close();
    emit ConfigAffichage_S(2);
}

void VueModele3D::ChangeTypeAffichage_0() { MenuContextuel.close(); emit TypeAffichage_S(0); }
void VueModele3D::ChangeTypeAffichage_1() { MenuContextuel.close(); emit TypeAffichage_S(1); }
void VueModele3D::ChangeTypeAffichage_2() { MenuContextuel.close(); emit TypeAffichage_S(2); }
void VueModele3D::ChangeTypeAffichage_3() { MenuContextuel.close(); emit TypeAffichage_S(3); }

#ifdef SUPPORT_GIF
void VueModele3D::ExportAnimation()
{
    MenuContextuel.close();
    emit ExportAnimation_S();
}
#endif // SUPPORT_GIF

void VueModele3D::Imprimer()
{
    MenuContextuel.close();
    emit Imprimer_S();
}

void VueModele3D::ProprietesSolide()
{
    MenuContextuel.close();
    emit ProprietesSolide_S();
}

void VueModele3D::mouseMoveEvent(QMouseEvent *ev)
{
//    qDebug() << "mouseMoveEvent :: " << (void*)PerspGL << " : " << id_timer_animation << " : " << (ev->buttons() & Qt::LeftButton);
    if (!ValidePerspGL())
    {
        ev->accept();
        return;
    }

    QPoint pos = ev->pos();
    if (id_timer_animation != -1)
    {
        ev->accept();
        return;
    }

    if (ev->buttons() & Qt::LeftButton)
    {
        CalCAxeRotation(pos);
        MiseAJourAffichage();
    }
#if QT_VERSION < QT_VERSION_CHECK(5,4,0)
    else if (ev->buttons() & Qt::MidButton) /* Sera supprimé à partir de Qt6 */
#else
    else if (ev->buttons() & Qt::MiddleButton)
#endif // QT_VERSION
    {
        const Perspective3D::Ppoint2D diff = Perspective3D::Ppoint2D(pos.x(), pos.y()) - (PositionSouris_orig-sav_PositionSouris_orig);

        int dir = qMin(width(), height());
        pfloat dirx = ((diff.X()/dir));
        pfloat diry = ((-diff.Y() /dir));

        const pfloat echelle_deplacement = PerspGL->EchelleAffichage();

        /* Plus l'echelle est importante, plus la vitesse de déplacement est faible... */
        dirx /= echelle_deplacement;
        diry /= echelle_deplacement;

        if (!paramsGL.mode_ortho)
        {
            pfloat vitesse = POSITIFD(PerspGL->TranslationCamera().Z());
            if (vitesse < 0.1)
            {
                vitesse = 0.1;
            }
            dirx *= vitesse;
            diry *= vitesse;
        }

        PerspGL->defTranslationCamera(dirx+sav_DeplacementCam.X(), diry+sav_DeplacementCam.Y(), false);
        MiseAJourAffichage();
    }
    else if ((id_timer_animation == -1) && (!MenuContextuel.isVisible()))
    {
        if (!termine_perspective)
        {
            ev->accept();
            return;
        }

//        const float dist_min = 0.001;

        // Recherche collision position souris/centre d'une surface:
        if (pos.x() > 1 && pos.y() > 1)
        {
#ifdef UTILISE_QOPENGLWIDGET
            makeCurrent();
#endif // UTILISE_QOPENGLWIDGET

//            context()->makeCurrent(context()->surface());
            if (PerspGL->CalcProjectionCurseur(pos.x(), pos.y()))
            {
#ifdef UTILISE_QOPENGLWIDGET
                doneCurrent();
#endif // UTILISE_QOPENGLWIDGET
                /* Projection du curseur de la souris traduit dans les coordonées de la scène (sans projection matricielle, ni mise à l'échelle et translation): */
                const PGL::PVertex_GL p1_rayon = (PerspGL->ProjectionCurseur() / (Scene->Echelle())) + PGL::PVertex_GL(Scene->Centre());

                pfloat dist_min_sommet = COORDNULP;
                const Perspective3D::pident id_point_sel = (PerspGL->ModeAffichageSommets() || PerspGL->ModeAffichageFilaire()) ?
                                                         Scene->Maillage().SommetProche(p1_rayon, &dist_min_sommet) :
                                                         IDNUL;

                pfloat dist_min_segment = COORDNULP;
                const Perspective3D::pident id_segment_sel = (PerspGL->ModeAffichageFilaire()) ?
                                                         Scene->Maillage().SegmentProche(p1_rayon, &dist_min_segment) :
                                                         IDNUL;

                pfloat dist_min_surface = COORDNULP;
                const Perspective3D::pident id_surface_sel = (PerspGL->ModeAffichageSurfaces() && PerspGL->ModeAffichageModele()) ?
                                                           Scene->Maillage().SurfaceProche(p1_rayon, &dist_min_surface) :
                                                           IDNUL;

//                qDebug() << "id_point_sel:" << id_point_sel << ", id_surface_sel:" << id_surface_sel << "distance : (" << sqrt(dist_min_sommet) << " : " << sqrt(dist_min_surface) << ")";

                if (id_point_sel != IDNUL && ((id_segment_sel == IDNUL) || (dist_min_sommet < dist_min_segment)) && ((id_surface_sel == IDNUL) || (dist_min_sommet < dist_min_surface)))
                {
                    if ((SelectionSurface != IDNUL))
                    {
                        if (affiche_accroche)
                        {
                            AffichageCentreSurface(IDNUL);
                        }
                        emit(SelectioneSurface(0, false, false, true)); // Déselectionne toutes les surfaces si l'une a été précedemment sélectionnée.
                    }
                    SelectionSurface = IDNUL;

                    if ((SelectionSegment != IDNUL))
                    {
                        if (affiche_accroche)
                        {
                            AffichageCentreSegment(IDNUL);
                        }
                        emit(SelectioneSegment(0, false, false)); // Déselectionne touts les segment si l'un a été précedemment sélectionné.
                    }
                    SelectionSegment = IDNUL;

                    if (id_point_sel != SelectionPoint)
                    {
                        SelectionPoint = id_point_sel;
                        if (affiche_accroche_sommets)
                        {
                            AffichagePoint(SelectionPoint);
                        }
                        emit(SelectionePoint(SelectionPoint+1, false, false)); // Sélection du point le plus proche du curseur
                    }
//                    MiseAJourAffichage();
                }
                else if (id_segment_sel != IDNUL && ((id_point_sel == IDNUL) || (dist_min_segment < dist_min_sommet)) && ((id_surface_sel == IDNUL) || (dist_min_segment < dist_min_surface)))
                {
                    if ((SelectionPoint != IDNUL))
                    {
                        if (affiche_accroche_sommets)
                        {
                            AffichagePoint(IDNUL);
                        }
                        emit(SelectionePoint(0, false, false)); // Déselectionne tous les points.
                    }
                    SelectionPoint = IDNUL;

                    if ((SelectionSurface != IDNUL))
                    {
                        if (affiche_accroche)
                        {
                            AffichageCentreSurface(IDNUL);
                        }
                        emit(SelectioneSurface(0, false, false, true)); // Déselectionne toutes les surfaces si l'une a été précedemment sélectionnée.
                    }
                    SelectionSurface = IDNUL;

                    if (id_segment_sel != SelectionSegment)
                    {
                        SelectionSegment = id_segment_sel;
                        if (affiche_accroche)
                        {
                            AffichageCentreSegment(SelectionSegment);
                        }
                        emit(SelectioneSegment(SelectionSegment+1, false, false)); // Sélection du segment le plus proche du curseur
                    }
                }
                else if (id_surface_sel != IDNUL && ((id_point_sel == IDNUL) || (dist_min_surface < dist_min_sommet)) && ((id_surface_sel == IDNUL) || (dist_min_surface < dist_min_segment)))
                {
                    if ((SelectionPoint != IDNUL))
                    {
                        if (affiche_accroche_sommets)
                        {
                            AffichagePoint(IDNUL);
                        }
                        emit(SelectionePoint(0, false, false)); // Déselectionne tous les points.
                    }
                    SelectionPoint = IDNUL;

                    if ((SelectionSegment != IDNUL))
                    {
                        if (affiche_accroche)
                        {
                            AffichageCentreSegment(IDNUL);
                        }
                        emit(SelectioneSegment(0, false, false)); // Déselectionne touts les segment si l'un a été précedemment sélectionné.
                    }
                    SelectionSegment = IDNUL;

                    if (id_surface_sel != SelectionSurface)
                    {
                        SelectionSurface = id_surface_sel;
                        if (affiche_accroche)
                        {
                            AffichageCentreSurface(SelectionSurface);
                        }
                        emit(SelectioneSurface(SelectionSurface+1, false, false, true)); // Sélection de la surface la plus proche du curseur
                    }
//                    MiseaJourModele();
//                    MiseAJourAffichage();
                }
                else
                {
                    if ((SelectionSurface != IDNUL) && (id_surface_sel == IDNUL))
                    {
                        if (affiche_accroche)
                        {
                            AffichageCentreSurface(IDNUL);
                        }
                        SelectionSurface = IDNUL;
                        emit(SelectioneSurface(0, false, false, true));
//                        MiseAJourAffichage();
                    }
                }
            }
            else
            {
#ifdef UTILISE_QOPENGLWIDGET
                doneCurrent();
#endif // UTILISE_QOPENGLWIDGET
#ifdef DEBUG
//                qDebug() << "Erreur de calcul de la projection du curseur ?! : " << pos;
#endif // DEBUG
            }
        }
    }
//    MiseAJourAffichage();
    ev->accept();
}

void VueModele3D::mousePressEvent(QMouseEvent *ev)
{
    if (!ValidePerspGL())
    {
        ev->accept();
        return;
    }

    bool timer_actif = (id_timer_animation != -1);

//    if (!termine_perspective)
//    {
//        ev->accept();
//        return;
//    }

    if (MenuContextuel.isVisible())
        MenuContextuel.close();

#if QT_VERSION < QT_VERSION_CHECK(5,4,0)
    if (ev->button() == Qt::MidButton) /* Sera supprimé à partir de Qt6 */
#else
    if (ev->button() == Qt::MiddleButton)
#endif // QT_VERSION
    {
        ReinitSelection();
    }
    else if (ev->button() == Qt::LeftButton)
    {
        if (!timer_actif)
        {
            if (SelectionPoint != IDNUL)
            {
                emit(SelectionePoint(SelectionPoint+1, true, false));
            }
            else if (SelectionSegment != IDNUL)
            {
                emit(SelectioneSegment(SelectionSegment+1, true, false));
            }
            else if (SelectionSurface != IDNUL)
            {
                emit(SelectioneSurface(SelectionSurface+1, true, false, true));
            }
        }
    }
    PositionSouris_orig = Perspective3D::Ppoint2D(ev->pos().x(), ev->pos().y());
    ev->accept();
}

void VueModele3D::mouseDoubleClickEvent(QMouseEvent *ev)
{
#if QT_VERSION < QT_VERSION_CHECK(5,4,0)
    if (ev->button() == Qt::MidButton) /* Sera supprimé à partir de Qt6 */
#else
    if (ev->button() == Qt::MiddleButton)
#endif // QT_VERSION
    {
        ReInitEchelle();
        ReinitSelection();
//        sav_DeplacementCam.defCoords(0., 0., 0.);
        if (ValidePerspGL())
        {
            VuePerspective();
        }
        MiseAJourAffichage();
    }
    else if (ev->button() == Qt::LeftButton)
    {
//        emit(SelectioneSurface(SelectionSurface+1, false, true));

        if (!termine_perspective)
        {
            ev->accept();
            return;
        }

#ifdef DEBUG
//        qDebug() << "VueModele3D::mouseDoubleClickEvent :: Sélection : " << SelectionPoint << " : " << SelectionSegment << " : " << SelectionSurface;
#endif // DEBUG

        if (SelectionPoint != IDNUL)
        {
            emit(SelectionePoint(SelectionPoint+1, true, true));
        }
        else if (SelectionSegment != IDNUL)
        {
            emit(SelectioneSegment(SelectionSegment+1, true, true));
        }
        else if (SelectionSurface != IDNUL)
        {
            emit(SelectioneSurface(SelectionSurface+1, true, true, true));
        }
    }
    ev->accept();
}

void VueModele3D::mouseReleaseEvent(QMouseEvent *ev)
{
#ifndef UTILISE_QOPENGLWIDGET
//    QGLWidget::mouseReleaseEvent(ev);
#endif // UTILISE_QOPENGLWIDGET

    if (!ValidePerspGL())
    {
        ev->accept();
        return;
    }

    if (!termine_perspective)
    {
        ev->accept();
        return;
    }

    if (id_timer_animation == -1)
    {
        def_axeRotationCam = sav_axeRotationCam = PerspGL->RotationCamera();
        sav_DeplacementCam = PerspGL->TranslationCamera();
    }
    if (ev->button() == Qt::LeftButton)
    {
        PositionSouris_orig = Perspective3D::Ppoint2D(ev->pos().x(), ev->pos().y());
    }
    ev->accept();
}

void VueModele3D::wheelEvent(QWheelEvent *ev)
{
    if (!ValidePerspGL())
    {
        ev->accept();
        return;
    }

//    if (!termine_perspective)
//    {
//        ev->accept();
//        return;
//    }

    BloqueAnimationSolide();

    float fact = paramsGL.mode_ortho ? .8f : .15f;

    if (!paramsGL.mode_ortho)
    {
        float vitesse = POSITIFD(PerspGL->TranslationCamera().Z())*1.5f;
        if (vitesse < 0.1f)
        {
            vitesse = 0.1f;
        }
        fact *= vitesse;
    }

    pfloat d = ev->delta() * .008333333333333333; // (/ 120.)
    if (d == 0)
    {
        ReInitEchelle();
    }
    else if (d < 0)
    {
        if (paramsGL.mode_ortho)
        {
            def_m_Echelle *= fact;
        }
        else
        {
            PerspGL->defAvanceCamera(fact, true);
        }
    }
    else
    {
        if (paramsGL.mode_ortho)
        {
            def_m_Echelle /= fact;
        }
        else
        {
            PerspGL->defAvanceCamera(-fact, true);
        }
    }

    if (paramsGL.mode_ortho)
    {
        InitAnimationSolide();
    }
    MiseAJourAffichage();
    ev->accept();
}
