| Dibujando LíneasEn el último número hablamos sobre los
	    elementos básicos para la construcción de
	    polígonos bajo OpenGL. Éste sólo soporta unas pocas
	    primitivas de objetos grométricos: puntos, líneas,
	    polígonos y superfícies descritas por vectores de pequeños
	    trianguos o cuadrilateros. La ídea principal tras la simplicidad de OpenGL
	    es que es responsabilidad del desarrollador implementar
	    modelos geométricos más complejos a partir
	    de estos objetos simples. OpenGL contiene una serie de
	    comandos para controlar los detalles de los puntos, las
	    líneas y los polígonos. Por ejemplo el tamaño de los puntos se puede especificar
          en píxels empleando la primitiva
          glPointSize: void glPointSize(GLfloat tamaño)
 
 
 Por defecto el tamaño de los puntos es de 1.0 pixels y
          tamaño ha de ser siempre mayor que cero. El tamaño
          del punto se especifica con un número en coma floatante;
          están permitidos tamaños fraccionarios de puntos y
          líneas. OpenGL interpreta las fraccionarines de
          píxel según el contexto de trazado. Si el modo
          anti-aliasing está activado, entonces OpenGL modifica
          los píxels del entorno de la línea en
          cuestión para dar la sensación de que tiene
          anchura fraccional. El anti-aliasing es una técnica
          que se usa también para eliminar las escaleras que
          tienen las líneas inclinadas en las pantallas de baja
          resolución. Si el modo anti-aliasing no está
          activado entonces glPointSize redonderá el valor de
          tamaño al entero más próximo. El tamaño físico de un píxel depende
          realmente del dispositivo. Por ejemplo, en resoluciones de
          monitor bajas, el píxel parece más ancho. Del
          mismo modo, en dispositivos con resoluciones muy altas, como
          un plotter, el ancho de línea por defecto (1
          píxel) puede aparecer casi invisible. Para estimar el
          ancho real de tus líneas debes conocer las
          dimensiones físicas de los píxels en el
          dispositivo de salida. El ancho de las líneas se específica con la
          función glLineWidth, que se debe invocar
          antes del par de funciones glBegin() - glEnd() que
          dibujan la línea. Ésta es la sintaxis completa del
          comando: void glLineWidth(GLfloat ancho)
 
 
 Las implementaciones de OpenGL pueden limitar el ancho de
          las líneas sin anti-aliasing al ancho máximo
          de las líneas con anti-aliasing, redondeado al valor
          entero más próximo. Ten en cuenta también que
          el ancho de las líneas no se mide perpendicularmente
          a la línea, sino en la dirección del eje de
          las y si el valor absoluto de la pendiente de la curva es
          menor que 1; o en la dirección del eje de las x si es
          mayor que 1. Este mes hemos preparado otra animación 2D, simple
          pero esperemos que útil, que muestra cómo usar
          varios tipos de anchos de línea en las aplicaciones
          OpenGL (../../common/March1998/example2.c, ../../common/March1998/Makefile).  He elegido un ejemplo de
          Física Cuántica: una partícula
          cuántica atrapada en una pozo doble de
          potencial. ¿Por qué? Humm... pues lo he olvidado. De
          cualquier modo, imagino que será útil para que
          estudiantes de física e ingeniería vean
          cómo integrar la ecuación de Schroedinger
          dependiente del tiempo, los demás se pueden divertir
          viendo la naturaleza no intuitiva de la mecánica
          cuántica. En MC, una partícula no se
          representa por una posición y una velocidad, sino por
          una onda cuántica (línea púrpura
          sólida en nuestra animación) cuyo valor
          cuadrado absoluto representa la probabilidad de observar la
          partícula en una posición dada (línea
          blanca discontínua): ![[Click here to see the image]](../../common/March1998/ogl-thumb.gif)  Figura 1. Simulación Cuántica
 Para aquellos que tengan algunos conocimientos de
	 Ecuaciones Diferenciales Ordinarias, decir que la
	 ecuación de onda se integra usando el método de
	 FFT (Transformada Rápida de Fourier)
	 Split-Operator. Este método es mucho más exacto
	 y rápido que cualquier método de diferencias
	 finitas. Es aplicable a la propagación de ondas
	 no-lineales; el operador de evolución del tiempo se
	 divide en dos operadores de segundo o mayor orden que sólo
	 dependen o de la posición o del momento (frecuencia),
	 entonces se hace evolucionar en el tiempo a la función
	 de onda aplicando sucesivamente estos operadores cambiando
	 alternativamente entre el espacio de posiciones y el espacio
	 de momentos (frecuencias). El cuerpo del código fuente se puede usar para
	 muchas otras aplicaciones. Puedes cambiar mi
	 simulación cuántica por tu función
	 dependiente del tiempo y obtener una animación maja de
	 tu sistema. Puedes probar también a escribir un
	 gnuplot simplificado basado en OpenGL para plotear funciones
	 y ficheros de datos. Si el lector ha seguido los artículos previos sobre
        GLUT y OpenGL este código fuente será muy
        sencillo y fácil de entender (dejando a parte la
        mecánica cuántica). No hay nada
        extraordinario. En la función main() abrimos
        una sola ventana en modo buffer doble, entonces le pasamos
        unas funciones callback display() e idle()
        que se encargan de dibujar la función de onda e
        integrar la ecuación de onda, respectivamente. Entender
        lo que pasa en la función idle(), aunque es un
        truco muy bonito, no es necesario para captar el contenido de
        este artículo. Las cosas nuevas sobre OpenGL
        están en la función callback
        display: 
void
display (void)
{
  static char label[100];
  float xtmp;
  /* Limpiar el espacio de dibujo */
  glClear (GL_COLOR_BUFFER_BIT);
  /* Escribir la nota al pie */
  glColor3f (0.0F, 1.0F, 1.0F);
  sprintf (label, "(c)Miguel Angel Sepulveda 1998");
  glRasterPos2f (-1.1, -1.1);
  drawString (label);
  /* Dibujar una rejilla fina */
  glLineWidth (0.5);
  glColor3f (0.5F, 0.5F, 0.5F);
  glBegin (GL_LINES);
  for (xtmp = -1.0F; xtmp < 1.0F; xtmp += 0.05)
    {
      glVertex2f (xtmp, -1.0);
      glVertex2f (xtmp, 1.0);
      glVertex2f (-1.0, xtmp);
      glVertex2f (1.0, xtmp);
    };
  glEnd ();
  /* Dibujar el cuadrado del borde */
  glColor3f (0.1F, 0.80F, 0.1F);
  glLineWidth (3);
  glBegin (GL_LINE_LOOP);
  glVertex2f (-1.0F, -1.0F);
  glVertex2f (1.0F, -1.0F);
  glVertex2f (1.0F, 1.0F);
  glVertex2f (-1.0F, 1.0F);
  glEnd ();
  /* Dibujar la rejilla */
  glLineWidth (1);
  glColor3f (1.0F, 1.0F, 1.0F);
  glBegin (GL_LINES);
  for (xtmp = -0.5; xtmp < 1.0; xtmp += 0.50)
    {
      glVertex2f (xtmp, -1.0);
      glVertex2f (xtmp, 1.0);
      glVertex2f (-1.0, xtmp);
      glVertex2f (1.0, xtmp);
    };
  glEnd ();
  /* Dibujar los ejes de coordenadas */
  glLineWidth (2);
  glBegin (GL_LINES);
  glVertex2f (-1.0, 0.0);
  glVertex2f (1.0, 0.0);
  glVertex2f (0.0, -1.0);
  glVertex2f (0.0, 1.0);
  glEnd ();
  /* Etiquetas de los ejes */
  glColor3f (1.0F, 1.0F, 1.0F);
  sprintf (label, "Position");
  glRasterPos2f (0.80F, 0.025F);
  drawString (label);
  glColor3f (1.0F, 0.0F, 1.0F);
  sprintf (label, " Quantum Probability ");
  glRasterPos2f (0.025F, 0.90F);
  drawString (label);
  glColor3f (1.0F, 1.0F, 1.0F);
  sprintf (label, " Real(Psi) ");
  glRasterPos2f (0.025F, 0.85F);
  drawString (label);
  /* Dibujar la funcion de onda */
  psiDraw (NR_POINTS, psi, x);
  /* Dibujar la funcion de potencial */
  potentialDraw (NR_POINTS, potential, x);
  glutSwapBuffers ();
};
Lo primero que se hace es borrar el bit del buffer de
        color, que nos da un espacio de dibujo limpio
        (negro). Añadimos una nota al pie usando glRasterPos
        y glutBitmapCharacter (drawstring no es nada
        más que una envoltura para la utilidad clut). En
        lecciones futuras glRasterPos aparecerá de
        nuevo como una función auxiliar para el trazado de
        texturas. Ni OpenGL ni GLUT ofrecen una manera simple y
        potente de trazar texto en una ventana gráfica.  La
        función glutBitmapCharacter básicamente copia
        una fuente de mapas de bits en el buffer de color.  Después de la nota al pie vienen una serie de
        líneas: el borde exterior, la rejilla de fondo, los
        ejes de coordenadas y, naturalmente, las curvas en
        cuestión, dibujadas con psiDraw y
        potentialDraw. Antes de trazar cada línea hay
        una instrucción glLineWidth que especifica el
        número de píxels de ancho que se debe dar a la
        línea. La Figura 1 muestra la salida en un X Window
        System (Linux Alpha). Por alguna razón que desconozco,
        la salida del mismo programa en Windows 95 sale bastante mal,
        parece que la capacidad de antialiasing no está muy
        bien soportada en el driver de OpenGL de SGI; es
        difícil diferenciar líneas que en principio
        deberían tener anchura diferente, y la rejilla de
        líneas de fondo también aparece muy
        uniforme. Estos defectos aparecen cuando el monitor
        está puesto a alta resolución, luego no es un
        defecto de una resolución baja. Es para mi un placer
        decir que, una vez más, un X Window System en Linux
        sobrepasa por mucho a win95/NT. Hay dos tipos de trazado de líneas en la
        función display(), el modo GL_LINES, que une
        vértices con una línea continua abierta, y el
        modo GL_LINE_LOOP, que al final cierra el lazo.  Líneas con AntialiasingHe activado el antialiasing para las líneas en la
        función callback reshape(), 
void
reshape (int w, int h)
{
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
  glViewport (0, 0, w, h);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluOrtho2D (-1.2, 1.2, -1.2, 1.2);
  glEnable (GL_LINE_SMOOTH);     /* Activar lineas con Antialiasing */
  glEnable (GL_LINE_STIPPLE);
};
¿Para qué sirve GL_LINE_STIPPLE? OpenGL nos deja
       controlar no sólo el ancho de una línea sino
       también su patrón.  Activando GL_LINE_STIPPLE
       podremos dibujar líneas ralladas, punteadas o con
       cualquier otro patrón. La única línea que
       lo utiliza en nuestra animación aparece en la
       función psiDraw(): 
  glLineWidth (1);
  glPushAttrib (GL_LINE_BIT);
  glLineStipple (3, 0xAAAA);
  glBegin (GL_LINE_STRIP);
  for (i = 0; i < nx; i++)
    {
      xs = ratio1 * (x[i] - XMIN) - 1.0;
      ys = ratio2 * (psi[2 * i] - YMIN) - 1.0;
      glVertex2d (xs, ys);
    };
  glEnd ();
  glPopAttrib ();
Líneas PunteadasLa función glLineStipple específica el
        patrón usado para el punteado, en nuestro ejemplo hemos
        usado el patrón 0xAAAA. En binario este número
        es 0000100010001000 y OpenGL interpreta esto dibujando 3 bits
        apagados, 1 bit encendido, 3 bits apagados, 1 bit encendido, 3
        bits apagados, 1 bit encendido y por último 4 bits
        apagados. La patrón se lee hacia atrás porque
        los bits de menor orden se usan
        primero. glLineStipple tiene dos parámetros,
        el patrón de punteado que debe ser un número
        hexadecimal y un factor entero que sirve para escalar este
        patrón; con un factor de 3 nuestra línea
        punteada mostrará 9 bits apagados, 3 bits encendidos, 9
        bits apagados, 3 bits encendidos, 9 bits apagados, 3 bits
        encendidos y por último 12 bits apagados. Jugando con
        factores y patrones binarios uno puede dibujar todo tipo de
        líneas punteadas complicadas. Un detalle más: he puesto el trazado de la
        línea punteada entre dos sentencias de push y pop de
        atributos. ¿Recordáis cuando en nuestro primer
        artículo dijimos que OpenGL es una máquina de
        estados? En futuros artículos veremos con más
        detalle estas operaciones de push y pop, pero brevemente lo
        que estamos haciendo con la primera sentencia glPushAttrib
        (GL_LINE_BIT) es guardar en una pila el valor acutal de
        la variable de estado GL_LINE_BIT (esta variable decide el
        patrón de punteado), entonces podemos modificar
        GL_LINE_BIT con nuestra sentencia glLineStipple y
        cuando hemos acabado llamamos a glPopAttrib que
        devuelve el valor antiguo de la variable GL_LINE_BIT. Este
        mecanismo es una manera efectiva de modificar las variables de
        estado de OpenGL localmente. Si no lo hacemos así
        entonces todas las líneas dibujadas después de
        glLineStipple tendrían el mismo patrón
        de punteado y estaríamos forzados a declarar un
        patrón con glLineStipple para cada
        línea que trazásemos en nuestra
        aplicación. Push y pop nos evitan este molesto
        trabajo. Próximamente ....OpenGL es famoso por su maravillosa API 3D. Hasta
         aquí hemos explorado algunas posibilidades elementales
         de trazado 2D con OpenGL.  En el próximo número
         examinaremos el escenario de OpenGL 3D, cómo poner una
         perspectiva, sistemas de coordenadas, planos de corte y
         matrices de proyección. Hasta entonces, a divertirse con OpenGL....... |