Arduino y Processing 2: Lluvia controlada por puerto serie
En una entrada anterior habíamos visto cómo comunicarnos de una forma muy básica desde nuestra Arduino con Processing a través del puerto serie. Por si deseáis revisarla, podéis encontrarla en el siguiente enlace: Arduino y Processing 1: Comunicación por puerto serie.
Esta vez, vamos a ver cómo "dibujar" lluvia en Processing y controlar la dirección en la que cae con un potenciómetro en Arduino.
Para hacernos una idea de cómo debe funcionar, veamos el siguiente vídeo:
Aunque la calidad de imagen de la animación de Processing no ha sido la mejor, nos sirve perfectamente para entender qué deseamos hacer.
Lo primero, será preparar nuestra Arduino. Necesitamos un potenciómetro, una LED y una resistencia de 330 Ohm (o la que creáis conveniente) para la LED. El montaje que he realizado es el siguiente:
Imagen realizada con Fritzing |
En realidad, la LED sólo la utilizo para ver que los datos del potenciómetro se ven correctamente, ya que la intensidad de la LED variará según el valor obtenido. Por supuesto, toda la parte relativa a la LED podría eliminarse del código, lo dejo a vuestra elección.
A continuación, veamos el código que he utilizado en la Arduino:
#define PIN_POT A0 // pin de potenciómetro (analógico) #define PIN_LED 9 // pin de la LED (analógico) int valor_POT = 0; // para guardar el valor del potenciómetro int brillo = 0; // brillo de la LED void setup() { pinMode(PIN_POT, INPUT); // configuro pin del potenciómetro como Entrada pinMode(PIN_LED, OUTPUT); // configuro pin de la LED como Salida Serial.begin(9600); // inicializo puerto serie } void loop() { valor_POT = analogRead(PIN_POT); // obtengo el valor del potenciómetro (0 a 1023) brillo = map(valor_POT, 0, 1023, 0, 255); // calculo el valor del brillo de la LED analogWrite(PIN_LED, brillo); // cambio el brillo de la LED Serial.println(valor_POT); // envío el valor por el puerto serie delay(100); // pequeña pausa para no saturar el puerto }
Si cargamos el programa a nuestra Arduino, podremos ver como el brillo de la LED aumenta y disminuye según la posición en la que pongamos el potenciómetro.
Ahora que ya tenemos preparada la Arduino, vamos con Processing.
Necesitaremos dos archivos. Uno será el que gestione la animación y la comunicación por el puerto serie y, el otro, lo usaremos para crear la clase Gota, que gestionará el movimiento de una gota y también la dibujará.
Empezamos primero por la clase Gota. Este es el código que he utilizado:
class Gota { float x; // Coordenada horizontal float y; // Coordenada vertical float longitud; // Longitud de la gota float theta; // Ángulo de la gota (que variará según lo obtenido por el puerto serie) int velocidad; // Píxels que aumento en cada movimiento Gota(){ x = random(width); // Valor horizontal aleatorio, para que salga de cualquier sitio y = 0; // Las gotas caen de la parte superior longitud = 10; // El largo de la gota theta = radians(60); // Ángulo en radianes velocidad = 16; // Para controlar la velocidad a la que cae la gota } /** * @param l Longitud de la gota * @param a Ángulo de la gota en grados */ Gota(float l, float a){ this(); // Llamo al constructor sin parámetros, para no repetir... longitud = l; // Longitud que he recibido como parámetro theta = radians(a); // Ángulo que he recibido como parámetro, en radianes } /** * Desplaza la gota hacia abajo y la dibuja */ void caer(){ pushMatrix(); // guardo posición actual del "lienzo" x = x % width; // si la gota llega al borde, aparece por el otro y = y % height; // si la gota llega al fondo, aparece por arriba if(y == 0) // si la gota está arriba de todo... setX(random(width)); // ...obtengo una posición horizontal aleatoria if(x <= 0) // si la gota se ha ido por el borde izquierdo... setX(width); // ...la coloco a la derecha del todo translate(x, y); // colocamos el lienzo en la posición deseada rotate(-theta); // giramos lienzo los grados que queremos line(0, 0, 0, longitud); // dibujamos en el lienzo una línea con el largo deseado x += sin(theta) * velocidad; // desplazamos la gota proporcionalmente al ángulo y += cos(theta) * velocidad; // desplazamos la gota proporcionalmente al ángulo popMatrix(); // vuelvo al estado inicial del lienzo } /** * Establece el valor de la coordenada horizontal de la gota * @param nuevoX La nueva coordenada horizontal */ void setX(float nuevoX){ x = nuevoX; } /** * Establece ángulo de la gota en radianes * @param newAngle El nuevo ángulo en grados */ void setAngle(float newAngle){ theta = radians(newAngle); } }
Ahora que ya tenemos nuestra clase Gota creada, vamos con el programa principal. La base de comunicación, la habíamos visto ya en una entrada anterior. Simplemente hemos añadido lo necesario para gestionar el dato recibido: Aquí está el código necesario:
import processing.serial.*; // Para usar el puerto serie ArrayList<Gota> lluvia; // Para el conjunto de gotas static int num_gotas = 1500; // Para limitar el número máximo de gotas float theta = 45; // Ángulo float longitud = 15; // Largo de la gota Serial puerto; // Puerto serie String dato, dato_AUX; // Dato recibido por el puerto serie void setup(){ /* Configuración de ventana */ size(800, 600); // Tamaño de ventana background(0); // Color de fondo frameRate(60); // Fotogramas por segundo /* Configuración de pintura */ stroke(#A0DBFF); // Cambiamos el color del lápiz a un tono azul /* Inicialización de variables */ lluvia = new ArrayList<Gota>(); dato = "1"; dato_AUX = "1"; /* Configuración de puerto serie */ String nombrePuerto = Serial.list()[0]; println("Puerto establecido a " + nombrePuerto + "."); // Mostrar el puerto elegido puerto = new Serial(this, nombrePuerto, 9600); } void draw(){ background(0); // Pintamos color de fondo if(puerto.available() > 0){ // si el puerto está disponible... dato = puerto.readStringUntil('\n'); //...leo hasta encontrar un salto de línea } if(dato == null){ // Si he recibido un dato nulo... dato = dato_AUX; // ...utilizo el anterior } else { // Si el dato NO es nulo... if(trim(dato).equals(dato_AUX) == false){ // ...y es distinto del anterior... println("Ángulo: " + map((float) int(dato_AUX), 0, 1023, -90, 90)); dato_AUX = trim(dato); // Guardo el dato borrando los espacios en blanco } } /* Ángulo */ theta = map((float) int(dato_AUX), 0, 1023, -90, 90); // obtengo el ángulo (de -90º a 90º) if(lluvia.size() < num_gotas && random(10) < 1) // Si hay sitio para más gotas... lluvia.add(new Gota(longitud, theta)); // ...añado una nueva for(Gota gota : lluvia){ // Reviso todas las gotas del conjunto gota.setAngle(theta); // Establezco el ángulo en que deben orientarse gota.caer(); // Las desplazo y dibujo } }
Además de comprobar que no excedamos el número de gotas, también he añadido una condición que hará que no se creen todas al mismo tiempo, si no que caigan aleatoriamente. Es decir, si hay sitio para más gotas, obtengo un número aleatorio entre 0 y 9 y, si ha salido cero, entonces sí creo la gota... y si no, pues no la creo. Por eso el último "if" utiliza esa segunda condición. Si no fuera así, caerían las gotas todas al mismo tiempo y el efecto sería completamente diferente:
if(lluvia.size() < num_gotas && random(10) < 1) lluvia.add(new Gota(longitud, theta));
A continuación, podéis ver una imagen del resultado final:
Os recomiendo variar varios parámetros del código para ver cómo afecta la velocidad, longitud de la gota, número de gotas... incluso los fotogramas por segundo.
En la clase Gota, en el método caer, podéis cambiar line() por ellipse(), por ejemplo, y ver cómo caen pequeños círculos en vez de líneas (aunque así no apreciaréis el ángulo). Lo divertido está en experimentar.
Espero que os haya gustado.
Un saludo: Roi.
Comentarios
Publicar un comentario
En entradas antiguas, los comentarios quedarán pendientes de moderación. Si tu comentario tarda en aparecer, ten paciencia ;D