Introduccion y Vectores

Las matemáticas son una parte fundamental del desarrollo de videojuegos. Tanto si quieres centrarte en tecnología o en el gameplay, vas a necesitar una buena base matemática. Con esta serie de artículos intentaremos (yo me incluyo) ampliar dicha base.

Los primeros artículos que tengo pensados para esta serie serán:

  • Capítulo 1: Vectores.
  • Capítulo 2: Matrices.
  • Capítulo 3: Transformaciones.

Dependiendo de la acogida que tengan podría continuar con algo de cálculo, física, visibilidad, colisiones, etc. Todo el código que acompañará a estos artículos será C# 3.0, estará bajo la MIT License y podréis acceder a él en nuestro Github.

 

Download from github

 

Todas las aportaciones / sugerencias / correcciones / ideas / etc serán bienvenidas ;)

 

0.0 Nomenclatura

Para evitar confusiones y malentendidos, algo nefasto en el preciso mundo matemático, usaré estas sencillas normas tipográficas a la hora de nombrar los distintos entes que pueblan estos mundos:

  • Los escalares (números, constantes, magnitudes, variables, etc) irán en itálica y minúscula, por ejemplo: x, y, z, w.
  • Para los vectores (explicados en este artículo, no seas impaciente…) usarán bold y mayúscula, como: V, P.
  • Las matrices y quaternions (en próximos episodios) irán también en bold y mayúsculas, por ejemplo: M, N y Q.

 

1.0 Introducción a los vectores

Los vectores son la base matemática de cualquier videojuegos, ya sea 3D o 2D. Los usamos para representar posiciones, direcciones, colores, geometrías, etc.

Son una secuencia (o tupla, técnicamente hablando) de dos o más números reales que pueden representar una magnitud y dirección.

A cada uno de estos números los podemos llamar componentes del vector. Un vector de n componentes, siendo n > 1, tiene esta forma:

V = (v1, v2, …, vn)

Cada v1v2, etc son los componentes del vector V. Si el vector representa alguna magnitud y/o dirección espacial (algo muy común en videojuegos), también podemos llamarlos dimensiones.

De esta manera, si un vector tiene dos componentes, podemos decir que es un vector bidimensional.

Por ejemplo, el vector V = (4, 4) es bidimensional. Su primera componente, o dimensión, vale 4. Lo mismo que su segunda componente.

Podemos representarlo en un plano de dos dimensiones, o ejes, en el que medimos la primera componente en el eje horizontal (comúnmente llamado eje x) y la segunda en el eje vertical (llamado eje y).

 

Math_I_00

 

Si le añadimos otra componente, digamos de valor 4, el resultado será un vector tridimensional de esta forma P = (4, 4, 4). El tercer componente se suele medir en el eje z.

 

Math_I_01

 

Que no te asusten las dimensiones de un vector porque en videojuegos lo normal es tratar con dos o tres como máximo. Algunos vectores pueden tener 4 dimensiones, pero nunca hacen referencia a un espacio tetradimensional 8O .

 

La forma más sencilla y simple en la que podríamos programar un vector bidimensional podría ser:

public struct Vector2
{
  public double x, y;
}

 

Y uno tridimensional:

public struct Vector3
{
  public double x, y, z;
}

 

Para que sea más sencillo trabajar con ellos, le añadiremos este constructor básico:

public Vector2(double x, double y)
{
  this.x = x;
  this.y = y;
}

 

Para simplificar y hacer más sencillas las gráficas y el código, vamos a utilizar vectores de dos dimensiones. Para operar con vectores tridimensionales, únicamente tendrás que añadir la dimensión que falte. En nuestro Github podrás encontrar el código de dos y tres dimensiones.

 

 

1.1 Operaciones básicas

Ya sabemos que es un vector, ahora queremos que sea útil, queremos saber como operar con ellos. Para eso tenemos que conocer primero las operaciones básicas que podemos hacer con ellos y que serán lo pilares de otras más complejas que trataremos más adelante.

Estas operaciones son: opuesto, sumarestamultiplicación por escalar y división por escalar. Si ya te son familiares, puedes saltarte este apartado.

El opuesto de un vector V, representado como -V, es el resultado de cambiar de signo las componentes de V. Si, por ejemplo, V= (4, -4), entonces -= (-4, 4). Si la componente es positiva, pasa a negativa y viceversa:

 

Math_I_02

 

Como podéis ver, el resultado de hacer el opuesto a un vector, es otro vector que mira en dirección opuesta. Si en un videojuego, un objeto se mueve gracias a que se le aplica una fuerza (un vector) y queremos que vaya en dirección opuesta, no tenemos mas que aplicarle el opuesto a esa fuerza.

 

public static Vector2 operator -(Vector2 v)
{
  v.y = -v.x;
  v.y = -v.y;

  return v;
}

 

La suma de dos vectores es también un cálculo muy sencillo. El resultado es otro vector cuyas componentes son el resultado de sumar las componentes de los dos vectores a sumar.

Si queremos sumar los vectores P y Q, el resultado R = (p1 + q1, p+ q2). Si P = (3, 1) y Q = (-1, 2), entonces el resultado de sumar ambos es:

Math_I_03

R = (3 + -1, 1 + 2) = (2, 3)

public static Vector2 operator +(Vector2 v1, Vector2 v2)
{
  v1.x += v2.x;
  v1.y += v2.y;

  return v1;
}

La operación resta se puede realizar usando la suma con el opuesto. PQ es lo mismo que P + (-Q):Math_I_04

R = PQ = P + (-Q) = (3 + 1, 1 + -2) = (4, -2)

public static Vector2 operator -(Vector2 v1, Vector2 v2)
{
  v1.x -= v2.x;
  v1.y = v2.y;

  return v1;
}

 

Multiplicar un vector por un escalar, es tan sencillo como multiplicar todas las componentes de dicho vector por ese número:

aV = Va = (a * v1, a * v2, …, a * vn)

Al multiplicar por un escalar, no variamos la orientación de un vector, solo variamos lo que mide. A la longitud de un vector se le llama magnitud o módulo y se suele escribir ||V||.

Si multiplicamos un vector V = (3, 1) por 2, el resultado es otro vector, dos veces más largo que el original:

Math_I_05

|V| = (2 * 3, 2 * 1) = (6, 2)

Como puedes estar ya imaginando, si multiplicamos por -1 un vector, el resultado es su opuesto.

public static Vector2 operator *(Vector2 v, double scalar)
{
  v.x *= scalar;
  v.y *= scalar;

  return v;
}

 

Dados dos escalares a y b, y tres vectores P, Q y R, se cumple que:

  • P + Q = Q + P
  • (P + Q) + R = P + (Q + R)
  • (ab)P = a(bP)
  • a(P + Q) = aP + aQ
  • (a + b)P = aP + bP

Usando las propiedades asociativa y conmutativa de los números reales, es muy sencillo verificar estar reglas.

 

Dividir un vector por un escalar (distinto a cero), es muy similar a multiplicarlo por escalar, solo que dividimos sus componentes por dicho número:

V/a = ( va,  v2, / a, …,  vn / a)

public static Vector2 operator /(Vector2 v, double scalar)
{
  double factor = 1.0 / scalar;

  v.x *= factor;
  v.y *= factor;

  return v;
}

 

1.2 Magnitud.

P._Oxy._I_29

Como vimos en el apartado anterior, la magnitud o módulo de un vector es la longitud de dicho vector. Al medir una distancia, nunca será negativo (aunque si puede ser cero).

Para calcular el magnitud de un vector, usaremos el conocido Teorema de Pitágoras para hallar la hipotenusa del triangulo formado por ABC:

Math_I_06

 

Haciendo la raíz cuadrada a la suma del cuadrado de todas las componentes del vector, obtendremos su magnitud. En el caso anterior que teníamos un vector V = (3, 3), su magnitud es:

|V| = √(32 + 32) = 4.2426…

public double Length()
{
  return Math.Sqrt((x * x) + (y * y));
}

 

A los vectores con magnitud uno, se les suele llamar vector unidad. Una operación muy común en videojuegos es hallar el vector unidad de un vector. Esta operación recibe el nombre de normalización y es muy útil cuando solo nos interesa operar con la orientación de un vector.

Si dividimos un vector (que al menos tenga una componente distinta de cero) por su magnitud  (/ |V|), hallaremos su vector unidad.

 

public void Normalize()
{
  double value = 1.0 / Math.Sqrt((x * x) + (y * y));

  x *= value;
  y *= value;
}

 

La operación raíz cuadrada representa un mayor coste de cálculo que la suma o las multiplicaciones. Por eso si quieres comparar la magnitud de dos vectores y solo te interesa cuál es mayor o menor, no necesitas hacer la raíz cuadrada, te basta con la suma del cuadrado de todos sus componentes.

public double LengthSquared()
{
  return (x * x) + (y * y);
}

 

1.2 Producto escalar

El producto escalar, escrito como · Q, es una de las operaciones más usadas en videojuegos, ya que nos da una medida de la diferencia entre las orientaciones de dos vectores.

Math_I_07

Se calcula mediante la suma del producto de todos los componentes:

P · Q = (p1q1 + p2q2 + … pnqn)

public static double Dot(Vector2 v1, Vector2 v2)
{
  return (v1.x * v2.x) + (v1.y * v2.y);
}

Dados dos vectores P y Q, con un ángulo α entre ellos, se cumple que P · Q = |P| |Q| cos(α)

De esta propiedad del producto escalar podemos deducir algo que usaremos mucho. Si dos vectores P y Q son perpendiculares (forman entre ellos un ángulo de 90º, también llamados ortogonales) entonces P · Q = 0.

Otra información importante la podemos sacar del signo del productor escalar. El signo nos puede dar una pista de como están de cerca esos dos vectores de estar en la misma dirección.

Como podemos ver en la siguiente gráfica, los posibles vectores Q con un ángulo inferior a 90º con P tendrían un producto escalar positivo, los que tuvieran más de 90º lo tendrían negativo.

 

Math_I_08

 

1.3 Producto vectorial

El producto vectorial, escrito como P x Q, de dos vectores de tres dimensiones nos devuelve otro vector perpendicular a estos dos. Se calcula así:

P x Q = (pyqz – pzqy, pzqx – pxqz, pxqy – pyqx)

public static Vector3 Cross(Vector3 v1, Vector3 v2)
{
  double x = v1.y * v2.z - v2.y * v1.z;
  double y = -(v1.x * v2.z - v2.x * v1.z);
  double z = v1.x * v2.y - v2.x * v1.y;

  v1.x = x;
  v1.y = y;
  v1.z = z;

  return v1;
}

Y tiene esta forma:

Math_I_09

 

Al igual que el producto escalar, el producto vectorial también esconde un secreto. La magnitud del vector resultante, |P x Q|, es igual al área del paralelogramo formado por los vectores P y Q (en el gráfico de arriba mostrado con líneas grises).

 

Para averiguar el sentido del producto vectorial, usaremos la regla de la mano derecha, que dice que el resultado tendrá la dirección que marque el dedo pulgar de la mano derecha su los dedos van de P a Q por el camino más corto.

Dado un escalar a y tres vectores P, Q y R, se cumple que:

  • Q x P = -(P x Q)
  • (aP) x Q = a(P x Q)
  • P x (Q + R) = P x Q + P x Q
  • P x P = (0, 0, 0)
  • (P x Q) · R = (R x P) · Q = (Q x R) · P
  • P x (Q x P) = P x Q x P

 

1.4 Transformaciones.

Ya conocemos lo básico para operar con vectores y sacarles alguna utilidad. El uso más típico que podemos darles es para representar posiciones en el espacio, por ejemplo, donde esta un proyectil, que velocidad lleva y que dirección.

Las operaciones que vamos a querer hacer son: traslación, rotación y escalado.

En el siguiente capitulo, veremos como hacer esto mismo, y más cosas ;) , gracias a las matrices, de una manera mucho mas cómoda y elegante.

A la traslación de un vector, también podríamos llamarla desplazamiento o mover un vector. Usaremos la suma (y resta) que hemos visto:

P = P + D

 

D seria el vector desplazamiento que queremos aplicarle y P la posición. Si conocemos la velocidad, V, y el tiempo en segundos que pasaron desde el ultimo frame dibujado, ∇t (llamado delta time), una fórmula básica de movimiento podría ser:

P = P + ∇t V

 

El escalado de un vector lo podemos usar para, por ejemplo, aplicarle un turbo a la velocidad cuando cogemos un powerup:

V = aV

 

Por ultimo, queremos poder rotar un vector en cualquier eje. Eso nos puede servir para cambiar la dirección del protagonista de nuestro juego cuando se pulse una tecla.

Sin nuestras amigas las matrices, no lo conseguiremos de una forma tan simple como en las anteriores. Con algo de trigonométria, las rotaciones en el eje x:

vy = vcos(ß) – vsin(ß)

vz = vsin(ß) + vcos(ß)

En el eje y:

vx = vcos(ß) – vsin(ß)

vz = vsin(ß) + vcos(ß)

Y en el eje z:

vx = vcos(ß) – vsin(ß)

vy = vsin(ß) + vcos(ß)

 

Con esto se acaba el tema de los vectores. Podéis descargaros el código de esta serie de artículos de nuestro Github.

Download from github
En la siguiente capítulo: matrices. ¡Hasta pronto!

  1. #1 por angel adrian el 30/09/2009 - 10:09 pm

    ta chido

  2. #2 por StrongCod3r el 29/12/2009 - 2:24 am

    Buen tute, espero que lo continues.thx

  3. #3 por Roberto Glez el 29/07/2010 - 8:03 am

    gracias por el tutorial. Saludos ;)

  4. #4 por liz el 31/08/2010 - 11:23 pm

    hola y ps esta chido

  5. #5 por simon gonzaleez el 01/03/2011 - 10:59 pm

    liz :hola y ps esta chido

  6. #6 por mario el 29/11/2011 - 2:45 pm

    hola esta muy interesante el tema gracias por esta ayuda.

  7. #7 por mario el 29/11/2011 - 2:46 pm

    QUE TEMA MUY INTERESANTE GRACIAS POR PUBLICARLO ATENTAMENTE MARIO.

  8. #8 por Paco47 el 13/07/2012 - 1:11 pm

    muy buen artículo, si señor

(no sera publicado)
*