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.. ;)).
|