Curso OpenGL ES – Matemáticas (III): Transformaciones

07 Feb 2012 | OpenGL | 17 Comments
cubos3d

Manipulación de objetos y entidades

Cuando creamos una escena y mostramos objetos en el Espacio 2D/3D, lo habitual es que queramos darles vida o manipularlos de algún modo. Para ello contamos con otra "herramienta" matemática que nos va a dar solución a este problema: se trata de las Transformaciones.

Usaremos algunos de los conceptos vistos en capítulos anteriores: Espacios, Coordenadas, Vértices, Trigonometría, Vectores y Matrices. Por lo tanto, si aún no lo has hecho, te recomiendo que les eches un vistazo antes de continuar.

Las Transformaciones en el Espacio

Antes de comenzar con las transformaciones creo necesario resaltar que, al igual que con los conceptos vistos hasta el momento, vamos a continuar con nuestro enfoque de intentar evitar exposiciones y razonamientos que requieran una base de conocimientos matemáticos avanzados. Dada la finalidad y ámbito de aplicación de este cursillo, no nos es necesario profundizar más de lo estrictamente necesario.

Dicho esto, pongámonos manos a la obra.

Como recordarás, en el capítulo de Fundamentos Básicos hablamos de los "materiales de construcción" que utilizaremos para crear objetos y entidades en el Espacio. El más básico es el Punto (vertex), el cual suministraremos a OpenGL en forma de arrays. Partiendo de esos arrays de vértices se formarán los triángulos, que a su vez darán forma a las superficies y objetos en general.

Recuerda que en el ámbito de los gráficos generados por computador, cualquier superficie puede descomponerse en triángulos, y por tanto ser representada como una colección de los mismos. Incluso las superficies paramétricas que veremos en un futuro capítulo.

Tarde o temprano llegará un momento en el que necesitaremos manipular nuestros objetos, de forma que podamos:

  • Posicionarlos, moverlos de un lugar a otro.
  • Girarlos, usando como referencia uno o varios ejes del Sistema.
  • Modificar su tamaño manteniendo su forma.
  • Una combinación de lo anterior.

Dado que cualquier objeto o entidad en un Espacio puede describirse de forma matemática, nos enfrentamos a un problema que -afortunadamente- tiene fácil solución: podemos utilizar unas determinadas ecuaciones de transformación o, simplemente, "Transformaciones" para manipular dicho objeto dentro del espacio en el cual "vive". Para ello, tan sólo tendríamos que aplicar dichas ecuaciones a los puntos (vértices) que definen el objeto.

En base a lo anterior, es obvio que existirán tres tipos básicos de Transformación:

  • Traslación
  • Rotación (giro)
  • Escalado

Estas transformaciones funcionarán en cualquier espacio n-dimensional. Nosotros vamos a centrarnos en los Espacios 2D y 3D, que son los únicos que nos interesan. Y dado que siempre es mejor empezar con lo más sencillo, comenzaremos explicando las transformaciones en el Plano (Espacio 2D).

Transformaciones en el Espacio 2D

Vamos a aprovechar para ir introduciendo de forma progresiva nueva terminología, la cual usaremos en nuestro trabajo con OpenGL. En el caso que nos ocupa, al hablar de las transformaciones, es un buen momento para definir los términos "Primitivas", la "Geometría" y la "Topología".

  • En OpenGL, la forma de una entidad u objeto se denomina Geometría del objeto.
  • Esa forma o Geometría está constituída a su vez por una serie de formas o entidades básicas, denominadas Primitivas. Por ejemplo, los Puntos, Líneas y Triángulos son Primitivas.
  • La manera en que esas Primitivas definen la Geometría del objeto, o lo que es lo mismo: la forma en que se unen para construir el objeto, se denomina Topología.

La manipulación de un objeto, definido por su Geometría, se realiza en base a sus Primitivas. Para hacer más sencilla la explicación de las principales transformaciones que podemos aplicar a un objeto, vamos a usar como base una Primitiva, la más básica: el Punto.

Traslación en el Plano (2D)

Podemos posicionar y mover un objeto de un sitio a otro dentro del Espacio mediante la transformación de Traslación. Como hemos dicho antes, usaremos como ejemplo la primitiva básica: un Punto.

Trasladar un Punto consiste básicamente en leer las coordenadas originales (la posición inicial), para a continuación realizar ciertos cálculos con ellas, y así finalmente obtener unas coordenadas resultantes, las cuales van a definir la nueva ubicación (la posición final). Bastante simple ¿verdad?.

Pongamos un punto A definido por sus coordenadas (x,y). Estas coordenadas definen implícitamente la posición de ese punto en el espacio (un Espacio 2D en este caso). Las ecuaciones que empleamos para su traslación son:

x' = x + tx
y' = y + ty

Siendo:

(x,y) : Coordenadas del Punto en la posición original
(x',y'): Coordenadas del Punto en la posición final
tx = desplazamiento sobre el eje X (en número de unidades)
ty = desplazamiento sobre el eje Y (en número de unidades)

Se ve más claro de forma gráfica, en la Figura 11:

Figura 11: Transformación de Traslación en Espacio 2D

Implementar este sencillo sistema de ecuaciones en código es algo trivial, así que no hace falta desarrollarlo aquí.

Si te fijas bien, verás que la línea que une las coordenadas iniciales (x,y) con las finales (x',y') podría ser un vector, ¿cierto?. En este caso, un vector "desplazado", con origen en (x,y) y cabeza en (x',y'). Mirándolo de esa forma, ¿qué serían tx y ty?. Pues nada menos que las componentes del vector. ¡Vaya!, vamos viendo poco a poco que todo está relacionado. Desarrollaremos y haremos uso de ésto más adelante en el cursillo.

Como ves, la Traslación no parece tener demasiada complicación, ni a nivel teórico ni a nivel de implementación en código. Pero alguien podría decirme ahora...

Oye, muy interesante lo de mover puntos, pero... ¿qué tal si nos dices cómo mover un objeto completo?

Pues es igualmente sencillo: bastaría con aplicar las ecuaciones anteriores a todos y cada uno de los vértices del objeto. Las nuevas coordenadas de dichos vértices definirán el objeto en su nueva posición.

Ten en cuenta que esta operación no modifica la Geometría del objeto en absoluto, siempre y cuando tx y ty sean constantes (mantengan el mismo valor) durante toda la operación.

Rotación en el Plano (2D)

La siguiente Transformación que vamos a ver es la Rotación o Giro de un objeto en base a una referencia, ya sea un eje (3D) o un punto central de giro (2D).

Dado que estamos por el momento tratando transformaciones en el Plano, vamos a considerar la rotación alrededor de un punto de referencia (Espacio 2D).

El problema con esta transformación es que no es tan sencilla de explicar como la anterior, ya que implica algo más de cálculo. Llegados a este punto, se me plantean dos opciones: dar directamente las ecuaciones y su implementación en código, o desarrollar razonadamente el proceso que lleva a esas ecuaciones.

Recordarás que cuando tratamos el tema del cálculo de la inversa de una matriz en el capítulo anterior, opté por pasar por alto el razonamiento y dar directamente el método. El motivo fué que se trataba de una operación algo compleja, y no necesariamente interesante desde el punto de vista de programación gráfica.

En cambio, el tema de la transformación de rotación es diferente, dado que ahora estamos tratando un aspecto que tú, como desarrollador, deberías de conocer. Así pues, vamos con ello...

Debemos dar solución al siguiente problema: Dado un punto P(x,y) en un Plano, queremos aplicarle una rotación tomando como referencia el punto Origen(0,0). La posición Final será el punto P'(x',y'). La rotación o giro que queremos aplicarle viene definida por el ángulo θ, según mostramos en la Figura 12:

Figura 12: El Problema de la Rotación de un Punto P en el Plano.

¿Cómo nos enfrentamos a este problema?. Pues verás, en el capítulo sobre Espacios y Sistemas de Coordenadas, hablamos de los Sistemas de Referencia Absolutos y Relativos. En la escena sólo existe un Sistema Absoluto, aunque pueden existir varios Sistemas Relativos (Locales) definidos en referencia al principal.

Pues bien, dado un objeto cualquiera, por ejemplo un Cubo en un Espacio 3D, podemos construir dicho objeto en referencia al sistema principal (absoluto). Pero también podemos crear un Sistema Relativo (local), con origen en cualquier punto del espacio, sobre el cual construir ese Cubo. Para acceder a las coordenadas de los vértices del cubo, podemos hacerlo desde el sistema absoluto o desde el sistema local. Ambas formas son válidas, aunque las coordenadas obviamente serán distintas.

El Origen de ese Sistema Local podría ser, por ejemplo, el centro geométrico del propio cubo, o el punto que tú quieras.

Y aquí viene lo interesante: Si queremos aplicar una rotación al cubo, una forma estupenda de hacerlo sería rotando el propio sistema de coordenadas locales, sobre el que está construído el cubo, dado que al rotar los ejes de referencia, todas las coordenadas que definen los vértices del objeto rotarían igualmente, ¿verdad?.

Bien, pues esa es la idea general que vamos a usar para dar solución al problema de la rotación. Y la usaremos de la forma siguiente: En lugar de girar el propio punto P, vamos a girar el sistema de ejes de coordenadas, ya que a efecto de conseguir nuestras ecuaciones de transformación, es lo mismo.

Pero antes, hagamos un inciso "trigonométrico", dado que necesitamos conocer la forma de obtener las coordenadas de un punto en función del ángulo que forma con los ejes. Para ello, recuperamos nuestro ya conocido triángulo recto:

Figura 13: Necesitamos definir (x,y) en función de θ.

Recordando a Pitágoras, junto con lo visto en el capítulo sobre trigonometría, podemos ver que:

Es importante que recuerdes que:

x = h·cos θ
y = h·sin θ

Si ahora asumimos, por conveniencia para nuestros cálculos, que la hipotenusa del triángulo tiene una longitud de 1, (h = 1), obtendríamos:

sin θ = y
cos θ = x

Lo cual se cumpliría también en el caso de un punto P situado en cualquiera de los otros tres cuadrantes del plano, aunque con signos diferentes. No vamos a deducirlo aquí, pero puedes hacerlo tú si quieres, siguiendo el mismo razonamiento.

Bien, ya sabemos como referenciar unas coordenadas de cierto punto en función del ángulo θ. Ahora estamos listos para continuar con nuestra búsqueda de la solución al problema de la rotación.

Recuerda que nuestra forma de afrontar el problema era girando el sistema de ejes en lugar del propio punto. De esa forma, vamos a tratar de buscar la forma de poder referenciar el punto P en base a dos sistemas distintos: uno fijo (el que correspondería a la posición inicial de P) y uno girado un cierto ángulo. Fíjate en la figura 14:

Figura 14: Buscando la solución al problema de la rotación.

En la cual:

  • El sistema de referencia X | Y (dibujado en negro) es el sistema fijo.
  • El sistema X' | Y' (en azul), es el sistema al que hemos aplicado la rotación.
  • El ángulo de rotación aplicada es θ.
  • P(x,y) es el punto referenciado en base al sistema fijo.
  • P'(x',y') es el mismo punto anterior, referenciado en base al sistema girado.

Además, existen dos triángulos rectos que vamos a utilizar para nuestro razonamiento:

  • O A P: triángulo sobre el sistema fijo.
  • O A' P: triángulo sobre el sistema girado.
  • h es la hipotenusa común a ambos triángulos.
  • t es el ángulo de la hipotenusa sobre el eje X girado.
  • t + θ es el ángulo de la hipotenusa sobre el eje X fijo.

Por todo lo visto anteriormente, podemos obtener P(x,y) fijándonos en el triángulo OAP:

x = h·cos(t + θ) = h·cos t·cos θ - h·sen t·sen θ     (1)
y = h·sen(t + θ) = h·sen t·cos θ + h·cos t·sen θ     (2)

Ahora bien, hay que intentar relacionar de alguna forma (x,y) con (x',y'). Para ello, si ahora nos fijamos en el triángulo OA'P, podemos ver que (x',y') serían:

x' = h·cos t
y' = h·sen t

¡Ya tenemos la solución!, basta con sustituir las expresiones anteriores en las ecuaciones (1) y (2), con lo que finalmente obtenemos:

x = x'·cos θ - y'·sen θ    (3)
y = x'·sen θ + y'·cos θ    (4)

Las cuales nos dan la solución al problema.

Ahora bien, las ecuaciones anteriores son adecuadas para una rotación de un punto P en sentido contrario a las agujas del reloj: de P'(x',y') a P(x,y). Fíjate que si vuelves a colocar el sistema "azul" en su posición original -coincidiendo con el sistema "negro"-, verás que el punto P' se desplazaría hacia la derecha y hacia abajo, en el sentido de las agujas (con lo que la acción de rotar el punto desde la posición P' a la posición P equivaldría a ir en contra de las agujas).

Si queremos hacer lo contrario, es decir, definir la posición inicial en P(x,y) y rotar el punto hacia P'(x',y'), en el sentido de las agujas del reloj, necesitamos entonces obtener (x',y').

Jugando con las ecuaciones (3) y (4), podemos solucionarlo fácilmente:

x' =  x·cos θ + y·sen θ    (5)
y' = -x·sen θ + y·cos θ    (6)

Por fin tenemos nuestras ecuaciones de rotación (ya iba siendo hora). Implementarlas en código es trivial: Pongamos que queremos aplicar la rotación a un objeto en el plano, objeto que estará formado por una serie de vértices. Bastará con aplicar las ecuaciones (3) y (4) o (5) y (6) a cada uno de los vértices, dependiendo del sentido de rotación que queramos aplicar.

En realidad, hay una forma más sencilla de hacer todo esto, y es utilizando matrices. Lo veremos más adelante, cuando hablemos de las transformaciones en el Espacio 3D.

Escalado en el Plano 2D

Afortunadamente, y a diferencia de la Rotación, el Escalado es muy sencillo de explicar y fácil de entender.

Aplicar la transformación de Escalado a un objeto es, básicamente, cambiar su tamaño. Hasta el momento estábamos usando la primitiva básica, el Punto, para nuestros ejemplos. Pero ahora nos encontramos con el problema de que el Punto no tiene dimensiones, con lo cual, no se puede hacer más grande o más pequeño.

Por lo tanto, no tiene sentido aplicar el escalado a un Punto. Podemos, no obstante, aplicarlo a una línea, forma u objeto formado por varios puntos o vértices.

Utilizaremos para ello las siguientes ecuaciones:

x' = x · f
y' = y · f

Donde (x,y) son las coordenadas originales de cada vértice, f es el factor de escalado, y (x',y') son las coordenadas finales de cada vértice en el objeto resultante tras aplicar la escala.

Al igual que con la traslación, si queremos mantener inalterada la geometría (forma) del objeto, el factor "f" debe de permanecer constante durante toda la operación.

Fíjate que el escalado resultante dependerá de lo siguiente:

si f > 1.0 : el objeto se hará más grande
si 0.0 < f < 1.0: el objeto se hará más pequeño
si f < 0.0 : el objeto se hará más grande, pero 
	     obtendremos su geometría invertida, a modo de reflejo

Otro aspecto que debemos de tener muy en cuenta cuando usemos el Escalado es que debemos hacerlo siempre en base al sistema de coordenadas locales del objeto, con el Origen situado en el centro geométrico del mismo. De esa forma conseguimos un escalado uniforme sin que el objeto cambie de posición.

Si realizásemos el escalado en referencia al sistema de coordenadas absolutas de la escena, lo que obtendríamos sería un escalado + una traslación. Es decir: el objeto se escalaría correctamente, pero cambiaría de lugar. Y quizás no es eso lo que inicialmente deseábamos.

Si tienes lápiz y papel cuadriculado a mano, puedes comprobar esto por ti mismo. Dibuja un sistema absoluto y un cuadrado, referenciando las coordenadas de sus vértices en base a dicho sistema. Ahora aplica el factor de escalado f = 2 a cada vértice definido por esas coordenadas. Verás que el cuadrado se hace dos veces más grande, pero también se desplaza en la escena.

Pues bien, con todo esto hemos finalizado el estudio de las Transformaciones en el Plano. ¿Qué tal si damos el salto ahora a las tres dimensiones?...

Transformaciones en el Espacio 3D

Todos los conceptos anteriormente explicados tienen validez en el Espacio 3D. Lo que ocurre ahora es que tenemos una dimensión más. Esto complica en cierto modo las ecuaciones pero, antes de que salgas corriendo, te diré que nosotros nos va a dar igual. ¿Por qué? pues porque vamos a usar la herramienta a la que recientemente dedicamos todo un capítulo entero: ¡las matrices!.

En efecto, las matrices van a servirnos para simplificar tremendamente la aplicación de transformaciones en la escena. No sólo eso, sino que además los cálculos se realizarán de forma mucho más eficiente.

El proceso de aplicar transformaciones utilizando matrices es muy sencillo. Recordarás que un vértice A(x, y, z) podemos considerarlo como una pequeña matriz de una fila y tres columnas (o tres filas y una columna, si lo escribes de forma vertical). Pues bien, para aplicar una transformación, tan sólo necesitamos multiplicar el vértice por la correspondiente Matriz de Transformación, que veremos en cada caso.

Recuerda también lo que hablamos en el capítulo de Matrices acerca de las Coordenadas Homogéneas. No vamos a repetirlo aquí, pero resumiremos diciendo que, para aplicar las transformaciones en el espacio 3D necesitamos 4 coordenadas, siendo la cuarta el denominado factor de normalización, y que designaremos como w = 1, para simplificar los cálculos.

Traslación en el Espacio 3D

Análogamente al caso en el espacio 2D, la traslación en un espacio 3D vendría definida como:

x = x + tx
y = y + ty
z = z + tz

Si queremos aplicar una traslación a un Punto A, definido en coordenadas homogéneas como A(x,y,z,1), o lo que es lo mismo en forma matricial: A[x y z 1], sabemos que -de alguna forma- debemos llegar al siguiente resultado:

A[(x+tx) (y+ty) (z+tz) 1]

Como ya sabemos multiplicar matrices, es fácil intuir que la Matriz de Transformación correspondiente a la Traslación en Espacio 3D debería de ser:

    | 1   0   0   0 |
T = | 0   1   0   0 |
    | 0   0   1   0 |
    | tx  ty  tz  1 |

Con lo cual, podríamos realizar la multiplicación y obtener el resultado esperado:

            | 1   0   0   0 |
[x y z 1] · | 0   1   0   0 | = [(x+tx) (y+ty) (z+tz) 1]
            | 0   0   1   0 |
            | tx  ty  tz  1 |

Hemos aplicado la transformación a un Punto, pero ya sabes que podemos aplicarla igualmente a cualquier forma u objeto definida por un conjunto de vértices.

¿Te das cuenta ahora de lo útiles que son las matrices a la hora de aplicar transformaciones?. Una vez conozcas la matriz adecuada para cada caso, todo lo demás será coser y cantar, como quien dice.

Rotación en el Espacio 3D

Tranquilo, esta vez acabaremos antes que en el caso de la rotación 2D, pues ya conocemos el mecanismo.

La rotación en el Espacio 3D podemos realizarla en base a tres ejes: X, Y, Z, o una combinación de los mismos. Te mostraré a continuación las matrices de transformación para cada caso: Rotación sobre el eje X (Rx), sobre el eje Y (Ry) y sobre el eje Z (Rz).

     |   1      0       0      0 |
     |   0    COS θ   SIN θ    0 |
Rx = |   0   -SIN θ   COS θ    0 |
     |   0      0       0      1 |

     | COS θ    0    -SIN θ    0 |
Ry = |   0      0       0      0 |
     | SIN θ    0     COS θ    0 |
     |   0      0       0      1 |

     | COS θ  SIN θ     0      0 |
Rz = |-SIN θ  COS θ     0      0 |
     |   0      0       1      0 |
     |   0      0       0      1 |

La transformación se aplica, al igual que en el caso de la Traslación, multiplicando el vértice (o cada uno de los vértices del objeto) por la matriz de transformación adecuada: Rx, Ry o Rz.

¿Cómo aplicamos una rotación sobre varios ejes, para obtener movimientos complejos?. Pues muy sencillo: a través de la concatenación de matrices, concepto que vimos en el capítulo dedicado a las mismas. Con ello obtenemos una matriz final de transformación, que será la que usaremos en la operación.

Escalado en el Espacio 3D

Vamos con la última transformación. Recuerda lo comentado en el caso del espacio 2D, acerca de la necesidad de tener en cuenta las diferencias entre sistemas de coordenadas absoluto y relativo cuando realicemos esta operación. La matriz de escalado será:

    | fx  0   0   0 |
S = | 0   fy  0   0 |
    | 0   0   fz  0 |
    | 0   0   0   1 |

Observa que he utilizado tres factores de escalado: fx, fy, fz. Si deseas un escalado uniforme, que cambie el tamaño del objeto pero no su geometría, asegúrate de que fx = fy = fz. En caso de que no se cumpla eso, conseguirás un escalado que además modificará la forma del objeto. No obstante, en ciertas ocasiones querrás obtener precisamente ese resultado, y por lo tanto emplearás valores diferentes para fx, fy y fz.

Conclusiones

Hemos cubierto el tema de transformaciones en el Espacio, con lo cual damos por concluída la Parte I de este cursillo de Programación Gráfica 2D/3D, dedicada a los fundamentos matemáticos. ¡Ya iba siendo hora!.

Quedan por ver ciertos conceptos avanzados, tales como las Proyecciones, el Clipping, Teoría de Luces y las técnicas de eliminación de superficies ocultas. Todos ellos tienen su base matemática, pero creo que es mejor entrar ya directamente con OpenGL y dejar la explicación de esos conceptos para más adelante, para el momento en que nos sean realmente necesarios.

La peor parte ya ha pasado. Ahora comienza lo verdaderamente divertido:

¡Empezaremos con OpenGL en el próximo capítulo!.

Back to the Blog

17 Comments

  1. Muchas gracias!!!! muy buen contenido y de gran ayuda, esperando ansioso siguiente entrega

    • Gracias por tu comentario.

      Ultimamente he dejado el curso en standby dado que estoy bastante liado con algunos proyectos, pero espero poder retomarlo en breve con más capítulos.

      Saludos

  2. Hola, un capítulo muy interesante. Muchas gracias por tu trabajo y quedo a la espera de los próximos artículos

  3. muchas gracias por toda la informacion espero que sea pronto los siguientes temas saludos…

  4. Hola enhorabuena por el curso!! cuando vas a continuar con las siguientes lecciones??????????????????????

    Un saludo!!!

  5. ¡Me zampé toda la primera parte y me quedé con ganas de más! Ánimo, esto me está descubriendo una utilidad para aquello que en su momento ignoré por no poder darle sentido. Me muero de ganas por seguir.

  6. Que disfrute de Curso!!!, Luís espero que en breve puedas continuar dándonos lecciones, porque es realmente interesantísimo estó que nos explicas, a parte de útil…

    Un saludo,
    Iván J

  7. Gracias por vuestros comentarios, son un buen incentivo para continuar con el curso ;-)

    Os avisaré en cuanto haya nuevo material publicado.

    Saludos

  8. Luis, esto está parado? Espero ansioso que puedas acabar el curso, era de una calidad buenísima.
    Felicitaciones y si consigues sacar tiempo acábalo cuanto antes porque es un gran trabajo.
    Un saludo

  9. Hola Argonix. Sí, el curso ha estado varios meses parado. Avisaré en twitter cuando tenga nuevos capítulos publicados. ¡Muchas gracias por tu interés!.

  10. Estaria genial q se retomara pronto, hasta el momento, nos has dado una buena base, mientras esperamos, iré echando vistazos a otras cosillas, pero esperando noticias…
    Tienes pensado retomar lo pronto? O a aún no sabes cuándo andarás mejor de tiempo?
    Un saludo y GRACIAS de nuevo ;)

  11. Hola Luis:

    Soy de los primeros que seguía el curso, te animo a que sigas haciendo esta labor altruista ;) de hecho me tengo que volver a leer todos los otros capítulos (incluido este).

    Un saludo!

  12. Genial! Mucho mejor explicado que en otros libros! Gracias de nuevo por el inmenso aporte!

  13. Lo menos que podía hacer es comprar tu app y valorarla (ahora sólo me falta el avión, el título…). Espero que continues pronto. Gracias en cualquier caso!

    • Muchas gracias Alberto, es un buen detalle por tu parte! Si quieres darle uso práctico a la app, no necesitas avión ni título: puedes probar el mundillo de la simulación de vuelo :-) En mi otra web, http://www.simcrew.com , tengo publicados varios video-tutoriales para todos los niveles :-)

      Por lo demás, te agradezco el feedback sobre los artículos del curso. Tus comentarios, así como los del resto de personas que han publicado recientemente, son desde luego motivadores para continuar publicando más material. ¡Ojalá tuviese más tiempo para poder dedicar a este tema!

      Un saludo.

  14. La rotación en el espacio 3D se me ha hecho un poco pesada, intentare repasarlo haber si lo entiendo mejor. En general toda la documentación ha sido muy digerible xD

  15. Tal y como me he dado cuenta hace varios meses que la cosa esta bastante parada…espero que este curso no se de por finalizado en este post… mas vale tarde que nunca

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>