//==============================================================================
// Programme ... Affichage et Limitation du Frame Rate dans une application OpenGL
// Date ........ 11-13 septembre 2004 (MAJ 9 septembre 2005)
// Auteur ...... BeLZeL
//==============================================================================
// TOUCHE :
//   '+'   -> Je veux + de FPS
//   '-'   -> Je veux - de FPS
//   ENTER -> Je veux touplin de cubes
//   SPACE -> Je veux activer/stopper la MAJ de la console
//==============================================================================
// A connaitre !
//  _ QueryPerformanceFrequency (fonction) : Retourne le nombre de cycles par seconde
//  _ QueryPerformanceCounter (fonction) : Retourne le nombre de cycles depuis le démarrage de Windows
//  _ LONGLONG (type) : je crois que c'est l'équivalent d'un __int64 (à voir)
//  _ LARGE_INTEGER (type) : structure spéciale équivalente d'un LONGLONG
//  _ FPS (frames per second) : nombre d'images calculées ET affichées par seconde
//==============================================================================
// Pourquoi faire ?
//   _ Il est souvent inutile de gaspiller du temps machine pour afficher jusqu'à
// 3000 FPS avec des cartes graphiques dernier cri (aaaaaahhh !).
// L'oeil n'en percoit qu'une centaine au maximum.
// Ce programme calcule le nombre de FPS. Il limite le nombre de FPS selon votre choix.
// Il déplace les objets selon la méthode du TimeStep (déplacement en fonction du
// temps).
//==============================================================================
// Comment faire ?
//  _ Le principal soucis pour calculer le nombre d'images par seconde est de réussir
// à trouver des fonctions de timing très précises. Les fonctions classiques
// sont trop imprécises (de l'ordre de 1 à 10 ms). C'est une précision insuffisante
// lorsqu'on désire afficher plus de 100 FPS (ca nous fait une image toutes les
// 10ms ou moins). Les seules fonctions intéressantes sont QueryPerformanceFrequency
// et QueryPerformanceCounter (qui existent bien sur tous les processeurs, quoi
// qu'en dise la MSDN, qui doit dater un peu). Leur seul inconvénient est de ne
// pas donner directement le temps, il faut au préalable diviser le nombre de cycle
// par la fréquence du nombre de cycles par seconde. Il y a donc un coût en temps
// supplémentaire pour les divisions et également un coût en mémoire pour stocker
// les résultats en seconde dans des variables de type double.
//  _ Le nombre de cycles est un résultat beaucoup plus précis que le millième de seconde.
// Un cycle correspond environ (sur mon PC, Barton 3200+) à 270 nano secondes.
//  _ Pour calculer le nombre de frames par seconde (FPS), nous allons comptabiliser
// toutes les frames affichées (temps.nbFrames). Ensuite, toutes les secondes,
// on affiche le résultat et on remet le compteur à 0. Le résultat est assez précis.
//  _ Pour limiter le nombre de FPS, il faut 2 variables. Il faut comptabiliser
// le nombre de cycles maintenant (temps.cSuiv) et le nombre de cycles pour la
// prochaine frame (temps.cSuiv). Dès que temps.cNow > temps.cSuiv, on
// dessine une nouvelle image et on augmente temps.cSuiv. Si on veut beaucoup de FPS,
// on ajoutera un peu de cycles à temps.cSuiv, ou inversement.
//  _ Pour animer un objet, on n'incrémentera pas son déplacement de manière statique
// à chaque image (pos++). On utilisera le temps qu'il s'est écoulé entre deux frames
// (pos += temps.cSuiv - temps.cPrec) pour savoir où l'objet sera placé.
//  _ Concernant l'affichage OpenGL, la fonction glutSwapBuffers() permet
// un affichage en double buffering. Il faut uniquement l'utiliser lorsqu'on est
// sur le point d'afficher une nouvelle image (temps.cNow > temps.cSuiv).
// La fonction glutPostRedisplay() permet de redemander l'affichage d'une nouvelle
// image, il faut toujours utiliser cette fonction à la fin de l'affichage, que
// la frame précédente soit affichée ou non.
//==============================================================================


//------------------------------------------------------------------------------
// Includes
//------------------------------------------------------------------------------

    #include <windows.h>                // Header pour les Applications Windows
    #include <stdio.h>                  // Pour les prints
    #include <GL/glut.h>                // Header OpenGL Utility Toolkit (GLUT)

    // A changer selon votre carte 3D (j'ai une ATI Radeon 9600XT @ GPU533/RAM340)
    // J'ai 141 FPS avec 5000 cubes, sinon j'ai 1970 FPS avec 1 cube
    #define NB_CUBES 5000

//------------------------------------------------------------------------------
// Structures
//------------------------------------------------------------------------------


    // Gestion du temps : toutes les variables ne sont pas indispensables
    struct TEMPS
    {
        // Par rapport au démarrage de Windows
        LONGLONG cBase;             // Nombre de cycles depuis le démarrage de Windows, trouvé au démarrage de l'application
        LONGLONG cPrec;             // Nombre de cycles depuis le démarrage de Windows, trouvé lors du dernier rendu
        LONGLONG cNow;              // Nombre de cycles depuis le démarrage de Windows, trouvé actuellement
        LONGLONG cSuiv;             // Nombre de cycles depuis le démarrage de Windows, qu'on doit atteindre pour le prochain rendu

        // Par rapport au démarrage de l'application
        LONGLONG cBegin;            // Nombre de cycles écoulés uniquement depuis le démarrage de l'application
        LONGLONG allFrames;         // Nombre de frames depuis le démarrage de l'application
        LONGLONG allFramesPrec;     // Nombre de frames depuis le démarrage de l'application
        LONGLONG cUneSeconde;       // On attend une seconde pour comptabiliser le nombre de frames
        long int ns;                // Nombre de nano secondes depuis le démarrage de l'application

        // Par rapport aux conversions temps <=> cycle
        LONGLONG frequence;         // Nombre de cycles par seconde (de l'ordre de 3 millions avec un Barton 3200+)
        double   tpsPerC;           // Temps en seconde entre deux cycles

        // Par rapport aux frames
        LONGLONG cPerFrame;         // Nombre de cycles qui doivent s'écouler entre deux frames (pour la limitation)
        LONGLONG cEcoule;           // Nombre de cycles écoulés entre deux frames (pour le TimeStep)
        double   ecoule;            // Temps en secondes entre deux frames (pour le TimeStep)
        LONGLONG nbFrames;          // Nombre de frames affichées (remis à 0 toutes les secondes)
        double   fps;               // Nombre de frames par seconde (mis à jour toutes les secondes via nbFrames)
    };
    struct TEMPS temps;


//==============================================================================
// Variables Globales
//==============================================================================


    // Tableau qui indiquera à combien il faut limiter le nombre d'images par seconde (en nombres de FPS)
    int idFPS = 5;
    #define MAX_ID_FPS 12
    int tabFPS[MAX_ID_FPS+1] = { 1, 5, 10, 15, 20, 25, 30, 50, 100, 250, 850, 1200, 20000 };
    int MAX_FPS = 25;

    // Affiche les informations en temps réel dans la console DOS
    int showInfos = 1;

    // Affiche ou non plusieurs cubes
    int ramage    = 0;

//------------------------------------------------------------------------------
// Fonction de redimensionnement
//------------------------------------------------------------------------------


void    Reshape ( int largeur, int hauteur )
{
    glViewport ( 0, 0, largeur, hauteur );
    glMatrixMode ( GL_PROJECTION );
    glLoadIdentity ( );
    gluPerspective ( 90, (float)largeur / (float) hauteur, 1, 100 );
}


//------------------------------------------------------------------------------
// Fonction d'initialisation
//------------------------------------------------------------------------------


void    InitGL()
 {
    glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );    // Arrière Plan Noir
    glClearDepth ( 1.0f );                      // La profondeur du buffer
    glDepthFunc( GL_LEQUAL );
    glEnable ( GL_DEPTH_TEST );
    glDisable ( GL_TEXTURE_2D );
    glEnable ( GL_COLOR );

    //*** INITIALISATION DES VARIABLES *****************************************
	QueryPerformanceFrequency ( (LARGE_INTEGER *) &temps.frequence );
	QueryPerformanceCounter ( (LARGE_INTEGER *) &temps.cBase );
	temps.cPrec = temps.cSuiv = temps.cBase;
    temps.cPerFrame = temps.frequence / MAX_FPS;
    temps.tpsPerC = 1.0 / (double)temps.frequence;
    temps.nbFrames = 0;
    temps.allFrames = 0;
    temps.cUneSeconde = temps.cBase + temps.frequence;
    srand ( temps.cBase );
    //**************************************************************************
 }


//==============================================================================
// Affiche les informations en temps réel dans la console DOS
//==============================================================================


void ShowInfos ( )
{
//    system ( "CLS" );
    printf ( "-------------------------------------------------------------------------------\r\n" );
    printf ( "TEMPS.    VALEUR      DESCRIPTION\r\n" );
    printf ( "frequence %010lu  Nb cycles par seconde\r\n",                                 temps.frequence );
    printf ( "cBase     %010lu  Nb cycles trouve au demarrage de l'application\r\n",        temps.cBase );
    printf ( "ns        %010ld  Nb nano secondes depuis le demarrage de l'application\r\n", temps.ns );
    printf ( "cPrec     %010lu  Nb cycles trouve lors du dernier rendu\r\n",                temps.cPrec );
    printf ( "cNow      %010lu  Nb cycles trouve actuellement\r\n",                         temps.cNow );
    printf ( "cSuiv     %010lu  Nb cycles a atteindre pour le prochain rendu\r\n",          temps.cSuiv );
    printf ( "cBegin    %010lu  Nb cycles ecoules depuis le demarrage de l'application\r\n",temps.cBegin );
    printf ( "allFrames %010lu  Nb frames depuis le demarrage de l'application\r\n",        temps.allFrames );
    printf ( "cEcoule   %010lu  Nb cycles ecoules depuis le dernier rendu\r\n",             temps.cEcoule );
    printf ( "ecoule    %010.5lf  Temps en milli-secondes entre deux frames\r\n",           temps.ecoule*1000 );
    printf ( "cPerFrame %010lu  Nb cycles entre l'affichage de deux frames\r\n",            temps.cPerFrame );
    printf ( "tpsPerC   %010.6lf  Temps en nano seconde entre deux cycles\n",               temps.tpsPerC*1000000000 );
    printf ( "nbFrames  %010lu  Nb frames (Valeur qui change a chq nouvelle frame)\r\n",    temps.nbFrames );
    printf ( "fps       %010.0lf  Nb FPS (Valeur qui change toutes les secondes)\r\n",      temps.fps );
    printf ( "-------------------------------------------------------------------------------\r\n" );
    printf ( "Appuyez sur Entree pour stresser l'affichage 3D (avec %d cubes)\r\n", NB_CUBES );
    printf ( "Appuyez sur Espace pour stopper l'affichage de la console\r\n" );
    printf ( "-------------------------------------------------------------------------------\r\n" );
}


//==============================================================================
// Modifie le titre de la fenêtre
//==============================================================================


void    Titre ( )
{
    char    title[1024];

    if ( showInfos ) ShowInfos ( );

    // Information : wsprintf n'aime pas les floats (%f)
    wsprintf ( title, "%02d:%02d (%d frames) (%d FPS) (MAX:%d FPS)",
        (int)temps.ns/1000/1000/60,     // Minutes
        (int)temps.ns/1000/1000%60,     // Secondes
        (int)temps.allFrames,           // Nombre de frames (depuis le dernier changement de framerate)
        (int)(temps.fps+0.5),           // Nombre de FPS précis
        tabFPS[idFPS] );                // Nombre maximal de FPS

    glutSetWindowTitle ( title );
}

//------------------------------------------------------------------------------
// Gestion des touches standard
//------------------------------------------------------------------------------


void    GestionClavier ( unsigned char key, int x, int y )
{
    switch ( key )
    {

        //=== On réduit le framerate ===========================================
        case '-' :
            MAX_FPS = ( idFPS == 0 ) ? tabFPS[0] : tabFPS[--idFPS];
            temps.cPerFrame = temps.frequence / MAX_FPS;
            temps.cPrec = temps.cSuiv = temps.cNow;
            break;
        //======================================================================


        //=== On augmente le framerate =========================================
        case '+' :
            MAX_FPS = ( idFPS == MAX_ID_FPS ) ? tabFPS[MAX_ID_FPS] : tabFPS[++idFPS];
            temps.cPerFrame = temps.frequence / MAX_FPS;
            temps.cPrec = temps.cSuiv = temps.cNow;
            break;
        //======================================================================


        //=== On augmente la vitesse de changement du titre ====================
        case ' ' :
            showInfos = 1 - showInfos;
            break;
        //======================================================================


        //=== On change le nombre de cube à l'écran ============================
        case 13 :
            ramage = 1 - ramage;
            break;
        //======================================================================

        // Quitter avec Echap
        case 27 :
            exit ( 0 );
        break;
     }
}


//------------------------------------------------------------------------------
// Affiche une sphè ... heu non un cube ;)
//------------------------------------------------------------------------------


void Cube ( )
{
    // Notre Cube
    glBegin(GL_QUADS);
      // derrière
      glColor3f ( 0, 0, 0 ); glVertex3f ( -5,  5, -5 );
      glColor3f ( 1, 0, 0 ); glVertex3f (  5,  5, -5 );
      glColor3f ( 1, 1, 0 ); glVertex3f (  5, -5, -5 );
      glColor3f ( 0, 1, 0 ); glVertex3f ( -5, -5, -5 );
      // devant
      glColor3f ( 0, 0, 1 ); glVertex3f ( -5,  5,  5 );
      glColor3f ( 1, 0, 1 ); glVertex3f (  5,  5,  5 );
      glColor3f ( 1, 1, 1 ); glVertex3f (  5, -5,  5 );
      glColor3f ( 0, 1, 1 ); glVertex3f ( -5, -5,  5 );
      // gauche
      glColor3f ( 0, 0, 0 ); glVertex3f ( -5,  5, -5 );
      glColor3f ( 0, 1, 0 ); glVertex3f ( -5, -5, -5 );
      glColor3f ( 0, 1, 1 ); glVertex3f ( -5, -5,  5 );
      glColor3f ( 0, 0, 1 ); glVertex3f ( -5,  5,  5 );
      // droite
      glColor3f ( 1, 0, 0 ); glVertex3f (  5,  5, -5 );
      glColor3f ( 1, 1, 0 ); glVertex3f (  5, -5, -5 );
      glColor3f ( 1, 1, 1 ); glVertex3f (  5, -5,  5 );
      glColor3f ( 1, 0, 1 ); glVertex3f (  5,  5,  5 );
      // haut
      glColor3f ( 0, 0, 0 ); glVertex3f ( -5,  5, -5 );
      glColor3f ( 1, 0, 0 ); glVertex3f (  5,  5, -5 );
      glColor3f ( 1, 0, 1 ); glVertex3f (  5,  5,  5 );
      glColor3f ( 0, 0, 1 ); glVertex3f ( -5,  5,  5 );
      // bas
      glColor3f ( 0, 1, 0 ); glVertex3f ( -5, -5, -5 );
      glColor3f ( 1, 1, 0 ); glVertex3f (  5, -5, -5 );
      glColor3f ( 1, 1, 1 ); glVertex3f (  5, -5,  5 );
      glColor3f ( 0, 1, 1 ); glVertex3f ( -5, -5,  5 );
    glEnd ( );
    glColor3f ( 1, 1, 1 );
}


//------------------------------------------------------------------------------
// Fonction de dessin
//------------------------------------------------------------------------------


void    Draw ( )
 {
    static double a = 0;

    QueryPerformanceCounter ( (LARGE_INTEGER *) &temps.cNow );

    // Calcul des FPS
    if ( temps.cNow >= temps.cUneSeconde )
    {
        temps.fps          = (double)temps.nbFrames;
        temps.cUneSeconde += temps.frequence;
        Titre ( );
        temps.nbFrames     = 0;
    }

    // Doit-on afficher une image ?
    if ( temps.cNow >= temps.cSuiv )
    {
        //=== Mise à jour du temps =============================================
        temps.allFrames++;
        temps.nbFrames++;
        temps.cBegin  = temps.cNow - temps.cBase;
        temps.ns      = (long)(temps.cBegin * temps.tpsPerC * 1000000);
        temps.cEcoule = temps.cNow - temps.cPrec;
        temps.cPrec   = temps.cNow;
        temps.cSuiv += (LONGLONG)(temps.cPerFrame);
        temps.ecoule  = temps.cEcoule * temps.tpsPerC;
        //======================================================================

        // Reset de l'affichage
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
        glMatrixMode ( GL_MODELVIEW );
        glLoadIdentity ( );
        gluLookAt ( 10, 10, 10, 0, 0, 0, 0, 1, 0 );

        // Affichage du repère
        glBegin ( GL_LINES );
            glColor3d ( 1, 0, 0 ); glVertex3d ( 0, 0, 0 ); glVertex3d (  0,  0, 10 );
            glColor3d ( 0, 1, 0 ); glVertex3d ( 0, 0, 0 ); glVertex3d (  0, 10,  0 );
            glColor3d ( 0, 0, 1 ); glVertex3d ( 0, 0, 0 ); glVertex3d ( 10,  0,  0 );
        glEnd ( );

        // On rotate la scène
        glRotated ( a+=temps.ecoule*50, 0, 1, 0 );

        // On affiche 1 ou plusieurs Cube (emplacement randomizé)
        if ( ramage )
        {
            int i;
            for ( i = 0 ; i < NB_CUBES ; i++ )
            {
                glTranslated ( rand ( ) % 1000 / 25.0 - 20, rand ( ) % 1000 / 25.0 - 20, rand ( ) % 1000 / 25.0 - 20 );
                Cube ( );
            }
        }
        else Cube ( );

        glutSwapBuffers ( );
    }

    glutPostRedisplay ( );
 }


//------------------------------------------------------------------------------
// Fonction Principale
//------------------------------------------------------------------------------


int main ( int argc, char *argv[ ], char *envp[ ] )
{
    // Création de la fenêtre OpenGL
    glutInit ( &argc, argv );
    glutInitDisplayMode ( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH );
    glutInitWindowSize ( 512, 512 );
    glutInitWindowPosition ( 50, 50 );
    glutCreateWindow ( "" );

    // Lancement de l'application OpenGL
    InitGL ( );
    glutDisplayFunc ( Draw );
    glutIdleFunc ( Draw ); //<- le programme continue de fonctionner si vous réduisez la fenêtre
    glutReshapeFunc ( Reshape );
    glutKeyboardFunc ( GestionClavier );
    glutMainLoop ( );

    return 0;
 }

//------------------------------------------------------------------------------
// End Of File
//------------------------------------------------------------------------------