Pixel por pixel, polígono por polígono - Colisión por el bien de todos (CGA tarea 3) - 04/04/2007, 02:11

Bueno, la tercer tarea de Computación Gráfica consiste en hacer un programa que detecte colisiones entre dos humanoides, lo que es mejor explicado como un programa en el cual dos monigotes se puedan dar de trompones, usando como API gráfica OpenGL e implementando los algoritmos de colisión vistos en clase.

Para esta tarea, solicité la ayuda como protagonista principal al famoso personaje lanjobot, quien había sido olvidado, un poco opacado por la "caja de leche con alas" de r0cket scienc3. Afortunadamente aceptó y ahora lo tenemos de regreso con las funciones que tenía en GLUT (glutWireCube) reemplazadas por las mías (cubo_solido) :P.

Lo que llevo hasta ahora es prácticamente el cascarón, pero me tardé porque lo diseñé de manera que puedo reutilizar toda la parte de la interfaz y quedó mucho más ordenado.

Como cascarón, formalmente tengo main.cpp, que es la que maneja todo lo relativo a la interfaz gráfica y leer lo que pulsa el usuario en el teclado (por requerimientos de la tarea, en windows debe ser el API de WIN32, pero en GNU/Linux y en *nix lo tengo en SDL) y varias cosas en ../bibliotecas donde están todas las funciones vectoriales, los objetos a dibujar, etc.
Hay 2 main.cpp, uno para Windows y otro para GNU/Linux y FreeBSD; ambos hacen referencia a las otras bibliotecas antes descritas, pero la parte importante de la tarea, que es la parte reemplazable para poder intercambiarla fácilmente con lo que tenga que hacer para las otras tareas se encuentra en graficos.h

Básicamente sigue el esquema de funcionamiento de un motor de juegos simplón:

- Inicializar la aplicación
- Inicializar el "universo" a simular
- Entrar y permanecer en el ciclo de cálculo y dibujo, que corresponde a
* Solicitar entrada del usuario
* Actualizar situación del universo en base al tiempo transcurrido desde el último cálculo y la entrada del usuario
* Representar el universo con los cálculos realizados
* Calcular cuánto tiempo queda para dormir
* Dormir
- Al finalizar el ciclo del cálculo/dibujo, salir.

Tal como lo estructuré, el archivo graficos.h contiene la función inicializar_universo, que establece todas las condiciones iniciales (por ejemplo, la posición inicial de ambos humanoides y la cámara).
Contiene también una función llamada procesar_entrada que recibe la entrada del usuario en base a una estructura que se parece mucho a la de SDL_Event (pero no podía ser SDL_Event, porque habría quedado dependiente de SDL también en windows -- no es que yo tenga inconveniente alguno, pero el profesor pidió WIN32), esta función controla si el ciclo de simulación sigue o se termina la aplicación, además de prender o apagar los switches que necesita en cuanto a las acciones que sucederán al entrar en la siguiente función, actualizar_universo.

actualizar_universo recibe como parámetro el intervalo de tiempo entre el último cálculo y el instante en el que es invocado. Por compatibilidad con windows, lo recibe en milisegundos, aunque sería mucho más agradable que se pudiera recibir hasta en microsegundos, que es lo que manejan las estructuras de tiempo en FreeBSD y GNU/Linux, pero que es demasiado complicado obtenerlas en Windows (toca meterle mano a directx y otras cosas, según tengo entendido). De todas maneras los milisegundos permiten obtener una tasa de frames bastante aceptable (60 fps me parece bastante decente, más arriba de eso es difícil notarlo y además se gasta mucha energía).

En esta función entran en juego todas las ecuaciones de movimiento y cálculos de colisiones que se tengan. Las diferenciales de tiempo se alimentan precisamente con el intervalo recibido.

Finalmente se tiene la función redibujar_universo, que es donde se encuentra todo el código para realizar la representación gráfica.

El ciclo de dibujado se encuentra implementado en main.cpp y se encarga de realizar el intercambio de buffers, ya que esto cambia un poco cuando se usa SDL en conjunto con OpenGL (nada impresionantemente distinto, pero tendría una dependencia contra SDL si estuviera en graficos.h).

La idea es que graficos.h quede únicamente dependiente de OpenGL, GLU (ojo: GLU y no GLUT) y las bibliotecas hechas por uno mismo. De esta manera podrá compilar sin problemas en GCC tanto en FreeBSD como en GNU/Linux como en Windows (posiblemente también visual studio, pero no pienso gastar 3 horas de mi vida en instalar el platform sdk para que a los 10 días me toque reinstalarlo al reinstalar windows por alguna estupidez de su parte, como me pasó las últimas 2 veces el semestre pasado; la verdad prefiero escribir aquí).

Ahora, con respecto a la aplicación actual,es la primera vez que me topo con manejo de cámaras. Esto es algo bastante interesante, de hecho, toda una complicación en sí, hacer una cámara eficiente, pero por el momento lo dejaremos a algo muy simple: probablemente esté fija en el centro de la pantalla y mire siempre hacia el personaje que controlamos (aunque seguir al personaje y girar alrededor de él no lo veo tan complicado, quizás sí lo implemente).

Una de las primeras cosas con las que me topé en el manejo de cámaras, que me pareció interesante, fue el asegurar que la cámara no se inclinaría hacia un lado o hacia el otro, es decir, si tienen un tripié, el tripié permite a la cámara girar e inclinarse hacia los lados y hacia enfrente, pero uno le puede restringir el movimiento a que pueda girar e inclinarse sólo hacia adelante y hacia atrás (a menos que sea el tripié que usamos para hacer el streaming, ese se movía como se le daba la gana porque le hace falta una pieza; mi mamá y mi papá me matarán si leen esto, aunque la pieza ya faltaba desde antes del CONSOL (con más razón XD)).

Bueno, pues esto ¿cómo se puede asegurar matemáticamente?
El sistema que yo uso es completamente vectorial, porque los cuaterniones por el momento me siguen dando miedo (además de que finalmente son vectores "especiales"). El vector lo defino con 3 componentes: x, y y z.

Normalmente yo uso tres vectores para determinar completamente la situación espacial de un objeto: posición, dirección (hacia donde mira) y dirección vertical (si el brazo derecho lo apuntas hacia enfrente de tí y el izquierdo hacia arriba, paralelo a tu cuello, son los 2 últimos vectores que mencioné):



En la imagen, el vector rojo corresponde a la vertical; el verde al vector dirección. Yo me auxilié entonces poniendo otro vector que es perpendicular a los otros dos, representado por la flecha azul:



Este vector lo llamo yo la "horizontal".

Entonces si nos fijamos bien, la vertical y la dirección pueden inclinarse todo lo que queramos, pero la idea es que la horizontal no lo haga:





El problema entonces puede quedar expresado como "asegurar que la horizontal de la cámara siempre queda horizontal", es decir, paralela al plano XZ, o que la componente "y" del vector horizontal siempre sea 0 (perdón, me salté varios minutos de razonamiento de un jalón).


La dirección de la cámara estará dada por la relación entre la posición tanto del mono como de la cámara, ya que queremos que siempre la cámara mire al monigote.

Esto se puede lograr fijándonos en lo que implica que los vectores sean perpendiculares entre sí: el producto punto entre ellos será 0 (y si no me crees, pues mira un mapa--er el libro de Érick Castañeda de Isla Puga, iñooor!!).

El producto punto que nos conviene (el más ordinario en geometría analítica) se define como la suma de las multiplicaciones de las componentes de los dos vectores, en este caso si a la dirección la ponemos como U y la horizontal como V, sería

(U | V) = U.x*V.x + U.y*V.y + U.z*V.z ;

Ahora, de aquí, queremos que V.y sea 0, por lo que no nos importa qué pueda existir en U.y.

Entonces tenemos
(U | V) = U.x*V.x + U.z*V.z = 0

Por lo tanto, queremos que U.x*V.x sea igual a -(U.z*V.z).
U es dato; se nos proporciona por la posición de la cámara y la del mono. Como sabemos que U.x rara vez será igual a -U.z, esa solución no es factible.
Entonces hay que buscar modificar lo que hay en V, que después de todo es lo que queremos: forzar V a permanecer plano.
Lo ideal en mi opinión, es que sea un sistema coordenado derecho, como se muestra en la imagen del monigote con los 3 vectores. Entonces, fijándonos bien, se necesita que si U.x = 0 y U.z = 1, V.x sea 1 y V.z sea 0. Si U.x = 1 y U.z = 0, V.x = 0 y V.z = -1.

De aquí entonces podemos pensar V.z = -U.x, y finalmente V.x = U.z.
(De acuerdo, no tiene todo el rigor matemático de una demostración, pero si lo razonan verán que es correcto)

De esta manera resolvemos este detalle de la cámara :)

Iré publicando más según vaya resolviendo más detalles.
Uno que publicaré pronto será el de el giro de los objetos en instrucciones de OpenGL para que apunten según la dirección vectorial que tienen, que es lo que ha tenido estancado el juego de Nintendo DS que quiero hacer -- vean el histórico para más info.

Pongo aquí el código fuente de lo que llevo hasta ahora; la versión de windows compila pero no responde todavía al teclado. Me dio más flojera buscarle que publicar. Es posible que la versión de SDL también jale en windows, no he probado.

cga2007-2/lanjobot2.tar.bz2


< Back to blog

This site doesn't use cookies, does not log IPs and does not track you in any way.