Andengine, Box2D y SVG: cómo guardar el contorno en el propio SVG.

Hoy un regalito para aquellos programadores que utilizan Andengine, ese gran motor para gráficos bidimensionales y gratuito gracias a Nicolas Gramlich. Nunca le daré las gracias lo suficiente.


Andengine tiene una extensión Box2D que permite hacer que los elementos tengan propiedades como gravedad, fricción o elasticidad, como si de objetos reales se tratara. 

A un objeto se les llama sprite y para que tenga propiedades físicas debe asociarse a un body que es en realidad el que tiene las propiedades y mueve el sprite consigo.




El body tiene un tamaño y forma independiente del sprite y hay que definírselo. Puede tener forma redonda, cuadrada, o poligonal de hasta 8 vértices y obligatoriamente convexa. En los ejemplos se advierte de que las corrdenadas de los vértices deben estar centradas en el origen de coordenadas (0,0). 
Lo que no es fácil encontrar es que, si se utiliza una forma poligonal, la lista de los vértices debe indicarse en orden horario o de lo contrario se encuentra uno con el error "Fatal signal 11" y el programa termina.

Si un sprite con su body actúa en la pantalla de forma extraña, como quedándose atascado, revisa que realmente su borde sea convexo. Recordemos: una pelota es convexa y una olla es cóncava.


Andengine tiene también una extensión para poder utilizar gráficos SVG. Tampoco me cansaré de bendecir a Inkscape y el formato SVG. Así se pueden tener los grádicos vectoriales y convertirlos a mapa de bits (rasterizar, lo llaman) en el tamaño que mejor convenga y así tener siempre una calidad de imagen perfecta.

Volviendo a Box2D, resulta complicada la gestión de los bordes de los body asignados a los sprites. Uno puede crear uno a mano, o dos, pero como haya que cambiar estos bordes... ya empieza a ser engorrosa la actualización de archivos y código.

Para crear la lista de vértices de los bordes hay quien utiliza programas como Andengine-vertex-helper que son pequeños editores donde se carga el sprite y luego se dibuja sobre él el borde que se utilizará en el body. No está mal si el sprite es un dibujo en mapa de bits. La lista de vértices resultante se puede guardar en un archivo o directamente ponerlo en el código... no excesivamente práctico.

Pero SVG y Box2D tienen algo muy interesante cuando se usan juntos y es que en el mismo archivo SVG de la imagen se puede dibujar un polígono, ponerle un nombre, hacerlo transparente y luego  utilizarlo para definir el borde del body.

Si se necesita retocar el polígono se puede seleccionar por su nombre accediendo a la lista de objetos del dibujo SVG:


Gracias infinitas a que el formato SVG es un XML en el que los polígonos se definen con la etiqueta "path", el nombre en el atributo id y los puntos del polígono en el atributo d.
Para interpretar las coordenadas que hay en el atributo d sólo hay que tener en cuenta dos cosas:
1.- Si empieza por "M" las coordenadas son absolutas. Si empieza por "m" la primera coordenada es absoluta pero las siguientes son relativas a la previa.
2.- Si en medio de la lista de coordenadas aparece una "L" a partir de ahí las coordenadas son absolutas y si aparece una "l" entonces a partir de ahí las coordenadas serán relativas a la coordenada previa.

Si al final hay una "Z" o una "z" simplemente significa que el polígono es cerrado, que el último punto se une con el primero, pero para el borde del body de Box2D esto se ignora.

Por ejemplo:

 <path
     style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0"
     d="M 29.741167,5.0542306 125.52178,90.999178 127.62941,98.258833 90.394412,119.80362 48.241578,110.90468 16.392769,86.783894 3.0443715,47.441248 18.266229,1.0731295"
     id="BorderShape"
     inkscape:connector-curvature="0"
     inkscape:label="#path4361"
     sodipodi:nodetypes="cccccccc" />

Para leer el archivo SVG se puede utilizar XmlPullParser.

Ya sólo queda el detalle de ordenar las coordenadas en sentido horario. Ingenuo que es uno busqué si alguien ya había hecho esta parte: ordenar una lista de vértices en sentido horario.
Y sí, había quien lo había hecho y de una forma muy aceptable: usando Collections.sort y un Comparator en el que se calculaba la tangente de los dos puntos respecto a un punto origen, en nuestro caso (0,0). Todo muy apetecible.

Excepto por un error:


Mal, mal, mal. Esa comparación está mal: hay que usar Double.compare(a,b) y luego evaluar el resultado. De lo contrario nos encontraremos con sorpresas como que 0,4 y 2.0 resulta que "son iguales".

Mucho mejor esta versión:



Estos vértices se pueden leer una vez y luego utilizarlos cada vez que se necesite crear un sprite, reescalándolos al tamaño de ese sprite.

Espero que esto le ahorre tiempo a quien lo lea.