Video Mapping Toy – 03

En los posts anteriores hemos visto cómo crear un cuadrilátero cuyos vértices podemos arrastrar a la posición que queramos y cómo proyectar una animación de vídeo sobre ese cuadrilátero, a modo de textura, sin rebasar sus límites. Es la base para cualquier programa de vídeo mapping

En este vamos a limpiar un poco la cosa, y emplearemos una clase para crear tantos cuadriláteros como queramos. Por supuesto, cada uno puede tener su propia proyección, de manera que podamos mapear una o varias animaciones sobre cualquier superficie (… que tenga cuatro lados).

Al archivo build.pde vamos a sumar otros dos que nos ayudarán a mantener el código ordenadito: screens.pde y projections.pde. En uno guardaremos la clase Screens() y en el otro las texturas PGraphics que proyectaremos sobre ellas.

Al final del artículo encontrarás un enlace a los archivos de este ejemplo.

Lo primero es crear la clase. Si necesitas información básica sobre qué es una clase y cuál es su estructura puedes ir al artículo Crear una clase en este mismo blog.

Llamaremos a nuestra clase Screen, por aquello de que las vamos a utilizar como pantallas sobre las que proyectaremos nuestras animaciones.

La idea es la siguiente:

  • Creamos una clase que recibe cinco argumentos: los cuatro primero son las coordenadas de sus vértices, el quinto la animación que proyectaremos sobre ella.
  • Si el cursor está sobre la esquina, cambia a MANO, muestra una circunferencia sobre la esquina y queda disponible para ser movida.
  • Si hacemos clic y arrastramos, la esquina se mueve con el cursor.
  • Si soltamos, la esquina queda fija (hasta que volvamos a hacer clic sobre ella)

Vamos a ello:

La clase Screen()

Creamos un archivo screens.pde y escribimos:

//clase
class Screen{

¿Qué argumentos va a tomar la clase? Los cuatro primeros, de tipo PVector, definen las posiciones de sus cuatro esquinas. El quinto, tipo PImage, indica qué textura (animada o no) le aplicaremos.

  //VARIABLES///////////////
  PVector v1, v2, v3, v4;
  PImage pr;

Indicamos que el cursor NO está sobre ninguna esquina

  boolean _mv1, _mv2, _mv3, _mv4 = false;

Vamos con el constructor, al que pasamos los argumentos que hemos declarado hace un momento. Invocamos también el método display(), que dibujará el cuadrilátero.

  //CONSTRUCTOR/////////////
  Screen(PVector _v1, PVector _v2, PVector _v3, PVector _v4, PImage _pr ){
    v1 = _v1;
    v2 = _v2;
    v3 = _v3;
    v4 = _v4;
    pr = _pr;
 
    display();
  }

Y ahora definimos el método display().

Los vértices (vertex()) admiten cuatro argumentos: sus coordenadas x e y y el punto de anclaje de la textura. Sobre este último asunto nos extenderemos en el próximo artículo.

Dentro de este mismo método invocamos otro que definiremos a continuación: drag().

  //MÉTODOS/////////////////
  //Forma
  void display(){
    noFill();
    stroke(255);
    beginShape(QUADS);
      texture(pr); //La animación
      vertex(v1.x,v1.y,0,0);
      vertex(v2.x,v2.y,1,0);
      vertex(v3.x,v3.y,1,1);
      vertex(v4.x,v4.y,0,1);
    endShape();
 
    drag();
  }

Usaremos el método drag() para indicar que el cursor está sobre una de las esquinas de nuestra screen. Al usuario se lo indica de dos maneras: cambiando el cursor con forma de flecha por otro con forma de mano, y dibujando un círculo sobre la esquina. Al programa se lo indicamos cambiando el estado de las variables _mv1, etc. a ‘true’.

Comprobamos esto en cada una de las cuatro esquinas.

  
//Hot spot
  void drag(){
    if (mouseX>v1.x-5 && mouseX<v1.x+5 && mouseY>v1.y-5 && mouseY<v1.y+5) { 
      cursor(HAND); 
      ellipse(v1.x, v1.y, 5, 5); 
      _mv1 = true; } 
 
    else if (mouseX>v2.x-5 && mouseX<v2.x+5 && mouseY>v2.y-5 && mouseY<v2.y+5) { 
      cursor(HAND); 
      ellipse(v2.x, v2.y, 5, 5); 
      _mv2 = true; } 
 
    else if (mouseX>v3.x-5 && mouseX<v3.x+5 && mouseY>v3.y-5 && mouseY<v3.y+5) { 
      cursor(HAND); 
      ellipse(v3.x, v3.y, 5, 5); 
      _mv3 = true; } 
 
    else if (mouseX>v4.x-5 && mouseX<v4.x+5 && mouseY>v4.y-5 && mouseY<v4.y+5) {
      cursor(HAND);
      ellipse(v4.x, v4.y, 5, 5);
      _mv4 = true;
    }

Por último, si el cursor deja de estar sobre una esquina, o indicamos.

    else { 
      cursor(ARROW); 
      _mv1 = false;
      _mv2 = false;
      _mv3 = false;
      _mv4 = false;
    }
  }

A continuación usamos la función mouseDragged() para, si el cursor está sobre una esquina, arrastrarla con el ratón.

  //Drag &amp; drop
  void mouseDragged(){
    if (_mv1 == true) {
      v1.x = mouseX;
      v1.y = mouseY;
    }
    else if (_mv2 == true) {
      v2.x = mouseX;
      v2.y = mouseY;
    }
    else if (_mv3 == true) {
      v3.x = mouseX;
      v3.y = mouseY;
    }
    else if (_mv4 == true) {
      v4.x = mouseX;
      v4.y = mouseY;
    }
  }
}

Las texturas

En un archivo nuevo llamado projections.pde definimos las animaciones que queramos usar.

Observad que estamos actualizando constantemente un objeto PGraphics llamado pr01 que habremos declarado previamente en el archivo rincipal del programa (build.pde, lo describiremos al final).

Por el momento va a ser una animación muuuuuy sencilla, un simple rectángulo blanco que se mueva como loco arriba y abajo.

void textura(){
	pr01.beginDraw();
		pr01.background(0);
		pr01.fill(255);
		pr01.noStroke();
		pr01.rect(0,random(height),width,25);
	pr01.endDraw();
}

El archivo principal

En nuestro build.pde ponemos todo en orden:

Importamos las librerías necesarias, en este caso la de vídeo, ya que usaremos un archivo mp4 como textura.

//LIBRARIES/////////////
//Vídeo (usaremos un clip como textura)
import processing.video.*;

Ahora las variables: pr01, pr02 y pr03 serán las texturas que vamos a usar, cada una de un tipo diferente.

//VARIABLES/////////////
//Imágenes generadas con Processing
PGraphics pr01;
 
//Imagen fija
PImage pr02;
 
//Vídeo
Movie pr03;

Ahora las dimensiones de la pantalla. Recordad que en Processing 3 estas dimensiones no pueden pasarse a size() como variables, pero aún así nos ayudarán a definir las posiciones de las pantallas.

//Tamaño de la ventana
int ancho = 600;
int alto = 600;

Los vértices de cada pantalla pueden determinarse a partir de un punto, indicando a qué distancia de él está cada vértice. Así es más fácil crear varias pantallas de igual tamaño en diferentes posiciones.

//Vértices
//Cada screen define los vértices a partir de las coordenadas del centro
//Screen 01
PVector c1 = new PVector(ancho*0.175, alto*0.5);
PVector s1v1 = new PVector(c1.x-87.5, c1.y-285);
PVector s1v2 = new PVector(c1.x+87.5, c1.y-285);
PVector s1v3 = new PVector(c1.x+87.5, c1.y+285);
PVector s1v4 = new PVector(c1.x-87.5, c1.y+285);
//Screen 02
PVector c2 = new PVector(ancho*0.5, alto*0.5);
PVector s2v1 = new PVector(c2.x-87.5, c2.y-285);
PVector s2v2 = new PVector(c2.x+87.5, c2.y-285);
PVector s2v3 = new PVector(c2.x+87.5, c2.y+285);
PVector s2v4 = new PVector(c2.x-87.5, c2.y+285);
//Screen 03
PVector c3 = new PVector(ancho*0.825, alto*0.5);
PVector s3v1 = new PVector(c3.x-87.5, c3.y-285);
PVector s3v2 = new PVector(c3.x+87.5, c3.y-285);
PVector s3v3 = new PVector(c3.x+87.5, c3.y+285);
PVector s3v4 = new PVector(c3.x-87.5, c3.y+285);

Los objetos de la clase Screen()

//Screens
Screen scr01, scr02, scr03;

En el setup() definimos las texturas (pr01, pr03, pr03…) que declaramos arriba

//ESTRUCTURA///////////////////
void setup(){
  size(600, 600, P2D);
  background(0);
  stroke(100);
  //Texturas
  pr02 = loadImage("tex1.jpg"); // imagen fija
  pr03 = new Movie(this, "static_1.mp4"); // clip de vídeo
  pr03.loop();
  pr01 = createGraphics(1270/3,710); // nuestra animación
  //Ajustes para las texturas
  textureMode(NORMAL); //NORMAL o IMAGE
  textureWrap(REPEAT); //CLAMP o REPEAT
}

Ahora el bucle:

void draw(){
	background(0);
	textura();
	//Instancias de la clase Screen
  //Variables: 4 vectores (esquinas) y textura
	scr01 = new Screen(s1v1,s1v2,s1v3,s1v4,pr01);
        scr02 = new Screen(s2v1,s2v2,s2v3,s2v4,pr02);
        scr03 = new Screen(s3v1,s3v2,s3v3,s3v4,pr03);
}

Nos aseguramos de que se invoca la función mouseDragged() para cada objeto.

//Mover vértices
void mouseDragged(){
  scr01.mouseDragged();
  scr02.mouseDragged();
  scr03.mouseDragged();
}

Y actualizamos la imagen de vídeo.

void movieEvent(Movie m) {
  m.read();
}

Con esto tendremos tres pantallas, cada una con su proyección, que podemos modificar a gusto. Tienes los archivos completos de este ejemplo en Github: VJToy03.

Habrás notado que al mover los vértices de cada pantalla, la imagen de deforma. En la próxima entrega varemos cómo solucionar eso.

Leave a Comment.