Capítulo 7. Curvas no plano

Índice
7.1. Descrição do programa splines2d.c
7.2. Exercícios

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.

Figura 7-1. Traçado interativo de splines.

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

7.1. Descrição do programa splines2d.c

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