Introducción
Este artículo continúa nuestras series sobre GLUT, la biblioteca de herramientas GL escrita por Mark Kilgard para aplicaciones OpenGL. Tal y como mencionamos en nuestro artículo anterior (Ventanas y Animaciones) GLUT es una biblioteca muy interesante y útil para cualquier programador OpenGL, ya que permite escribir código portable. GLUT esconde al programador los detalles complicados del gestor de ventanas y del interfaz GUI.
GLUT se divide en varios subAPIs. En este número describiremos el subAPI de Gestión de Ventanas. Como su nombre indica, éste se ocupa de tareas relacionadas con las ventanas usadas por tu aplicación OpenGL: crear, cerrar, minimizar una ventana; poner delante, detrás esconder, mover; y poner títulos, posiciones, etc...
El Sub-Api de Gestión de Ventanas
Aquí está la lista completa de funciones soportadas por la gestión de ventanas en GLUT (En la versión 3.6):
int glutCreateWindow(char *name) |
Crea una nueva ventana top-level |
int glutCreateSubWindow(int win,
int x, int y, int width, int height) |
Crea una sub-ventana |
void glutSetWindow(int winId) |
Establece la ventana con ID winId como ventana actual |
int glutGetWindow(void) |
Solicita el identificador de la ventana actual |
void glutDestroyWindow(int winId) |
Cierra la ventana especificada por winId |
void glutPostRedisplay(void) |
Le dice al procesador de eventos de GLUT que la ventana actual necesita ser redibujada |
void glutSwapBuffers(void) |
Intercambia los buffers de la ventana actual |
void glutPositionWindow(int x, int y) |
Solicita un cambio en la posición de la ventana |
void glutReshapeWindow(int width, int height) |
Solicita un cambio en el tamaño de la ventana |
void glutFullScreen() |
Solicita que la ventana actual cambie a full screen |
void glutPopWindow(void)
void glutPushWindow(void) |
Hace un Push o un Pop de la ventana actual relativo a las otras en la pila |
void glutShowWindow(void)
void glutHideWindow(void)
void glutIconifyWindow(void) |
Muestra, esconde o minimiza la ventana actual |
void glutSetWindowTitle(char *name)
void glutSetIconTitle(char *name) |
Pone la barra de título en la ventana o en la ventana minimizada |
Usar Sub-ventanas
El uso de la mayoría de las funciones anteriores es muy simple. Algunas fueron analizadas en nuestro primer artículo: glutPostRedisplay, glutCreateWindow, glutPositionWindow, glutSwapBuffers,..etc. Otras, aunque nuevas, tienen un uso trivial y la descripción anterior explica muy bien lo que hacen, como glutSetIconTitle, glutFullScreen, ...etc. Sin embargo el uso de sub-ventanas no es tan simple, por lo que hemos decidido poner a continuación un ejemplo simple y explicar brevemente los detalles de su implementación.
Aquí está el código fuente de la pequeña demo OpenGL-GLUT (../../common/March1998/example1.c, ../../common/March1998/Makefile). Su proposito es enseñarte: (a) Cómo manejar una sub-ventana, (b) Cómo usar el teclado para interaccionar con tu aplicación OpenGL (c) Cómo trazar texto en una ventana OpenGL.. (Por favor, imprime o ten a mano el código fuente del ../../common/March1998/example1.c a mano mientras seguimos con la explicación.)
Primero vamos a echar un vistazo a la función main(). Ésta empieza, como cualquier otra aplicación GLUT, con inicializaciones: analizar las opciones de la línea de comandos, seleccionar el modo de pantalla y establecer la posición y el tamaño de la ventana inicial. Como nuestra aplicación va a manejar más de una ventana, es necesario guardar el entero ID de la ventana devuelto por la expresión glutCreateWindow. La variable winIdMain es un manejador de ventana. Es ahora el momento de establecer las funciones callback para los eventos asociados a la ventana winIdMain; algunas funciones callback están definidas y todas realizan una tarea en la ventana principal: una función de pantalla (mainDisplay) que dibuja la escena, una función de cambio de forma (mainReshape) que maneja cualquier transformación del marco de la ventana, otra llamada keyboard que maneja las acciones disparadas por el teclado y otra llamada idle para manejar la animación cuando no hay otros eventos pendientes (ver Animaciones y Ventanas para una descripción más detallada de la función idle;
La primera cosa importante a saber es que en una aplicación GLUT sólo puede haber una función callback idle. La función idle es global para todas las ventanas de la aplicación. Ten esto en cuenta cuando diseñes las funciones idle(), ellas deben tener cuidado de refrescar todas las ventanas y subventanas de la aplicación.
Después, en el código, viene la creación de una subventana (winIDSub). Para crear una sub-ventana debes proporcionar el ID de la ventana de nivel superior, en el presente caso winIDMain, las coordenadas de x e y en pixels para la subventana, relativas a las coordenadas internas de winIDMain, y el ancho y el alto en pixels de la ventana solicitada. Después de crear la subventana GLUT devuelve un manejador a nuestro programa y, entonces, ya estamos listos para monar las funciones callback apropiadas para winIDSub. En nuestra dema hemos establecido dos funciones callback: una display (subDisplay) y otra reshape (subReshape).
Cuando GLUT abre una subventana le proporciona un contexto OpenGL completo. Hay pues una pérdida de rendimiento al usar subventanas, ya que el driver de la tarjeta de video tiene que refrescar el área de memmoria para cada una de las ventanas en pasadas separadas. Gracias a tener un contexto OpenGL independiente cada ventana tiene su propio sistema de coordenadas, por ejemplo. En ../../common/March1998/example1.c los sistemas de coordenadas se ponen en mainDisplay() y subDisplay() respectivamente. Ahora ve y examina esas dos funciones de pantalla, son bastante simples y si has seguido nuestro artículo de Enero sobre OpenGL ("Trazado de Polígonos Simples") no tendrás problemas para entenderlas.
La función mainDisplay() dibuja un triángulo con vértices Verde, Rojo y Azul. OpenGL interpola los colores entre los tres vértices para llenar el polígono. Antes de trazar el triángulo hemos añadido un glRotate que gira el triángulo alrededor del eje z (perpendicular a la pantalla), el ángulo de rotación (spin) se incrementa lentamente en idle() para dar la ilusión de que la figura está girando.
También la función de pantalla asociada con winIdSub está bastante clara. Primero pinta el fondo con un color gris, luego dibuja un borde verde alrededor de la subventana y, finalmente, traza un texto. Más tarde explicaremos como funciona el trazado de texto bajo GLUT. Por el momento basta señalar que glRasterPos2f(x,y) establece la posición dónde se va a dibujar el texto, y que las coordenadas x e y usadas son relativas a las coordenadas del sistema de la subventana (definida en subReshape()).
La subventana actúa como un tablero de texto para los datos provenientes de la animación. Es una aplicación tonta, bastaría con haber dibujado el tablero de texto en la ventana principal para conseguir el mismo resultado (incluso de forma más eficiente). Sin embargo, bajo algunas circunstancias, tiene sentido abrir una ventana para la salida de texto. Por ejemplo cuando la animación es en 3D con luces y efectos ambientales y no quieres deformar tu tablero de texto con luces molestas, efectos de perspectiva, sombras o niebla, etc. Bajo esas circunstancias una subventana es muy útil ya que está completamente aislada de la animación 3D.
Hay una diferencia crucial entre la función callback de cambio de tamaño para la ventana de más alto nivel o para una subventana. Cuando un evento de cambio de tamaño se dispara, sólo se invoca la función callback de cambio de tamaño de la ventana de más alto nivel, en nuestro ejemplo mainReshape(). Hay que llamar a la función callback de cambio de tamaño subReshape desde dentro de mainReshape. Esto tiene sentido ya que la localización y la forma de las subventanas está condicionado al tamaño y la forma de su ventana principal. De esta forma, si lees ahora el código de mainReshape()verás que primero damos valor a la matriz de proyección para la ventana de más alto nivel, entonces cambiamos a la subeventana winIDsub y vamos invocando secuencialmente la función de cambio de tamañode la subventana con el ancho y el alto relativos a winIDMain que queremos usar.
Antes se mencionó que la función callback idle() debe actualizar todas ventanas principales y subventanas en la aplicación OpenGL. En nuestro ejemplo idle() actualiza primero las variables de estado de la animación (time y spin) y luego pide a la ventana principal y la subventana que se revisualicen.
El teclado
He añadido dos teclas activas al programa. Pulsando la tecla "i" puedes activar o desactivar el tablero de texto y con la tecla "q" puedes salir de la aplicación. Pruébalas :)
En el momento que pulses una tecla en tu teclado, el driver de proceso de eventos de GLUT registra un evento de teclado. Estos eventos son manejados por las funciones callback de teclado. En principio cada ventana tiene su propia función callback. Cuando el ratón está en la posición (x, y) dentro de una ventana (o subventana) y se dispara un evento de teclado entonces la función callback de teclado asociada con esa ventana es invocada. Esta función callback toma como argumentos el código ASCII unsigned char asociado con la tecla y la posición x, y del cursor en ese momento. En ../../common/March1998/example2.c no hay ningún uso para x, y pero estoy seguro de que se te ocurriran aplicaciones donde puedas tomar ventaja de esta interesante característica.
Únicamente la ventana de más alto nivel en nuestra demo tiene una función callback. Si pruebas a pulsar las teclas "i" o "q" mientras el cursor está dentro de la subventana verás que no pasa nada. Por defecto cuando se crea una ventana y no se registra ningún callback de teclado entonces todas las pulsaciones de teclado se ignoran. Ten esto en cuenta en el futuro si usas múltiples ventanas y quieres que el teclado esté activo.
Finalmente mencionar que la generación de callbacks de teclado se puede deshabilitar pasando NULL a glutKeyBoardFunc().
Trazar texto
Renderizar texto en OpenGL y GLUT es un coñazo!. Perdón por decirlo pero es la verdad. No estoy seguro de por qué el renderizado de texto ha sido desatendido en la librería OpenGL. La vieja librería GL de SGI tenía unas pocas funciones de alto nivel para manejar el trazado de texto en el modo gráfico y había una librería auxiliar adicional para cambiar fuentes. OpenGL sólo proporciona directivas muy primitivas para el trazado de bitmaps, y eso significa que te tienes que hacer tu propia librería de bitmaps para cada carácter, tener en cuenta la resolución, el escalado de fuentes... todo lo necesario!.
GLUT resuelve un poco el dilema de usar texto en OpenGL. Proporciona glutBitmapCharacter, que traza un único carácter en la posiciçón especificada por glRasterPos. He añadido unas pocas funciones, drawString(), y drawStringBig() que hacen la vida un poco mejor al trazar cadenas de caracteres.
Conclusión
Aquí concluye una introducción muy simple al uso de subventanas de GLUT. En este punto tengo que mencionar que, aunque es bueno experimentar con él bajo varias plataformas, el soporte de subventanas de GLUT no es completamente funcional en todos los entornos. Usuarios con tarjetas basadas en 3Dfx comprobarán que las subventanas aún no son funcionales debido a limitaciones hardware. También he encontrado una gran penalización en las prestaciones al usar subventanas en algunas plataformas. Por ejemplo, bajo mi Linux Alpha con una Matrox Millenium con 2Mb, una subventana hace que la aplicación vaya el doble de lento, probablemente porque desafortunadamente el servidor X para Alpha todavía no soporta ninguna aceleración hardware. Por el otro lado, la misma aplicación en windows 95 y una ATI RageII con 2Mb con el driver de SGI para OpenGL va de maravilla.
Como el desarrollo de Linux se mueve tan rápido es posible que en un futuro cercano alguno de los problemas de rendimiento e incompatibilidades desaparezcan. Por el momento simplemente tener en cuenta que existen, por lo que usa múltiples ventanas con cautela.
Por supuesto, usuarios avanzados pueden encontrar siempre una forma alternativa de usar subventanas jugando con la pila matriz, pero como todavía no hemos estudiado esto, perdóname si lo dejo para más tarde.. ;)).
|