Vectores, Velocidad, y Aceleración (NC2)

Esta es la segunda entrada en la serie de artículos sobre el libro The Nature of Code. No es una traducción, sino una discusión breve sobre las ideas y una reimplementación de los algoritmos. En vez de usar Java usaré Javsascript con la librería p5.js.

El primer capítulo trata sobre vectores y cómo utilizarlos en animaciones. Normalmente uno representa por medio de vectores la posición, velocidad, y aceleración de un objeto.

Vectores

Los vectores son tuplas de números que soportan varias operaciones. Lo más útil de esto es que las operaciones se traducen en fenómenos físicos. Así, en vez de declarar la posición de un objeto con sus coordenadas x y y, podemos definir ambos valores como un vector de posición.

Nota

Para aprender lo básico sobre programación en javascript y p5.js puedes revisar una serie de videos en el canal de Daniel Shiffman. Subtítulos en español están disponibles.

Empecemos dibujando un círculo en el centro de la pantalla usando esta idea de vector de posición. Escribiremos un código muy similar al de la entrada anterior.

let pelota;

let color = [85, 142, 11];
let fondo = 220;
let diametro = 45;

function setup()
{
   createCanvas(400, 400);
   let posicion = createVector(width/2, height/2);
   pelota = new Pelota(posicion, color, diametro);
}

function draw()
{
   background(fondo);
   pelota.mostrar();
}

Una Pelota queda definida por la siguiente clase:

class Pelota
{
   constructor(posicion, color, diametro)
   {
      this.posicion = posicion;
      this.color = color;
      this.diametro = diametro;
   }

   mostrar()
   {
      stroke(35);
      fill(this.color);
      ellipse(this.posicion.x, this.posicion.y, this.diametro, this.diametro);
   }
}

El resultado del código es el siguiente:

Velocidad

En preparatoria aprendimos que la velocidad es una medida de qué tanto se mueve un objeto. O en lenguaje más técnico, el vector velocidad determina la dirección y la rapidez con la que cambia su posición un objeto. Contrario a la idea «real» de que la velocidad se mide respecto a una unidad de tiempo: metros por segundo, kilómetros por hora; la velocidad en una animación la medimos en pixeles por cuadro.

Movamos la pelotita creada en la sección anterior. Para ello creamos el vector (0.5, 0.75) que representa la velocidad, y luego lo sumamos a la posición anterior para obtener la nueva posición. La dirección es un poco más de 56 grados, la rapidez es la magnitud del vector, 0.9 pixeles por cuadro.

let pelota;

let color = [85, 142, 11];
let fondo = 220;
let diametro = 45;

let velocidad;

function setup()
{
   createCanvas(400, 400);
   velocidad = createVector(0.5, 0.75);
   let posicion = createVector(width/2, height/2);
   pelota = new Pelota(posicion, color, diametro);
}

function draw()
{
   background(fondo);
   pelota.mostrar();
   pelota.mover(velocidad);
   pelota.verificarBorde();
}

A la clase Pelota de la sección anterior le agregamos dos nuevos métodos: mover que mueve el objeto de acuerdo al vector velocidad que se pasa como parámetro, y verificarBorde que determina si la pelota llegó al borde del lienzo y la redibuja en el borde opuesto.

class Pelota
{
   constructor(posicion, color, diametro)
   {
      this.posicion = posicion;
      this.color = color;
      this.diametro = diametro;
   }

   mover(velocidad)
   {
      this.posicion.add(velocidad);
   }

   verificarBorde()
   {
      let radio = this.diametro/2;
      if (this.posicion.x > width + radio)
         this.posicion.x = -radio;
      if (this.posicion.x < -radio)
         this.posicion.x = width + radio;

      if (this.posicion.y > height + radio)
         this.posicion.y = -radio;
      if (this.posicion.y < -radio)
         this.posicion.x = height + radio;
   }

   mostrar()
   {
      stroke(35);
      fill(this.color);
      ellipse(this.posicion.x, this.posicion.y, this.diametro, this.diametro);
   }
}

El resultado es una animación que, como dijera un tutorial de shell que leí hace quince años, seguirá corriendo hasta que las ranas críen pelo.

Aceleración

Así como definimos la velocidad como la medida en el cambio de posición, podemos definir la aceleración como la medida del cambio en la velocidad. El truco es el mismo, creamos un vector de aceleración, luego se lo sumamos al vector de velocidad, que a su vez se añade al vector de posición.

Esto parece una complicación innecesaria. Lo cierto es que si se quiere hacer una animación dinámica e interesante lo mejor es tener una velocidad que no sea constante.

Nuestro ejemplo final es una pelotita que acelera en dirección del ratón, lo que nos permite dirigirla a lo largo de lienzo:

let pelota;

let color = [85, 142, 11];
let fondo = 220;
let diametro = 45;

function setup()
{
   createCanvas(400, 400);
   let posicion = createVector(width/2, height/2);
   pelota = new Pelota(posicion, color, diametro);
}

function draw()
{
   background(fondo);
   pelota.mostrar();
   pelota.mover();
   pelota.verificarBorde();
}

Hay que tener varias cosas en cuenta sobre cómo hay que cambiar nuestra clase Pelota para que funcione:

  • La magnitud de la aceleración debe ser muy pequeña, porque su efecto se magnifica al acumularse dos veces, primero en la velocidad y luego en la posición.
  • El vector de aceleración cambiará de forma dinámica dependiendo de la dirección del ratón.
  • El método mover no requiere de ningún parámetro. Es una consecuencia inmediata del punto anterior.
  • El vector velocidad en principio puede aumentar sin límite. Necesitamos una forma de prevenir ese escenario. La librería p5 incluye el método limit que resuelve ese problema.

Así es como luce la clase luego de implementar estas ideas:

class Pelota
{
   constructor(posicion, color, diametro)
   {
      this.posicion = posicion;
      this.color = color;
      this.diametro = diametro;
      this.velocidadMaxima = 8;
      this.velocidad = createVector(0, 0);
   }

   mover()
   {
      let raton = createVector(mouseX, mouseY);
      let aceleracion = p5.Vector.sub(raton, this.posicion);
      aceleracion.normalize();
      aceleracion.mult(0.5);

      this.velocidad.add(aceleracion);
      this.velocidad.limit(this.velocidadMaxima);
      this.posicion.add(this.velocidad);
   }

   verificarBorde()
   {
      let radio = this.diametro/2;
      if (this.posicion.x > width + radio)
         this.posicion.x = -radio;
      if (this.posicion.x < -radio)
         this.posicion.x = width + radio;

      if (this.posicion.y > height + radio)
         this.posicion.y = -radio;
      if (this.posicion.y < -radio)
         this.posicion.x = height + radio;
   }

   mostrar()
   {
      stroke(35);
      fill(this.color);
      ellipse(this.posicion.x, this.posicion.y, this.diametro, this.diametro);
   }
}

Esto es lo que obtenemos:

El resultado es interesante. No solo obtenemos una aplicación dinámica, además hay un efecto de inercia a rededor del ratón. Observar fenómenos adicionales a lo que se programó explícitamente fue una de las cosas más emocionantes que experimenté cuando empecé a crear mis primeras animaciones. Eso fue hace unos 13 años, en Python, con una librería que se llamaba pygeo.