2. Reto 2. Autómatas

2.3. Segunda parte: estructuras de control

2.3.3. Traslación, rotación y escala

Hasta ahora hemos visto cómo poner formas gráficas en el canvas usando las coordenadas donde las queremos poner. Siempre hemos partido de que el punto (0, 0) (el origen de coordenadas) se encuentra en el vértice superior izquierda, pero con p5.js podemos decidir en qué lugar del canvas situamos el punto (0, 0). En el capítulo 6 del libro se explica cómo podemos mover el origen de coordenadas. Moviendo el origen de coordenadas moveremos las formas que tenemos dibujadas, dado que las habremos dibujado a partir del origen de coordenadas. Por lo tanto, esto será útil para mover formas complejas creadas a partir de varias formas simples.

Veremos tres maneras de hacer este cambio del origen de coordenadas.

Traslación

La opción más sencilla para mover el origen de coordenadas es la traslación. Consiste en desplazarlo un determinado número de píxeles. Estos dos ejemplos son bastante claros:

La función que usamos para determinar dónde tenemos el origen de coordenadas es translate(). Esta instrucción mueve el origen de coordenadas desde la posición actual a la posición indicada en sus parámetros. Así:

translate(40,20);

Mueve el origen de coordenadas a la posición (40,  20).

Atención, porque, dado que el origen de coordenadas se mueve a partir de su posición actual, si ponemos dos translate() seguidos el segundo cogerá como origen de coordenadas el que seleccionó el primero.

Y un detalle más. El origen de coordenadas se restablece (vuelve a su lugar) cada vez que se vuelve a ejecutar la función draw() (que, como ya sabemos, se ejecuta 50 veces por segundo).

Este ejemplo puede ser bastante aclaratorio:

function setup() {
  createCanvas(200, 200);
}

function draw() {
  background(220);
    for (var i = 10; i < 400; i += 10){ // Dibujamos las líneas
  	 line(i,0,i,height);   // Verticales
  	 line(0,i,width,i);    // Horizontales
    }
    fill(255,0,0);
    rect(20,20,20,20);
    translate(20,20);
    fill(0,255,0);
    rect(20,20,20,20);
    fill(0,0,255);
    translate(20,20);
    rect(20,20,20,20);
}

Si lo ejecutamos este es el resultado:

Si nos fijamos en el código, el dibujo del rectángulo es siempre el mismo: rect(20,20,20,20); lo que cambiamos es que trasladamos el origen de coordenadas cada vez. Pero, además, también el cambio del origen de coordenadas es siempre el mismo translate(20,20); porque, como he visto antes, el segundo cambio parte del origen de coordenadas que ha seleccionado primero.

Así, el primer rectángulo se dibuja en la posición (20, 20), el segundo en la (40, 40) y el tercero en la (60, 60).

Ahora que ya hemos visto cómo funciona la función translate(), ya nos podemos poner con los ejemplos del libro.

El 6-1 es muy sencillo: usamos la posición del ratón para determinar el nuevo origen de coordenadas. De este modo, al dibujar el cuadrado, se dibuja allá donde está el ratón.

En el 6-2 se hacen dos traslaciones, de forma que se dibujan dos cuadrados ligeramente separados. Bien es verdad que una vez tenemos claro el funcionamiento de la función translate(), estos dos ejemplos son muy fáciles de entender.

Rotación

Rotar es hacer girar un objeto. En el caso de p5.js, lo que haremos girar es el origen de coordenadas. La función que usamos para hacer esta rotación es rotate().

Como estamos hablando de hacer un giro, el parámetro que le pasaremos a la función rotate() serán unos grados, normalmente en radianes, a pesar de que podemos usar grados. En este punto vale la pena revisar, en la guía del reto 2, la explicación de la función arc().

Mirad este ejemplo:

function setup() {
  createCanvas(200, 200);
}
function draw() {
  background(220);
  translate(100,100)
  for (var i=0;i<16;i++){
    line(20, 20, 40, 40);
    rotate(QUARTER_PI/2.0);
  }
}

Dibujad esto:

Lo primero que hacemos es trasladar el origen de coordenadas al centro del canvas.

A continuación pintamos una línea y rotamos, pintamos línea y rotamos. Así 16 veces hasta completar el círculo.

Como habíamos visto en el apartado anterior, los desplazamientos del origen de coordenadas son acumulativos.

Una vez entendido cómo funciona la función rotate() podemos ver los ejemplos del libro.

El 6-3 hace una rotación a partir de la posición x del ratón. Dibujamos el cuadrado y empezamos a hacer la rotación a partir de la posición (0, 0) actual.

En este ejemplo el libro da una explicación que no es muy real. Dice que mouseX solo puede tomar valores de 0 a 120 (el tamaño del canvas), pero esto ya hemos visto que no es así, pues guarda el valor de la posición x del ratón incluso cuando el ratón está fuera del canvas.

El ejemplo 6-4 es muy parecido. Pero, en este caso, lo que se ha hecho es situar el inicio del cuadrado fuera del canvas, de forma que el origen de coordenadas queda en medio del rectángulo. Así da la sensación de que el rectángulo gira sobre su eje. Una manera diferente de hacer lo mismo sería usando la función rectMode() que nos permite definir cómo se dibuja el rectángulo y dibujarlo a partir de su centro.

Modificad este ejemplo para que el origen de coordenadas quede en medio del canvas. Usad rectMode() para dibujar el rectángulo.

El ejemplo 6-5 genera un efecto curioso. El cuadro va rotando continuamente y, a la vez que movemos el ratón, vamos dibujando cuadrados. En este caso, la variable ángulo es la que determina el ángulo en que se rota el cuadrado. Recordad que cada vez que se ejecuta la función draw() se vuelve a poner el origen de coordenadas en su lugar. Por eso necesitamos guardar el ángulo en una variable.

En este caso (y lo veremos en el ejemplo siguiente) es importante el orden en que llamamos a las funciones translate() y rotate() porque, recordad, dentro de la función draw() los cambios en su origen de coordenadas son acumulativos. Así, al hacer el rotate() en segundo término, lo que hacemos es que el cuadrado esté continuamente rotando, pero solo se mueve cuando movemos el ratón.

El ejemplo 6-6 cambia el orden, primero hace el rotate() y después el translate(). La diferencia es notable. Ahora el cuadrado se está moviendo continuamente haciendo un círculo. Y el movimiento del ratón solo determina cuán grande es el círculo que estamos dibujando.

El ejemplo 6-7 puede parecer complejo, pero en la práctica solo usa funciones que ya hemos visto. Define tres variables:

var angle = 0.0;
var angleDirection = 1;
var speed = 0.005;

La variable ángulo determina el ángulo que hay que usar en cada momento. Es la misma variable (o al menos se usa por el mismo motivo) que la que ya habíamos usado en un ejemplo anterior.

La variable angleDirection se usa para determinar si el brazo va en una dirección o en la contraria. Cambia su valor a -1 cuando se llega al tope determinado.

La variable speed determina la velocidad a la que se mueve el brazo.

Este ejemplo usa la función strokeWeight() que lo que hace es determinar la anchura de la línea que se dibujará a continuación. Y el ejemplo no tiene más misterio.

Escala

El escalado es el cambio de tamaño de un objeto. Con la función scale() lo que podemos hacer es cambiar el tamaño de las coordenadas del canvas, de forma que no habrá una correspondencia entre los píxeles de la pantalla y los píxeles del canvas.

Recuperamos este ejemplo que hemos visto antes. Pero añadimos una nueva variable y una nueva línea:

var escala = 2;  // Variable añadida

function setup() {
  createCanvas(200, 200);
}

function draw() {
  background(220);
  scale(escala);  // Llama a la función scale()
  for (var i = 10; i < 400; i += 10){ // Dibujamos las líneas
    line(i,0,i,height);   // Verticales
    line(0,i,width,i);    // Horizontales
  }
  fill(255,0,0);
  rect(20,20,20,20);
  translate(20,20);
  fill(0,255,0);
  rect(20,20,20,20);
  fill(0,0,255);
  translate(20,20);
  rect(20,20,20,20);
}

Variad el valor de la variable escala y comprobad lo que pasa. A continuación tenéis tres capturas de pantalla con tres casos diferentes:

El ejemplo 6-8 hace también escalado, pero usando el ratón para controlarlo. Y el ejemplo 6-9 explica qué hay que hacer para evitar que las líneas también se hagan más gruesas.

Con nuestro ejemplo haciendo este cambio (en rojo la línea añadida):

scale(escala);  // Llama a la función scale()
strokeWeight(1.0 / escala);
for (var i = 10; i < 400; i += 10){ // Dibujamos las líneas

(con escala = 5)

push() y pop()

Estas dos funciones nos permiten guardar la situación actual del origen de coordenadas. push() la guarda y pop() la recupera. Puede ser útil en programas muy largos para volver a una versión anterior del origen de coordenadas. El ejemplo 6-10 hace una pequeña demostración.