/*********************************
 **  Nome:  picacaixa.c
 **  Autor: Paulo Matos
 **
 **  Exemplo de picking, este exemplo desenha 4 caixas e indica que objectos existem debaixo
 **  do rato quando fazemos click.
 **
 **  Para fazer picking  necessrio
 **
 **     - atribuir nomes (glPushName(id)) s primitivas OpenGL
 **        + ver as funes Draw e draw_box, existe uma pilha de nomes para poder distinguir
 **          entre grupos de objectos. Ex.:  as peas do xadrez brancas e pretas tm o nome(id) da cor
 **          e o nome(id) da pea.
 **
 **     - quando se faz picking na funo Mouse  necessrio
 **        + criar um buffer para guardar resultados
 **        + mudar o glRenderMode para GL_SELECT
 **        + iniciar a pilha de nomes
 **        + redefinir a vista para picking (vista de desenho mais matriz de picking)
 **        + Desenhar os objectos aos quais se quer fazer picking
 **        + mudar o glRenderMode para GL_RENDER (so devolvidos os hits)
 **        + Analisar os resultados
 **
 **  O picking neste programa  realizado para 3 projeces diferentes, escolhidas com as teclas
 **  0, 1 e 2.
 **
 **  Para isso a funo Reshape  alterada de modo a que a parte da projeco esteja numa funo
 **  separada (setview) que recebe 3 parmetros
 **    picking - booleano que diz se est a definir a projeco para picking ou no (GL_TRUE ou GL_FALSE)
 **    x,y     - coordenadas do rato para definir a zona de picking (s interessa
 **              quando picking=GL_TRUE)
 **  esta funo quando em modo picking vai buscar a informao sobre o viewport, e depois de
 **  converter as coordenadas do rato para coordenadas de viewport chama a funo glPickMatrix para
 **  definir a zona da janela  qual vai fazer picking
 **
 *********************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <limits.h>

#ifdef  __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#ifndef M_PI
#define M_PI 3.1415926
#endif

/* VARIAVEIS GLOBAIS */
typedef struct {
  GLint vista; // vista a ser usada (0-ortho2D, 1-ortho ou 2-perspective)
}Modelo;

Modelo modelo;

/* INICIALIZAR */
void Init(void)
{
  GLfloat mat_specular[] = { 0.8f, 0.8f, 0.8f, 1.0f };
  GLfloat mat_shininess = 104;

  GLfloat luz_pos[4] = {-5.0, 5.0, 5.0, 0.0};
  GLfloat luz_ambient[] = { 0.2f, 0.2f, 0.2f, 1.0f };
  GLfloat luz_diffuse[] = { 0.8f, 0.8f, 0.8f, 1.0f };
  GLfloat luz_specular[] = { 0.5f, 0.5f, 0.5f, 1.0f };

  modelo.vista=0;

  glClearColor(0.0, 0.0, 0.0, 0.0);
  glEnable(GL_DEPTH_TEST);

  // ligar iluminao
  glEnable(GL_LIGHTING);

  // ligar e definir fonte de luz 0
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0, GL_AMBIENT, luz_ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, luz_diffuse);
  glLightfv(GL_LIGHT0, GL_SPECULAR, luz_specular);
  glLightfv(GL_LIGHT0, GL_POSITION, luz_pos);

  // ligar material a partir da cor e definir os parmetros a serem alterado automaticamente
  glEnable(GL_COLOR_MATERIAL);
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

  // definir os outros parmetros estaticamente
  glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
  glMaterialf(GL_FRONT, GL_SHININESS, mat_shininess);

  glShadeModel(GL_SMOOTH);
}

// define projeco
void setview(GLboolean Picking, int x, int y)
{
  int vport[4];

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (Picking) { // se est no modo picking, l viewport e define zona de picking
    glGetIntegerv(GL_VIEWPORT, vport);
    gluPickMatrix(x, vport[3] - y, 4, 4, vport); // zona sobre o rato (+/-)
  }

  switch (modelo.vista) {
    case 0:
      gluOrtho2D(-1, 1, -1, 1);
      break;
    case 1:
      glOrtho(-1.5,1.5,-1.5,1.5,-100,100);
      break;
    case 2:
      gluPerspective(60,1,1,100);
      break;
  }

  glMatrixMode(GL_MODELVIEW);
}

/* CALLBACK PARA REDIMENSIONAR JANELA */
void Reshape(int width, int height)
{
  GLint size;

  if (width < height)
    size = width;
  else
    size = height;
  glViewport(0, 0, (GLint) size, (GLint) size);
  setview(GL_FALSE, 0, 0);
}

void help()
{
  printf("Exemplo de picking, picar com o rato no ecr\n");
  printf("0 - vista ortho 2D\n");
  printf("1 - vista ortho 3D\n");
  printf("2 - vista perspectiva\n");
  printf("h - esta ajuda\n");
}

/* CALLBACK PARA INTERACCAO VIA TECLADO */
void Key(unsigned char key, int x, int y)
{
  switch (key) {
    case '0':
    case '1':
    case '2':
      modelo.vista=key-'0';
      printf("%d\n",modelo.vista);
      setview(GL_FALSE,x,y);
      glutPostRedisplay();
      break;
    case 'h':
      help();
      break;
    case 27:
      exit(1);
      /* ... accoes sobre outras teclas ... */
    default:
      return;
  }
  /* OBRIGAR A REDESENHAR */
  glutPostRedisplay();
}

/* ... definio das rotinas auxiliares de desenho ... */
void draw_box(int n, float x0, float y0, float x1, float y1)
{
  glPushName(n); // nome do objecto a ser usado no picking
  if(!modelo.vista)
  { // projeco 2D (sem camera)
    glNormal3f(0,0,1);
    glRectf(x0, y0, x1, y1);
  }
  else // projeco 3D (com camera)
  {
    glPushMatrix();
		  glTranslatef((x0+x1)/2,(y0+y1)/2,0);
		  glutSolidCube(0.7);
    glPopMatrix();
  }
  glPopName();
}

void setCamera(){
  if(modelo.vista) // se projeco  3D usa lookAt para colocar a cmara
    gluLookAt(2,2,2,0,0,0,0,1,0);
}

void desenhaObjectos(){
  GLint mode;

  glGetIntegerv(GL_RENDER_MODE,&mode);

  if(mode==GL_SELECT)
    glPushName(10); // nome comum a todos os objectos a ser usado no picking

  glColor3f(1, 1, 1);
  draw_box(0, -0.8, -0.8, -0.2, -0.2);
  glColor3f(1, 0, 0);
  draw_box(1, 0.8, -0.8, 0.2, -0.2);
  glColor3f(0, 1, 0);
  draw_box(2, 0.8, 0.8, 0.2, 0.2);
  glColor3f(0, 0, 1);
  draw_box(3, -0.8, 0.8, -0.2, 0.2);

  if(mode==GL_SELECT)
    glPopName();

}

/* CALLBACK DE DESENHO */
void Draw(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  glLoadIdentity();

  setCamera();

  /* ... chamada das rotinas auxiliares de desenho ... */

  desenhaObjectos();


  glFlush();
  glutSwapBuffers();
}

#define BUFF_SIZE		100

void Mouse(int bt, int st, int x, int y)
{
  int hits,i,k;
  GLuint   buffer[BUFF_SIZE],*bufp, numnames,name;
  GLdouble zmax,zmin;

  if (st != GLUT_DOWN)
    return;

  // inicio do picking

  glSelectBuffer(BUFF_SIZE, buffer);  // buffer onde coloca os dados do picking
  glRenderMode(GL_SELECT);            // inicializa o modo de picking
  glInitNames();                      // inicializa a pilha de nomes
  setview(GL_TRUE, x, y);             // define a vista para picking e a zona de picking
  glLoadIdentity();
  setCamera();
  desenhaObjectos();                  // desenha os objectos aos quais quer fazer picking
  hits = glRenderMode(GL_RENDER);     // inicializa o modo de rendering(devolve o numero de hits do picking)

  // analise dos resultados do picking

  printf("**** %d Hits ****\n",hits);
  if (hits)
  {
    k=0;
    bufp=buffer;
    for(i=0;i<hits;i++)
    {
      // quantidade de nomes
      numnames = *bufp++;
      // profundidades da janela (serve para saber qual o objecto que esta 'a frente)
      zmin = (GLdouble)*bufp++/UINT_MAX;  // cordenada z de janela minima do objecto entre 0 e 1
      zmax = (GLdouble)*bufp++/UINT_MAX;  // cordenada z de janela maxima do objecto entre 0 e 1
      printf(" Hit %d, %d nomes - zmax=%.4f zmin=%.4f\n",i,numnames,zmax,zmin);

      // nomes que o objecto tem
      while(numnames--){
        name = *bufp++;
        printf("  box: %u\n", name);
      }
    }
  }

  // redefinico da vista (pois foi alterada pelo picking)
  setview(GL_FALSE, x, y);
  glutPostRedisplay();
}

int main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutInitWindowPosition(0, 0);
  glutInitWindowSize(300, 300);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
  if (glutCreateWindow("Pica Caixa") == GL_FALSE)
    exit(1);
  Init();

  help();
  /* REGISTAR CALLBACKS */
  glutReshapeFunc(Reshape);
  glutKeyboardFunc(Key);
  glutDisplayFunc(Draw);
  glutMouseFunc(Mouse);
  
  /* COMECAR... */
  glutMainLoop();
  return 0;
}
