sábado, 24 de mayo de 2008

Manejo de eventos con IEventReceiver

Como ya hablamos en la introduccion, uno de los inconvenientes en los tutoriales de irrlicht, es la gestión de eventos para los controles (botones, cajas de texto, etc), cámaras, acciones de usuario, etc.

Para su gestión, Irrlicht dispone de la interfaz IEventReceiver. Cualquier Clase que herede de dicha interfaz, debe implementar el método onEvent. Si miráis en cualquiera de los tutoriales, vereis como el metodo onEvent del objeto de tipo MyEventReceiver usado en la aplicación tiene un super-switch múltiple de la muerte con todos los eventos de todos los controles, todo ello puesto a piñón fijo. Si tienes poquitos botones y posibles acciones esta bien, pero no nos sirve cuando la aplicacion crece y quieres que sea mantenible, dinámica y extensible. Lo bonito seria hacerlo al estilo de los lenguajes de nueva generación como Java y la familia .Net, esto es, yo tengo un botón, y un método que quiero que se ejecute cuando se produzca el evento onClick del botón.

Continuamos con un ejemplo típico de los tutoriales.

Sigue leyendo >

Estructura de ventanas con Irrlicht

Buenas a todos, en esta entrada trataremos de explicar como sacarle partido a las clases e interfaces que nos provee Irrlicht para montarnos una estructura de menús decente.

Si alguna vez has seguido los tutoriales de irrlicht, como por ejemplo, el User Interface y Mesh Viewer, habréis visto que presentan dos grandes inconvenientes.

El primero es que no deja claro, o no explica, cómo hacer varias ventanas, esto es, cómo tener dividido la aplicación en múltiples ventanas o formularios. Cualquier programa que se precie, tendrá una ventana de configuración, distintas secciones e incluso, subaplicaciones. Esto ocurre en [L], donde tendremos a priori, el menú principal, el editor de escenarios y el simulador en sí.

El segundo gran inconveniente es la gestión de eventos para los controles (botones, cajas de texto, etc), cámaras, acciones de usuario, etc.

Así pues, vamos a organizarnos y a poner las cosas claras. Abordaremos estos dos temas poco a poco y dividiendolo en articulos. He aqui los puntos a tratar:

Puedes ya empezar a leerlos comenzando por el primer punto: Manejo de eventos con IEventReceiver.

Sigue leyendo >

Cambios en entradas

Buenas a tod@s!!

Últimamente, a pesar de que quedamos todos los sabados para avanzar en el proyecto, carecemos de tiempo para actucalizar el blog.

A mi me ocurre que a la hora de elaborar un post sobre alguno de los bloques del proyecto, no me da tiempo a hacerlo "de una sentada", con lo que, si lo guardo como borrador hasta completarlo y despues lo publico ya como noticia visible, pasa mucho tiempo entre noticia y noticia, dando la sensación de inactividad.


Por otro lado, si publico la noticia a medias y poco a poco la voy modificando, el post no reaparece como nuevo y va quedando enterrado tras otros posts. Supongo que todo esto ya le habrá pasado a mas de uno y también supongo que es cosa de que soy un novato en el mundo de los blogs ;).

Así pues, lo que haré sera publicar una noticia con un índice en el que indicaré las secciones en las que se divide el tema a tratar, vamos, a modo de serie de articulos con el primer articulo con la introducción y el índice.

Nada mas publicar este post me pongo con el que dejé a medias: Estructura de Menús con Irrlicht

--
Un saludo.

Sigue leyendo >

Anchura del blog

Hemos "toqueteado" un poco la plantilla para aumentar el ancho general del blog. Lo sentimos por aquellos equipos que usen resoluciones de menos de 1024 x 768.

Más adelante ya buscaremos otra plantilla mejor e independiente del tamaño de la ventana.

Sigue leyendo >

domingo, 18 de mayo de 2008

Compilando el proyecto

Vamos a utilizar esta entrada para ir anotando los detalles de la compilación del proyecto en los diferentes sistemas que lo vayamos probando.
Quizás fuera más correcto utilizar un wiki para este tipo de cosas, pero como por ahora no hemos generado mucho contenido seguiremos con el blog. Además, facilitamos la comunicación con otros usuarios a través de los comentarios.


MsWindows + MinGW
- enlazar con: -lIrrlicht -lode

GNU/Linux
- enlazar con: -lIrrlicht -lode -lGL -lGLU -lX11 -lXxf86vm [-lXext]

* el proyecto incluirá todas aquellas librerías externas de las que haga uso

Sigue leyendo >

domingo, 11 de mayo de 2008

Vehículos: primeros pasos

Tras tener resuelto el tema del terreno, pasamos con la implementación de los vehículos. Hasta el momento, si miráis el código, todo está hecho muy ad hoc mientras vamos experimentando y asimilando las APIs y configuración de los motores. Cuando veamos que funciona pasaremos a la generalización y automatización.



Por ejemplo en la captura vemos un vehículo sobre el terreno. El vehículo tiene una configuración física sencilla basada en una caja para el chasis y en 4 esferas para las ruedas. Las cajas de alambres que se aprecian son la representación visual de las boundingbox de estas geometrías físicas (siempre en su perfecta horizontalidad). Se aprecia como el vehículo es posado en la inclinación del terreno, si bien no es preciso en cuanto al contacto de las ruedas con el terreno es debido a que la posición de la geometría física respecto a las ruedas está ajustada de forma numérica rápida y manualmente. La idea es que en un futuro ésto sea ajustado semi-automáticamente mediante un sencillo editor, ya que uno puede de forma sencilla automatizar la definición de una geometría física similar a la del modelo mediante malla o boundingbox, pero en el momento que deseemos mover las ruedas independientemente del chasis deberán ser representadas como un modelo a parte habiendo perdido así la posición relativa respecto al chasis. Y ésto va a suceder para cualquier elemento independiente que incorpore un objeto complejo como es el caso de un vehículo.
Por lo tanto, implementaremos un editor que nos permita cargar modelos, asignarles una configuración geométrica de su física y posicionarlos entre sí de forma relativa. Este editor deberá volcar dicha información a fichero, al igual que sucederá con la creación de los escenarios, por lo tanto en tiempo de simulación no habrán más ajustes ad hoc y todo será puesto en escena de forma automática.

El siguiente paso va a ser dotar de movimiento a las ruedas para poder pasear el cochecito por el terreno y empezar a hacer pruebas con los ajustes de "animación" (realmente con ajustes de la configuración del motor físico, ya que la animación en este caso es totalmente indirecta).

Sigue leyendo >

miércoles, 7 de mayo de 2008

Sincronizando mapa de alturas gráfico + físico

Una de nuestras primeras decisiones ha sido utilizar los mapas o campos de alturas como geometría para describir el terreno del simulador ya que los motores en los que nos apoyamos incorporan este tipo de geometrías en sus versiones más recientes. Así que vamos a realizar una mini-guía para saber como define la geometría cada uno de los motores y como podemos sincronizarlas para que la colisión proporcionada por el motor físico coincida con la representación gráfica que realiza el motor gráfico.



Introducción

Los motores que utilizamos en nuestro proyecto son: Irrlicht como motor gráfico y ODE como físico.
La terminología para referirse a los mapas de alturas es algo diversa, encontrándonos con términos como: terrain, heightfield, heightmap o sus respectivas traducciones al castellano. En general, Irrlicht los define como terrains y ODE como heightfields. Pero todos hacen referencia a exactamente el mismo tipo de geometría.

Por si alguien anda perdido sobre qué es un campo de alturas, la idea más sencilla es que se trata de un área (cuadrada normalmente) cuya altura en cada punto viene definida por una función o algún otro tipo de representación. Por eso su utilidad más directa es la de representar terrenos tridimensionales.

Irrlicht Terrain Scene Node

Para éste motor gráfico, un campo o mapa de alturas es una geometría de tipo TerrainSceneNode.
La representación de la altura es obtenida directamente de una imagen en escala de grises donde el blanco indica el punto más alto y el negro el punto más bajo. Como un color no define una altura concreta, debemos entender la representación de la altura como algo proporcional (entre el blanco y el negro, entre 0 y 1..), de forma que la altura final deseada se consigue mediante la aplicación de un escalado. Esto nos permite representar el mismo campo de alturas de forma suave o abrupta.
Recordad que al estar en entornos 3D (X,Y,Z), por defecto el terreno se representa en las coordenadas (X,Z) de forma horizontal y la altura viene dada por la componente (Y).
Algo importante a tener en cuenta es que Irrlicht define la esquina inferior izquierda del terrain como el origen de coordenadas de la geometría.

ODE Heightfield Geom

La definición del campo de alturas por parte del motor físico es algo más delicada.
El tipo de datos utilizado es un geomheightfield, y espera recibir los datos de la componente altura desde una función matemática. Por lo tanto, hay que codificar un método en nuestra aplicación que haga de callback para el campo de alturas, indicando el valor de la componente (Y) para un par (X,Z) dado.
(Si tienes problemas para utilizar un método (C++) como función de callback (C) encontraras la solución aquí.)
La principal sutileza de utilizar una función matemática como fuente de datos para las alturas es que los valores de entrada de la función son enteros, de este modo ODE construye su heightfield mediante coordenadas del tipo: { (0,0), (0,1), (0,2) ... (m,n) }, siendo independientes a las coordenadas (X,Z) del mundo tridimensional. Y algo que puede ser ahora obvio, es la necesidad de indicarle al motor físico el número de divisiones (en sus 2 dimensiones (X,Z)) de nuestro heightfield. Esas divisiones discretizan el área definida y corresponden a las coordenadas enteras utilizadas en el callback (sus valores máximos: (m,n)).
Otro detalle a tener en cuenta, sobre todo de cara a la sincronización, es que para ODE el origen de coordenadas de la geometría obtenida se sitúa en el centro (X,Z) del campo de alturas generado.

Synching Irrlicht-ODE Heighfields

La forma más fácil de afrontar el problema es generar inicialmente el terreno con Irrlicht mediante una imagen BMP que podamos modificar a nuestro antojo, y a partir de él obtener la componente altura para ODE mediante el método getHeight.

Como hemos visto, Irrlicht y ODE localizan el origen de coordenadas de la geometría en puntos distintos, con lo que habrá que corregir este desfase para que la representación de ambos por pantalla coincida.
En resumen, la posición de la geometría de ODE vendrá dada por la siguiente función:
(X,Z)_ODE = (X,Z)_IRR + (Width,Depth)/2

Irrlicht trabaja siempre con las coordenadas del mundo tridimensional, por lo que nuestro campo de alturas está correctamente localizado por su parte. Al construir el terrain definiremos su altura mediante un mapa de bits y su tamaño mediante el escalado oportuno. Y para construir el heightfield de ODE obtendremos las características directamente del terrain de Irrlicht.
El resto de parámetros del constructor por parte de Irrlicht afectan a los algoritmos de renderizado del terreno y no a su estricta definición, con lo cual no son importantes de cara a la sincronización con ODE.
En la construcción del heightfield de ODE tendremos que indicar el método callback que va a proporcionar la altura del terreno, las dimensiones del mismo que obtendremos de nuestro terrain ya construido, y las divisiones en las que queremos discretizar el área del heightfield. Estos últimos valores son independientes a Irrlicht y tendremos que sacárnoslos de la manga. Básicamente estamos definiendo el detalle y complejidad del terreno para nuestro motor físico, así que cuantas más divisiones hagamos más se ajustará la representación física del terreno a la representación gráfica, pero más cálculos físicos tendremos y más lenta irá la simulación. Clásico tradeoff de optimización, así que todo depende de la calidad de los resultados que requiramos en un momento dado. También acepta ODE un escalado en el constructor, pero como nuestro terreno ya está completamente generado por Irrlicht, debemos privarle a ODE de hacer modificaciones sobre los datos que le vamos a proporcionar, por lo tanto indicaremos siempre que NO haga escalado (lo que supone un valor real de 1.0).

Y por último, sólo quedaría definir el callback para completar la sincronía entre ambos motores.
Éste es un punto delicado ya que recordemos que Irrlicht trabaja con las coordenadas del mundo y ODE con sus componentes enteras en las que ha discretizado el terreno. Por lo tanto, la idea es transformar las coordenadas enteras que utiliza ODE en la llamada del callback en coordenadas del mundo para obtener de Irrlicht la altura correcta en el punto deseado. Teniendo esto claro y recordando los atributos que han entrado en juego no debe suponer un problema ver la solución:

anchuraDivision = (Width,Depth) / divisiones
(X,Z)_IRR = (X,Z)_ODE * anchuraDivisión + (X,Z)_POSITION

Con anchuraDivision obtenemos la distancia en coordenadas del mundo que está discretizando ODE entre entero y entero. Ya solo falta multiplicar por el entero dado y desplazar la coordenada a donde hayamos colocado nuestro terrain en el entorno tridimensional.

Demo

En el Box podéis encontrar la demo test_terrain-balls.zip en la que se muestra la sincronización explicada. Hay representado un terreno y una matriz de pelotas cae del cielo colisionando contra él. Es posible mover la cámara y desplazarse por el mundo con las teclas (W,A,S,D). También es posible modificar la forma del terreno editando el mapa de bits ./res/terrain-heightmap.bmp (debe respetarse el tamaño del BMP).

Se incluyen las fuentes, aunque éstas tienen mucho más código del necesario y quizás marean más que ayudan a entender el asunto. Así que para cualquier duda no dudéis en contactar con nosotros!

Sigue leyendo >