Vamos a suponer que deseamos programar un Caleidoscopio con Processing. Los Caleidoscopios proveen de interesantes efectos visuales dada su característica de simetría en torno a los ejes coordenados X e Y (ver figura 1). Podemos dibujar en uno de los 4 cuadrantes y, acto y seguido, nuestros trazos se muestran en el resto. Los ejes actúan como espejos de manera que el dibujo aparece mimetizado en todas partes. Lo interesante es que a partir de formas primitivas y simples de dibujo, podemos obtener patrones altamente complejos que podemos emplear como texturas para nuestros diseños gráficos o web.
Lo que haremos es dibujar triángulos a partir de 3 puntos en 2D que posicionaremos donde deseemos gracias a realizar 3 clic’s con el ratón. Pulsando 3 veces se generará un triángulo, pulsando otras 3 veces otro y así sucesivamente. Lo haremos en cualquier punto de la ventana de dibujo y el Caleidoscopio replicará nuestros triángulos en todos los cuadrantes. Podemos observarlo en la figura 2.
Para ello será necesario que programemos una rutina tal que:
• Se detecte el evento de “clic” del ratón.
• Se muestre un punto en la posición 2D de éste, al realizar el clic.
• Se almacenen hasta 3 puntos y, en ese caso, se utilicen para generar un triángulo.
• Una vez dibujado el triangulo, volvemos a empezar.
• Paralelamente, cada vez que se genere un punto o un triángulo, tiene que replicarse en los 4 cuadrantes que definen ambos ejes X e Y.
El código que vamos a realizar va a ser más “didáctico que eficiente” dado que nos encontramos en un plano formativo y en un nivel inicial. No utilizaremos objetos por lo tanto. Si emplearemos código Processing dinámico (basado en las funciones setup() y draw()) y no estático (una simple secuencia de comandos que se ejecutan una sola vez). Es por lo tanto más interesante que se entienda lo que hace el código y no tanto que se ejecute con la máxima eficiencia, ésta última relegada a niveles más avanzados. Además, se trata de un ejemplo sencillo.
Recordemos que en Processing inicializamos las condiciones a partir de las cuales tiene que ejecutarse el código gracias a la función setup(). Ésta se ejecutará sólo una vez:
// Several global integer variables that we will use
// A first set of variables defining points: (x1a,y1a),(x2a,y2a)...
int x1a,y1a,x2a,y2a,x3a,y3a;
// A second set of variables defining points: (x1b,y1b),(x2b,y2b)...
int x1b,y1b,x2b,y2b,x3b,y3b;
// We are in need of a counter! And we set it to zero to begin
int counter=0;
// The height and width of the rendering window: 300 x 300 pixels
int height=300;
int width=300;
// The background color will be white
int background_color= 255;
// Objects will be black
int objects_color=0;
// We set the width of the stroke used for lines and points to 5
int stroke_width=5;
// The setup function
void setup(){
// We call the size function that creates the window
size(height,width);
// We setup a white background for the window
background(background_color);
// We decide that the objects that we will render should be black
fill(objects_color);
// We setup a bigger width for our points/lines
strokeWeight(stroke_width);
}
Código 1. Variables e inicialización de nuestro Caleidoscopio.
En el código 1 definimos una serie de variables globales de tipo entero que vamos a necesitar a lo largo de todo el proceso. Se trata de las variables que emplearemos para almacenar los puntos que crearemos, de 3 en 3. Están “redundadas” y veremos en un momentito el por qué. Además las relativas al tamaño de la ventana, grueso de trazo y colores que deseamos utilizar. Operamos de una manera bastante clásica en lo relativo a las inicializaciones en Processing. Definimos 2 variables, height y width, que pasaremos a la función size() para que se cree una ventana de esas dimensiones (en pixeles). Posteriormente le indicamos al sistema que el fondo de ésta deberá ser blanco (variable background_color), los puntos y trazos negros (variable objects_color) y el grueso de éstos de 5 pixeles (variable stroke_width). Obtenemos una simple ventana de 300 x 300 pixeles, tal y como muestra la figura 3.
Antes de pasar a la función draw(), que será la responsable de implementar la “inteligencia del Caleidoscopio, veamos como trataremos los eventos de ratón, en concreto el de clic, en el código 2:
// A click event implies calling the mousePressed function
void mousePressed(){
// We check if we are rendering the first,
// the second or the third point by using our counter
if(counter==0){ // Saving the 1st point + incrementing counter
x1a=mouseX; // mouseX is the X coordinate of the mouse
y1a=mouseY; // mouseY is the Y coordinate of the mouse
counter++; // Remember that counter++ equals counter=counter+1
}
else if(counter==1){ // Saving the 2nd point + incrementing counter
x2a=mouseX;
y2a=mouseY;
counter++;
}
else if (counter==2){ // Saving the 3rd point + incrementing counter
x3a=mouseX;
y3a=mouseY;
counter++;
}
}
Código 2. Almacenando puntos gracias al evento de clic del ratón.
¿Qué es lo que ocurre en el código 2? Lo primero que tenemos que aclarar es que utilizamos la variable counter como contador. Ésta se inicia en 0 y podrá tomar los valores 0, 1 y 2. Éstos dependerán de si se trata de un “primer clic” que genera un primer punto de un nuevo triángulo, o de un “segundo y tercer clic’s” del mismo. Observemos que al producirse un clic con el ratón, se llamará a la función asociada mousePressed(). Dependiendo del valor del contador sabremos cual es el punto 2D a almacenar: el primero (x1a,y1a), el segundo (x2a,y2a) o el tercero (x3a,y3a). Acto y seguido incrementaremos el contador, para que éste listo para el próximo clic (próximo punto).
Antes de pasar el código 3 que cierra el programa, recordemos cómo trata Processinglas coordenadas de ventana y averigüemos cuales serán las fórmulas e emplear para, a partir de un punto, obtener el resto. Observemos la figura 4:
En Processing la coordenada (0,0) es la superior izquierda mientras que la (300,300) es la inferior derecha. Por tanto, las X’s aumentan hacia la derecha y las Y’s hacia abajo. Además contamos con las dimensiones de la ventana, ancho (width) y alto (height). A partir de un punto de coordenadas (x,y) realizado al hacer clic con el ratón, podemos obtener sus “3 espejos” tal y como indica la figura 4. Se puede observar que son operaciones muy simples que garantizan la simetría característica del Caleidoscopio.
Para terminar, observemos en el código 3 como se procede a dibujar el Caleidoscopio a partir de 1, 2 o 3 puntos así como del triángulo que generaremos en éste último caso:
// draw() will be called infinite times (it is a loop)
void draw(){
// 1st case: there is only 1 point
// We “repeat” it for the 4 quadrants
if(counter==1){
// From the X coordinate for the 1st/4th quadrants
// we generate the X coordinate for the 2nd/3rd quadrants
x1b=width-x1a;
// From the Y coordinate for the 1st/2nd quadrants
// we generate the Y coordinate for the 3rd/4th quadrants
y1b=height-y1a;
// Therefore we can render 4 points! For the 4 quadrants!
// There are 4 combinations available
point(x1a,y1a);
point(x1a,y1b);
point(x1b,y1a);
point(x1b,y1b);
}
// 2nd case: there are 2 points
// We “mirror” the 2nd point for the 4 quadrants
else if(counter==2){
x2b=width-x2a;
y2b=height-y2a;
point(x2a,y2a);
point(x2a,y2b);
point(x2b,y2a);
point(x2b,y2b);
}
// 3rd case: there are 3 points
// We render the 4 triangles for the 4 quadrants
else if(counter==3){
x3b=width-x3a;
y3b=height-y3a;
// Now it’s time to render 4 triangles
beginShape(TRIANGLES);
vertex(x1a,y1a); // Top-left triangle
vertex(x2a,y2a);
vertex(x3a,y3a);
vertex(x1a,y1b); // Bottom-left triangle
vertex(x2a,y2b);
vertex(x3a,y3b);
vertex(x1b,y1a); // Top-right triangle
vertex(x2b,y2a);
vertex(x3b,y3a);
vertex(x1b,y1b); // Bottom-right triangle
vertex(x2b,y2b);
vertex(x3b,y3b);
endShape();
// After rendering the triangle we should set the counter
// to zero again
counter=0;
}
}
Código 3. Creando el Caleidoscopio a partir del loop draw().
En el código 3 tenemos la función draw() que es el bucle infinito de Processing. Se llamará a esta función continuamente. Observemos como se contemplan los 3 casos posibles. Un sólo punto a replicar, dos puntos a replicar o tres puntos a replicar, caso éste en el que se dibujará un triángulo a partir de ellos. Los puntos originales que se obtuvieron al hacer clic con el ratón muestran el subíndice “a”. Se trata de (x1a,y1a), (x2a,y2a) y (x3a,y3a). Por contra, los puntos “espejo” que se calculan para cada caso a partir de las fórmulas de la figura 4 disponen del subíndice “b”. Es decir (x1b,y1b), (x2b,y2b) y (x3b,y3b). Para generar puntos empleamos la función point() de Processing mientras que para definir un triángulo, utilizamos la estructura que se inicia con beginShape(TRIANGLES) y se cierra con endShape(). Nótese que dentro de la estructura se definen los 3 vértices de cada uno de los 4 triángulos, el original y “sus espejos”, vía la función vertex(). Finalmente y para el tercer caso, reinicializamos el contador counter a 0 dado que en el siguiente clic empezaremos de nuevo.
El código 4 muestra el programa completo:
// Several global integer variables that we will use
// A first set of variables defining points: (x1a,y1a),(x2a,y2a)...
int x1a,y1a,x2a,y2a,x3a,y3a;
// A second set of variables defining points: (x1b,y1b),(x2b,y2b)...
int x1b,y1b,x2b,y2b,x3b,y3b;
// We are in need of a counter! And we set it to zero to begin
int counter=0;
// The height and width of the rendering window: 300 x 300 pixels
int height=300;
int width=300;
// The background color will be white
int background_color= 255;
// Objects will be black
int objects_color=0;
// We set the width of the stroke used for lines and points to 5
int stroke_width=5;
// The setup function
void setup(){
// We call the size function that creates the window
size(height,width);
// We setup a white background for the window
background(background_color);
// We decide that the objects that we will render should be black
fill(objects_color);
// We setup a bigger width for our points/lines
strokeWeight(stroke_width);
}
// A click event implies calling the mousePressed function
void mousePressed(){
// We check if we are rendering the first,
// the second or the third point by using our counter
if(counter==0){ // Saving the 1st point + incrementing counter
x1a=mouseX; // mouseX is the X coordinate of the mouse
y1a=mouseY; // mouseY is the Y coordinate of the mouse
counter++; // Remember that counter++ equals counter=counter+1
}
else if(counter==1){ // Saving the 2nd point + incrementing counter
x2a=mouseX;
y2a=mouseY;
counter++;
}
else if (counter==2){ // Saving the 3rd point + incrementing counter
x3a=mouseX;
y3a=mouseY;
counter++;
}
}
// draw() will be called infinite times (it is a loop)
void draw(){
// 1st case: there is only 1 point
// We “repeat” it for the 4 quadrants
if(counter==1){
// From the X coordinate for the 1st/4th quadrants
// we generate the X coordinate for the 2nd/3rd quadrants
x1b=width-x1a;
// From the Y coordinate for the 1st/2nd quadrants
// we generate the Y coordinate for the 3rd/4th quadrants
y1b=height-y1a;
// Therefore we can render 4 points! For the 4 quadrants!
// There are 4 combinations available
point(x1a,y1a);
point(x1a,y1b);
point(x1b,y1a);
point(x1b,y1b);
}
// 2nd case: there are 2 points
// We “mirror” the 2nd point for the 4 quadrants
else if(counter==2){
x2b=width-x2a;
y2b=height-y2a;
point(x2a,y2a);
point(x2a,y2b);
point(x2b,y2a);
point(x2b,y2b);
}
// 3rd case: there are 3 points
// We render the 4 triangles for the 4 quadrants
else if(counter==3){
x3b=width-x3a;
y3b=height-y3a;
// Now it’s time to render 4 triangles
beginShape(TRIANGLES);
vertex(x1a,y1a); // Top-left triangle
vertex(x2a,y2a);
vertex(x3a,y3a);
vertex(x1a,y1b); // Bottom-left triangle
vertex(x2a,y2b);
vertex(x3a,y3b);
vertex(x1b,y1a); // Top-right triangle
vertex(x2b,y2a);
vertex(x3b,y3a);
vertex(x1b,y1b); // Bottom-right triangle
vertex(x2b,y2b);
vertex(x3b,y3b);
endShape();
// After rendering the triangle we should set the counter
// to zero again
counter=0;
}
}
En la figura 5 podemos observar algunos resultados interesantes:
Tan sólo queda desear que el ejemplo haya resultado práctico e interesante. Puede modificarse a gusto con interesantes variantes que consigan, por ejemplo, que cada nuevo triángulo se pinte de un color distinto o incluso que a partir de unas determinadas acciones asociadas a distintas teclas, se decida cuál es la primitiva a generar: triángulo, círculo, cuadrado, etc.