Trigonometría elemental en la programación creativa

Árticulo original escrito por: Ira Greenberg

La trigonometría es fundamental dentro de la programación gráfica creativa, pero seguramente, si eres como una gran parte de nosotros, tus recuerdos sobre esta materia se encontraran bastante oxidados y difusos en lo más profundo de tus recuerdos. Quizá te acuerdes del código nemónico SOH-CAH-TOA que se utilizaba para recordar las relaciones entre un triángulo rectángulo y sus funciones trigonométricas. Pero si no es así, aquí va un diagrama para refrescar la memoria:

Soh-Cah-Toa!

Probablemente te habrás dado cuenta en la imagen de como también la Tangente del ángulo es igual al Seno partido por el Coseno de este (tan = sine(θ) / cos(θ)).

Quizá también recuerdes que tanto el Seno como el Coseno son similares cuando son representados gráficamente, ambos formando ondas periódicas, solo que la onda del Coseno esta ligeramente desplazada (90 grados o PI/2) en el gráfico, lo cual está técnicamente denominado 'Desplazamiento de Fase'.

Ya que esto no es algo facil de entender dentro de su abstracción, existe un modelo llamado Circunferencia Unitaria que podemos ver a continuación, el cual es usado para visualizar y estudiar las funciones trigonométricas:

Círculo

La circunferencia unitaria es una circunferencia con un radio igual a una unidad de longitud, de ahí su nombre tan imaginativo y elaborado. Cuando trabajes con la circunferencia unitaria, no usaras el sistema habitual de Coordenadas Cartesianas y en su lugar lo harás con el sistema de Coordenadas polares.

El sistema cartesiano funciona bien en los espacios rectangulares cuadriculados, donde un punto puede ser situado por una coordenada tal como (x, y). Sin embargo, en un sistema de coordenadas polares, la ubicación se especifica mediante (r, θ), donde r es el radio y θ (la letra griega theta) es el ángulo de rotación.

La circunferencia unitaria tiene como origen su centro, y los ángulos de rotación se miden empezando desde el borde central-derecho de esta (el equivalente a las 3 en punto) y se mueven girando en el sentido contrario a las agujas del reloj.

En el gráfico de la circunferencia unitaria, el punto p está a 45° o π / 4. Puedes usar π también para medir alrededor del circulo unitario así como se ve en la imagen. A medio camino alrededor de la circunferencia (180º) equivale a PI (π) radianes y todo el camino alrededor de la circunferencia es igual a PI*2 () Radianes y también a 0 radianes ya que un circulo es continuo y empieza en el mismo sitio que termina. El numero π es una constante matemática que aquivale a la circunferencia de un círculo dividido por su propio diámetro, es aproximadamente igual al número 3.141592

En el sistema de coordenadas polar, se usan los radianes para medir ángulos en vez de usar grados. El ángulo de rotación en radianes se le conoce comúnmente como θ (la letra griega theta). La longitud del arco de esta rotación se calcula mediante r * θ, donde r es el radio. En una circunferencia unitaria de radio 1, θ es igual a la longitud del arco de rotación (arco s en el anterior diagrama del círculo unitario). Esta bien saber la longitud del arco, pero la mayoría de veces que estés trabajando con gráficos digitales, en realidad lo que solo quieres saber es la ubicación de un punto en relación al circulo unitario. Por ejemplo, si quisiera rotar un punto alrededor del círculo unitario, necesitaría saber cómo y donde colocar y mover el punto en un círculo. Con la circunferencia unitaria esta es una tarea increíblemente fácil y es precisamente el tipo de acción para el que la trigonometría es usada.

Existe una simple relación entre las funciones trigonométricas y la circunferencia unitaria. Observa en el diagrama de la circunferencia unitaria que a partir del punto p en la elipse, se forma un triángulo rectángulo dentro de este. Esto debería hacerte pensar inmediatamente en el viejo Pitágoras. Notese también que r (el radio) es la hipotenusa del triángulo rectángulo. Además, ahora también conoce que con las funciones trigonométricas, podrás usar theta y cualquier otro lado (opuesto, adyacente o hipotenusa) para resolver el resto del triángulo. La gran recompensa de estas relaciones para nuestro propósito, es que para traducir el punto p desde el sistema de coordenadas polares a el sistema de coordenadas cartesiano (el sistema utilizado por nuestras pantallas), los podrás hacer usando estas simples expresiones:

# Pseudo código
x = coseno(theta) * radio
y = seno(theta) * radio

Estas aparentemente inofensivas pequeñas expresiones son muy poderosas y pueden ser explotadas para todo tipo de propósitos expresivos y orgánicos.

Así es como realmente usarías estas funciones trigonométricas en JavaScript :

// Funcion para convertir de Grados a Radianes
let aRadianes = (angulo) => angulo * (Math.PI / 180);

// Eqivalente en ES5:
// function aRadianes (angulo) {
//    return angulo * (Math.PI / 180);
// }

// Calculo de x, y
let x = Math.cos( aRadianes(angulo) ) * radio;
let y = Math.sin( aRadianes(angulo) ) * radio;

Toma nota de la llamada a la función aRadianes(angulo) dentro de cada una de las llamadas a las funciones trigonométricas. Recuerda que theta será medido en radianes al usar el sistema de coordenadas polares. Sin embargo, en el sistema de coordenadas cartesianas, se trabaja en grados (º), para realizar la conversión entre radianes y grados (y viceversa), puedes usar las siguientes expresiones:

let theta = angulo * Math.PI / 180;
let angulo = theta * 180 / Math.PI;

o mejor aún, puedes simplemente usar las útiles funciones de conversión que incluye p5.js:

let theta = radians(angulo);
let angulo = degrees(theta);

Por último, podremos ver a continuación un sketch en p5.js que demuestra como están relacionadas la circunferencia unitaria y la función del seno :

const app = {
    px : null,
    py : null,
    angle : 0,
    frequency : 3,
    x : 0,
    cx : null,
    cy : null,
    radius : 60,
    xlimit : null
}

const wave = {
    px : null,
    py : null,
    angle : app.angle,
    frequency : app.frequency,
}

function setup() {

    // Inicializar
    createCanvas(640, 200);
    rectMode(CENTER);
    background(255);
    frameRate(60);

    // Establecer valores iniciales
    app.cx = width / 8;
    app.cy = 85;
    app.xlimit = width - 160;
}

function draw() {

    // - Fondo Global
    background(255);

    // - Guías
    stroke(212);
    line( app.cx-app.radius, app.cy, app.xlimit+app.cx+app.radius, app.cy );
    line( app.cx, app.cy-app.radius, app.cx, app.cy+app.radius );

    // - Círculo principal
    stroke(128);
    noFill();
    ellipse( app.cx, app.cy, app.radius * 2, app.radius * 2);

    // - Rotación del puntero alrededor del círculo
    app.px = app.cx + cos(radians(app.angle)) * (app.radius);
    app.py = app.cy + sin(radians(app.angle)) * (app.radius);

    // - Dibuja los punteros del círculo
    fill(0, 96, 128);
    noStroke();
    rect(app.px, app.py, 5, 5);  // Puntero sobre la circunferencia
    rect(app.cx, app.cy, 5, 5);  // Puntero en el centro del círculo

    // - Linea del círculo
    stroke(0, 96, 128);
    line(app.cx, app.cy, app.px, app.py);

    // - Mantener establecido el valor a 0 para evitar el flasheo
    wave.angle = 0;

    // - Dibuja curva estática - y = sin(x)
    stroke(196);
    for (let i = 0; i < app.xlimit; i++) {
        wave.px = app.cx + cos(radians(wave.angle)) * (app.radius);
        wave.py = app.cy + sin(radians(wave.angle)) * (app.radius);
        point( app.cx + app.radius + i, wave.py);
        wave.angle -= wave.frequency;
    }

    // - Puntero junto a la curva del Seno para ilustrar la relacion del círculo con la onda
    noStroke();
    rect(app.cx + app.radius + app.x, app.py, 5, 5);
    app.angle -= app.frequency;
    app.x += 1;

    // - Resetear valores cuando el puntero de onda alcanza el límite en X
    if (app.x >= app.xlimit ) {
        app.x = 0;
        app.angle = 0;
    }

    // - Linea dinámica conectando el puntero de la circunferencia y la onda
    stroke(0, 96, 128);
    line(app.px, app.py, app.cx + app.radius + app.x, app.py);

    // - Sacar los calculos en pantalla
    fill(128);
    noStroke();
    textSize(12);
    textFont('monospace');
    text("y = sin(x)", 30, 180);
    text("px = " + app.px.toFixed(1), 130, 180);
    text("py = " + app.py.toFixed(1), 230, 180);
}

Esta es una traducción libre basada en un tutorial escrito por Ira Greenberg perteneciente al libro Processing: Creative Coding and Computational Art. Friends of ED, © 2007 bajo una licencia Creative Commons CC BY-NC-SA 4.0.