Modelagem de Sólidos

Capítulo 5. Modelagem de Sólidos

O propósito desta lição é introduzir o uso de listas de apresentação (display lists), mapeamento de texturas e o uso de teclas
especiais.

As listas de apresentação permitem que vários comandos sejam executados de
uma vez só apenas com uma chamada da lista. Cada lista de apresentação precisa
ser armazenada apenas uma vez, de modo que quando uma chamada é realizada, todos
os comandos declarados nesta lista já estarão presentes máquina interna do
OpenGL, tornando o processo de desenho mais fácil e com performance melhorada.

O mapeamento de texturas nos objetos torna a cena mais realística,
principalmente quando texturas de objetos reais são utilizadas. A maioria das
texturas existentes não podem ser geradas de forma artificial. Quando isto é
possível, os algoritmos utilizados são complexos e computacionamente custosos.
Assim, geramente as texturas são lidas de arquivos de imagens digitais
capturadas com dispositivos de arquisição de dados.

Diversos formatos podem ser usados para armazenar uma imagem digital: bmp,
jpeg, gif, tif, tga etc. Neste curso usaremos o formato IRIS RGB, criado pela
Silicon Graphics. As funções que permitem a leitura de arquivos RGB e seus
respectivos protótipos estão implementados nos arquivos image.c e image.h.

O objeto utilizado nesta lição será um modelo simples de uma avião a jato com
fuselagem texturizada, imóvel sobre um plano com textura montanhosa, como mostra
a Figura
5-1
. As vistas laterais, superior e frontal estão esquematizadas na Figura
5-2
.

Neste exemplo, o olhar do observador é fixo aproximadamente no centro do
avião, de modo que apenas a posição do observador é alterada para o usuário
poder visualizar diversas tomadas da cena.

O programa usado para modelar o avião é mostrado no Exemplo
5-1
. As teclas LEFT e RIGHT servem
para rotacionar a posição do observador em torno do eixo y contra e a favor do
sentido dos ponteiros do relógio, respectivamente. A distância entre o
observador e o centro de rotação (raio de observação) é alterado pelas teclas r e R, que aumentam o diminuem o seu valor,
respectivamente. As teclas UP e DOWN,
controlam a altitude do observador (no eixo y). A tecla t
habilita ou desabilita o uso de texturas. Para finalizar o programa, basta
digitar ESC. As teclas e suas respectivas ações estão
definidas nas funções keyboard() e special().

Exemplo 5-1. programa jato.c

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>
#include "image.h"

#define PI 3.1415

#define COORD_TEXTURA_PLANO 1.0
#define COORD_TEXTURA_AVIAO 1.0
#define COR_DO_PLANO 0.52,0.52,0.78,1.0
#define COR_DO_AVIAO 0.3,0.52,0.18,1.0
#define TEXTURA_DO_PLANO "montanhas.rgb"
#define TEXTURA_DO_AVIAO "camuflagem.rgb"

GLint WIDTH =800;
GLint HEIGHT=600;

GLfloat obs[3]={0.0,7.0,0.0};
GLfloat look[3]={0.0,3.0,0.0};
GLuint  textura_plano;
GLuint  textura_aviao;

GLshort texturas=1;
GLfloat tetaxz=0;
GLfloat raioxz=6;
GLuint  jato;

GLfloat ctp[4][2]={
  {-COORD_TEXTURA_PLANO,-COORD_TEXTURA_PLANO},
  {+COORD_TEXTURA_PLANO,-COORD_TEXTURA_PLANO},
  {+COORD_TEXTURA_PLANO,+COORD_TEXTURA_PLANO},
  {-COORD_TEXTURA_PLANO,+COORD_TEXTURA_PLANO}
};

GLfloat cta[4][2]={
  {-COORD_TEXTURA_AVIAO,-COORD_TEXTURA_AVIAO},
  {+COORD_TEXTURA_AVIAO,-COORD_TEXTURA_AVIAO},
  {+COORD_TEXTURA_AVIAO,+COORD_TEXTURA_AVIAO},
  {-COORD_TEXTURA_AVIAO,+COORD_TEXTURA_AVIAO}
};


void reshape(int width, int height){
  WIDTH=width;
  HEIGHT=height;
  glViewport(0,0,(GLint)width,(GLint)height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(70.0,width/(float)height,0.1,30.0);
  glMatrixMode(GL_MODELVIEW);
}

void compoe_jato(void){
  GLUquadricObj *quadric;

  GLfloat asa[][3]={
    {-4.0,0.0,0.0},
    {+4.0,0.0,0.0},
    {0.0,0.0,3.0}
  };

  GLfloat cauda[][3]={
    {0.0,0.0,0.0},
    {0.0,2.0,-1.0},
    {0.0,2.0,0.0},
    {0.0,0.0,2.0}
  };
  /* inicia a composicao do jato */
  jato = glGenLists(1);
  glNewList(jato, GL_COMPILE);

  /* asas */
  glBegin(GL_TRIANGLES); 
  glTexCoord2fv(cta[0]); glVertex3fv(asa[0]);
  glTexCoord2fv(cta[1]); glVertex3fv(asa[1]);
  glTexCoord2fv(cta[3]); glVertex3fv(asa[2]);
  glEnd();
  
  /* corpo */
  quadric = gluNewQuadric();
  gluQuadricTexture(quadric, GL_TRUE);
  gluCylinder(quadric, 0.5, 0.5, 4, 12, 3);
  
  /* nariz */
  quadric = gluNewQuadric();
  gluQuadricTexture(quadric, GL_TRUE);
  glPushMatrix();
  glTranslatef(0,0,4);
  gluCylinder(quadric, 0.5, 0.0, 1.5, 12, 3);
  glPopMatrix();

  /* cauda */
  glBegin(GL_POLYGON); 
  glTexCoord2fv(cta[0]); glVertex3fv(cauda[0]);
  glTexCoord2fv(cta[1]); glVertex3fv(cauda[1]);
  glTexCoord2fv(cta[2]); glVertex3fv(cauda[2]);
  glTexCoord2fv(cta[3]); glVertex3fv(cauda[3]);
  glEnd();

  /* cabine do piloto */
  glTranslatef(0,0.3,3.5);
  glPushMatrix();
  glScalef(0.7,0.7,2.0);
  quadric=gluNewQuadric();
  glColor3f(0.3,0.5,1);
  glDisable(GL_TEXTURE_2D);
  gluSphere(quadric,0.5,12,12);
  glPopMatrix();

  /* termina a composicao do jato*/
  glEndList();
}

void display(void){
  glEnable(GL_DEPTH_TEST);
  
  glDepthMask(GL_TRUE);
  glClearColor(1.0,1.0,1.0,1.0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  
  glPushMatrix();

  /* calcula a posicao do observador */
  obs[0]=raioxz*cos(2*PI*tetaxz/360);
  obs[2]=raioxz*sin(2*PI*tetaxz/360);
  gluLookAt(obs[0],obs[1],obs[2],look[0],look[1],look[2],0.0,1.0,0.0);

  /* habilita/desabilita uso de texturas*/
  if(texturas){
    glEnable(GL_TEXTURE_2D);  
  }
  else{
    glDisable(GL_TEXTURE_2D);
  }

  glColor4f(COR_DO_PLANO);
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
  glBindTexture(GL_TEXTURE_2D,textura_plano);
   
  glBegin(GL_QUADS);
  glTexCoord2fv(ctp[0]);  glVertex3f(-10,0,10);
  glTexCoord2fv(ctp[1]);  glVertex3f(10,0,10);
  glTexCoord2fv(ctp[2]);  glVertex3f(10,0,-10);
  glTexCoord2fv(ctp[3]);  glVertex3f(-10,0,-10);
  glEnd();
  glTranslatef(0.0,2.0,-3.0);

  glColor4f(COR_DO_AVIAO);
  glBindTexture(GL_TEXTURE_2D,textura_aviao);
  glCallList(jato);

  glPopMatrix();
  glutSwapBuffers();
}


void special(int key, int x, int y){
  switch (key) {
  case GLUT_KEY_UP:
    obs[1]=obs[1]+1;
    glutPostRedisplay();
    break;
  case GLUT_KEY_DOWN:
    obs[1] =obs[1]-1;
    glutPostRedisplay();
    break;
  case GLUT_KEY_LEFT:
    tetaxz=tetaxz+2;
    glutPostRedisplay();
    break;
  case GLUT_KEY_RIGHT:
    tetaxz=tetaxz-2;
    glutPostRedisplay();
    break;
  }
}

void keyboard(unsigned char key, int x, int y){
  switch (key) {
  case 27:
    exit(0);
    break;
  case 't':
    texturas = !texturas;
    glutPostRedisplay();
    break;
  case 'r':
    raioxz=raioxz+1;
    glutPostRedisplay();
    break;
  case 'R':
    raioxz=raioxz-1;
    if(raioxz==0){
      raioxz=1;
    }
    glutPostRedisplay();
    break;
  }
}

void carregar_texturas(void){
  IMAGE *img;
  GLenum gluerr;

  /* textura do plano */
  glGenTextures(1, &textura_plano);
  glBindTexture(GL_TEXTURE_2D, textura_plano);
  
  if(!(img=ImageLoad(TEXTURA_DO_PLANO))) {
    fprintf(stderr,"Error reading a texture.n");
    exit(-1);
  }

  gluerr=gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 
			   img->sizeX, img->sizeY, 
			   GL_RGB, GL_UNSIGNED_BYTE, 
			   (GLvoid *)(img->data));
  if(gluerr){
    fprintf(stderr,"GLULib%sn",gluErrorString(gluerr));
    exit(-1);
  }

  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);

  /* textura do aviao */
  glGenTextures(1, &textura_aviao);
  glBindTexture(GL_TEXTURE_2D, textura_aviao);

  
  if(!(img=ImageLoad(TEXTURA_DO_AVIAO))) {
    fprintf(stderr,"Error reading a texture.n");
    exit(-1);
  }

  gluerr=gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 
			   img->sizeX, img->sizeY, 
			   GL_RGB, GL_UNSIGNED_BYTE, 
			   (GLvoid *)(img->data));
  if(gluerr){
    fprintf(stderr,"GLULib%sn",gluErrorString(gluerr));
    exit(-1);
  }

  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
  
}

void init(){
  carregar_texturas();
  compoe_jato();
  glShadeModel(GL_FLAT);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_TEXTURE_2D);
}

int main(int argc,char **argv){
  glutInitWindowPosition(0,0);
  glutInitWindowSize(WIDTH,HEIGHT);
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_RGB|GLUT_DEPTH|GLUT_DOUBLE);

  if(!glutCreateWindow("Avião a jato")) {
    fprintf(stderr,"Error opening a window.n");
    exit(-1);
  }

  init();
  
  glutKeyboardFunc(keyboard);
  glutSpecialFunc(special);
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutMainLoop();

  return(0);
}

Para compilar e executar o programa jato.c, salve-o juntamente com os
arquivo Makefile.modelagem, image.c, image.h, montanhas.rgb e camuflagem.rgb em um diretório e execute a seguinte seqüência de
comandos:

$ make -f Makefile.modelagem jato
$ ./jato

5.1. Descrição do programa jato.c

#define COORD_TEXTURA_PLANO 1.0
#define COORD_TEXTURA_AVIAO 1.0
#define COR_DO_PLANO 0.52,0.52,0.78,1.0
#define COR_DO_AVIAO 0.3,0.52,0.18,1.0
#define TEXTURA_DO_PLANO "montanhas.rgb"
#define TEXTURA_DO_AVIAO "camuflagem.rgb"

Define as cores e coordenadas das texturas do plano e do avião, além dos
nomes dos arquivos que contém as imagens das texturas.

GLint WIDTH =320;
GLint HEIGHT=240;

Especifica a largura (WIDTH) e a altura (HEIGHT) iniciais da janela de desenho.

GLfloat obs[3]={0.0,7.0,0.0};
GLfloat look[3]={0.0,3.0,0.0};

As coordenadas da posição do observador é armazenada no vetor obs[] e as do ponto para onde o observador olha é armazenado no
vetor look[]. De acordo com estes vetores, o observador
encontra-se no ponto (x,y,z)=(0,7,0) e está olhando para o ponto
(x,y,z)=(0,3,0).

GLuint  textura_plano;
GLuint  textura_aviao;

As variáveis textura_plano e textura_aviao armazenam os identificadores das texturas do
plano e do avião.

GLshort texturas=1;
GLfloat tetaxz=0;
GLfloat raioxz=6;
GLuint  jato;

A variável texturas é usada para habilitar/desabilitar
o uso de texturas nos objetos. As variáveis tetaxz e
raioxz armazenam o ângulo de rotação do ponto de
observação e a distância entre o observador e a origem, respectivamente. A
variável jato amazena o identificador para a lista de
apresentação do avião a jato.

GLfloat ctp[4][2]={
  {-COORD_TEXTURA_PLANO,-COORD_TEXTURA_PLANO},
  {+COORD_TEXTURA_PLANO,-COORD_TEXTURA_PLANO},
  {+COORD_TEXTURA_PLANO,+COORD_TEXTURA_PLANO},
  {-COORD_TEXTURA_PLANO,+COORD_TEXTURA_PLANO}
};

GLfloat cta[4][2]={
  {-COORD_TEXTURA_AVIAO,-COORD_TEXTURA_AVIAO},
  {+COORD_TEXTURA_AVIAO,-COORD_TEXTURA_AVIAO},
  {+COORD_TEXTURA_AVIAO,+COORD_TEXTURA_AVIAO},
  {-COORD_TEXTURA_AVIAO,+COORD_TEXTURA_AVIAO}
};

Quando uma textura é carregada, o OpenGL guarda em uma matriz e assume
coordenadas (0,0), (1,0), (1,1) e (0,1) para os quatro cantos da textura. Neste
exemplo, é assumido a repetição das texturas, de modo a poder cobrir todo o
objeto. As coordenadas especificadas neste trecho de código indicam que o objeto
será carimbado com sua respectiva textura e que as coordenadas das texturas que
serão usadas para criar os carimbos do plano e do avião serão ctp[] e cta[], respectivamente.

  GLUquadricObj *quadric;

Quádricas são superfícies definidas pela seguinte equação
a1x2 + a2y2
+a3z2 + a4xy + a5yz +
a6xz + a7x + a8y +a9z +
a10. Os tipos de quádricas mais conhecidos são os cones, os
cilindros, as esferas e os discos, modelados pelo ajuste adequado dos parâmetros
ai. Estes objetos são guardados em uma estrutura de dados do tipo
GLUquadricObj.

  GLfloat asa[][3]={
    {-4.0,0.0,0.0},
    {+4.0,0.0,0.0},
    {0.0,0.0,3.0}
  };

  GLfloat cauda[][3]={
    {0.0,0.0,0.0},
    {0.0,2.0,-1.0},
    {0.0,2.0,0.0},
    {0.0,0.0,2.0}
  };

As variáveis asa[][] e cauda[][] armazenam as coordenadas dos polígonos que compõem a
asa e a cauda do avião, respectivamente.

  jato = glGenLists(1);
  glNewList(jato, GL_COMPILE);

A função glGenLists() aloca um conjunto contínuo de
listas de apresentação vazias e retorna o identificador utilizado para a lista
alocada. Neste exemplo, o conjunto contém apenas uma lista de apresentação. Mais
listas poderão ser alocadas com subseqüentes chamadas à mesma função.

A função glNewList() especifica o início de uma lista
de apresentação. Todos os comandos executados até a chamada de glEndList() ficarão armazenados nesta lista. A função glNewList() possui o seguinte protótipo:

void glNewList(GLUint list, GLenum
mode);

list é um número inteiro maior que zero que
identifica de forma única a lista de apresentação. O parâmetro mode pode assumir os valores GL_COMPILE e GL_COMPILE_AND_EXECUTE. O primeiro apenas armazena
os comandos na lista; o segundo executa os comandos enquanto são armazenados na
lista.

  quadric = gluNewQuadric();
  gluQuadricTexture(quadric, GL_TRUE);
  gluCylinder(quadric, 0.5, 0.5, 4, 12, 3);

Inicia, usando a função gluNewQuadric(), a primeira
das quádricas do exemplo: o corpo do avião. Para esta quádrica, o mapeamento de
textura está habilitado. A função gluCylinder() possui o
seguinte protótipo:

void gluCylinder(GLUquadric* quad, GLdouble
base, GLdouble top, GLdouble height, GLint slices, GLint stacks);

O parâmetro quad é o objeto de quádrica; base, top e height especificam o raio da base, o raio do topo e
a altura do cilindro, respectivamente; slices
stacks especificam o número de subdivisões ao
redor do eixo z e ao longo do mesmo.

  obs[0]=raioxz*cos(2*PI*tetaxz/360);
  obs[2]=raioxz*sin(2*PI*tetaxz/360);
  gluLookAt(obs[0],obs[1],obs[2],look[0],look[1],look[2],0.0,1.0,0.0);

Na função display() as coordenadas x e z da posição
do observador são calculadas e a função gluLookAt() é
chamada para mudar a posição do observador. Recomenda-se a execução do Tutorial do Nate Robins (ver TelEduc) relacionado
ao uso da função gluLookAt().

  if(texturas){
    glEnable(GL_TEXTURE_2D);  
  }
  else{
    glDisable(GL_TEXTURE_2D);
  }

Aqui o mapeamento de texturas é habilitado ou desabilitado, de acordo com o
estado da variável texturas. Quando o mapeamento de
texturas é desabilitado, o objeto é desenhado utilizando as cores especificadas
pela função glColor*().

  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);

A função glTexEnvf() define os parâmetros do ambiente
de textura. O primeiro parâmetro é sempre GL_TEXTURE_ENV; o segundo parâmetro é sempre GL_TEXTURE_ENV_MODE e o terceiro especifica como a
textura será combinada com a cor para formar a superfície do objeto, neste caso
substituindo completamente a cor do objeto pela textura corrente.

  glBindTexture(GL_TEXTURE_2D,textura_plano);
   
  glBegin(GL_QUADS);
  glTexCoord2fv(ctp[0]);  glVertex3f(-10,0,10);
  glTexCoord2fv(ctp[1]);  glVertex3f(10,0,10);
  glTexCoord2fv(ctp[2]);  glVertex3f(10,0,-10);
  glTexCoord2fv(ctp[3]);  glVertex3f(-10,0,-10);
  glEnd();

glBindTexture() carrega a textura bidimensional
associada com a variável textura_plano. Em seguida, um
plano é desenhado usando GL_QUADS. Observe que,
antes de desenhar cada vértice, a função glTexCoord2fv()
é chamada para definir as coordenadas de textura correntes.

void carregar_texturas(void){
  IMAGE *img;
  GLenum gluerr;

  /* textura do plano */
  glGenTextures(1, &textura_plano);
  glBindTexture(GL_TEXTURE_2D, textura_plano);
  
  if(!(img=ImageLoad(TEXTURA_DO_PLANO))) {
    fprintf(stderr,"Error reading a texture.n");
    exit(-1);
  }

A carga das texturas é feita com uso da função ImageLoad(), implementada em image.c e definida em image.h. Esta função recebe como
parâmetro o nome do arquivo com a imagem da textura e retorna um ponteiro para
uma estrutura de dados do tipo IMAGE.

A função glGenTextures() gera 1 nome de textura em
textura_plano. Um nome de textura é qualquer inteiro
diferente de zero que identifique de forma única a textura. Assim, texturas
diferentes possuem nomes diferentes. Caso o segundo parâmetro desta função seja
um vetor de elementos GLuint, mais nomes de textura
podem ser gerados, um para cada elemento deste vetor.

  gluerr=gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 
			   img->sizeX, img->sizeY, 
			   GL_RGB, GL_UNSIGNED_BYTE, 
			   (GLvoid *)(img->data));

Mipmaps são séries de versões em baixa resolução de um mapa de textura.
Geralmente é utilizado para texturizar um objeto cuja resolução na tela difere
da resolução no mapa de textura. Por exemplo, um objeto próximo da tela pode ser
desenhado utilizando uma resolução de textura maior que um objeto distante da
tela. O uso de mipmaps evita o efeito de serrilhamento (aliasing) e outros distúrbios de exibição quando um
objeto é aproximado ou afastado da tela.

Mipmaps bidimensionais são construídos com a função gluBuild2dMipmaps(), que possuiu seguinte protótipo:

void gluBuild2dMipmaps(GLenum target, GLint
internalFormat, GLsizei width, GLsizei height, GLenum format, GLenum type, const
void *data);

target indica o tipo de mipmaps que se deseja
construir – neste caso uma textura bidimensional. internalFormat indica o tipo de armazenagem interna
do arquivo de textura. Neste exemplo, as texturas são imagens RGB, incluindo 3
(três) componentes de cor. Os parâmetros width e
height especificam a largura e a altura do dado.
Ambas estas dimensões devem ser potências de 2. format especifica o formato dos pixels do dado
(RGB). type especifica o tipo de dado
representado no vetor de dados. data especifica
o ponteiro para a posição de memória onde os dados de textura residem.

  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);

A função glTexParameterf() define uma série de
parâmetros que controlam como uma textura é tratada e aplicada a um fragmento de
um dado objeto. Aqui, através dos parâmetros GL_TEXTURE_WRAP_S e GL_TEXTURE_WRAP_T, a função define que nas direções
s e t (coordenadas) a textura deverá será repetida no objeto.

  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

Neste trecho do código, as funções glTexParameterf()
define os tipos de filtro usados quando a textura for minimizada ou maximizada.
Pelo parâmetro GL_LINEAR_MIPMAP_LINEAR, o OpenGL
escolhe dois mipmaps que mais aproximam o tamanho do pixel a ser texturizado
calcula a média dos quatro elementos de textura mais próximos do centro do
pixel. O valor da textura para o pixel será a média desses dois valores. O
parâmetro GL_LINEAR, por sua vez não utiliza
mipmaps: associa ao pixel a ser texturizado a média dos quatro elementos de
textura mais próximos do centro do pixel.