280 likes | 390 Views
Desenhos Tridimensionais. Primitivas Gráficas em 3D.
E N D
Primitivas Gráficas em 3D A forma de representação de objetos 3D mais utilizada em computação gráfica consiste na especificação de uma malha de faces poligonais. Uma superfície discretizada por faces planas que podem ser triângulos (preferencialmente) ou quadrados com tabela de vértices e tabela de faces.
A utilização de primitivas gráficas em três e duas dimensões é praticamente igual. A principal diferença está na especificação dos vértices , pois em 3 dimensões deve-se atribuir um valor para a coordenada Z. Qualquer uma das primitivas gráficas podem ser utilizadas quando se está trabalhando em 3 dimensões.
Implementação de um objeto A primeira providência é definir algumas estruturas para armazenar vértices e faces. A estrutura de vértices é simplesmente uma coordenada 3D: // Define um vértice typedef struct { float x,y,z; // posição no espaço } VERT;
Já na estrutura de faces é preciso armazenar o total de vértices sendo utilizados por cada uma e um outro vetor, que contém os índices para cada vértice. (Triângulos e Quadriláteros) • // Define uma face • typedef struct { • int total; // total de vértices • int ind[4]; // índices para o vetor de vértices • } FACE;
A última estrutura é para representar um objeto – aqui se guardam apontadores para o vetor de vértices, para o vetor de faces e também a quantidade de faces, uma vez que essa informação não existe em nenhum outro local: • // Define um objeto 3D • typedef struct { • VERT *vertices; // aponta para os vértices • FACE *faces; // aponta para as faces • int total_faces; // total de faces no objeto • } OBJ;
Vetor de vértice • // Definição dos vértices • VERT vertices[ ] = { • { -1, 0, -1 }, // 0 canto inf esquerdo tras. • { 1, 0, -1 }, // 1 canfo inf direito tras. • { 1, 0, 1 }, // 2 canto inf direito diant. • { -1, 0, 1 }, // 3 canto inf esquerdo diant. • { 0, 2, 0 }, // 4 topo • };
Vetor de Faces • // Definição das faces • FACE faces[] = { • { 4, { 0,1,2,3 }}, // base • { 3, { 0,1,4,-1 }}, // lado traseiro • { 3, { 0,3,4,-1 }}, // lado esquerdo • { 3, { 1,2,4,-1 }}, // lado direito • { 3, { 3,2,4,-1 }} // lado dianteiro • };
Definição do Objeto • // Finalmente, define o objeto pirâmide • OBJ piramide = { • vertices, faces, 5 };
Faces diretamente no código? - Separar o modelo 3D do código que desenha o mesmo; * Se trocar o modelo não será necessário modificar o código. • Eficiência no armazenamento: se substituirmos as estruturas por um conjunto de chamadas de desenho, potencialmente vários vértices serão repetidamente informados.
Pirâmide: Cada vértice é compartilhado por pelo menos três faces. Isso também criaria um problema considerável no caso da necessidade de alterar o modelo: todas as ocorrências de cada vértice teriam que ser modificadas.
Função: Partindo dessas razões, escrevemos uma função que recebe um objeto genérico, indicado por sua estrutura devidamente preenchida, e traça o contorno de todas as suas faces (usando GL_LINE_LOOP):
// Desenha um objeto em wireframe • void DesenhaObjetoWireframe(OBJ *objeto) • { • OBJ *obj = objeto; • // Percorre todas as faces • for(int f=0; f < obj->total_faces; ++f) • { • glBegin(GL_LINE_LOOP); • // Percorre todos os vértices da face • for(int v=0; v < obj->faces[f].total; ++v) • glVertex3f(obj->vertices[faces[f].ind[v]].x, • obj->vertices[faces[f].ind[v]].y, • obj->vertices[faces[f].ind[v]].z); • } • glEnd(); • }
Para desenhar um objeto, basta agora chamar a função “DesenhaObjetoWireframe” e passar um apontador para o objeto desejado (se houver mais de um). Isso é feito na função callback de desenho:
// Função callback de redesenho da janela de visualização • void Desenha(void) • { • // Limpa a janela de visualização com a cor • // de fundo definida previamente • glClear(GL_COLOR_BUFFER_BIT); • // Altera a cor do desenho para preto • glColor3f(0.0f, 0.0f, 0.0f); • // Desenha o objeto definido anteriormente: uma pirâmide • DesenhaObjetoWireframe(&piramide); • // Executa os comandos OpenGL • glFlush(); • }
Este exemplo também introduz um conceito muito importante: comandos de navegação, que serão incluidos em quase todos os demais códigos, como segue: - Movimento do mouse: botões esquerdo, direito e centro. - Teclas Home e End
A implementação desses comandos é realizada em funções diferentes: para rotacionar o objeto, utilizam-se duas variáveis – rotX e rotY - que são empregados na função que posiciona o observador (PosicionaObservador). Nessa mesma função também se empregam as variáveis obsX, obsY e obsZ, que informam a posição do objeto em relação ao observador.
// Função usada para especificar a posição do observador virtual • void PosicionaObservador(void) • { • // Especifica sistema de coordenadas do modelo • glMatrixMode(GL_MODELVIEW); • // Inicializa sistema de coordenadas do modelo • glLoadIdentity(); • // Posiciona e orienta o observador • glTranslatef(-obsX,-obsY,-obsZ); • glRotatef(rotX,1,0,0); • glRotatef(rotY,0,1,0); • }
Ângulo de Visão Já o ângulo de visão (variável angle) é empregada na função EspecificaParametrosDeVisualizacao:
// Função usada para especificar o volume de visualização • void EspecificaParametrosVisualizacao(void) • { • // Especifica sistema de coordenadas de projeção • glMatrixMode(GL_PROJECTION); • // Inicializa sistema de coordenadas de projeção • glLoadIdentity(); • // Especifica a projeção perspectiva(angulo,aspecto,zMin,zMax) • gluPerspective(angle,fAspect,0.1,1200); • PosicionaObservador(); • }
Callbacks de mouse • // Função callback para eventos de botões do mouse • void GerenciaMouse(int button, int state, int x, int y) • { • if(state==GLUT_DOWN) • { • // Salva os parâmetros atuais • x_ini = x; • y_ini = y; • obsX_ini = obsX; • obsY_ini = obsY; • obsZ_ini = obsZ; • rotX_ini = rotX; • rotY_ini = rotY; • bot = button; • } • else bot = -1; • }
Durante o movimento em si, a GLUT chama a função GerenciaMovim, a qual recebe as coordenadas atualizadas do mouse. Primeiramente, determina-se qual botão foi pressionado: se foi o botão esquerdo, calculam-se as diferenças (em X e Y) entre a posição atual e a inicial (salva na função GerenciaMouse), e atualizamos as variáveis que controlam a rotação do objeto. A constante SENS_ROT, definida antes da função, permite especificar a sensibilidade do movimento: quanto maior o valor, mais o mouse terá que ser deslocado para afetar a rotação.
// Função callback para eventos de movimento do mouse • #define SENS_ROT 5.0 • #define SENS_OBS 15.0 • #define SENS_TRANSL 30.0 • void GerenciaMovim(int x, int y) • { • // Botão esquerdo ? • if(bot==GLUT_LEFT_BUTTON) • { • // Calcula diferenças • int deltax = x_ini - x; • int deltay = y_ini - y; • // E modifica ângulos • rotY = rotY_ini - deltax/SENS_ROT; • rotX = rotX_ini - deltay/SENS_ROT; • }
No segundo trecho, trata-se o botão direito: nesse caso, basta calcular a diferença apenas em Y e atualizar a variável obsZ. É utilizada outra constante, SENS_OBS, também para definira sensibilidade do movimento:
// Botão direito ? • else if(bot==GLUT_RIGHT_BUTTON) • { • // Calcula diferença • int deltaz = y_ini - y; • // E modifica distância do observador • obsZ = obsZ_ini + deltaz/SENS_OBS; • }
Por fim, o último trecho considera o botão do meio: o processo é praticamente o mesmo do primeiro trecho, mas atualizam-se as variáveis que controlam a translação em X e Y: obsX e obsY. Porém, note que as diferenças são somadas em X, mas subtraídas em Y, de modo que se o mouse for deslocado para baixo, o objeto também será movido na mesma direção. A constante de sensibilidade empregada aqui é denominada SENS_TRANSL:
// Botão do meio ? • else if(bot==GLUT_MIDDLE_BUTTON) • { • // Calcula diferenças • int deltax = x_ini - x; • int deltay = y_ini - y; • // E modifica posições • obsX = obsX_ini + deltax/SENS_TRANSL; • obsY = obsY_ini - deltay/SENS_TRANSL; • } • PosicionaObservador(); • glutPostRedisplay(); • }