[XNA] Animando un robot en 3D

En este artículo continuaré hablando de las animaciones en 3D, basadas en transformaciones de matrices. En esta ocasión animaré un modelo más complejo (y también más guapo, creado por el artista 3D Jordi Gimenez Ruiz -gracias Jordi!!), concretamente un robot. El robot en sí no es todavía un T1000, sus movimientos se limitan a la cabeza, brazos y ruedas con las que se desplaza.

Estos tipos de animaciones son básicamente traslaciones y rotaciones de matrices en las que se basa la posición del modelo en el espacio tridimensional. Lo más importante en estos casos, para empezar, es recordar que estas operaciones son multiplicaciones de matrices, y que el órden en que apliquemos la multiplicación variará el resultado de la animación. Para ello, os paso el enlace a un artículo anterior donde introduzco el tema de las animaciones y explico cuál es el orden en que se deben aplicar siempre las multiplicaciones sobre las matrices, leyendo primero ese artículo se entenderá mejor este tutorial.

Siguiente paso… si queremos animar un Modelo, necesitaremos acceder a sus distintos Mesh, para ello, desde la herramienta de diseño 3D que utilicemos tendremos que poner nombre a los distintos objetos que queramos animar. Lo mejor será usar nombres que recordemos fácilmente, u hacernos un esquema, como este:

esquema 

Después, desde el código podremos aplicar transformaciones a cada uno de estos objetos del modelo de forma individual. Como aplicar las transformaciones a distintos mesh de un modelo puede llegar a complicar el código, hay que pensar bien como hacerlo. En este caso os ofrezco una implementación que creo mejorable, pero que es muy muy sencilla, y sobretodo reutilizable en otros modelos.

esquema

Si os fijáis tenemos una clase abstracta “ModeloExtendido”, que se encarga de pintar un modelo y aplicarle todas las transformaciones que le apliquemos mediante el objeto Transformaciones. El propio ModeloExtendido tendrá una instancia de estas transformaciones, que aplicarán al global del modelo (por ejemplo, si queremos hacer que el robot avance, tendremos que transformarlo “todo”, no las ruedas, la cabeza o cualquier otra parte de él, pero obviamente queremos que la cabeza y demás se muevan con el resto del cuerpo… Veamos en detalle la clase Transformaciones, que como vemos es prácticamente un contenedor de datos:

   1: public class Transformaciones

   2: {

   3:     public Matrix Rotacion { get; set; }

   4:     public Matrix Traslacion { get; set; }

   5:     public Matrix Orbitacion { get; set; }

   6:     public Matrix Escala { get; set; }

   7:  

   8:     public string NombreMesh { get; set; }

   9:  

  10:     /// <summary>

  11:     /// Inicializa las matrices

  12:     /// </summary>

  13:     public Transformaciones(string Mesh) 

  14:     {

  15:         this.NombreMesh = Mesh;

  16:         Rotacion = Matrix.Identity;

  17:         Traslacion = Matrix.Identity;

  18:         Escala = Matrix.Identity;

  19:         Orbitacion = Matrix.Identity;

  20:     }

  21:  

  22:     /// <summary>

  23:     /// Genera la matriz mundo

  24:     /// </summary>

  25:     /// <returns></returns>

  26:     public Matrix ObtenerMundo() 

  27:     {

  28:         return Escala * Rotacion * Orbitacion * Traslacion;

  29:     }

  30: }

Así pues las clases que hereden de ModeloExtendido podrán contener una colección de N transformaciones, en este caso, el objeto RobotPatines tiene las siguientes instancias, que representan cada uno de los mesh que hemos visto en el esquema anterior:

   1: private Transformaciones patines_derecha_delante = new Transformaciones("patins_dreta_davant");

   2: private Transformaciones patines_derecha_detras = new Transformaciones("patins_dreta_darrera");

   3: private Transformaciones patines_izquierda_delante = new Transformaciones("patins_esquerra_davant");

   4: private Transformaciones patines_izquierda_detras = new Transformaciones("patins_esquerra_darrera");

   5: private Transformaciones cabeza = new Transformaciones("cap");

   6: private Transformaciones brazo_derecho = new Transformaciones("brac_dret");

   7: private Transformaciones brazo_izquierdo = new Transformaciones("brac_esquerra");

En el método Update() de RobotPatines hay que leer del teclado y calcular las transformaciones matriciales necesarias. En XNA esto es sencillo! Veamos:

   1: private void LeerInput() 

   2: {

   3:     KeyboardState kbState = Keyboard.GetState();

   4:  

   5:     if (kbState.IsKeyDown(Keys.Up))

   6:     {

   7:         this.patines_derecha_delante.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_RUEDAS);

   8:         this.patines_izquierda_delante.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_RUEDAS);

   9:         this.patines_derecha_detras.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_RUEDAS);

  10:         this.patines_izquierda_detras.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_RUEDAS);

  11:  

  12:         base.transformacionRaiz.Traslacion *= Matrix.CreateTranslation(base.transformacionRaiz.Rotacion.Backward * VELOCIDAD_DESPLAZAMIENTO);

  13:     }

  14:     if (kbState.IsKeyDown(Keys.Down))

  15:     {

  16:         this.patines_derecha_delante.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_RUEDAS);

  17:         this.patines_izquierda_delante.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_RUEDAS);

  18:         this.patines_derecha_detras.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_RUEDAS);

  19:         this.patines_izquierda_detras.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_RUEDAS);

  20:  

  21:         base.transformacionRaiz.Traslacion *= Matrix.CreateTranslation(base.transformacionRaiz.Rotacion.Forward * VELOCIDAD_DESPLAZAMIENTO);

  22:     }

  23:     if (kbState.IsKeyDown(Keys.Left))

  24:     {

  25:         this.patines_derecha_delante.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_RUEDAS);

  26:         this.patines_izquierda_delante.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_RUEDAS);

  27:         this.patines_derecha_detras.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_RUEDAS);

  28:         this.patines_izquierda_detras.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_RUEDAS);

  29:  

  30:         base.transformacionRaiz.Rotacion *= Matrix.CreateRotationY(VELOCIDAD_GIRO);

  31:     }

  32:     if (kbState.IsKeyDown(Keys.Right))

  33:     {

  34:         this.patines_derecha_delante.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_RUEDAS);

  35:         this.patines_izquierda_delante.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_RUEDAS);

  36:         this.patines_derecha_detras.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_RUEDAS);

  37:         this.patines_izquierda_detras.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_RUEDAS);

  38:  

  39:         base.transformacionRaiz.Rotacion *= Matrix.CreateRotationY(-VELOCIDAD_GIRO);

  40:     }

  41:     if (kbState.IsKeyDown(Keys.O))

  42:     {

  43:         this.cabeza.Rotacion *= Matrix.CreateRotationY(-VELOCIDAD_CABEZA);

  44:     }

  45:     if (kbState.IsKeyDown(Keys.P))

  46:     {

  47:         this.cabeza.Rotacion *= Matrix.CreateRotationY(VELOCIDAD_CABEZA);

  48:     }

  49:     if (kbState.IsKeyDown(Keys.U))

  50:     {

  51:         this.brazo_derecho.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_CABEZA);

  52:     }

  53:     if (kbState.IsKeyDown(Keys.I))

  54:     {

  55:         this.brazo_derecho.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_CABEZA);

  56:     }

  57:     if (kbState.IsKeyDown(Keys.J))

  58:     {

  59:         this.brazo_izquierdo.Rotacion *= Matrix.CreateRotationX(VELOCIDAD_CABEZA);

  60:     }

  61:     if (kbState.IsKeyDown(Keys.K))

  62:     {

  63:         this.brazo_izquierdo.Rotacion *= Matrix.CreateRotationX(-VELOCIDAD_CABEZA);

  64:     }

  65: }

Fácil verdad? La única operación que tiene un poco más de “chicha” es la traslación de la instancia de transformaciones transformacionRaiz, que afecta al modelo entero, en concreto a la transformación de Traslación. Esto lo que hará será mover el robot hacia el frente teniendo en cuenta su giro:

   1: base.transformacionRaiz.Traslacion *= Matrix.CreateTranslation(base.transformacionRaiz.Rotacion.Forward * VELOCIDAD_DESPLAZAMIENTO);

Aquí tenemos que dar gracias a los programadores del framework de XNA porque realmente nos están facilitando mucho la vida con el tema de las matrices…

Vale, ahora viene lo más “complicado”, aplicar todas estas transformaciones al modelo… pues no es tan difícil no creáis. He creado una sobrecarga al Draw de la clase base, al cual le envío todas las transformaciones de esta forma (esto es todavía RobotPatines):

   1: public override void Draw(GameTime gameTime, Matrix vista, Matrix proyeccion)

   2: {

   3:     List<Transformaciones> transformaciones = new List<Transformaciones>();

   4:  

   5:     transformaciones.Add(patines_derecha_delante);

   6:     transformaciones.Add(patines_derecha_detras);

   7:     transformaciones.Add(patines_izquierda_delante);

   8:     transformaciones.Add(patines_derecha_delante);

   9:     transformaciones.Add(patines_izquierda_detras);

  10:     transformaciones.Add(cabeza);

  11:     transformaciones.Add(brazo_derecho);

  12:     transformaciones.Add(brazo_izquierdo);

  13:  

  14:     base.Draw(gameTime, vista, proyeccion, transformaciones);

  15: }

Y así es como se dibujaría el modelo en la clase base, en ModeloExtendido:

   1: public virtual void Draw(GameTime gameTime, Matrix vista, Matrix proyeccion, List<Transformaciones> transformacionesHijas) 

   2: {

   3:     modelo.CopyAbsoluteBoneTransformsTo(transformaciones);

   4:  

   5:     foreach(ModelMesh mesh in modelo.Meshes)

   6:     {

   7:         foreach (BasicEffect efecto in mesh.Effects) 

   8:         {

   9:             efecto.EnableDefaultLighting();

  10:  

  11:             if (transformacionesHijas != null && transformacionesHijas.Find(n => n.NombreMesh != string.Empty && n.NombreMesh == mesh.Name) != null)

  12:             {

  13:                 efecto.World = transformacionesHijas.Find(n => n.NombreMesh == mesh.Name).ObtenerMundo() * transformaciones[mesh.ParentBone.Index] * transformacionRaiz.ObtenerMundo();

  14:             }

  15:             else

  16:                 efecto.World = transformaciones[mesh.ParentBone.Index] * transformacionRaiz.ObtenerMundo();

  17:  

  18:             efecto.View = vista;

  19:             efecto.Projection = proyeccion;

  20:         }

  21:  

  22:         mesh.Draw();

  23:     }

  24: }


Observad que utilizo una expresión lambda para aplicar las transformaciones enviadas por parámetro a los mesh que correspondan.

Este es el resultado:

Os dejo el código por si queréis juguetear con él. Enjoy!

2 comentarios en “[XNA] Animando un robot en 3D”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *