Tutorial 2 – #tinycode controlando el tiempo

Este tutorial, de una serie de ellos, pretende servir de apoyo para la convocatoria #tinycode que está activa hasta el 12 de Diciembre de 2020. El mismo día que termina proyectaremos estos pequeños sketches hechos con Processing que caben en un tweet en la fachada digital de Medialab Prado (Madrid). Aquí podrás encontrar más info sobre la convocatoria. Anímate y participa! Happy tinycoding! 😉

En la mayoría de las animaciones se usa una variable para simular el paso del tiempo. Esa variable se incrementa en cada frame y se emplea para colocar los objetos en la pantalla, aplicar efectos, etc. Normalmente, la variable siempre se incrementa la misma cantidad, pero si en cada frame el incremento es variable podemos obtener efectos muy interesantes con muy poco código.

Partimos de este sketch:

//Color palette from Color Hunt: https://colorhunt.co/palette/220248
float t;

void setup() {
  size(360, 360);
  fill(#f05454);
  stroke(#e8e8e8);
  strokeWeight(3);
}

void draw() {
  t+= 0.015;
  background(#222831);
  circle(180 + cos(t)*120.0, 180 + sin(t)*120.0, 35);
}

El código pinta un círculo que gira alrededor del centro de la pantalla a una velocidad constante. Cuanto mayor sea el valor que sumamos, más rápido se moverá el círculo (el ángulo se mide en radianes, así que tened en cuenta que 6.28 o π*2 es una vuelta completa, en vez de 360º. Si el tema radianes no te queda muy claro, en esta página encontrarás una animación que te puede ayudar)

En este caso, la variable que simula el paso del tiempo es t (en la línea 12), y se incrementa en 0.015 en cada frame. Es un valor totalmente arbitrario que me parecía adecuado para el sketch. No hay valores correctos ni incorrectos, todo depende del efecto que queramos lograr. Para otros casos puede ser mucho mayor o más pequeño, o ser una expresión más complicada.

En los siguientes ejemplos solo vamos a cambiar esa línea y ver qué resultados obtenemos.

Avance aleatorio

Podemos hacer que el tiempo avance a tirones, añadiendo a cada frame 0.015 y restando una cantidad al azar entre 0 y 0.02.

t+= 0.015 - random(2)/100.0;

El avance puede ser más suave si usamos noise. Para conseguir comportamientos más complejos, usamos frameCount, una variable interna de Processing que aumenta automáticamente de uno en uno cada vez que se pinta un frame.

t+= noise(frameCount/75.0)/12.0;

Y con los parámetros adecuados, podemos retroceder en el tiempo (a partir del segundo 8)

t+= noise(frameCount/65.0)/15.0 - 0.03;

Avance controlado

En vez de depender de funciones aleatorias, podemos usar funciones con una salida predecible.

t+=0.015 + sin(frameCount/75.0)/10.0;

En cada paso, además de sumar 0.015, añadimos un valor que oscila regularmente entre -0.1 y 0.1. En total, el avance del tiempo variará entre -0.085 y 0.115.

Si no queremos retroceder en el tiempo, añadimos la función abs. Esta función devuelve el valor absoluto del número que le pasemos como parámetro.

t+= 0.015 + abs(sin(frameCount/75.0)/10.0);

Con la función exp podemos simular un acelerón rápido seguido de un frenado gradual.

t += exp(-frameCount%100/20.0)/10.0;

Y con una función similar, pow, tendremos una aceleración y frenado más gradual.

t += pow(sin(frameCount/33.0), 4)*0.1;

¡Recuerda! Si haces algo raro, bonito, interesante o glitchy, y te cabe en un tuit, compártelo con el hashtag #tinycode y citando a @CCodeMadrid. Y si controlas el paso del tiempo de otra manera, cuéntanoslo y la incluimos en esta recopilación.

Juan Alonso (@kokuma) – Diciembre 2020

Tutorial 1 – #tinycode paso a paso

Este tutorial, de una serie de ellos, pretende servir de apoyo para la convocatoria #tinycode que está activa hasta el 12 de Diciembre de 2020. El mismo día que termina proyectaremos estos pequeños sketches hechos con Processing que caben en un tweet en la fachada digital de Medialab Prado (Madrid). Aquí podrás encontrar más info sobre la convocatoria. Anímate y participa! Happy coding! 😉

Cuando descubrí este tuit me quedé hipnotizado por el movimiento de los tentáculos. El movimiento que consigue @Hau_kun es muy fluido y natural, y lo ha logrado con un código relativamente sencillo.

Vamos a construir este sketch paso a paso, desde cero, para entender cómo funciona e incorporar alguna de sus técnicas y trucos a nuestro repertorio.

Sólo necesitamos unos conocimientos básicos de Processing (variables y bucles) y saber cómo funciona el «Perlin noise«. Hay un video estupendo de The Coding Train donde Daniel Shiffman explica en detalle cómo funciona y cómo usarlo.

Si modificas el sketch original, comparte los resultados con nosotros publicando el código en Twitter, con el hashtag #tinycode y citando a @CCodeMadrid

El proceso

1- El canvas

Creamos un canvas de 720 x 720, y en cada frame, lo primero, borramos su contenido.

void setup() {
  size(720, 720);
}
void draw() {
  background(0);
}

2- El tentáculo (I)

Empezamos con un tentáculo. Lo pintamos empezando desde el centro del canvas (720/2 = 360) El tentáculo está formado por 50 círculos de radio 5, separados 7 píxeles.

void setup() {
  size(720, 720);
}
void draw() {
  background(0);
  float x = 360;
  float y = 360;
  for (float d=0; d<50; d++) {
    circle(x, y, 5);
    x += 7;
  }
}

3- El tentáculo (II)

Vamos a variar el diámetro de los círculos, y desplazarlos para que tampoco estén a la misma distancia. Para ello aprovechamos la variable d.

Para variar el diámetro de momento usamos un map . Cuando d pasa de 0 a 50, el diámetro cambiará de 20 a 0. Así pintaremos círculos grandes en el centro y más pequeños en el extremo, imitando la forma de un tentáculo.

Para variar la distancia usamos otro map, en este caso variando de 5 a 10, para que los círculos de los extremos estén más alejados entre si. Luego nos cargaremos esta parte.

void setup() {
  size(720, 720);
}
void draw() {
  background(0);
  float x = 360;
  float y = 360;
  for (float d=0; d<50; d++) {
    circle(x, y, map(d,0,50,20,0));
    x += map(d,0,50,5,10);
  }
}

4- Primera reducción y un efecto

Si quitamos espacios y ponemos todo en una línea estamos ya en 153 caracteres, un 54% del espacio total y ni siquiera estamos animando nada. Empezamos a reducir el código, tratando –de momento– de mantener la legibilidad.

Lo primero que podemos hacer es mover todas las declaraciones a una misma línea. Además, como x e y empiezan valiendo lo mismo, podemos compactar la asignación.

En vez de background(0), podemos usar clear(), que es casi lo mismo y nos ahorra algunos caracteres. Y de paso, eliminamos los map, sustituyéndolos por las correspondientes fórmulas matemáticas. Hemos bajado a 122 caracteres, el 43%, sin perder demasiada legibilidad.

De paso, añadimos blendMode(DIFFERENCE) para que se invierta el color de la superposición de los círculos (Si pinto sobre negro lo hago en blanco, si pinto sobre blanco, lo hago en negro). Aunque en el código original blendMode aparece dentro de la función draw(), por mi paz mental lo he movido a setup(), ya que realmente no hace falta llamarlo en cada fotograma.

float d, x, y;
void setup() {
  size(720, 720);
  blendMode(DIFFERENCE);
}
void draw() {
  clear();
  x = y = 360;
  for (d=.5; d>0; d-=.01) {
    circle(x, y, d*40);
    x += (1-d)*5+4;
  }
}

5- Movimiento en un eje

Vamos con el movimiento. Para ello, necesitamos una variable t que simule el paso del tiempo y aumente un poco en cada iteración. Empezamos extendiendo y contrayendo el tentáculo en el eje x. Para que el movimiento sea fluido, llamamos a la función noise En este caso, la reescalamos entre -15 y 15 y la usamos para modificar la posición de cada círculo. En cada iteración la llamamos 50 veces, pero sumamos el valor de t, de tal manera que en la primera vuelta noise nos devuelve 50 valores entre 0.5 y 0, la segunda entre 0.505 y 0.005, la tercera entre 0.510 y 0.010… Así garantizamos que tanto la posición de los círculos como su animación es fluida y continua.

float t, d, x, y;
void setup() {
  size(720, 720);
  blendMode(DIFFERENCE);
}
void draw() {
  clear();
  t+=.005;
  x = y = 360;
  for (d=.5; d>0; d-=.01) {
    circle(x, y, d*40);
    x += (noise(d+t)-.5)*30;
  }
}

6- Movimiento en dos ejes

Repetimos el paso anterior, pero ahora con el eje y. Para que los valores no sean los mismos que los del eje x, pasamos otras coordenadas a la función noise. El autor ha usado 9 como punto de partida, pero cualquier otro número valdría.

float t, d, x, y;
void setup() {
  size(720, 720);
  blendMode(DIFFERENCE);
}
void draw() {
  clear();
  t+=.005;
  x = y = 360;
  for (d=.5; d>0; d-=.01) {
    circle(x, y, d*40);
    x += (noise(d+t)-.5)*30;
    y += (noise(d+t,9)-.5)*30;
  }
}

7- Y ahora ¡con más tentáculos!

Añadir más tentáculos es sorprendentemente simple: basta con meter el bucle que pinta el tentáculo (el que modifica la variable d) dentro de otro bucle, que se repetirá tantas veces como tentáculos queramos –9 en este caso, asignado a la variable i– y añadir i a la función noise como una tercera coordenada.

Aunque i es un entero, lo declaramos como float, para ahorrarnos los caracteres extra que supondría una declaración de tipo int i;

float t, i, d, x, y;
void setup() {
  size(720, 720);
  blendMode(DIFFERENCE);
}
void draw() {
  clear();
  t+=.005;
  for (i=0; i<9; i++) {
    x=y=360;
    for (d=.5; d>0; d-=.01) {
      circle(x, y, d*40);
      x += (noise(i, d+t)-.5)*30;
      y += (noise(i, d+t,9)-.5)*30; 
    }
  }
}

8- Segunda reducción

En esta segunda reducción metemos la asignación de x e y dentro del circle, y quitamos todos los espacios y saltos de línea innecesarios (dejo un salto de línea por legibilidad), quedándonos en unos holgados 206 caracteres, así que lo mismo es interesante volver a los espacios y saltos de línea 😅.

float t,i,d,x,y;void setup(){size(720,720);blendMode(DIFFERENCE);}void draw(){clear();t+=.005;
for(i=0;i<9;i++){x=y=360;for(d=.5;d>0;d-=.01){circle(x+=(noise(i,d+t)-.5)*30,y+=(noise(i,d+t,9)-.5)*30,d*40);}}}

Trucos y técnicas empleadas

💡Agrupa todas las declaraciones en una línea, y usa variables del mismo tipo (todas float o todas int)

float t, i, d, x, y;

💡Usa los operadores ++, +=, … siempre que sea posible.

t+=.005;
...
i++
...
x+=(noise(i,d+t)-.5)*30

💡Para conseguir un movimiento fluido y que tenga continuidad entre fotogramas, usa la función noise.

x+=noise(d+t)*30

💡Usa clear() en vez de background(0)

clear();

💡Agrupa las asignaciones.

x=y=360;
circle(
  x+=(noise(i,d+t)-.5)*30,
  y+=(noise(i,d+t,9)-.5)*30,
  d*40)

¡Recuerda! Si haces algo raro, bonito, interesante o glitchy, y te cabe en un tuit, compártelo con el hashtag #tinycode y citando a @CCodeMadrid

Juan Alonso (@kokuma) – Noviembre 2020