O propósito desta lição é mostrar como gerar em um plano as curvas paramétricas mais comuns no OpenGL: as curvas de Bézier e as NURBS (Non Uniform Rational B-Splines). A forma destas curvas é controlada pelo posicionamento dos vértices de um polígono característico, cuja influência será estudada de forma interativa no programa exemplo. Será estudada também a influência dos vetores de nós sobre as NURBS, vetores estes que servem para definir a região de influência de cada vértice do polígono característico na forma da curva.
É introduzido também o uso de uma nova função do GLUT: glutMotionFunc(), ativada quando o mouse é movimentado pela janela enquanto um ou mais botões são pressionados.
O programa utilizado nesta lição permite que o usuário controle a forma de uma curva através da seleção e movimentação dos vértices (destacados em vermelho) de um polígono (em amarelo), como mostra a Figura 7-1.
O programa usado para modelar as splines é mostrado no Exemplo 7-1. As teclas b e n definem o tipo de curva que será desenhada: Bézier ou NURBS, respectivamente.
Exemplo 7-1. programa splines2d.c
#include <stdlib.h> #include <GL/glut.h> #include <math.h> GLint nVertices=6; GLfloat vertices[6][3] = { {-4.0, 0.0, 0.0}, {-4.0, +4.0, 0.0}, {+4.0, -4.0, 0.0}, {-4.0, -4.0, 0.0}, {+4.0, +4.0, 0.0}, {+4.0, 0.0, 0.0} }; GLint largura, altura; GLint mudaCurva=0; GLint verticeCorrente=0; GLfloat esquerda=-5; GLfloat direita =+5; GLfloat fundo =-5; GLfloat topo =+5; GLfloat longe =+5; GLfloat perto =-5; enum {BEZIER, NURBS}; GLint spline; GLUnurbsObj *nc; GLfloat nos[10]={0.0, 0.0, 0.0, 0.0, 1.0, 2.5, 3.0, 3.0, 3.0, 6.0}; GLint nNos=10; GLint matrizViewport[4]; GLdouble matrizModelview[16], matrizProjecao[16]; GLint yreal; /* posição da coordenada y no OpenGL */ GLdouble wx, wy, wz; /* coordenadas no mundo real: x, y, z */ void display(void){ int i; glClear(GL_COLOR_BUFFER_BIT); switch(spline){ case BEZIER: glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, nVertices, &vertices[0][0]); glBegin(GL_LINE_STRIP); for (i = 0; i <= 30; i++){ glEvalCoord1f((GLfloat) i/30.0); } glEnd(); break; case NURBS: gluBeginCurve(nc); gluNurbsCurve(nc, nNos, nos, 3, &vertices[0][0], 4, GL_MAP1_VERTEX_3); gluEndCurve(nc); break; } glPointSize(5.0); glColor3f(1.0, 1.0, 0.0); glBegin(GL_LINE_STRIP); for (i = 0; i < nVertices; i++) glVertex3fv(&vertices[i][0]); glEnd(); glColor3f(1.0, 0.0, 0.0); glBegin(GL_POINTS); for (i = 0; i < nVertices; i++) glVertex3fv(&vertices[i][0]); glEnd(); glColor3f(1.0, 1.0, 1.0); glFlush(); glutSwapBuffers(); } void init(void){ glClearColor(0.0, 0.0, 0.0, 0.0); spline=BEZIER; nc= gluNewNurbsRenderer(); gluNurbsProperty(nc, GLU_SAMPLING_TOLERANCE, 5.0); glEnable(GL_MAP1_VERTEX_3); display(); } void reshape(int w, int h) { glViewport(0, 0, (GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); largura=w; altura=h; glLoadIdentity(); glOrtho(esquerda,direita, fundo, topo, perto, longe); glMatrixMode(GL_MODELVIEW); glGetIntegerv(GL_VIEWPORT, matrizViewport); glGetDoublev(GL_MODELVIEW_MATRIX, matrizModelview); glGetDoublev(GL_PROJECTION_MATRIX, matrizProjecao); glLoadIdentity(); glutSwapBuffers(); } /* ARGSUSED1 */ void keyboard(unsigned char key, int x, int y) { switch (key) { case 'b': spline = BEZIER; glutPostRedisplay(); break; case 'n': spline = NURBS; glutPostRedisplay(); break; case 27: exit(0); break; } } void proximidade(){ int i; double tam=0, tamin=32000; verticeCorrente=0; for(i=0; i<nVertices; i++){ tam = (wx-vertices[i][0])*(wx-vertices[i][0])+ (wy-vertices[i][1])*(wy-vertices[i][1]); if(tam < tamin){ tamin=tam; verticeCorrente=i; } } tamin=sqrt(tamin); if(tamin > 0.5){ mudaCurva=0; } } void mouse(int button, int state, int x, int y){ switch (button) { case GLUT_LEFT_BUTTON: if (state == GLUT_DOWN) { yreal = matrizViewport[3] - (GLint) y - 1; gluUnProject ((GLdouble) x, (GLdouble) yreal, 0.0, matrizModelview, matrizProjecao, matrizViewport, &wx, &wy, &wz); mudaCurva=1; proximidade(); } if (state == GLUT_UP) { mudaCurva=0; } break; } } void motion(int x, int y){ if(mudaCurva){ yreal = matrizViewport[3] - (GLint) y - 1; gluUnProject ((GLdouble) x, (GLdouble) yreal, 0.0, matrizModelview, matrizProjecao, matrizViewport, &wx, &wy, &wz); vertices[verticeCorrente][0]=wx; vertices[verticeCorrente][1]=wy; glutPostRedisplay(); } } int main(int argc, char** argv){ glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); glutInitWindowSize(500, 500); glutInitWindowPosition(100, 100); glutCreateWindow(argv[0]); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutMotionFunc(motion); glutMouseFunc(mouse); glutMainLoop(); return 0; } |
Para compilar e executar o programa splines2d.c, salve-o juntamente com o arquivo Makefile em um diretório e execute a seguinte seqüência de comandos:
$ make splines2d
$ splines2d |
GLint nVertices=6; GLfloat vertices[6][3] = { {-4.0, 0.0, 0.0}, {-4.0, +4.0, 0.0}, {+4.0, -4.0, 0.0}, {-4.0, -4.0, 0.0}, {+4.0, +4.0, 0.0}, {+4.0, 0.0, 0.0} }; |
Define o número de vértices do polígono característico (variável nVertices) e as posições no espaço dos seus respectivos vértices (vetor vertices).
GLint largura, altura; GLint mudaCurva=0; GLint verticeCorrente=0; |
As variáveis largura e altura armazenam a largura e a altura da janela corrente, em pixels. mudaCurva grava o estado da curva; quando mudaCurva=1, a posição dos vértices pode ser alterada; quando mudaCurva=0, a curva permanece inalterada com o movimento do mouse. A variável verticeCorrente indica o vértice cuja posição poderá ser alterada através da movimentação do mouse.
GLfloat esquerda=-5; GLfloat direita =+5; GLfloat fundo =-5; GLfloat topo =+5; GLfloat longe =+5; GLfloat perto =-5; |
Define as coordenadas do volume de recorte a ser utilizado na projeção ortográfica.
GLUnurbsObj *nc; GLfloat nos[10]={0.0, 0.0, 0.0, 0.0, 1.0, 2.5, 3.0, 3.0, 3.0, 6.0}; GLint nNos=10; |
A variável nc contém a referência para a spline que irá modelar a curva usando NURBS. nos e nNos são o vetor de nós e o número de nós neste vetor, respectivamente.
GLint matrizViewport[4]; GLdouble matrizModelview[16], matrizProjecao[16]; |
Define as matrizes de viewport, de modelo e de projeção para a cena. Estas matrizes serão usadas no cálculo da posição do mouse no mundo real.
glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, nVertices, &vertices[0][0]); |
Caso a spline seja do tipo Bézier, o traçado da curva se com o uso de avaliadores. Tais evaluators proporcionam uma forma de utilizar o mapeamento de polinômios para produzir vértices, coordenadas de texturas e cores, baseados em funções de base de Bernstein, ou de Bézier.
A função glMap1f() define um avaliador (evaluator) unidimesional. Neste caso, os valores gerados por esta função são utilizados pelos estágios posteriores do processamento como se houvessem sido gerados utilizando a função glVertex*().
A função glMap1f() possui o seguinte protótipo:
void glMap1f
(GLenum target, GLfloatu1, GLfloatu2, GLint stride, GLint order, const GLfloat *points);
O parâmetro target especifica o tipo de valores gerados pelo evaluator; neste caso, especifica que cada ponto de controle é usado para mapear três valores x, y e z. Comandos internos glVertex*() são gerados quando este mapeamento é realizado. u1 e u2 especificam a faixa de mapeamento linear para u, ou seja, a faixa de variação do parâmetro que define a curva; neste caso [0,1]. stride especifica o número de elementos entre o início de um ponto de controle e o início do ponto de controle seguinte; como cada ponto de controle possui três coordenadas, stride=3 para este exemplo. O parâmetro order especifica o número de pontos de controle para a curva (6 pontos). Finalmente, o parâmetro *points deve conter a posição do primeiro ponto do polígono de controle.
glBegin(GL_LINE_STRIP); for (i = 0; i <= 30; i++){ glEvalCoord1f((GLfloat) i/30.0); } glEnd(); |
Neste laço, quando a função glEvalCoord1f() é chamada, o valor do ponto a ser traçado na spline é calculado (ou avaliado) para o valor passado como referência para esta função. Quando o laço terminar, um conjunto de pontos interligados (via GL_LINE_STRIP) irá compor a forma da curva de Bézier, usando o polígono de controle especificado a priorio com a função glMap1f().
gluBeginCurve(nc); gluNurbsCurve(nc, nNos, nos, 3, &vertices[0][0], 4, GL_MAP1_VERTEX_3); gluEndCurve(nc); |
Se a curva a ser desenhada for do tipo NURBS, então as funções gluBeginCurve() e gluEndCurve() serão utilizadas para demarcar o início e o fim do seu traçado. A função usada para desenhar esta classe de curvas é gluNurbsCurve, cujos parâmetros são bastante semelhantes aos da função glMap1f(), acrescentando apenas a referência para a curva, nc e os dados do vetor de nós (nos e nNos). A variável nc é iniciada na função init().
glPointSize(5.0); |
O diâmetro dos pontos rasterizados é mudado para facilitar a visualização dos vértices do polígono de controle (5 pixels).
nc= gluNewNurbsRenderer(); gluNurbsProperty(nc, GLU_SAMPLING_TOLERANCE, 5.0); glEnable(GL_MAP1_VERTEX_3); |
A função gluNewNurbsRenderer() cria um objeto NURBS, que pode ser referenciado durante a chamada para o traçado de curvas desta caterogia. A função gluNurbsProperty() aqui colocada define o espaçamento máximo, em pixels, usado na amostragem de pontos, durante o traçado da curva, geralmente traçada por aproximação poligonal. Neste exemplo, pontos adjacentes do polígono que aproxima a curva têm espacamento máximo de 5 pixels.
glGetIntegerv(GL_VIEWPORT, matrizViewport); glGetDoublev(GL_MODELVIEW_MATRIX, matrizModelview); glGetDoublev(GL_PROJECTION_MATRIX, matrizProjecao); |
As chamadas às funções glGetIntergerv e glGetDoublev gravam em matrizViewport, matrizModelview e matrizProjecao as matrizes de viewport, de modelo e de projeção. Na matriz de viewport ficam armazenadas as coordenadas da origem da janela, seguidas pela sua altura e largura.
void proximidade(){ int i; double tam=0, tamin=32000; verticeCorrente=0; for(i=0; i<nVertices; i++){ tam = (wx-vertices[i][0])*(wx-vertices[i][0])+ (wy-vertices[i][1])*(wy-vertices[i][1]); if(tam < tamin){ tamin=tam; verticeCorrente=i; } } tamin=sqrt(tamin); if(tamin > 0.5){ mudaCurva=0; } } |
A função proximidade() compara os valores das distâncias entre a posição do mouse em coordenadas do mundo real, (wx, wy), com as coordenadas de cada vértice do polígono de controle, em busca do vértice mais próximo. Caso a distância entre a posição do mouse e o vértice mais próximo seja maior que uma dada tolerância, o vértice não será selecionado para alteração.
yreal = matrizViewport[3] - (GLint) y - 1; gluUnProject ((GLdouble) x, (GLdouble) yreal, 0.0, matrizModelview, matrizProjecao, matrizViewport, &wx, &wy, &wz); |
A variável yreal armazena a coordenada y (em pixels) da posição do cursor, assumindo origem na parte inferior esquerda da janela.
A função gluUnProject() serve para mapear as coordenadas da janela para as coordenadas do mundo real. Com base nas coordenadas do mouse na janela, x e yreal, nas matrizes de modelo, de projeção e de viewport, e assumindo a posição z=0, a função gluUnProject() calcula as respectivas coordenadas wx, wy e wz da posição do mouse no mundo real, possibilitando a comparação das distâncias para cada vértice do polígono característico.
void motion(int x, int y){ if(mudaCurva){ yreal = matrizViewport[3] - (GLint) y - 1; gluUnProject ((GLdouble) x, (GLdouble) yreal, 0.0, matrizModelview, matrizProjecao, matrizViewport, &wx, &wy, &wz); vertices[verticeCorrente][0]=wx; vertices[verticeCorrente][1]=wy; glutPostRedisplay(); } } |
A função motion() é chamada cada vez que o mouse é movimentado na janela e um ou mais botões encontra-se pressionado. Caso a alteração dos vértices esteja habilitada, as coordenadas da posição do mouse são mapeadas para o mundo real e atribuídas ao vértice a ser modificado.
glutMotionFunc(motion); |
Define a função de movimentação do mouse para a janela corrente. A função passada como parâmetro deve possuir o seguinte protótipo:
void funcao()
(int x, int y);
Os parâmetros x e y passados para esta função indicam a posição do mouse em coordenadas relativas à janela.
Caso seja necessário, a função glutPassiveMotionFunc pode ser utilizada para monitorar o movimento do mouse quando não há botões pressionados, e possui o mesmo protótipo da função glutMotionFunc