Las Leyes de Newton (NC3)

Ésta es la tercera entrada en la serie sobre el libro The Nature of Code. En esta entrada hablaré sobre cómo simular las leyes de Newton en una animación. Por supuesto que no estamos hablando de una simulación de física, sino de una animación que actúe de forma relativamente natural, y que nos permita manipular objetos en pantalla de forma sencilla aplicando fuerzas.

Analizando las animaciones de la entrada anterior, primero iniciamos con un objeto fijo en pantalla, luego le asignamos una velocidad constante, y finalmente modificamos el comportamiento por medio de una aceleración constante.

En esta entrada iremos un paso más allá, aplicando fuerzas sobre un objeto, que se traducirán en un cambio en la aceleración. Para esto repasaremos brevemente las Leyes de Newton.

Primera Ley de Newton

La primera ley de Newton establece que un cuerpo permanecerá en un estado de reposo o movimiento constante si no hay ninguna fuerza externa actuando sobre él.

Sin mencionarlo explícitamente, este este el fenómeno que usamos en las dos primeras animaciones de la entrada anterior.

Por supuesto esas animaciones son muy aburridas. Más adelante usaremos de nuevo esta ley de una forma ligeramente más interesante.

Segunda Ley de Newton

La segunda ley de Newton dice que la aceleración de un cuerpo es directamente proporcional a la fuerza que se le aplica, e inversamente proporcional a su masa.

En términos simples, si empujas mucho las cosas se mueven más rápido; si un cuerpo es muy pesado necesitarás más fuerza para moverlo.

Y por supuesto cuantitativamente el vector de aceleración lo calculamos con la fórmula que aprendimos en la preparatoria:

\[\hat{a} = \frac{\hat{f}}{m}\]

Tercera Ley de Newton

Finalmente, la más famosa y peor entendida de las leyes de Newton (te estoy viendo a tí, «Destino Final»). A toda acción corresponde una reacción igual y opuesta.

Animación

Vamos a combinar las tres leyes en una animación sencilla usando la misma clase de una pelota mostrada en las sesiones anteriores. Tenemos un par de cosas a considerar:

  • La pelota ahora debe tener masa. Al constructor de la clase le agregaremos este parámetro. Para representar visualmente la masa usamos ese mismo valor como el diámetro del círculo al dibujarlo. Así se ve el constructor de la clase:

    class Pelota
    {
       constructor(posicion, color, masa)
       {
          this.posicion = posicion;
          this.velocidad = createVector(0, 0);
          this.velocidadMaxima = 15;
          this.aceleracion = createVector(0, 0);
          this.masa = masa;
    
          this.color = color;
          this.diametro = masa;
       }
    
       ...
     }
    
  • Ahora necesitamos un método para aplicar fuerza a la pelota y modificar su velocidad de acuerdo a la segunda ley de Newton. Bastante sencillo, aplicar la fórmula tal cual.

    aplicarFuerza(f)
    {
       let fuerza = f.copy();
       fuerza.div(this.masa);
       this.aceleracion.add(fuerza);
    }
    
  • Finalmente, para simular de forma sencilla la tercera ley, haremos rebotar la pelota al tocar la pared, en vez de hacerla reaparecer en la pared opuesta como lo habíamos hecho en las entradas anteriores. Lo ideal sería usar el método aplicarFuerza() del párrafo anterior, pero tendría que ser de forma precisa en un cuadro. Aplicar este método exactamente en el momento en que el borde de la pelota toque el borde de la pantalla es virtualmente imposible. La solución simple es invertir la dirección del vector velocidad cuando la pelota toque la pared. Para esto hay que modificar el método verificarBorde:

    verificarBorde()
    {
       let radio = this.diametro/2;
       if (this.posicion.x > width - radio)
       {
          this.posicion = width - radio;
          this.velocidad.x *= -1;
       }
       if (this.posicion.x < radio)
       {
          this.posicion.x = radio;
          this.velocidad.x *= -1;
       }
    
       if (this.posicion.y > height - radio)
       {
          this.posicion.y = height - radio;
          this.velocidad.y *= -1;
       }
       if (this.posicion.y < radio)
       {
          this.posicion.y = radio;
          this.velocidad.y *= -1;
       }
    }
    

Así es como luce la clase Pelota:

class Pelota
{
   constructor(posicion, color, masa)
   {
      this.posicion = posicion;
      this.velocidad = createVector(0, 0);
      this.velocidadMaxima = 15;
      this.aceleracion = createVector(0, 0);
      this.masa = masa;

      this.color = color;
      this.diametro = masa;
   }

   aplicarFuerza(f)
   {
      let fuerza = f.copy();
      fuerza.div(this.masa);
      this.aceleracion.add(fuerza);
   }

   mover()
   {
      this.velocidad.add(this.aceleracion);
      this.velocidad.limit(this.velocidadMaxima);
      this.posicion.add(this.velocidad);

      this.aceleracion.mult(0);
   }

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

      if (this.posicion.y > height - radio)
      {
         this.posicion.y = height - radio;
         this.velocidad.y *= -1;
      }
      if (this.posicion.y < radio)
      {
         this.posicion.y = radio;
         this.velocidad.y *= -1;
      }
   }

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

Para el script de la animación usaremos un poco de interactividad. Hacer click sobre el lienzo activará una fuerza hacia abajo sobre la pelota.

let pelota;

let color = [85, 142, 11];
let fondo = 220;
let masa = 20;

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

function draw()
{
   background(fondo);
   let fuerza = createVector(0, 1.3);
   if (mouseIsPressed)
   {
      pelota.aplicarFuerza(fuerza);
   }
   pelota.mover();
   pelota.verificarBorde();
   pelota.mostrar();
}

Y este es el resultado:

Para exponer de forma más clara la segunda ley vamos a agregar más pelotas de distintos tamaños, y ver cómo la misma fuerza las afecta de forma distinta.

El código es casi idéntico, solo agregamos las pelotas a un arreglo.

let pelotas = [];

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

function setup()
{
    createCanvas(400, 400);
    for (let i = 0; i < 3; i++)
    {
        let masa = 25*i + 25;
        const pelota = new Pelota(
            createVector(80*i + 80, height/2 - masa/2),
            color,
            masa
        );

        pelotas.push(pelota);
    }
}

function draw()
{
    background(fondo);
    let fuerza = createVector(0, 1.3);
    for (const pelota of pelotas)
    {
        if (mouseIsPressed)
           pelota.aplicarFuerza(fuerza);

        pelota.mover();
        pelota.verificarBorde();
        pelota.mostrar();
    }
}

Y el resultado es el siguiente

Gravedad

Si pensamos en la fuerza de la animación anterior como la gravedad, tú tendrías el poder de activar y desactivar la gravedad arbitrariamente. Pero hay un problema, en el mundo real las cosas caen al mismo tiempo.

Si la gravedad es una fuerza, ¿por qué no cumple la segunda ley de Newton?. La realidad es que sí cumple la segunda ley de Newton, el truco está en cómo determinamos la fuerza de gravedad:

\[F = G\frac{M m}{r^2}\]

La multiplicación de la masa de los dos objetos dividida entre la distancia al cuadrado. Todo eso multiplicado por una constante. Así que para la aceleración resulta que:

\[\hat{a} = G\frac{M m}{r^2 m}\]

Tomando \(m\) como la masa de la pelota. Resulta que la masa «no pinta», porque aparece en el numerador y denominador de la fórmula. Por supuesto estamos suponiendo implícitamente que la la masa de la pelota es tan insignificantemente pequeña que no afecta en nada al cuerpo más grande, el planeta tierra en nuestro caso.

Todo lo anterior para justificar por qué para implementar gravedad en nuestra animación tenemos que dividir la fuerza entre la masa, para deshacernos de ese factor:

let pelotas = [];

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

function setup()
{
    createCanvas(400, 400);
    for (let i = 0; i < 3; i++)
    {
        let masa = 25*i + 25;
        const pelota = new Pelota(
            createVector(80*i + 80, height/2 - masa/2),
            color,
            masa
        );

        pelotas.push(pelota);
    }
}

function draw()
{
    background(fondo);
    let gravedad = createVector(0, 0.2);
    for (const pelota of pelotas)
    {
        let fuerzag = gravedad.copy();
        fuerzag.mult(pelota.masa);
        pelota.aplicarFuerza(fuerzag);
        pelota.mover();
        pelota.verificarBorde();
        pelota.mostrar();
    }
}

El resultado es el siguiente

El capítulo sobre fuerzas es muy extenso, aún falta la última parte por discutir. Hablaré sobre eso en la siguiente entrada.

Como siempre las animaciones fueron creadas en javascript con la librería p5.js.