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