Osciladores (NC5)

Esta es la quinta entrada sobre el libro The Nature of Code de Daniel Shiffman. Hablaremos sobre trigonometría y movimientos armónicos. Este es el tema del capítulo 3.

Este capítulo estudia movimientos oscilatorios y su implementación. Es simplemente una aplicación directa de algunos conceptos básicos de trigonometría.

Las ideas que se presentan en el libro cubren los aspectos más relevantes del tema y en este caso no añadiré demasiados comentarios al respecto, con excepción del código traducido a Javascript en vez de Java (Processing).

Ángulos

En las entradas anteriores hablé sobre movimiento lineal, en el sentido de que la aceleración y la velocidad son vectores que apuntan hacia donde estará el objeto en el siguiente cuadro. Cualquier rotación es una consecuencia directa de este tipo de movimiento. ¿Cómo hacer para rotar un objeto directamente a una velocidad determinada?.

Para rotar un objeto cambiaremos su posición actual a partir de una velocidad angular. Existe una función en p5, llamada rotate que toma como parámetro un ángulo, y rota el objeto el ángulo indicado. El siguiente ejemplo el uso de esta función para rotar un bastón.

let angspeed = 0.05;
let angulo = 0.0;
function setup()
{
    createCanvas(400, 400);
    fill(1, 205, 110);
    strokeWeight(4);
    background(150);
    translate(width/2, height/2);
    line(0, -100, 0, 100);
    ellipse(0, -100, 30, 30);
    ellipse(2, 100, 30, 30);
 }

function draw()
{
    background(150);
    angulo += angspeed;
    translate(width/2, height/2);
    rotate(angle);
    line(0, -100, 0, 100);
    ellipse(0, -100, 30, 30);
    ellipse(2, 100, 30, 30);
}

La animación usa radianes en lugar de grados. Puedes encontrar un poco más de información sobre cuál es la diferencia en el capítulo original del libro.

La función translate mueve el origen de las coordenadas al centro del lienzo, y luego tomando el centro del lienzo como punto de referencia, lo rotamos y dibujamos una linea con círculos en sus extremos.

Nótese como la velocidad angular se añade al ángulo en cada cuadro para dar el efecto de rotación. A mayor velocidad angular más rápido gira la animación.

El resultado de la animación es el siguiente:

Manteniendo la dirección del movimiento

Dibujar una pelota y moverla en la pantalla es simple, pero si en vez de dibujar una pelota dibujamos un rectángulo las cosas son ligeramente distintas. ¿Cómo garantizar que la parte frontal del rectángulo siempre está en la posición correcta?.

Usando trigonometría la solución es inmediata.

\[\tan(angulo) = \frac{velocidad_y}{velocidad_x}\]

Así que para determinar el ángulo en el cuál dibujar el rectángulo hay que usar la función opuesta, arco tangente. Y la cosa es incluso más inmediata, los vectores en p5 tiene un método llamado heading().

Usando la clase Pelota de entradas anteriores, dibujamos un rectángulo en vez de una elipse. Luego aplicamos una fuerza en la dirección de la posición del ratón.

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

function setup()
{
    let canvas = createCanvas(400, 400);
    let posicion = createVector(width/2, height/2);
    rectangulo = new Rectangulo(posicion, color, 15);
}

function draw()
{
    let atractor = createVector(mouseX, mouseY);
    let direccion = p5.Vector.sub(atractor, rectangulo.posicion);
    let fuerza = direccion.mult(rapidez);
    rectangulo.aplicarFuerza(fuerza);
    rectangulo.verificarBorde();
    rectangulo.mover();


    background(fondo);
    rectangulo.mostrar();
}

Y este es el código de la case rectángulo. La mayor parte de este código ya fue presentado en entradas anteriores de este blog:

class Rectangulo
{
    constructor(posicion, color, masa)
    {
        this.posicion = posicion;
        this.velocidad = createVector(0, 0);
        this.velocidadMaxima = 5;
        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)
        {
            this.posicion.x = 0;
            // this.velocidad.x *= -1;
        }
        if (this.posicion.x < 0)
        {
            this.posicion.x = width;
            // this.velocidad.x *= -1;
        }

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

    mostrar()
    {
        let angulo = atan(this.velocidad.y/this.velocidad.x);
        stroke(35);
        fill(this.color);
        push();
        rectMode(CENTER);
        translate(this.posicion.x, this.posicion.y);
        rotate(angulo);
        rect(0, 0, 2*this.diametro, this.diametro);
        pop();
    }
}

El método mostrar incorpora el rotar la figura en el ángulo adecuado. El resultado es la siguiente animación. Mueve tu cursor para dirigir el rectángulo:

Movimiento armónico simple

Un movimiento oscilatorio se refiere a un movimiento repetitivo conocido como movimiento harmónico simple. También se puede encontrar como oscilación sinusoidal periódica.

Ya sea una ola, un resorte o un péndulo cualquier movimiento de este tipo se caracteriza por dos valores:

  • Amplitud: La distancia del centro del movimiento hacia cada extremo.
  • Periodo: El tiempo que toma terminar un ciclo completo de movimiento.

La forma natural de implementar esta clase de comportamiento es usando una función cuyo valor oscile. Los candidatos inmediatos son las funciones seno y coseno. Lo único que hay que hacer es manipular un poco los números.

Consideremos el movimiento armónico unidimensional. Vamos a dibujar una pelota que se mueva de izquierda a derecha. Así que lo único que necesitamos es calcular la coordenada x.

\[x = amplitud \cdot \cos(2 \cdot \pi \cdot cuadros / periodo)\]

Esto si lo que importa es definir el periodo de animación a partir de los cuadros por segundo. Si por otro lado prefieres establecer la velocidad de tu animación usando cierta velocidad angular, la formula se simplifica.

\[x = amplitud \cdot \cos(valor-incremental)\]

El código correspondiente es el siguiente:

let angulo = 0;
let velocidadAngular = 0.05;

function setup()
{
    let canvas = createCanvas(640, 360);
}

function draw()
{
    background(200);

    let amplitud = 300;
    let x = amplitud * cos(angulo);
    angulo += velocidadAngular;

    stroke(10);
    fill(150);
    translate(width/2, height/2);
    line(0, 0, x, 0);
    ellipse(x, 0, 20, 20);
}

Y el resultado es el siguiente:

Referencias adicionales

Todas las animaciones en este capítulo estás escritas en Javascript con la librería p5.js. 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.

El código de todas las animaciones usadas en este blog están disponibles en mi cuenta de GitLab.