Crea tu Skynet. Juego de Inteligencia Artificial militar DefCon

3 comments »

¿Os acordais de SkyNET? La perversa IA que crea el caos y la destruccion en Terminator. Ahora teneis la oportunidad de colaborar en el desarrollo de una inteligencia artificial capaz de liderar y defenderse de un ataque nuclear. El juego DEFCON.

El IEEE Symposium Computational on Intelligence  and Games (CIG para los amigos) ha puesto en marcha un concurso en el que se trata de construir un agente de IA(bot) que permita manejar las unidades y los edificios. La finalidad es causar el mayor numero de bajas perdiendo al minimo.

Ataque Termonuclear en Defcon

Ataque en Defcon

Robin Baumgarten ha creado una API que permite trabajar con el juego orientada a crear Bots que cntrolen los sistemas.

Para ello se debe descargar la version de prueba del juego que permite realizar todo lo que necesitemos.

La informacion de la API la podemos obtener aqui:

Poco a poco se iran dando detalles de esta iniciativatan interesante.A lo mejor alguno de vosotros gana la competicion este año. ¿Quien sabe?

Hasta la proxima

¿Os acordais de SkyNET? La perversa IA que crea el caos y la destruccion en Terminator. Ahora teneis la oportunidad de colaborar en el desarrollo de una inteligencia artificial capaz de liderar y defenderse de un ataque nuclear. El juego DEFCON.

El IEEE Symposoum CIG ha puesto en marcha un concurso en el que se trata de construir un agente de IA(bot) que permita manejar las unidades y los edificios. La finalidad es causar el mayor numero de bajas perdiendo al minimo.

(FOTO)

Robim Baumgarthen ha creado una API que permite trabajar con el juego orientada crear Bots que cntrolen los sistemas.

(VIDEO)

Para ello se debe descargar la version de prueba del juego que permite realizar todo lo que necesitemos.

(URL)

La informacion de la API la podemos obtener aqui:

(FILES)

Poco poco se iran dando detalles de esta iniciativatan interesante.A lo mejor alguno de vosotros gana la competicion este ño. ¿Quien sabe?

Hasta la proxima

Algoritmo A* (a estrella) y el Super Mario en java

No comments »

Hola AI Padawans,

Hoy hablaremos de un algoritmo muy utilizado en la inteligencia artificial, el A* o ‘a estrella’. Mediante este algoritmo pretendemos realizar una búsqueda en nuestro espacio de soluciones de una forma más eficaz, es decir, con conocimiento de causa.

En primer lugar a grandes rasgos comentar que para que el algoritmo funcione se debe poder establecer un valor de calidad a la solución parcial actual, es decir, un estimador o heurístico. Cada nueva solución propuesta tendrá su propio valor de calidad, lo que determinará el orden en el que la evaluaremos y continuaremos buscando.

De este modo, el algoritmo A* utiliza una función de evaluación f(n) = g(n) + h‘(n), donde h‘(n) representa el valor heurístico del nodo a evaluar desde el actual, n, hasta el final, y g(n), el coste real del camino recorrido para llegar a dicho nodo, n.

A* mantiene dos estructuras de datos auxiliares, que podemos denominar nodos abiertos, implementado como una cola de prioridad (ordenada por el valor f(n) de cada nodo), y nodos cerrados, donde se guarda la información de los nodos que ya han sido visitados. En cada paso del algoritmo, se expande el nodo que esté primero en abiertos, y en caso de que no sea un nodo objetivo, calcula la f(n) de todos sus hijos, los inserta en abiertos, y pasa el nodo evaluado a cerrados.

En resumen, buscamos primero en los más prometedores teniendo en cuenta lo que nos cuesta llegar a la solución parcial y lo que estimamos que nos costará llegar a la solución final.

El algoritmo es una combinación entre búsquedas del tipo primero en anchura con primero en profundidad: mientras que h‘(n) tiende a primero en profundidad, g(n) tiende a primero en anchura. De este modo, se cambia de camino de búsqueda cada vez que existen nodos más prometedores.

El pseudocódigo será:

ABIERTOS := [INICIAL] 	   //inicialización
CERRADOS := []
f'(INICIAL) := h'(INICIAL)
repetir
   si ABIERTOS = [] entonces FALLO
   si no                           // quedan nodos
      extraer MEJORNODO de ABIERTOS con f' mí­nima
         // cola de prioridad
      mover MEJORNODO de ABIERTOS a CERRADOS
      si MEJORNODO contiene estado_objetivo entonces
         SOLUCION_ENCONTRADA := TRUE
      si no
         generar SUCESORES de MEJORNODO
         para cada SUCESOR hacer TRATAR_SUCESOR
hasta SOLUCION_ENCONTRADA o FALLO

Qué tiene que ver el Super Mario en todo esto. Pues bien para ver el ejemplo práctico de aplicación hay que ver algo que nos convenza de la utilidad y de la aplicación práctica del mismo. Con este propósito se muestra la Mario AI Competition, un concurso de desarrolladores de IA para juegos donde la finalidad es generar un Mario Inteligente capaz de sortear a los enemigos.

ABIERTOS := [INICIAL] 	   //inicialización
CERRADOS := []
f'(INICIAL) := h'(INICIAL)
repetir
   si ABIERTOS = [] entonces FALLO
   si no                           // quedan nodos
      extraer MEJORNODO de ABIERTOS con f' mí­nima
         // cola de prioridad
      mover MEJORNODO de ABIERTOS a CERRADOS
      si MEJORNODO contiene estado_objetivo entonces
         SOLUCION_ENCONTRADA := TRUE
      si no
         generar SUCESORES de MEJORNODO
         para cada SUCESOR hacer TRATAR_SUCESOR
hasta SOLUCION_ENCONTRADA o FALLO

TRATAR_SUCESO

Como veis las lineas rojas son las estimaciones de posición futura. En la pasada edición el algoritmo A* ganó la convocatoria. Se trata de calcular sobre el modelo de Mario en Java un agente o entidad inteligente capaz de hacer que Mario llegue lo más rápido y lejos posible.

En este último documento se incluye la presentación del evento, de sus participantes y de sus estrategias predefinidas. Entre ellas una gran descripción de la utilizada por el ganador, Robin Baumgarten, el A Estrella.

>> GIC2009Competition

    Veremos más detalles más adelante!

    Hasta la próxima!

    Algoritmo Minimax, un jugador incansable

    4 comments »

    Visto el backtracking vamos un poco más allá con uno de los algoritmos utilizados en la IA y
    con más medida en la IA orientada al entretenimiento, el minimax.

    En teoría de juegos, Minimax es un método de decisión para minimizar la pérdida máxima esperada en juegos con adversario y con información perfecta. Este cálculo se hace de forma recursiva.

    El funcionamiento de Minimax puede resumirse como elegir el mejor movimiento para ti mismo suponiendo que tu contrincante escogerá el peor para ti.

    ejemplo de uso

    La receta del algoritmo Minimax:

    • 1. Generación del árbol de juego. Se generarán todos los nodos hasta llegar a un estado terminal o determinando una profundidad concreta.

    Vamos aplicando el algoritmo por un número fijo de iteraciones hasta alcanzar una determinada profundidad. En estas aplicaciones la profundidad suele ser el número de movimientos o los incluso el resultado de aplicar diversos pasos de planificación en un juego de estrategia.

    • 2. Cálculo de los valores de la función de utilidad para cada nodo terminal.

    Para cada resultado final, cómo de beneficioso me resulta si estamos en MAX o cuanto me perjudicará si estamos en MIN.

    • 3. Calcular el valor de los nodos superiores a partir del valor de los inferiores. Alternativamente se elegirán los valores mínimos y máximos representando los movimientos del jugador y del oponente, de ahí el nombre de Minimax.
    • 4 . Elegir la jugada valorando los valores que han llegado al nivel superior.

    El algoritmo explorará los nodos del árbol asignándoles un valor numérico mediante una función de utilidad, empezando por los nodos terminales y subiendo hacia la raíz. La función de utilidad como se ha comentado, definirá lo buena que es la posición para un jugador cuando la alcanza.

    Versiones más avanzadas como el minimax con poda alfa beta hacen que se reduzca considerablemente el número de nodos a visitar por lo que el tiempo de cálculo se reduce ampliamente.

    Y para terminar comentar un ejemplo cásico, el tres en raya (juego del gato, tatetí, triqui, tres en gallo, michi, la vieja o tic tac toe). Se trata de hacer una fila de tres para ganar y evitar que el oponente la haga antes que tu.

    Al aplicar el algoritmo, se suceden una serie de estados que se resumen en la fotografía. Un estado -1 significa que MAX gana, 0 empate o -1 pierde.

    tres en raya con minimx

    Se distinguen los nodos terminales con jugada finalizada y los del trayecto. En este juego se puede llegar a la profundidad máxima puesto que se trata de 9 movimientos fijos, en otros como el ajedrez o las damas es muy necesario limitar la profundidad y aplicar medidas que aumenten la eficiencia.

    El código se adjunta en python muy comentado. El bloque principal, y a partir de ahi todo lo demás en el documento adjunto, es el siguiente:

    b = Board()
    turn = 1
    while True:
    print “Turno %i.” % turn
    jugadorHumano(b, Jugador_X)
    if b.gameOver():
    break

    jugadorMaquina(b, Jugador_O)
    if b.gameOver():
    break
    turn += 1

    Donde se crea una instancia del tablero, necesaria para detallar los elementos que intervienen en juego y e ir aplicando el algoritmo. Se pueden intercambiar jugadores o incluso hacer que jueguen dos máquinas o dos humanos simplemente modificando estas lineas de arriba.

    En resumen.

    • El minimax aporta una herramienta de proceso recursiva muy útil
    • Se pueden aplicar modificaciones al algoritmo para hacerlo más eficiente

    En el tres en raya:

    • Gana el 1, pierde el -1 y empate 0
    • La profundida máxima es de 9, como el número de jugadas posible
    • La cota superior de nodos a visitar es en el peor caso (primer movimiento) 9 factoria -A> 9!
    • No hay restricciones sobre la validez de un movimiento, simplemente que no se haya hecho antes, por lo que el coste del cálculo es bajo (no hay que aplicar reglas complejas).
    • Almacenar las soluciones intermedias no es excesivamente complejo
    • Generar los diferentes tableros con las soluciones intermedias a explorar no es costoso pero podría ser un problema en otros juegos y limitar la profundidad por memoria
    • La máquina nunca pierde, el juego está completado
    • Las partidas entre jugadores máquina siempre quedan en tablas.

    El tiempo que tarda un jugador pc en procesar la primera jugada es de 4047,181 ms en un ordenador medio, el que tarda cuando procesa la jugada que lo hace ganador en el último movimiento 0,568 ms y cuando procesa la que lo deja en tablas 0,337. La diferencia de tiempo de cálculo entre los primeros movimientos cuando quedan muchas opciones y el resto es 8000 veces mayor, por lo que el tema de la eficiencia habrá que tocarlo.

    El próximo tema trataremos el concepto de agente y probaremos con otro juego. Que lo disfruteis !!!

    Tutorial OpenGL V

    No comments »

    Aquí estamos de nuevo tras una pausa de no haber publicado nada del tutorial de openGL. El tiempo es limitado y las cosas que uno tiene que hacer a veces supera ese tiempo.

    En este capítulo lo que haremos es rotar una figura bidimensional en alguno de sus ejes. Concretamente el triángulo en el eje de las x y el cuadrilátero en el eje de las y. Las modificaciones al código anterior son pocas. Recordemos que los métodos de inicialización y manejo de la ventana son idénticos que en el resto de publicaciones.

    Recordemos el código del método DrawGLScene del capítulo anterior:

    def DrawGLScene(self):
           glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
           glLoadIdentity()
    
           glTranslatef(-1.5,0.0,-6.0)
    
           glBegin(GL_POLYGON)
           glColor3f(1.0,0.0,0.0)
           glVertex3f(0.0,1.0,0.0)
           glColor3f(0.0,1.0,0.0)
           glVertex3f(1.0,-1.0,0.0)
           glColor3f(0.0,0.0,1.0)
           glVertex3f(-1.0,-1.0,0.0)
           glEnd()
    
           glTranslatef(3.0,0.0,0.0)
    
           glColor3f(0.3,0.5,1.0)
    
           glBegin(GL_QUADS)
           glVertex3f(-1.0,1.0,0.0)
           glVertex3f(1.0,1.0,0.0)
           glVertex3f(1.0,-1.0,0.0)
           glVertex3f(-1.0,-1.0,0.0)
           glEnd()
    
           glutSwapBuffers()

    Si hacemos memoria este código lo que hacía era dibujar en nuestra ventana OpenGL dos figuras geométricas básicas distanciadas en el eje de las x: un triángulo con un degradado de color (debido a la imposición de diferentes colores en sus vértices) y un cuadrilátero con un único color (debido a la imposición de color a nivel de figura).
    En esta publicación introduciremos el concepto de rotación. La rotación en OpenGL se hace mediante la función glRotatef. La definición de la función es la siguiente:

    void glRotatef(GLDouble angle, GLDouble x, GLDouble y, GLDouble z)

    donde angle especifica el ángulo de rotación en grados y x,y y z definen las coordenadas x,y,z de un vector.
    glRotate produce una rotación en un ángulo determinado alrededor de un vector (x,y,z). La matriz actual (dada por glMatrizMode) es multiplicada por la matriz de rotación reemplazando la actual matriz por el resultado del producto. Si la matriz actual es GL_MODELVIEW o GL_PROJECTION todos los objetos dibujados después de la llamada a glRotate serán rotados. Si queremos salvar nuestro sistema de coordenadas sin rotar podemos guardar la matriz previa a la modificación en una pila con la función glPushMatrix y recuperarla posteriormente con glPopMatrix.
    Por tanto, nuestro código anterior donde únicamente se veía dos figuras estáticas coloreadas puede cambiar a ser dinámicas con una simple modificación:

    def DrawGLScene(self):
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
         glLoadIdentity()
    
         glTranslatef(-1.5,0.0,-6.0)
    
         glRotatef(self.rtri,0.0,1.0,0.0)
    
         glBegin(GL_POLYGON)
         glColor3f(1.0,0.0,0.0)
         glVertex3f(0.0,1.0,0.0)
         glColor3f(0.0,1.0,0.0)
         glVertex3f(1.0,-1.0,0.0)
         glColor3f(0.0,0.0,1.0)
         glVertex3f(-1.0,-1.0,0.0)
         glEnd()
    
         glLoadIdentity()
    
         glTranslatef(1.5,0.0,-6.0)
         glRotatef(self.rquad,1.0,0.0,0.0)
    
         glColor3f(0.3,0.5,1.0)
         glBegin(GL_QUADS)
         glVertex3f(-1.0,1.0,0.0)
         glVertex3f(1.0,1.0,0.0)
         glVertex3f(1.0,-1.0,0.0)
         glVertex3f(-1.0,-1.0,0.0)
         glEnd()
    
         self.rtri=self.rtri+1.0
         self.rquad=self.rquad-1.0
    
         glutSwapBuffers()

    Como podemos observar, al triángulo lo rotamos rtri sobre el vector (0,1,0) o lo que viene a ser lo mismo que decir que modificamos en rtri el eje de las y. Al cuadrilátero le modificamos en rquad el vector (1,0,0) o lo que viene a ser que modificamos en rquad el eje de las x. Cada vez que GLU llama a GLScene (cada vez que necesita redibujar la escena) rtri se incremente en 1 grado y rquad se decrementa en un grado y da esa sensación de movimiento.

    ¿Cansados ya de figuras en el plano bidimensional? En la próxima entrega ya entraremos en el plano tridimensional y crearemos a partir del cuadrilátero, un cubo; y del triángulo, un cono y las haremos rotar en el espacio tridimensional.

    ¡hasta entonces!

    Backtracking ++

    No comments »

    En el capítulo anterior vimos de que iba el tema del backtracking, ahora vamos a ver un nuevo ejemplo ybacktracking algún que otro concepto de la IA, veremos otra aplicación de esta técnica como el Circuito del Caballo.

    El Circuito del Caballo es un problema muy recurrido en los libros de programación, ya que si se hace con una implementación estructurada entonces es necesario conocer muy bien las estructuras de control para poder lograr llegar a la solución, y si se pide hacer recursivamente pues es muy importante estar seguro de cómo funciona la “pila de ejecución”.

    El Problema

    El planteamiento: Se coloca en un tablero de ajedrez vacío un caballo en la casilla que se quiera, y a partir de ahí se debe lograr “pasar” -siguiendo las reglas del movimiento del caballo en el juego de ajedrez- por las 63 casillas restantes una única vez por cada casilla.

    Este problema se presta mucho para el backtracking porque no hay manera de idear una solución en general, no es como resolver un sistema de ecuaciones, y quien lo trate de resolver “a mano” tendrá obligadamente que anotar los caminos que ha seguido para ir descartando posibilidades y evitar repetir caminos. El problema no es nada nuevo, desde el siglo XVIII el matemático Leonhard Euler había encontrado una solución, y todavía sigue la pregunta abierta sobre ¿cúantas soluciones diferentes existen?

    El backtracking

    Ahora a los que nos atañe, necesitamos idear una función recursiva que logre “recordar” los caminos que se han tomado desde cada posición; y, para aplicar el backtracking también necesitamos que sólo retroceda hasta la casilla anterior si ya no hay posibilidad de resolver el problema desde cierto camino. El pseudocódigo sería el siguiente:

    Mueve el caballo a la casilla dada
    mueveCaballo (casilla)
    Pone un caballo sobre el tablero en la casilla dada.
    ponCaballo (casilla)
    Obtiene las casillas “no visitadas” que se pueden
    alcanzar con un movimiento válido de caballo desde
    la casilla dada
    casillasDesde (casilla)
    
    El contador lleva el número de casillas visitadas
    Booleano resuelveCaballo (casilla, contador)
      if contador = 64 then
         return VERDADERO
      else
         if contador = 0 then
            ponCaballo (casilla)
    	contador\leftarrow1
         endif
    
         for i \in casillasDesde (casilla) do
            mueveCaballo (i)
            if resuelveCaballo (i, contador + 1) then
               return VERDADERO
            else
               mueveCaballo (casilla) aqui el Backtracking
            endif
         endfor
    
         return FALSO
    
      endif
    end
    

    Una solución

    Esta es la animación de cómo el caballo podría lograr el circuito o el método en que se resuelve una de las soluciones. El circuito del Caballo

    La Heurística

    Como ya os habreis dado cuenta el algoritmo es muy ineficiente, porque es muy fácil llegar a combinaciones en donde ya es claramente imposible hacer el circuito (ej. cuando alguna casilla queda inaccesible). La primer heurística que se nos podría ocurrir es: verificar si hay casillas inaccesibles para descartar un camino de búsqueda; pero esa heurísitica no es la mejor, ya que se tendría que verificar en las 64 casillas si ya había sido visitada o si está inaccesible, cosa que no parecería importante, pero tomando en cuenta que al principio hay muchas casillas sin visitar, entonces habría que verificar muchas casillas y eso requiere más cómputo.

    El concepto de heurística representa al de un indicador de la calidad de la solución parcial de la rama que estamos mirando, puede ser excluyente o dar una idea de lo buena que puede llegar a ser esa solución.

    Otra Heurística histórica y hasta la fecha muy buena es la de Warnsdorff, que propone las siguientes reglas: (1) Se debe ir a la casilla desde la que se puedan hacer menos movimientos válidos (a casilla no visitadas) en el siguiente paso. (2) En caso de tener más de una opción se elije una al azar.

    La heurística se basa en la idea de que es mejor usar primero las casillas que tengan menos posibilidades de ser alcanzadas en movimientos subsecuentes y así evitar tener casillas inaccesibles a lo largo del camino.

    Path

    De cualquier modo, la exploración se debe hacer dando prioridad a la heurística más prometedora. En el mundo real se puede hacer la apreciación de elegir un camino, el oscuro y tenebroso o el iluminado y transitado.

    El Backtracking

    12 comments »

    El backtracking es una técnica usada en computación que en pocas palabras va explorando un problema porcaballo todo un árbol de combinaciones en donde forzosamente alguna de las posibles combinaciones es solución del problema. Es casi la técnica humana cuando se quiere resolver un problema nuevo y no tan obvio, en donde sólo se conocen las reglas del problema y se tiene que ir probando por caminos diferentes para encontrar la solución.

    En éste artículo se va a dar una aproximación a la estrategia.

    Esta técnica existió mucho antes que los lenguajes de programación y la computadora moderna. La implementación de esta técnica, en lenguajes que no están hechos para ella, se hace comúnmente mediante funciones recursivas. Hay otros lenguajes como Prolog en donde el lenguaje hace nativamente una implementación de la técnica. La razón por la que se usan funciones recursivas es que la “pila de ejecución” del programa se encarga de preservar ordenados y en memoria los estados anteriores, así que es más fácil volver sobre tus propios pasos.

    Lo primero que se hace para identificar un problema digno de backtracking es pensar cómo lo resolverías tú como humano, si tu mejor solución es empezar con algunas condiciones iniciales e ir probando caminos hasta llegar a un punto en donde no hay solución y regresar entonces un poco para explorar por otro lado, quizá estes haciendo backtracking. Lo más importante para hacer un backtraking es hacer un “retroceso mínimo” o sea, no destruir todo si no pudiste encontrar una solución, sino sólo borrar lo necesario para poder seguir explorando sin perder todo el camino que has recorrido.

    El caso más similar a lo que se debe hacer para implementar un backtracking es pensar en la manera como se sale de un laberinto, primero amarras un hilo en el principio del laberinto y vas decidiendo sobre cada bifurcación hacia donde vas a seguir; al momento de encontrar un callejón sin salida sabes que debes regresar y buscar por el lado contrario, y si los dos lados no te llevan a la salida entonces podrías marcar esa bifurcación con alguna señal, regresar más y buscar otro camino. Es lo que se haría en muy pocas palabras, porque nunca se debería regresar hasta el principio al encontrar un callejón sin salida, se debería retroceder lo menos posible siguiendo al hilo que indica lo que se ha recorrido y no se debería volver a entrar a caminos en marcados.

    Backtracking

    De modo que el código sería el siguiente:

    Avanza por el camino de la derecha desde cierta posición.
    avanza_derecha (posicion)
    Avanza por el camino de la derecha desde cierta posición.
    avanza_izquierda (posicion)
    Indica si ya se alcanzó la salida
    es_la_salida (posicion)
    Indica si ya no se puede seguir desde cierta posición.
    es_callejon_sin_salida (posicion) 
    
    Booleano sal_del_laberinto (posicion_actual)
       if es_la_salida (posicion_actual) then
          return VERDADERO
       else
          nueva_posicion\leftarrowavanza_derecha (posicion_actual)
          if es_callejon_sin_salida (nueva_posicion) then
             return FALSO
          else
             if sal_del_laberinto (nueva_posicion) then
                 return VERDADERO
             else
                 nueva_posicion\leftarrowavanza_izquierda (posicion_actual)
                 return sal_del_laberinto (nueva_posicion)
    
             endif
          endif
       endif
    end

    Estareis de acuerdo que es muy engorroso hacerlo así, pero sabemos que con todas las precauciones en algún momento saldremos del laberinto, esa es la forma en como se debe pensar un backtracking, nunca se deben recorrer caminos que ya han sido recorridos y retroceder lo menos posible para explorar más a profundidad. Lo que hace tan poderosa a la ténica es que la computadora tiene muchísima más memoria que un humano y no piensa, entonces ella no titubea.

    En teoría de gráficas la técnica de backtracking es parecida a la “búsqueda a profundidad” (depth-first search) aunque no necesariamente equivalente. Para los que están familiarizados con las estructuras discretas, imaginad que hay un árbol y cada nodo del árbol es una bifurcación del laberinto; algunas hojas del árbol son las soluciones al problema y algunas otras son callejones sin salida. Entonces para recorrer un árbol lo que se hace (que finalmente es una gráfica) es ir bajando desde la raíz a sus nodos y tratar de abarcar lo más que se pueda por cada camino.

    Una aclaración importantísima es que backtracking no es equivalente a recursión, resolver por recursión ciertos problemas (por ej. las torres de Hanoi) no tiene nada que ver muchas veces con usar backtracking. Se está usando backtracking cuando no se sabe exactamente la solución a un problema y se tiene que “andar a ciegas” buscando posibles soluciones, no se buscan soluciones aleatoriamente sino que se planea una estrategia sistemática que agote posibilidades. Cuando se piensa un problema de manera recursiva, únicamente se busca abstraer un caso base del que se pueda partir para resolver todo el problema; en backtracking no hay un caso base, hay condiciones de solubilidad, que no sirven para resolver el problema en sí, sirven para saber que se ha encontrado una solución o no.

    Si el problema no es tan general como salir de un laberinto, en el cual no se puede deducir nada estando dentro de él, entonces es posible mejorar el backtracking agregando condiciones de insolubilidad evitando así combinaciones ociosas que definitivamente no van a llegar nunca a una solución, esto se llama “poda” del backtracking. También se puede mejorar el backtracking eligiendo ciertas combinaciones iniciales que nos “den la corazonada” de que así se logrará resolver el problema explorando menos combinaciones, esto se llama “heurística”.

    Un ejemplo interesante en el que se puede usar backtracking es:

    Imagina que tienes fábricas en distintas ciudades, y cada fábrica tiene al menos otra que está conectada con ella por una carretera. Ahora quieres poner estaciones de servicio en algunas fábricas de tal manera que todas las fábricas tengan al menos una estación de servicio adyacente. Las fábricas que tienen estación de servicio no necesitan tener otra estación adyacente.

    La pregunta sería: ¿en qué fábricas se deben instalar estaciones de servicio para lograr tener el mínimo número de estaciones de servicio posibles?

    En este problema se puede “podar” el backtracking muy fácilmente evitando que siga buscando una solución cuando ya se alcanzó el mínimo actual y una buena heurística es empezar colocando estaciones de servicio en las fábricas que tengan un mayor número de fábricas adyacentes.

    Agradecimientos a AmarellOcio.

    En el próximo capítulo veremos otro ejemplo de backtracking muy bueno.

    Hasta la próxima!!

    Tutorial OpenGL IV

    No comments »

    Después de una semana de ansiona espera (supongo) aquí llega la cuarta entrega del tutorial de OpenGL basado en Python. En esta entrega iremos un paso más allá ahora que ya comprendemos todo lo que necesitamos para crear un programa OpenGL. Si no lo recordamos, haré algo de memoria:
    Para crear un contexto OpenGL con todo lo necesario necesitaremos de tres métodos obligatorios y uno opcional básicos:

    • método InitGL: inicializa OpenGL al estado incicial. Ésto supoce inicializar el color de borrado de la pantalla, inicializar el buffer de profundidad (el z-buffer), el smooth shading, la matriz de proyección para dimensionar la ventana y la matriz de vista.
    • método ResizeGLScene: La idea de este método es que se active cuando redimensionamos la ventana. Ya la hagamos más grande o más pequeña ha de modificar la matriz de proyección para adaptar la proyección al nuevo tamaño de la ventana y la correspondiente nueva perspectiva del dibujo.
    • método DrawGLScene: Este método es el básico de todo el montaje. Es el que define lo que se dibuja en la ventana OpenGL. Depende de lo complejo que sea lo que queramos dibujar, será más elaborado o menos pero aquí es donde se define lo que visualizaremos en la ventana OpenGL. Básicamente borrará el contenido de la ventana, dibujará lo que tenga que dibujar y activará el double buffer para evitar problemas de visualización consecuencia del refresco de la pantalla.
    • método KeyPressed: Controla que se pulse la tecla ESC (en nuestro caso).

    Una vez implementados estos métodos y inicializada la ventana con glut (el controlador de la ventana) en el constructor de la clase los registramos con los métodos glutXXXFunc para que observe el estado y dispare el método adecuado cuando ocurre el evento.

    Una vez hecho este pequeño resumen de lo básico de OpenGL entramos ya en lo que haremos en este segundo tutorial. Básicamente el esqueleto de la aplicación es el mismo a excepción del método DrawGLScene que se amplia con algo más de contenido. Era frustrante ver esas línias de código que parecía que no hiciesen nada. Eso no era cierto, inicializaban el contexto OpenGL pero no dibujaban nada. Que no se vea algo no significa que no exista. :)

    Este cuarto tutorial lo dividiremos en dos partes. La primera, dibujará un triángulo y un cuadrado en dos dimensiones sin relleno de color y, después, modificaremos esa implementación para darle un relleno de color a cada una de esas figuras.

    Los métodos principales se conservarán igual que el tutorial anterior ya que han de hacer el mismo trabajo, el único que variará es el DrawGLScene que pasará de no dibujar NADA a dibujar ALGO. Tampoco lo cargaremos de trabajo,la verdad, pero algo hará.

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()

    Estas dos llamadas las ha de tener todo método DrawGLScene ya que inicializa los buffers de color y de profundidad dejandolos preparados para comenzar a pintar.

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glTranslate(-1.5,0.0,-6.0)

    glTranslate produce una translación en (x,y,z). La matriz actual (modelview en este caso) es multiplicada por la matriz de translación reemplazándola en la matriz actual obteniendo así el resultado de la translación. En definitiva, glTranslate reemplaza la matriz de vista del modelo por una matriz transladada dada por el producto del vector (x,y,z) y la propia matriz. Al modificar la matriz todos los objetos entre glBegin y glEnd dibujados después de la llamada a glTranslate serán transladados según defina esta llamada.

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glTranslate(-1.5,0.0,-6.0)
        glBegin(GL_POLYGON)

    glBegin define el inicio de los limitadores de definición de los vértices de las primitiva o grupo de primitivas. Entendiendo como primitivas las figuras geométricas básicas:

    n es un tipo entero incremental y N el número total de vértices

    • glBegin(GL_POINTS): Trata cada vértice como un punto simple.El vértice n define el punto n. Se dibujarán N puntos.
    • glBegin(GL_LINES): Trata cada par de vértices como un segmento linear independiente. Los vértices 2n-1 y 2n formarán las línia n. Se dibujarán un total de N/2 línias.
    • glBegin(GL_LINE_STRIP): Dibuja un conjunto de segmentos de línia conectados desde el primer vértice al último. Los vértices n y n+1 definen la línia n. Se dibujarán N-1 línias.
    • glBegin(GL_LINE_LOOP): Dibuja un conjunto de segmentos de línia conectados desde el primer vértice al último, entonces vuelve al primero. No obstante, la última línia es definida por los vértices 1 y N. Se dibujarán N línias.
    • glBegin(GL_TRIANGLES): Trata cada tres vértices como un triángulo independiente. Los vértices 3n-2, 3n-1 y 3n definirán el triángulo n. Se dibujarán N/3 triángulos.
    • glBegin(GL_TRIANGLE_STRIP): Dibuja un grupo conectado de triángulos. Un triángulo es definido por cada vértice presentado después del primero de dos vértices. Para n impar, los vértices n, n+1 y n+2 formarán un triángulo n. Para n par, n+1, n y n+2 formarán un triángulo n. Se dibujarán N-2 triángulos.
    • glBegin(GL_TRIANGLE_FAN): Dibuja un grupo conectado de triángulos. Un triángulo es definido por cada vértice presentado después del primero de dos vértices. Los vértices 1, n+1 y n+2 definirán el triángulo n. Se definirán N-2 triángulos.
    • glBegin(GL_QUADS): Trata cada grupo de cuatro vértices como un cuadrilatero independiente. Los vértices 4n-3, 4n-2, 4n-1 y 4n definen el cuadrilátero n. N/4 cuadriláteros serán dibujados.
    • glBegin(GL_QUAD_STRIP): Dibuja un grupo conectado de cuadriláteros. Un cuadrilátero es definido por cada par de vértices presentados después del primer par. Los vértices 2n-1, 2n, 2n+2 y 2n+1 definen el cuadrilátero n. Se dibujarán (N/2)-1 cuadriláteros. El orden de la información dada para dibujar este tipo de cuadriláteros es diferente al anterior.
    • glBegin(GL_POLYGON): Dibuja un polígono simple y convexo. Los vértices 1 hasta el N definen el polígono.

    glBegin acepta un solo parámetro que especifica con cuál de los diez tipos de vertices se ha de interpretar.

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glTranslate(-1.5,0.0,-6.0)
        glBegin(GL_POLYGON)
        glVertex3f(0.0,1.0,0.0)
        glVertex3f(1.0,-1.0,0.0)
        glVertex3f(-1.0,-1.0,0.0)

    glVertex es la función que dibuja un vértice (o punto) con tres parámetros x, y y z que son de tipo glFloat.

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glTranslate(-1.5,0.0,-6.0)
        glBegin(GL_POLYGON)
        glVertex3f(0.0,1.0,0.0)
        glVertex3f(1.0,-1.0,0.0)
        glVertex3f(-1.0,-1.0,0.0)
        glEnd()

    glEnd define el final de la definición de vértices de la figura o figuras primitivas. Lo que acabamos de hacer nos dibujará un triángulo en las coordenadas definidas ((0.0,1.0,0.0),(1.0,-1.0,0.0),(-1.0,-1.0,0.0)) sin ningún tipo de color de relleno. Ésto sería equivalente a:

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glTranslate(-1.5,0.0,-6.0)
        glBegin(GL_POLYGON)
        glVertex2f(0.0,1.0)
        glVertex2f(1.0,-1.0)
        glVertex2f(-1.0,-1.0)
        glEnd()

    Definimos vértices únicamente con las coordenadas x e y.

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glTranslate(-1.5,0.0,-6.0)
        glBegin(GL_POLYGON)
        glVertex3f(0.0,1.0,0.0)
        glVertex3f(1.0,-1.0,0.0)
        glVertex3f(-1.0,-1.0,0.0)
        glEnd()
        glTranslate(3.0,0.0,0.0)

    Transladamos la primitiva que dibujaremos a continuación 3.0 unidades en el eje de las x.

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glTranslate(-1.5,0.0,-6.0)
        glBegin(GL_POLYGON)
        glVertex3f(0.0,1.0,0.0)
        glVertex3f(1.0,-1.0,0.0)
        glVertex3f(-1.0,-1.0,0.0)
        glEnd()
        glTranslate(3.0,0.0,0.0)
        glBegin(GL_QUADS)
        glVertex3f(-1.0,1.0,0.0)
        glVertex3f(1.0,1.0,0.0)
        glVertex3f(1.0,-1.0,0.0)
        glVertex3f(-1.0,-1.0,0.0)
        glEnd()

    Con el código anterior lo que hemos hecho es definir un cuadrilátero simple representando sus cuatro vértices.

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glTranslate(-1.5,0.0,-6.0)
        glBegin(GL_POLYGON)
        glVertex3f(0.0,1.0,0.0)
        glVertex3f(1.0,-1.0,0.0)
        glVertex3f(-1.0,-1.0,0.0)
        glEnd()
        glTranslate(3.0,0.0,0.0)
        glBegin(GL_QUADS)
        glVertex3f(-1.0,1.0,0.0)
        glVertex3f(1.0,1.0,0.0)
        glVertex3f(1.0,-1.0,0.0)
        glVertex3f(-1.0,-1.0,0.0)
        glEnd()
        glutSwapBuffers()

    Y como comentamos en el tutorial anterior hemos de activar el double buffer para evitar problemas de representación gráfica dados por el refresco de la pantalla.

    Este código lo que hará será dibujar dos figuras sin relleno de color aliniadas en el eje de las x y separadas tres unidades.

    En el caso que queramos añadirle color a la figura tendremos que definir el ámbito del color ANTES de llamar a la función que dibuja el vértice o primitiva. En nuestro caso el color del triángulo será de ámbito de vértice y el color del cuadrilátero será de ámbito de primitiva:

    def DrawGLScene(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()
        glTranslate(-1.5,0.0,-6.0)
        glBegin(GL_POLYGON)
        glColor3f(1.0,0.0,0.0)
        glVertex3f(0.0,1.0,0.0)
        glColor3f(0.0,1.0,0.0)
        glVertex3f(1.0,-1.0,0.0)
        glColor3f(0.0,0.0,1.0)
        glVertex3f(-1.0,-1.0,0.0)
        glEnd()
        glTranslate(3.0,0.0,0.0)
        glColor3f(0.3,0.5,1.0)
        glBegin(GL_QUADS)
        glVertex3f(-1.0,1.0,0.0)
        glVertex3f(1.0,1.0,0.0)
        glVertex3f(1.0,-1.0,0.0)
        glVertex3f(-1.0,-1.0,0.0)
        glEnd()
        glutSwapBuffers()

    glColor3f define el color de lo que se dibujará a continuación basándose en tres parámetros de tipo glFloat que son la cantidad de rojo, verde y azul en el rango [0.0,1.0]. Donde 0.0 es el mínimo y 1.0 es el máximo. Existe otra función que se puede definir el parámetro alpha o de transparencia glColor4f. Esta función tiene un último parámetro que se especifica también en el rango anteriormente definido.

    En nuestro código, en el triángulo el color se define a nivel de vértico con lo que la figura se verá con un color de degradado suma de los diferentes colores de los vértices. En cuadrilátero al definirse a nivel de primitiva todo él será del mismo color.

    Y con ésto acabamos este cuarto tutorial de OpenGL. En el próximo tutorial hablaremos de rotaciones y movimientos de primitivas.

    ¡Hasta entonces!

    La Inteligencia Artificial

    No comments »

    Una de las principales ramas de conocimiento sobre la que profundizar es la inteligencia artificial.

    Seguramente es una de las más interesantes en todos los niveles, optimización, consecución de objetivos e interés de la comunidad. Pero también es una de las areas más complejas y con más futuro de las tecnologías de la información.

    Tener la capacidad de crear agentes ‘inteligentes’ para nuestros proyectos es básica y representa un factor diferenciador respecto al resto. En nuestro caso, una de las metas principales es la de llegar a aplicarlo a la creación de software de entretenimiento principalmente. Hoy en día, aún teniendo la importancia que tiene el tema gráfico, unos buen diseño sin una buena IA se queda en nada. Para estar al día en este mundillo hay que controlar este tema dentro de las posibilidades.

    El objetivo principal es llegar a conocer los mecanismos y las técnicas actuales de la inteligencia artificial, cómo se definen las pautas de comportamiento en personajes de un juego, cómo se responde a una determinada pregunta, de que forma un juego sabe responder a una jugada del adversario y mil cosas más.

    Claro está no hay que dejar, aunque se va de momento de las aspiraciones de la comunidad, el tema de la robótica. La principal disciplina impulsora de la IA. Tener la capacidad de dotar de comportamientos inteligentes a una entidad física es una gran ventaja.

    Sin más dilaciones, por donde empezamos.

    1. Veremos la parte teórica del asunto
    2. Teoría de búsquedas
    3. Juegos con oponente
    4. Algoritmos de búsqueda local. Hill climbing y simulated annealing
    5. Genéticos.
    6. Aplicaciones prácticas
    7. Redes neuronales
    8. En paralelo con el 3D, definiremos comportamientos inteligentes a entidades de un escenario
    9. Fase de planteamiento de problemas relaccionados con la IA,daremos buena cuenta de ellos.

    Todo esto claro está buscando un entorno bien preparado para poder llevar a cabo nuestros objetivos. Principalmente adquirir el conocimiento necesario para definir una respuesta inteligente a las diferentes entidades que forman parte de un juego. Si se trata de los malos del duke nukem, de la estrategia de un oponente o de la respuesta de un compañero de juego a un estímulo externo.

    Espero que lo disfruteis y colaboreis conmigo, hasta la próxima!

    Tutorial OpenGL III

    No comments »

    Primero de todo lo que vamos a hacer es adaptar el – a mi entender – uno de los mejores tutoriales de openGL que hay en la red a Python. El tutorial en cuestión es el de NeHe.

    Empezaremos por crear una ventana donde no se vea nada. Puede parecer ridículo pero creando un contexto gráfico donde no hay nada podemos ver las funciones esenciales que se necesitan de GLU(T) y openGL.

    Nuestro código en Python comienza con:

    from OpenGL.GL import *
    from OpenGL.GLUT import *
    from OpenGL.GLU import *
    import sys
    

    En el fragmento de código anterior lo que hacemos es importar las funciones de las librerías de pyOpenGL. Concretamente GL,GLU y GLUT. También importamos el módulo sys para trabajar con algunas llamadas al sistema.

    Antes de continuar me gustaría definir unos cuantos conceptos que no se ven explícitos en el código Python pero que es bueno comprenderlos ya que en otros lenguajes es necesario definirlos de forma explícita.

    El primero de ellos es el Rendering Context (RC) o el Contexto de Renderizado. Todo programa en OpenGL está enlazado a un RC que no es otra cosa que una capa intermedia que enlaza las llamadas a OpenGL con el Device Context (DC) o Contexto Hardware. Para que el tinglado funcione también es necesario definir (explícita o implicitamente) el Contexto Hardware. El Contexto Hardware no es más que otra capa que enlaza la ventana que el llamado GDI (Graphics Device Interface). Por tanto, el RC lo que hace es conectar OpenGL con el DC para tratar lo que en la ventana se haya de visualizar.

    Una vez definidos estos conceptos ya podemos continuar con nuestro código. :) Una vez importadas las librerías necesarias hemos de definir la clase ventana.

    class Ventana:
    

    La primera de las variables que hemos definido declara el código de tecla que se utilizará para salir del programa. En nuestro caso, Esc. La segunda variable define el número de la ventana en GLUT.

    A continuación definiremos el método que trata lo que ocurre en la ventana cuando la redimensionamos:
    Lo primero que vamos a hacer es hacer la definición de la función que admitirá dos parámetros de entrada: el ancho y el alto.

    def ReSizeGLScene(Width, Height):

    Una vez definida lo que hemos de hacer es evitar la división por 0 en el caso que la ventana sea demasiado pequeña

    def ReSizeGLScene(Width, Height):
        if Height == 0:

    El siguiente paso es hacer una llamada a la función glViewPort(Width, Height). Esta función definida en el API de OpenGl – observemos el gl que antecede al nombre de la función – lo que hace es crear la equivalencia más fina posible entre las coordenadas del hardware gráfico y las coordenadas de la ventana.

    def ReSizeGLScene(Width, Height):
        if Height == 0:

    A continuación lo que hay que hacer es redefinir la matriz de projección definida en la función de inicialización a GL_PROJECTION ya que hemos de aplicar las operaciones a la pila de proyección.

    def ReSizeGLScene(Width, Height):
        if Height == 0:
           Height = 1
        glViewPort(0, 0, Width, Height)
        glMatrixMode(GL_PROJECTION)

    Reinicializamos la matriz de proyección para vaciar la pila de proyección

    def ReSizeGLScene(Width, Height):
        if Height == 0:
           Height = 1
        glViewPort(0, 0, Width, Height)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
    

    A continuación hemos de recalcular el Aspect Radio de la ventana. A modo de resumen y sin entrar demasiado en detalle el Aspect Radio (o relación de aspecto) vendría a ser la relación del ancho de la ventana con su altura.

    def ReSizeGLScene(Width, Height):
        if Height == 0:
           Height = 1
        glViewPort(0, 0, Width, Height)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)

    Y volvemos a restaurar el modo a GL_MODELVIEW una vez ya hechas las operaciones de redimensionado.

    def ReSizeGLScene(Width, Height):
        if Height == 0:
           Height = 1
        glViewPort(0, 0, Width, Height)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)
        glMatrixMode(GL_MODELVIEW)

    En el próximo paso lo que haremos es configurar todo lo relativo a OpenGL. Configuraremos qué color utilizará el programa para limpiar la pantalla, activaremos el buffer de profundidad, … Todas estas rutinas no han de ser llamadas hasta que la ventana OpenGL sea creada. El procedimiento devolverá un valor pero por ahora no nos hemos de preocupar del valor devuelto ya que la inicialización de OpenGL que haremos no es lo suficientemente compleja como para ello.

    def InitGL(Width, Height):

    Lo primero que hemos hecho es declarar el método que se encargará de la inicialización de OpenGL. Como parámetro le pasaremos la altura y la anchura ya que algunas de las rutinas que utilizará necesitarán estos parámetros.

    def InitGL(Width, Height):
       glClearColor(0.0, 0.0, 0.0, 0.0)

    La función glClearColor define con qué color se pintará el fondo de la ventana cuando se haya de limpiar. Limpiar en este sentido corresponde a eliminar objetos que hayan en la misma.

    def InitGL(Width, Height):
       glClearColor(0.0, 0.0, 0.0, 0.0)
       glClearDepht(1.0)

    Para entender lo que hace la función glClearDepth hemos de entender qué carai es el buffer de profundidad (o Depth Buffer o z-buffer). Un z-buffer no es más que la parte de la memoria de un adaptador de vídeo encargada de gestionar las coordenadas de profundidad de imágenes en 3D. El z-buffer es la solución a la problemática de la visibilidad. El problema consiste en decidir qué elementos de una escena renderizada son visibles y cuáles ocultos.
    Básicamente el funcionamiento sería el siguiente: cuando un objeto se renderiza, la profundidad del píxel generado se almacena en un buffer de datos. Este buffer está construído en forma de matriz (coordenadas x e y) con un elemento por cada píxel en la pantalla. Si hay algún elemento en la escena que se ha de mostrar en el mismo píxel, se calcula la profundidad y se compara con la almacenada en la matriz y el objeto que tenga profundidad más cercana al observador será la que se muestre. La profundidad elegida se guardará en el z-buffer reemplazando a la que había. Esta forma de emular la profundidad acabará creando una verdadera percepción de profundidad natural.
    Una vez definido lo que es el buffer de profundidad podemos entender la lógica de eliminar los valores del mismo en la inicialización de una nueva escena OpenGl.

    def InitGL(Width, Height):
       glClearColor(0.0, 0.0, 0.0, 0.0)
       glClearDepth(1.0)
       glDepthFunc(GL_LESS)
       glEnable(GL_DEPTH_TEST)

    Estas dos rutinas habilitan el testeo del buffer de profundidad de nuestra tarjeta de vídeo. La primera de ellas selecciona qué tipo de testeo se va a hacer y la segunda habilita el testeo del buffer.

    def InitGL(Width, Height):
       glClearColor(0.0, 0.0, 0.0, 0.0)
       glClearDepht(1.0)
       glDepthFunc(GL_LESS)
       glEnable(GL_DEPTH_TEST)
       glShadeModel(GL_SMOOTH)

    La función glShadeModel habilita el llamado Smooth Shading. El método del Smooth Shading mezcla los colores a través de un polígono de forma agradable. De esta manera se eliminan las diferencias de color y luz entre polígonos de un mismo objeto.

    def InitGL(Width, Height):
       glClearColor(0.0, 0.0, 0.0, 0.0)
       glClearDepht(1.0)
       glDepthFunc(GL_LESS)
       glEnable(GL_DEPTH_TEST)
       glShadeModel(GL_SMOOTH)
       glMatrixMode(GL_PROJECTION)
       glLoadIdentity()

    Como ya hemos comentado anteriormente, estas dos funciones lo que hacen es definir que tipo de matriz utilizar, en este caso utilizaremos la matriz de proyección. Una vez seleccionada lo que hace es inicializarla.

    def InitGL(Width, Height):
       glClearColor(0.0, 0.0, 0.0, 0.0)
       glClearDepht(1.0)
       glDepthFunc(GL_LESS)
       glEnable(GL_DEPTH_TEST)
       glShadeModel(GL_SMOOTH)
       glMatrixMode(GL_PROJECTION)
       glLoadIdentity()
       gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)

    Necesitamos calcular la relación de aspecto – recordemos que es la relación del alto con el ancho- de la ventana para dibujar de forma adecuada los objetos en la ventana.

    def InitGL(Width, Height):
       glClearColor(0.0, 0.0, 0.0, 0.0)
       glClearDepht(1.0)
       glDepthFunc(GL_LESS)
       glEnable(GL_DEPTH_TEST)
       glShadeModel(GL_SMOOTH)
       glMatrixMode(GL_PROJECTION)
       glLoadIdentity()
       gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0)
       glMatrixMode(GL_MODELVIEW)

    Una vez dimendionada la ventana OpenGL, volvemos a cambiar a la matriz de modelo ya que no necesitamos hacer más operaciones sobre la matriz de proyección.
    En el próximo bloque definiremos la sección principal de pintado. Esta sección es donde va todo el código de dibujado. Todo lo que tengamos planeado de dibujar en la pantalla irá en esta sección. Este bloque de código se irá incrementando a lo largo del tutorial cuanto más complejo sea lo que deseamos dibujar. Por ahora todo lo que haremos es dibujar una pantalla negra. Para cosas más complejas habrá que esperar a las próximas publicaciones del tutorial. :)
    Si todo va bien, la función devuelve un TRUE, en caso contrario devuelve un FALSE y para la ejecución del programa.

    def DrawGLScene():

    Lo primero que hemos hecho es definir la función. Es una función que no recibe ningún tipo de parámetro.

    def DrawGLScene():
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
         glLoadIdentity()

    A continuación lo que hemos de hacer es eliminar el contenido del buffer de profundidad y borrar el contenido de la pantalla.

    def DrawGLScene():
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
         glLoadIdentity()
         glutSwapBuffers()

    En la aplicación, para evitar problemas en el refresco de la pantalla utilizamos los llamados Double Buffers. Un Double Buffer lo que hace es pintar primero la información gráfica en un buffer y cuando ha acabado de pintar un objeto completo volcarlo a la pantalla. Con ésto lo que evitamos es ver parpadeos y representaciones extrañas debidas al refresco cuando un objeto está a medio pintar (hay objetos que pueden tardar un intervalo de tiempo relativamente largo). Con esta función lo que hacemos es eliminar el contenido y activar el double buffer

    def keyPressed(*args):

    La sección código que hemos definido se llamará cada vez que una tecla sea pulsada. Cuando una tecla se pulsa, se comprueba si es ESCAPE, si lo es va finalizando el RC, DC y, finalmente el manejador de ventanas.

    def keyPressed(*args):
         if args[0] == ESCAPE:
    	glutDestroyWindow(window)
    	sys.exit()

    La primera de las llamadas destruye, como hemos comentado, la ventana OpenGL y, una vez eliminada la ventana, finaliza la ejecución del programa.

    A continuación continuamos con la implementación del constructor de la clase.

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())

    En este primer ejemplo, le pasamos una lista vacía a glutInit. No obstante, no siempre será así.

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)

    Esta última función lo que hace es inicializar el modo gráfico de la ventana. Para una inicialización completa del modo gráfico se han de especificar como mínimo el tipo de modo gráfico, si se activa el double buffer, el color RGBA, los componentes de transparencia soportados y la activación del buffer de profundidad.

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(640, 480)

    Una vez seleccionado el modo gráfico lo que hemos de hacer es es inicializar el tamaño de la ventana. En nuestro caso la inicializaremos a un tamaño de 640×480

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(640, 480)
        glutInitWindowPosition(0, 0)

    La ventana toma como coordenadas iniciales (las de referencia) el píxel de más arriba a la izquierda.

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(640, 480)
        glutInitWindowPosition(0, 0)
        window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ")

    Lo que hemos hecho con esta última función es crear la ventana asignándole un texto de título.

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(640, 480)
        glutInitWindowPosition(0, 0)
        window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ")
        glutDisplayFunc(DrawGLScene)

    Hemos de registrar la función de pintado con GLUT, esta función ha de capturar eventos y responder a los mismos

       #glutFullScreen()
    

    Si quisieramos crear una ventana a pantalla completa descomentariamos la función glutFullScreen().

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())     glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(640, 480)
        glutInitWindowPosition(0, 0)
        window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ")
        glutDisplayFunc(DrawGLScene)
        glutIdleFunc(DrawGLScene)

    En los tiempos de silencio (cuando no estamos haciendo nada) el manejador aprobechará para repintar la pantalla.

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())     glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(640, 480)
        glutInitWindowPosition(0, 0)
        window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ")
        glutDisplayFunc(DrawGLScene)
        glutIdleFunc(DrawGLScene)
        glutReshapeFunc(ReSizeGLScene)

    Hemos de registrar la función que es llamada cuando la ventana es redimensionada.

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())     glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(640, 480)
        glutInitWindowPosition(0, 0)
        window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ")
        glutDisplayFunc(DrawGLScene)
        glutIdleFunc(DrawGLScene)
        glutReshapeFunc(ReSizeGLScene)
        glutKeyboardFunc(keyPressed)

    Ídem que la anterior pero cuando una tecla es presionada.

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(640, 480)
        glutInitWindowPosition(0, 0)
        window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ")
        glutDisplayFunc(DrawGLScene)
        glutIdleFunc(DrawGLScene)
        glutReshapeFunc(ReSizeGLScene)
        glutKeyboardFunc(keyPressed)
        InitGL(640, 480)

    Inicializamos nuestra ventana openGL.

    def __init__(self):
        self.window=0
        self.ESCAPE='\O33'
        glutInit(())
        glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH)
        glutInitWindowSize(640, 480)
        glutInitWindowPosition(0, 0)
        window = glutCreateWindow("Nuestro primer tutorial en OpenGL basándonos en Nehe '99 ")
        glutDisplayFunc(DrawGLScene)
        glutIdleFunc(DrawGLScene)
        glutReshapeFunc(ReSizeGLScene)
        glutKeyboardFunc(keyPressed)
        InitGL(640, 480)
        glutMainLoop()

    Y iniciamos el escuchador de eventos (Event Processing Engine).

    Una vez programado esto, simplemente hemos de crear el objeto Ventana como en cualquier otro programa Python, dar privilegios de ejecución al script y se nos abrirá una ventana donde no habrá nada. No obstante, este tutorial inicial ha servido para establecer todo lo necesario que necesita -como mínimo- una aplicación OpenGL para funcionar.

    ¡Nos vemos en la próxima entrega!

    En el fragmento de código anterior lo que hacemos es importar las funciones de las librerías de pyOpenGL. Concretamente GL,GLU y GLUT. También importamos el módulo sys para trabajar con algunas llamadas al sistema.

    Antes de continuar me gustaría definir unos cuantos conceptos que no se ven explícitos en el código Python pero que es bueno comprenderlos ya que en otros lenguajes es necesario definirlos de forma explícita.

    El primero de ellos es el Rendering Context (RC) o el Contexto de Renderizado. Todo programa en OpenGL está enlazado a un RC que no es otra cosa que una capa intermedia que enlaza las llamadas a OpenGL con el Device Context (DC) o Contexto Hardware. Para que el tinglado funcione también es necesario definir (explícita o implicitamente) el Contexto Hardware. El Contexto Hardware no es más que otra capa que enlaza la ventana que el llamado GDI (Graphics Device Interface). Por tanto, el RC lo que hace es conectar OpenGL con el DC para tratar lo que en la ventana se haya de visualizar.

    Una vez definidos estos conceptos ya podemos continuar con nuestro código. :) Una vez importadas las librerías necesarias hemos de definir la clase ventana.

    Tutorial OpenGL II

    No comments »

    Como comenté en el post previo, vamos a empezar a definir la tecnología que utilizaremos.

    El lenguaje de programación por excelencia en este tipo de aplicaciones viene a ser C/C++. No obstante doy por supuesto que cualquiera que se hace llamar informático domina ya bastante bien este par de lenguajes. Lo que no dominames es un “nuevo” lenguaje emergente llamado Python. Por ello, orientaré esta serie de tutoriales a este lenguaje. No lo he comentado antes pero TODO absolutamente TODO lo basaré en software libre. Con ello quiero decir que la plataforma en la que trabajo es una Gentoo Linux. No debería haber ninguna diferencia respecto a otras plataformas en las que sigais lo que aquí se dice. Únicamente necesitareis el intérprete Python y sus respectivos módulos, y las versiones adecuadas de las diferentes librerías que utilizaremos para la plataforma que esteis utilizando. Una vez dicho ésto, paso a describir la tecnología en la que nos basaremos:

    • Python: Lenguaje de programación de alto nivel diseñado por Guido van Rossum a principios de los años 90. No obstante actualmente es un lenguaje libre que se administra desde la Python Software Foundation. Los programas escritos en Python facilitan la reutilización de código ya que proporciona gran cantidad de módulos estándard. Ésto facilita la tarea al programador el cual únicamente deberá centrarse en la lógica de su problema y no en los problemas que pueda dar el lenguaje. Python es un lenguaje interpretado y multiparadigma. Ésto quiere decir que el código no se compila ni se enlaza, únicamente se interpreta y se ejecuta. Al ser así, el programador se ahorra un tiempo más que considerable en el desarrollo del programa. La propiedad multiparadigma del lenguaje significa que no fuerza a programar en un estilo concreto si no que permite diferentes estilos y será el programador el que elija el que mejor se adecue a su forma de programar. Los paradigmas estándard de Python son la programación orientada a objetos, programación estructurada y la programación funcional. No obstante, mediante el uso de módulos podemos añadirle otros paradigmas de programación. Hay otro concepto importante a definir, no es otro que lo “pythónico”. Un código es pythónico si sigue la filosofía de legibilidad y transparencia. Contrariamente, un código ofuscado y opaca se le llama “no pythónico”. Esta filosofía queda bastante clara en el Zen de Python. A continuación colocaremos dos códigos para comprender la facilidad respecto a C de Python:
    • Nos disponemos a programar el cálculo de la serie de Fibonacci en C:

      long fibonacci(int n)

      {
          if (1 == n || 2 == n)
          {
             return 1;
          }
          else
          {
              return (fibonacci(n-1) + fibonacci(n-2));
          }
      }
      

    Podemos hacer un código no pythónico haciendo una simple conversión:

    def fibonacci(n):
        if x == 0 || 2==n:
           return 1
       else:
           return (fibonacci(n-1)+fibonacci(n-2))
    
    

    O por el contrario crear un código pythónico:

    O por el contrario crear un código pythónico:

    import operator
    factorial = lambda x: reduce(operator.__mul__, range(1, x))
    
    

    Básicamente estas son las herramientas que utilizaremos. Las primeras a modo de introducción para irnos haciendo a la forma de trabajar y, una vez ya dominemos las bases nos olvidaremos del árduo trabajo de llamar directamente a físicas y contextos gráficos y lo haremos todo a partir de una capa de abstracción que nos facilita Crystal Space. Seguramente me habré dejado alguna herramienta, cuando me venga a la cabeza, es decir, cuando la necesitemos, ya la comentaré.

    • OpenGL: OpenGL es una especificación estándard que define una API multilenguaje y multiplataforma para escribir aplicaciones que produzcan gráficos 2D y 3D. Es una librería libre teniendo en cuenta su política de licencias. OpenGL se utiliza en CAD o en realidad virtual, también en representación científica o en simulaciones de vuelo o, como no, en el desarrollo de videojuegos en el que compite directamente con Direct3D de M$ Windows. En la actualidad OpenGL tiene dos propósitos principales: el primero, ocultar la complejidad de la interfaz con las diferentes tarjetas gráficas, presentando al programador una API única y uniforme; y la segunda, ocultar las diferentes capacidades de las diversas plataformas hardware, requiriendo que todas las implementaciones soporten el conjunto completo de características OpenGL. Basándonos en esta API nosotros utilizaremos un par de bibliotecas externas para desarrollar nuestros gráficos iniciales. Estas bibliotecas serán GLU y GLUT. La primera ofrece funciones ampliadas para la renderización de gráficos y la segunda para la interacción con teclado y ratón.
    • PyOpenGL: Ahora toca conectar el lenguaje de programación que utilizaremos, es decir, Python; y las librerías de dibujo gráfico, es decir OpenGL y sus extensiones GLU y GLUT. Para ello instalaremos PyOpenGl que no es más que un módulo para Python que enlaza con las librerías anteriormente definidas. Es una libería completamente libre basada en la licencia BSD. Sólo comentar que es aconsejable instalar los siguientes módulos para trabajar de forma cómoda con PyOpenGL: Doy por sentado que las librerías OpenGL, GLU, GLUT y GLE estás instaladas correctamente en el sistema. Además de éstas y del intérprete Python más reciente hemos de instalar Numpy para cálculo numérico complejo, PIL para el tratamiento fácil de imágenes, PyDisptcher es un módulo de control de eventos, SimpleParse es un módulo para parsear documentos basados en el formato VRML97, TTFQuery y FontTools para el tratamiento de fuentes, PyGame para añadir funcionalidades de contextos gráficos y de ventana y win32all.
    • Open Dinamics Engine: Open Dinamics Engine o como se le suele conocer, ODE, es una librería de altas características y libre para simular la dinámica de cuerpos rígidos. Las características de la misma están completas, estables y maduras. Es fácil acceder a ellas mediante lenguajes C/C++. Su uso suele ser en detección de colisiones con fricción. Suele ser útil para la simulación de coches, objetos en entornos de realidad virtual o criaturas virtuales. Es actualmente una de las más usadas en entornos de juegos, herramientas 3D y herramientas de simulación.
    • Crystal Space: Crystal Space es una plataforma de desarrollo de gráficos 3D en tiempo real, concretamente orientada a juegos. Tiene una capa de abstracción a OpenGL desarrollada por Nvidia llamada Cg y, otra a ODE, entre muchas otras características como audio o vídeo. Crystal Space tiene el componente que crea el concepto de entidad al que se le pueden aplicar características físicas concretas. Esta librería es parte del Engine y se llama CEL. Este componente es multilenguaje y soporta bastante bien Python.

    Sin más, ¡comencemos!