Matemáticas y videojuegos (II)

d933ba34a2f3e38112cb5d99960ae1b3.1000x563x1¿Te gustaría saber lo que es Matrix?

Matrix nos rodea. Esta por todas partes, incluso ahora, en este mismo blog, puedes verla si miras una animación o cuando explota algo. Puedes sentirla, cuando vas a cine, cuando programas, cuando juegas con tu consola.

Es la realidad que a sido colocada ante ti para ocultarte que vives una mentira: todo lo que ves no es 3D, y es gracias a Matrix.

¿Qué es Matrix?

Las matrices nos proporcionan una manera compacta y útil de representar sistemas de ecuaciones lineales, algo que usaremos para aplicar transformaciones básicas como traslación, rotación y escalado. Una matriz es básicamente un array de números, formado por n filas y m columnas. Una matriz M podría ser:

 

Math_II_00

 

 

Las filas están marcadas en verde, y las columnas en azul. La primera fila sería (1, 2, 3, 4). La segunda columna (2, 6, 10, 14). Para referirnos a un elemento en concreto lo haremos así Mij, donde i es el indice de la fila y j de la columna del elemento. Así, el elemento M11 es 1 y el M32 sería 10. Podemos codificarla así:

 

public struct Matrix4
{
  public double m11, m12, m13, m14,
                m21, m22, m23, m24,
                m31, m32, m33, m34,
                m41, m42, m43, m44;
}

Por ser la matriz que encaja mejor, como veremos más adelante, con las transformaciones típicas (traslación, rotación y escalado) en un espacio de tres dimensiones, en video-juegos se suelen utilizar matrices de 4 filas y 4 columnas, osea matrices de 4×4 como la del ejemplo anterior.

 

A los elementos de una matriz cuyos indices cumplen que i = j, los llamamos la diagonal de la matriz. En el caso de la matriz del ejemplo, su diagonal seria (1, 6, 11, 16):

 

Math_II_01

Operaciones básicas.

Ya sabemos que aspecto tiene una matriz, es hora de aprender a usarlas. Empezaremos por lo básico, multiplicar por un escalar y suma de matrices. Algunas de estas operaciones te resultarán familiares si vistes el anterior tutorial sobre vectores, y es que un vector no es más que una matriz de n x 1.

 

Dado un escalar a y una matriz M de n x m, multiplicar a por M, o aM es:

 

Math_II_02

public static Matrix4 Multiply(Matrix4 matrix, double factor)
{
  matrix.m11 *= factor;
  matrix.m12 *= factor;
  matrix.m13 *= factor;
  matrix.m14 *= factor;
  matrix.m21 *= factor;
  matrix.m22 *= factor;
  matrix.m23 *= factor;
  matrix.m24 *= factor;
  matrix.m31 *= factor;
  matrix.m32 *= factor;
  matrix.m33 *= factor;
  matrix.m34 *= factor;
  matrix.m41 *= factor;
  matrix.m42 *= factor;
  matrix.m43 *= factor;
  matrix.m44 *= factor;

  return matrix;
}

Osea, multiplicar cada elemento de la matriz por el escalar. Con la matriz del ejemplo anterior, si la multiplicamos por 2, o 2M, el resultado sería:

 

Math_II_03

 

De una manera también muy simular a los vectores, la suma de dos matrices, digamos F y G, es la suma de sus elementos:

 

Math_II_04

public static Matrix4 Add(Matrix4 matrix1, Matrix4 matrix2)
{
  matrix1.m11 += matrix2.m11;
  matrix1.m12 += matrix2.m12;
  matrix1.m13 += matrix2.m13;
  matrix1.m14 += matrix2.m14;
  matrix1.m21 += matrix2.m21;
  matrix1.m22 += matrix2.m22;
  matrix1.m23 += matrix2.m23;
  matrix1.m24 += matrix2.m24;
  matrix1.m31 += matrix2.m31;
  matrix1.m32 += matrix2.m32;
  matrix1.m33 += matrix2.m33;
  matrix1.m34 += matrix2.m34;
  matrix1.m41 += matrix2.m41;
  matrix1.m42 += matrix2.m42;
  matrix1.m43 += matrix2.m43;
  matrix1.m44 += matrix2.m44;

  return matrix1;
}

Si sumamos, por ejemplo, la matriz M con 2M el resultado es:

 

Math_II_05

 

Como puedes imaginar, las operaciones división por un escalar y resta de matrices, son similares a las anteriores.

Multiplicar dos matrices es un poco más complicado, y no siempre vamos a poder hacerlo. Si tenemos dos matrices, A y B, solo vamos a poder multiplicarlas si el número de columnas de A es igual que el número de filas de B. Si A es una matriz de n x m y B de m x p:

 

Math_II_06

 

El resultado, escrito AB, es otra matriz de n x p:

 

Math_II_07

 

Donde cada elemento i, j de AB es el resultado de multiplicar Aik (fila i de A) por Bkj (columna  j de B), para k = 1, 2, …, m, como ilustra este gráfico:

 

Math_II_08

 

 

public static Matrix4 Multiply(Matrix4 matrix1, Matrix4 matrix2)
{
  matrix1.m11 = (((matrix1.m11 * matrix2.m11) + (matrix1.m12 * matrix2.m21)) + (matrix1.m13 * matrix2.m31)) + (matrix1.m14 * matrix2.m41);
  matrix1.m12 = (((matrix1.m11 * matrix2.m12) + (matrix1.m12 * matrix2.m22)) + (matrix1.m13 * matrix2.m32)) + (matrix1.m14 * matrix2.m42);
  matrix1.m13 = (((matrix1.m11 * matrix2.m13) + (matrix1.m12 * matrix2.m23)) + (matrix1.m13 * matrix2.m33)) + (matrix1.m14 * matrix2.m43);
  matrix1.m14 = (((matrix1.m11 * matrix2.m14) + (matrix1.m12 * matrix2.m24)) + (matrix1.m13 * matrix2.m34)) + (matrix1.m14 * matrix2.m44);
  matrix1.m21 = (((matrix1.m21 * matrix2.m11) + (matrix1.m22 * matrix2.m21)) + (matrix1.m23 * matrix2.m31)) + (matrix1.m24 * matrix2.m41);
  matrix1.m22 = (((matrix1.m21 * matrix2.m12) + (matrix1.m22 * matrix2.m22)) + (matrix1.m23 * matrix2.m32)) + (matrix1.m24 * matrix2.m42);
  matrix1.m23 = (((matrix1.m21 * matrix2.m13) + (matrix1.m22 * matrix2.m23)) + (matrix1.m23 * matrix2.m33)) + (matrix1.m24 * matrix2.m43);
  matrix1.m24 = (((matrix1.m21 * matrix2.m14) + (matrix1.m22 * matrix2.m24)) + (matrix1.m23 * matrix2.m34)) + (matrix1.m24 * matrix2.m44);
  matrix1.m31 = (((matrix1.m31 * matrix2.m11) + (matrix1.m32 * matrix2.m21)) + (matrix1.m33 * matrix2.m31)) + (matrix1.m34 * matrix2.m41);
  matrix1.m32 = (((matrix1.m31 * matrix2.m12) + (matrix1.m32 * matrix2.m22)) + (matrix1.m33 * matrix2.m32)) + (matrix1.m34 * matrix2.m42);
  matrix1.m33 = (((matrix1.m31 * matrix2.m13) + (matrix1.m32 * matrix2.m23)) + (matrix1.m33 * matrix2.m33)) + (matrix1.m34 * matrix2.m43);
  matrix1.m34 = (((matrix1.m31 * matrix2.m14) + (matrix1.m32 * matrix2.m24)) + (matrix1.m33 * matrix2.m34)) + (matrix1.m34 * matrix2.m44);
  matrix1.m41 = (((matrix1.m41 * matrix2.m11) + (matrix1.m42 * matrix2.m21)) + (matrix1.m43 * matrix2.m31)) + (matrix1.m44 * matrix2.m41);
  matrix1.m42 = (((matrix1.m41 * matrix2.m12) + (matrix1.m42 * matrix2.m22)) + (matrix1.m43 * matrix2.m32)) + (matrix1.m44 * matrix2.m42);
  matrix1.m43 = (((matrix1.m41 * matrix2.m13) + (matrix1.m42 * matrix2.m23)) + (matrix1.m43 * matrix2.m33)) + (matrix1.m44 * matrix2.m43);
  matrix1.m44 = (((matrix1.m41 * matrix2.m14) + (matrix1.m42 * matrix2.m24)) + (matrix1.m43 * matrix2.m34)) + (matrix1.m44 * matrix2.m44);

  return matrix1;
}

Matrices especiales.

Al igual que en los números reales, existe una matriz que al multiplicarse por otra, da como resultado la  matriz original. Es la matriz identidad. Es una matriz cuadrada, osea que tiene el mismo número de filas que de columnas, con todos sus elementos a 0 menos su diagonal, que están a 1. Se le suele anotar como In y se cumple que MIn = InM = M.

 

Math_II_09

 

 

public static Matrix4 Identity = new Matrix4(1.0, 0.0, 0.0, 0.0,
                                             0.0, 1.0, 0.0, 0.0,
                                             0.0, 0.0, 1.0, 0.0,
                                             0.0, 0.0, 0.0, 1.0);
[alert-announce]Dados dos escalares a y b y tres matrices n x m F, G y H, se cumple que:

  • F + G = G + F
  • (F + G) + H = F + (G + H)
  • a(bF) = (ab)F
  • a(F + G) = aF + aG
  • (a + b)F = aF + bF[/alert-announce]

 

También existe otra matriz interesante que al multiplicarla por otra matriz, da como resultado la matriz identidad. A esta matriz la llamamos matriz inversa y se suele anotar como M-1. Diremos que M-1 es la matriz inversa de M si se cumple que MM-1 = M-1M = I.

 

Ten en cuenta que no todas las matrices tienen una matriz inversa. Por ejemplo una matriz con alguna fila o columna llena de ceros, no puede tener inversa. A este tipo de matrices las llamamos matrices singulares.

 

Transformaciones lineales.

Ya sabes Kung Fu. Es hora de utilizarlo. Supongamos un sistema de coordenadas C tridimensional, en el que tenemos un punto P definido por el vector (x, y, z). Cada componente de ese vector representa la distancia que se debe recorrer en cada eje desde el origen (0, 0, 0) hasta el punto P.

 

Imaginemos ahora otro sistema de coordenadas C’, con otro punto en (x’, y’, z’). Podemos expresar con sistemas de ecuaciones lineales P’ en C de la siguiente manera:

 

Math_II_10

Gracias a nuestro Kung Fu lo podemos escribir de esta forma:

Math_II_11

 

 

El vector T representa la traslación del origen de C al origen de C’, y la matriz con las columnas U, V y W representan la orientación de los ejes para pasar de C a C’.

¡Escala esto!

Ya vimos en el post anterior, para escalar un punto P por un escalar a, simplemente tenemos que hace P’ = aP. Como un vector no es mas que una matriz de una sola columna, lo podemos hacer de esta forma:

Math_II_12

 

Con esto lo escalaríamos de forma uniforme en los tres ejes. Si queremos escalar cada eje con un valor distinto, usando el vector V:

 

Math_II_14

 

Veamos lo con un ejemplo. Si queremos escalar el punto P = (2, 2, 2) por V = (1, 2, 3), los cálculos son:

 

Math_II_15

Y gráficamente el resultado, P’, sería:

 

Math_II_16

 

 

Para crear una matrix que escale por un vector:

public static void CreateScale(ref Vector3 scale, out Matrix4 result)
{
  result.m11 = scale.x;
  result.m12 = 0.0;
  result.m13 = 0.0;
  result.m14 = 0.0;
  result.m21 = 0.0;
  result.m22 = scale.y;
  result.m23 = 0.0;
  result.m24 = 0.0;
  result.m31 = 0.0;
  result.m32 = 0.0;
  result.m33 = scale.z;
  result.m34 = 0.0;
  result.m41 = 0.0;
  result.m42 = 0.0;
  result.m43 = 0.0;
  result.m44 = 1.0;
}

Rotando a Miss Daisy.

Es relativamente sencillo encontrar la matriz que nos sirva para rotar un punto P por un ángulo θ en alguno de los ejes x, y o z. Usando algo de trigonometría y simplificando los cálculos a un plano bidimiensional, podemos hallar fácilmente un punto Q perpendicular a P, intercambiando las componentes de P y multiplicando por -1 la componente x:

 

Math_II_17

 

P’ puede expresarse como la combinación de P y Q de esta manera:

Math_II_18

P’ = P cosθ + Q sinθ

Si la descomponemos por sus componentes:

P’x = Px cosθ – Py sinθ

P’y = Py cosθ + Px sinθ

Que como sistema lineal de ecuaciones que es, se puede expresar también así:

Math_II_19

 

Si extendemos esa matriz a una de 3×3 usando la matriz identidad, nos aseguraremos que la componente z permanece fija y podremos rotar en un espacio tridimensional. La matriz Rz(θ) que rota un ángulo θ en el eje z es:

Math_II_20

 

De una forma simular podemos averiguar las matrices necesarias para rotar en el eje x e y:

 

Math_II_21

[tabs] [tab title=”X”]
public static Matrix4 CreateRotationX(double radians)
{
  Matrix4 result = Identity;

  double val1 = Math.Cos(radians);
  double val2 = Math.Sin(radians);

  result.m22 = val1;
  result.m23 = val2;
  result.m32 = -val2;
  result.m33 = val1;

  return result;
}
[/tab] [tab title=”Y”]
public static Matrix4 CreateRotationY(double radians)
{
  Matrix4 result = Identity;

  double val1 = Math.Cos(radians);
  double val2 = Math.Sin(radians);

  result.m11 = val1;
  result.m13 = -val2;
  result.m31 = val2;
  result.m33 = val1;

  return result;
}
[/tab] [tab title=”Z”]
public static Matrix4 CreateRotationZ(double radians)
{
 Matrix4 result = Identity;

 double val1 = Math.Cos(radians);
 double val2 = Math.Sin(radians);

 result.m11 = val1;
 result.m12 = val2;
 result.m21 = -val2;
 result.m22 = val1;

 return result;
}
[/tab] [/tabs]

Dimensión desconocida.

Nos falta una última transformación: la traslación, o desplazamiento. Para las operaciones anteriores nos ha bastado con matrices de 3×3. Desgraciadamente desplazar un punto P en un sistema de coordenadas C a otro sistema C’ aplicando un desplazamiento, no se puede representar en una matriz de 3×3.

 

Necesitamos extender nuestros vectores con una dimensión extra y usar matrices de 4×4. En realidad nuestros vectores seguirán siendo de 3 dimensiones, la cuarta dimensión solo la usaremos dentro de las matrices. A este tipo de coordenadas las llamamos coordenadas homogéneas. Así pues, un punto P, tendría sus tres componentes normales y un cuarto componente (comúnmente llamado w) igual a 1.

 

De una manera similar, extendemos en dimensiones las matrices de 3×3 usando la matriz identidad y añadiéndole el vector traslación T:

 

Math_II_22

 

 

public static Matrix4 CreateTranslation(Vector3 position)
{
  Matrix4 result = Identity;

  result.m41 = position.x;
  result.m42 = position.y;
  result.m43 = position.z;

  return result;
}

Si ampliamos a 4×4 todos los cálculos con el resto de transformaciones, podremos concatenar operaciones de transformación multiplicando esas matrices de 4×4.

Con esto acabamos el tema de matrices. Podéis descargaros el código de esta serie de artículos de nuestro Github.

githubLink

En la siguiente capítulo: quarteniones. ¡Hasta pronto!

You Might Also Like

2 Comments

  • Reply Matemáticas y videojuegos (I) | Ibuprogames June 18, 2015 at 4:16 pm

    […] Capítulo 2: Matrices. […]

  • Reply manuel escalante June 24, 2015 at 7:31 pm

    Excelente trabajo!!!

  • Leave a Reply