

//
//  SIVOP - Sistema de visualizacao de objetos poliedricos
//

//  Andre Detsch 
//
//  Trabalho 1
//  Computacao Grafica 2000/1
//


#include <string.h>
#include <stdio.h>
#include <iostream.h>

#include <GL/glut.h> 

#define MAX_PALAVRAS 4             //max. de palavras a serem lidas por linha do arquivo
#define MAX_LETRAS 50              //max. de letras por palavra
#define MAX_LINHA 100              //max. de letras por linha

#define DIVISOR_TRANSLATE 10       //movimento do mouse eh dividido por ...

#define USE_LIST 1                 // usar Display Lists? (0 = nao)

//variaveis globais
int objectList;                    //id da Display List do objeto

struct MouseStatus {  
  int pressTeclaL; 
  int x;
} mouseStatus;                     //usado para monitorar movimento do mouse
  
struct AxisStatus {
  float x, y, z;
} rotate, translate;               //valores acumulados de translacao e rotacao

int fillit;                        //"preencher" ou nao as faces

enum action_e { T, R } action;     //que acao esta sendo feita
enum axis_e { X, Y, Z } axis;      //sobre que eixo a acao esta sendo feita


class NornalVectorsList {
  


}

class List {
  
public:
  
  void insertElement(float x, float y, float z);
  float* getElement(int i);
  List(int size_);


private:

  struct xyz_s {
    float xyz[3];
  };
  int nextIndex;            //posicao a ser ocupada pelo proximo vertice
  xyz_s* int_list;          // lista de vertices / vetores normais

};

List::List(int size_) {
  nextIndex = 0;
  int_list = new xyz_s[size_];
}

void VertexList::insertElement(float x, float y, float z) {
  int_list[nextIndex].xyz[0] = x;
  int_list[nextIndex].xyz[1] = y;
  int_list[nextIndex].xyz[2] = z;  
  
  nextIndex++;

}

float* List::getElement(int i) {
  return  vertex_list[i-1].xyz;

}

class Polygon {

public:
  int* vertex;
  int* normal_vector;
  int size;
  Polygon* next;
  Polygon(int* vertex_, int* normal, int size_);
};

Polygon::Polygon(int* vertex_, int* normal_vector, int size_) {
  vertex = new int[size_];
  normal_vector = new int[size_];
  size = size_;
  for (int i = 0; i<size ; i++) {
    vertex[i] = vs[i];
    normal_vector[i] = normal_vector[i];
  }
}

class PolygonList {

  private:
    PolygonXYZ* first;
    PolygonXYZ* current;

  public:

    void addPolygon(int* vertex_, int* normal_vector_, int size_);
    Polygon* getCurrent();
    void reset();
    PolygonList();
};


PolygonList::PolygonList() {
  first = 0;
  current = 0;
}

void PolygonList::addPolygon(int* vertex_, int* normal_vector_, int size_) {
  Polygon* tmp = new Polygon(vertex_, normal_vector_, size_);
  
  if (first == 0)
    first = tmp;
  else
    current->next = tmp;
  current = tmp;
  tmp->next = 0;
}

void PolygonList::reset() {
  current = first;
}

Polygon* PolygonList::getCurrent() {
  if (current == 0)
    return 0;
  else {
    Polygon* tmp = current;
    current = current->next;
    return tmp;
  }
}


//objetos com as estruturas usadas na leitura dos vertices e faces 

PolygonList* pl;
VertexList* vl;


class FileReader {

public:
  
  FILE* file;

  FileReader();
  void getsLine(FILE* file, char* s);
  void le_palavras (char* s_linha, int n_palavras, char palavras[][MAX_LETRAS]);
  int le_palavras_max (char* s_linha, char palavras[][MAX_LETRAS]);
  int readFile( char* fileName_);
};



void FileReader::getsLine(FILE* file, char* s) {
  char c;
  int i = 0;

  while (   !feof(file) &&  ( (c=fgetc(file)) != '\n')  ) {
    s[i] = c;
	  i++;	  
  }

  s[i] = 0;

}


int FileReader::le_palavras_max (char* s_linha, char palavras[][MAX_LETRAS]) {

  int indice = 0;
  int i, j, k;
  int p1, p2;

	if (n_palavras == 0) {
	  palavras[0][0] = 0;
		return;
	}

	for (i = 0; i< MAX_PALAVRAS; i++) {
    while ((s_linha[indice] != 0) && ((s_linha[indice] == ' ') || (s_linha[indice] == 9)))
      indice++;
    p1 = indice;
      
    while ((s_linha[indice] != 0) && (s_linha[indice] != ' ') && (s_linha[indice] != 9))
      indice++;
    p2 = indice;

    if (p2 > p1) {
      for (j = p1, k = 0; j < p2; j++, k++)
        palavras[i][k]= s_linha[j];
		  palavras[i][k] = 0;
	  }
	  else 
        break;
  }  
  return i;
}



void FileReader::le_palavras (char* s_linha, int n_palavras, char palavras[][MAX_LETRAS]) {

  int indice = 0;
  int i, j, k;
  int p1, p2;

	if (n_palavras == 0) {
	  palavras[0][0] = 0;
		return;
	}

	for (i = 0; i< n_palavras; i++) {
    while ((s_linha[indice] != 0) && ((s_linha[indice] == ' ') || (s_linha[indice] == 9)))
      indice++;
    p1 = indice;
      
    while ((s_linha[indice] != 0) && (s_linha[indice] != ' ') && (s_linha[indice] != 9))
      indice++;
    p2 = indice;

    if (p2 > p1) {
      for (j = p1, k = 0; j < p2; j++, k++)
        palavras[i][k]= s_linha[j];
		  palavras[i][k] = 0;
	  }
	  else 
        break;
  }
}


FileReader::FileReader() {

}

int FileReader::readFile(char* fileName_) {
  char line[MAX_LINHA];
  char palavras[MAX_PALAVRAS][MAX_LETRAS];  
  int achouf = 0;

  FILE* file = 0;
  file = fopen (fileName_,"r");

  if (file == 0) {
    return (1);
  }

  
  //contagem de vertices
  int nvertex = 0;
  int nNormalVectors = 0;

  while (!feof(file)) {
    getsLine(file, line);
    le_palavras(line, 1, palavras);
    if (feof(file))
      break;
  	if ((line[0] == 0))
      continue;
    if (!strcmp(palavras[0],"v") || !strcmp(palavras[0],"v"))
      nvertex++;
    else
      if (!strcmp(palavras[0],"nv") || !strcmp(palavras[0],"nv"))
        nNormalVectors++;
      else 
        if (!strcmp(palavras[0],"f") || !strcmp(palavras[0],"F"))
          break;
  }

  //vertices e vetores normais contados, array pode ser criado
  vl = new List(nvertex);
  nvl = new List (nNormalVectors) 
  pl = new PolygonList();
  
  fseek(file, 0, 0);

  cout <<"\n-------------------------------\n\n";
  cout <<" File: " << fileName_ << "\n";
  cout <<" Vertex Count: " << nvertex << "\n";
  if (!USE_LIST)
    cout <<" Not";
  cout <<" Using Call List\n";
  cout <<"\n-------------------------------\n";
    
  
  while (!feof(file)) {
    line[0] = 0;
	
    getsLine(file, line);

  if (feof(file))
    break;
	if ((line[0] == 0))
    continue;
  else
    le_palavras(line,4,palavras);

  if (palavras[0][0] == '#')
	  continue;
  if ( !achouf && ( !strcmp(palavras[0],"v") || !strcmp(palavras[0],"V") ) ){
    vl->insertVertex(atof(palavras[1]),atof(palavras[2]),atof(palavras[3]));
  }
  else 
    if ( !strcmp(palavras[0],"f") || !strcmp(palavras[0],"F")) {
      if (!achouf)
        achouf = 1;
      pl->addPolygon((int)atof(palavras[1]),(int)atof(palavras[2]),(int)atof(palavras[3]));
    }
    else {
      fclose(file);
      return(1);
    }
  }
  fclose(file);
  return(0);
}



void drawText() {
  switch (action) {
  case R : 
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'R');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'o');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 't');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'a');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'c');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'a');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'o');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , ' ');
    break;
  case T :
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'T');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'r');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'a');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'n');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 's');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'l');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'a');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'c');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'a');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'o');
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , ' ');
    break;
  default :
    cout << "inconsistencia em \"action\" " ;
    exit(1);
    break;
  }
  
  switch (axis) {
  case X:
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'X');
    break;

  case Y:
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'Y');
    break;

  case Z :
    glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24 , 'Z');
    break;

  default:
    cout << "inconsistencia em \"axis\" ";
    exit(1);
    break;
  }
}
  

//------------------------------------------------
// Estabelece o tipo de projecao, posicao e orientacao
// da camera
//------------------------------------------------

static void setaCamera( void )
{
  gluPerspective( 45.0, 1.0, 0.1, 5000.0 );
  glTranslatef( 0, 0, -5 );             // Move a posicao default da camera
                                        // longe da origem
}

//
// drawObject: rotina usada quando Display List nao esta ativa
//
void drawObject() {
  int* vs;
  pl->reset();
  while ((vs = pl->getCurrent()) != 0) {
  	glBegin(GL_TRIANGLES);
    glVertex3fv(vl->getVertex(vs[0]));
		glVertex3fv(vl->getVertex(vs[1]));
		glVertex3fv(vl->getVertex(vs[2]));
	  glEnd();
  }
}


//
// redrawObject: rotina usada quando Display List esta ativa
//

void redrawObject() {
  glCallList(objectList);
}

//------------------------------------------------
// Esta rotina desenha a cena assumindo que a posicao
// da camera foi estabelecida
//------------------------------------------------

static void desenhaCena( void )
{

	// glClear limpa tambem o buffer de profundidade
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  
  //guarda estado da matriz
  glPushMatrix();
  
  glTranslatef (translate.x, translate.y, translate.z);
  glRotatef( rotate.x, 1.0, 0.0, 0.0);
	glRotatef( rotate.y, 0.0, 1.0, 0.0);
  glRotatef( rotate.z, 0.0, 0.0, 1.0);
	
  
  if (fillit) {
    glColor3f (0.5,0.5,0.5);
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);    
    if (USE_LIST)
      redrawObject();
    else
      drawObject();
    glColor3f (0.0,0.0,0.0);
    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
  }

  //desenha objeto
    if (USE_LIST)
      redrawObject();
    else
      drawObject();

  //recupera estado da matriz
  glPopMatrix();

  //posicionamento do texto
  glRasterPos3f( -2, -2, 0 );

  //desenha o texto (rotacao/translacao X/Y/Z)
  drawText();

	glFlush();
}






//------------------------------------------------------
// Rotina para tratamento de evento - exibicao de janela
//------------------------------------------------------

static void exibeCB()
{
	// Desenha a cena propriamente dita 
	
  desenhaCena();

	// Estamos usando double-buffering. Precisamos trocar
	//   os buffers 
	glutSwapBuffers();
}


//------------------------------------------------
// Rotina para tratamento de evento - Teclado
// tecla - tecla pressionada pelo usuario
// x - posicao x do mouse
// y - posicao y do mouse
//------------------------------------------------

static void tecladoCB( unsigned char tecla, int x, int y )
{
  switch( tecla ) {
  
  case 'x':
  case 'X': 
    axis = X;
    break;
  
  case 'y':
  case 'Y': 
    axis = Y;
    break;
  
  case 'z':
  case 'Z': 
    axis = Z;
    break;
    
  case 'r':                    
  case 'R':
    action = R;
    break;


  case 't':                     
  case 'T':
    action = T;
    

    break;

  case 'f': 
  case 'F': 
      fillit = !fillit;
      break;
  
  case 'q':                     
  case 'Q':
    exit(0);
  
  default:
	  cout << "Opcao nao definida!\n";
    break;
  }
  glutPostRedisplay();
}  

//------------------------------------------------
// Rotina para tratamento de evento
// Alteracao do tamanho da janela
// w - largura atual da janela
// h - altura atual da janela
//------------------------------------------------

static void redesenhaCB( int w, int h )
{
   
  //para manter proporcao, o maximo entre "h" e "v" é usado para os dois parametros
  glViewport( 0, 0, (w > h) ? w : h, (w > h) ? w : h ); 

}   

//-----------------------------
// captura cliques do mouse
//-----------------------------
static void mouseCB(int botao, int estado, int x, int y) {
 
  
  if ( botao == GLUT_LEFT_BUTTON && estado == GLUT_DOWN) {
    mouseStatus.pressTeclaL = 1;
    mouseStatus.x = x;
  }
  else {
    if ( botao == GLUT_LEFT_BUTTON && estado == GLUT_UP)
      mouseStatus.pressTeclaL = 0;
  }
}


//-----------------------------
// captura movimentos do mouse
//-----------------------------
static void mouseMov(int x, int y) {
    
  if ( mouseStatus.pressTeclaL) {
      switch (action) {
        case R:
          if (axis == X) {
            rotate.x += (x - mouseStatus.x);
            if (rotate.x >= 360.0) 
              rotate.x -= 360.0;
            else
              if (rotate.x < 0)
                rotate.x += 360.0;
          
          }
          else if (axis == Y)  {
            rotate.y += (x - mouseStatus.x);
            if (rotate.y >= 360.0) 
              rotate.y -= 360.0;
            else
              if (rotate.y < 0)
                rotate.y += 360.0;
          }
          else if (axis == Z) {
            rotate.z += (x - mouseStatus.x);
            if( rotate.z >= 360.0 ) 
              rotate.z -= 360.0;
            else
              if (rotate.z < 0)
                rotate.z += 360.0;
        
          }
        break;

        case T:
          if (axis == X)
            translate.x += ((x - mouseStatus.x)/DIVISOR_TRANSLATE);
          else if (axis == Y)
            translate.y += ((x - mouseStatus.x)/DIVISOR_TRANSLATE);
          else if (axis == Z)
            translate.z += ((x - mouseStatus.x)/DIVISOR_TRANSLATE);
          break;

      default : break;        

      }
      mouseStatus.x = x;
    }

  glutPostRedisplay();
}


//-------------------------------
// Inicializa Display List
//-------------------------------

void initializeDisplayList() {

  objectList = glGenLists(1);              //gera um id para a call list

  glNewList(objectList, GL_COMPILE);       //comeca "gravacao" de acoes
  int* vs;
  pl->reset();
  while ((vs = pl->getCurrent()) != 0) {
  	glBegin(GL_TRIANGLES);
    glVertex3fv(vl->getVertex(vs[0]));
		glVertex3fv(vl->getVertex(vs[1]));
		glVertex3fv(vl->getVertex(vs[2]));
    glEnd();

  }
  glEndList();                             //termina "gravacao" de acoes

}


static void inic()
{
	
  glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH ); 
  //posicao inicial da janela
  glutInitWindowPosition( 100, 100 );
  
  //tamanho inicial da janela
  glutInitWindowSize(400, 400);

  // Titulo da janela
  glutCreateWindow( "Sistema Visualizador de Objetos Poliédricos - André Detsch" ); 


  // Registra as rotinas de callback 
  glutDisplayFunc( exibeCB );
  glutKeyboardFunc( tecladoCB );
  glutMouseFunc( mouseCB );
  glutMotionFunc( mouseMov );
  glutReshapeFunc( redesenhaCB );

  // Cor de fundo default 
  glClearColor( (float)0.8 , (float)0.8 , (float)0.8 , 0 );       
  
  //cor do desenho: preto
  glColor3f (0.0,0.0,0.0);
	
  //modo aramado
  glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);

  // Habilita remocao de elementos ocultos 
  glEnable( GL_DEPTH_TEST );            //eliminacao de elementos ocultos
  glDepthFunc( GL_LESS );
  glEnable( GL_CULL_FACE );
  setaCamera();                         //posicao inicial da camera.

  if (USE_LIST)
    initializeDisplayList();            //inicializa call list (se call list estiver em uso)

}

int main(int argc,char** argv) {

  if (argc != 2) {
    cerr << "Numero incorreto de parametros. Use \"sivop <nome_arquivo>\"";
    return 1;
  }
  //inicializacao de variaveis globais
  mouseStatus.pressTeclaL = 0;
  rotate.x = 0;
  rotate.y = 0;
  rotate.z = 0;
  translate.x = 0;
  translate.y = 0;
  translate.z = 0;
  fillit = 0;
  axis = X;
  action = T;


  //leitura do arquivo
  FileReader* fr = new FileReader();
  if (fr->readFile(argv[1])) {
    cerr << "Erro lendo arquivo \"" << argv[1] << "\"";
    return 1;
  }
  
  //inicializacao da parte grafica
  inic();
  

  //Loop principal do Glut
  glutMainLoop(); 

  return 0;
}
