Title: WATERFALL
Year: 2013
Type: Interactive Installation , Processing 2.0 + Webcam
Site: http://santi.tv/wf

WATERFALL explores the possibilities of using a webcam as the main tool for interaction in a spatial installation. Using frame differentiation, the outline of any moving object is clearly determined with high accuracy, causing a response on a variable amount of three dimensional particles that behave as a waterfall. The installation is adaptable to any space and works in low to very well lit conditions, and depending on the processor speed, can control up to 5000 particles at a time. I chose not to use any external Processing libraries or highly unique hardware - such as an IR camera or Kinect - in order to make the project as universal as possible minimizing the technical requirements. It can run non-stop for long periods of time ( 20 days and counting with no crashes) and has been presented on a large HD 16 monitors video wall in Kuwait as well as on a standard resolution monitor at Electronics Alive VII, in Tampa. Waterfall has been tested on Mac Intels running in 64 Bits, and I am still trying to make it work on a windows machine...

Waterfall runs on any Mac computer with OSX, with either the existing or an attached webcam.

WATERFALL / SANTIAGO ECHEVERRY 2013 WATERFALL / SANTIAGO ECHEVERRY 2013 WATERFALL / SANTIAGO ECHEVERRY 2013 WATERFALL / SANTIAGO ECHEVERRY 2013 WATERFALL / SANTIAGO ECHEVERRY 2013 WATERFALL / SANTIAGO ECHEVERRY 2013 WATERFALL / SANTIAGO ECHEVERRY 2013 WATERFALL / SANTIAGO ECHEVERRY 2013

PROCESSING CODE



// ############################################################################################ //
// ####### WATERFALL V 1.0 FOR 2 POINT 0 CONCEPTS ############################################ //
// ########################################################################################## //
// ####### SANTIAGO ECHEVERRY - 2012 ####################################################### //
// ######################################################################################## //
// ####### INSPIRED BY GOLAN LEVIN'S MOTION DIFFERENCING ################################# //
// ###################################################################################### //
// ####### ON THE MAC SET THE MAIN SCREEN AS #1 AND THE VIDEOWALL DISPLAY AS #2 ######## //
// ####### #1 IS THE MAIN SCREEN #2 IS THE VIDEOWALL ################################## //
// ####### BEFORE STARTING PROCESSING CONNECT THE ROCKETFISH EXTERNAL USB CAMERA ##### //
// ################################################################################# //
// ####### OPEN PROCESSING PREFERENCES USING  UNDER THE PROCESSING MENU ## //
// ####### CHECK BOX AND INCREASE AVAILABLE MEMORY TO 512 MB ####################### //
// ####### VERY IMPORTANT >>>> RUN SKETCHES ON DISPLAY 2 = VIDEOWALL ############## //
// ####### LAUNCH PROGRAMS IN 64 BIT MODE ######################################## //
// ####### ###################################################################### //
// ####### THE SHARP VIDEO WALL HAS A RESOLUTION OF 1366X768 PX PER DISPLAY #### //
// ####### THT TOTAL RESOLUTION OF THE WALL IS DISPLAYRES X AMOUNT OF DISPLAYS #//
// ####### CHANGE resW AND resH TO THE DISPLAY'S RES IF NOT 1366X768 ######### //
// ####### AND CHANGE THE AMOUNT OF COLUMNS AND ROWS CREATED BY THE DISPLAYS  //
// ####### FOR A 2X2 DISPLAY CHANGE screencols AND screenrows TO 2 EACH #### //
// ####### MAKE SURE THE CODE WINDOW IS ON DISPLAY #1 SO YOU CAN SEE IT ### //
// ####### UNDER "SKETCH" AT THE TOP >>>> SELECT "PRESENT" >>NOT<< "RUN" # //
// ####### UNDER "SKETCH" AT THE TOP >>>> SELECT "PRESENT" >>NOT<< "RUN"# //
// ####### TO STOP PRESENTATION UNDER "SKETCH" AT THE TOP SELECT "STOP"  //
// #################################################################### //




import java.util.ArrayList;
import processing.video.*;






int resW = 1440;  //1366;                // HORIZONTAL RESOLUTION FOR EACH INDIVIDUAL DISPLAY
int resH = 900; // 768;                 // VERTICAL RESOLUTION FOR EACH INDIVIDUAL DISPLAY



int screencols     =   1;       // CHANGE TO THE AMOUNT OF HORIZONTAL MONITORS IN THE VIDEO WALL
int screenrows     =   1;       // CHANGE TO THE AMOUNT OF VERTICAL MONITORS IN THE VIDEO WALL

int ratio          =   2;       // DO NOT USE 1, 3, 6, 7, 9


int stageW = resW * screencols; // HORIZONTAL RES ON VIDEO WALL, HOW MANY MONITORS
int stageH = resH * screenrows; // VERTICAL RES ON VIDEO WALL, HOW MANY MONITORS

int videoW = 320; // 640; //1280; // DO NOT CHANGE
int videoH = 180; // 360; // 720 // DO NOT CHANGE

float resRatio = 1366/1280; 
float HD = 16/9;


///////////////////////////////////////////////////////////////////////////////////////

int       cols     =     videoW / ratio; // AMOUNT OF GRID CELLS HORIZONTALLY
int       rows     =     videoH / ratio; // AMOUNT OF GRID CELLS VERTICALLY

float cW = stageW / cols; // CELL WIDTH
float cH = stageH / rows;  // CELL HEIGHT
float cR = (cW + cH)/2;           // CELL DEPTH

Capture video;

float diffR;
float diffG;
float diffB;

float diffT;

int numPixels;

int[] previousFrame;

float movementSum = 0;

ArrayList brightestCellsX;
ArrayList brightestCellsY;
ArrayList brightestCellsZ;
ArrayList brightestCells;


///// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


float newX;
float newY;
float newZ = 0;

// ################################## LOGOS ##################

PImage sharp;

PImage delite;

PImage twopto;


// ############################################################

///// PARTICLE VARS
  

ball p[];

////////////////////////////////

//// ######################## ADJUST TO FIT AMBIENT LIGHT ##################
//// ############################# THRESHHOLD LIMIT ########################
//// ################ USE THE LEFT AND RIGHT ARROW KEYS TO CONTROL #########

                         int th; // threshhold between 0 and 255

//// ######################## ADJUST TO FIT AMBIENT LIGHT ##################

/////////

int flagvideo; //// THIS TURNS VIDEO DISPLAY ON AND OFF FOR TESTING PURPOSES
int flaglogos; //// THIS SHOWS THE LOGOS

////////

int np; // NUMBER OF PARTICLES:::: VERY IMPORTANT

////////////////////////////////

void setup() {  // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> SETUP
  
////// INITIAL VALUE OF THRESHOLD

th = 30;

///// VARIABLES FOR VISIBILITY OF ELEMENTS ON STAGE

flagvideo = -1; /// HIDE THE VIDEO BY DEFAULT 
flaglogos = 1; /// SHOW THE LOGOS BY DEFAULT

//////////////////////////////////

size(stageW, stageH, P3D); 

background(0);

/////////////////////////////////////////////////// init IMAGES

sharp = loadImage("logo_sharp.jpg");   // 1354, 311

delite = loadImage("logo_delite.jpg"); // 2170, 1676

twopto = loadImage("logo_twopto.png"); // 1950, 1850 

/////////////////////////////////////////////////// init video
  String[] cameras = Capture.list();
  
  if (cameras.length == 0) {
                                println("There are no cameras available for capture.");
                                exit();
  } else {
                                println("Available cameras:");
                                
                                for (int i = 0; i < cameras.length; i++) {
                                  println("INDEX = " + i + " // CAMERAS = " + cameras[i]);
                                }
  }
  
video = new Capture(this, videoW, videoH);
  
// video = new Capture(this, videoW, videoH, "Rocketfish HD Webcam");
  
  frameRate(30);
  
  video.start();  

  /// >>>>>>>>>>>>>>>>>>>>>>>>>>>

 /// >>>>>>>>>>>>>>>>>>>>>>>>>>> DECLARE ARRAYLISTs  TO STORE ALL THE X,Y,Z,B VALUES

  brightestCellsX = new ArrayList();

  brightestCellsY = new ArrayList();

  brightestCellsZ = new ArrayList();

  brightestCells = new ArrayList();

  /// >>>>>>>>>>>>>>>>>>>>>>>>>>>
  
 ////////////////////////////////// AMOUNT OF PARTICLES

  np = 2000; // AMOUNT OF PARTICLES
  
  ////////////////////////////////// INIT PARTICLES
  
  createParticles();


  
//////////////////////////////////////////// SETUP AMOUNT OF PIXELS FOR pixels[] ARRAY

  numPixels = video.width * video.height;

  // Create an array to store the previously captured frame
  previousFrame = new int[numPixels];

  loadPixels();

}

//////// END OF SETUP 

void createParticles() {
 
     // init particles
  p = new ball[np];
  
  for (int i=0;i 255) { th = 255; }
  println("THRESHOLD ++ >> " + th);
  break;

//****************************************************************
  
  case 37: /// LEFT ARROW
  th --;
  if (th < 1) { th = 1; }
  println("THRESHOLD -- >> " + th);
  break;

//****************************************************************
  
  case 38: //// UP ARROW => INCREASE THE AMOUNT OF SPHERES - RESETS BUT GOOD FOR TESTING PERFORMANCE
  
  np+=20;
  
  println("BLOCKS ++ >> " + np);
  
  createParticles();  //////// RESET THE AMOUNT OF PARTICLES
  
  for (int i=0;i => INCREASE THE AMOUNT OF SPHERES - RESETS BUT GOOD FOR TESTING PERFORMANCE
  
  np-=20;
  
  println("BLOCKS -- >> " + np);
  
    createParticles(); //////// RESET THE AMOUNT OF PARTICLES
  
  if (np < 1) { np = 1; }
  
  for (int i=0;i SHOW THE LOGOS
  flaglogos *= -1;
  break;
  
//****************************************************************
  
  case 86: //// V ====== > SHOW THE VIDEO
  flagvideo *= -1;
  break;

//****************************************************************

} // END SWITCH 


}


// ############################################################


void draw() {

  background(0);
  
  ///////////// LOGOS
  
if (flaglogos > 0) {

  pushMatrix();
  translate(0,0,20);
  beginShape(); ///////////////////////////////////// SHARP
  fill(255);
  rect((width/2 - width/16),(height - width*0.23/4) ,width/8,width*0.23/8);
  texture(sharp);
  endShape();
  beginShape();  //////////////////////////////////// DELITE
  fill(255);
  rect((width/25),(height - width/10), width*1.294/14,width/14);
  texture(delite);
  endShape();
  beginShape();  ///////////////////////////////////// 2PT0
  fill(255);
  rect((width - width/7),(height - width*1.054/8) ,width/10,width*1.054/10);
  texture(twopto);
  endShape();
  popMatrix();

} else {
 
  background(0); 
  
}
  
  //////////////
  
  
  
  lights();
  noStroke();

  video.read(); 
  
  video.loadPixels(); 
  
  //// DRAW VIDEO FOR TESTING PURPOSES 

if ( flagvideo > 0 ) { image(video,0,0, width/9, width/16);  } else { image(video,0,0, -320, 90); } // COMMENT WHEN PRESENTING
  
// >>>>>>>>>>>>>>>>>>> START MOTION DETECTION 

  movementSum = 0; // Amount of movement in the frame

  for (int i = 0; i < numPixels; i++) { // For each pixel in the video frame...

    color currColor = video.pixels[i];

    color prevColor = previousFrame[i];

    // Extract the red, green, and blue components from current pixel
    float currR = (currColor >> 16) & 0xFF; // Like red(), but faster
    float currG = (currColor >> 8) & 0xFF;
    float currB = currColor & 0xFF;

    float currT = brightness(currColor);

    // Extract red, green, and blue components from previous pixel
    float prevR = (prevColor >> 16) & 0xFF;
    float prevG = (prevColor >> 8) & 0xFF;
    float prevB = prevColor & 0xFF;

    float prevT = brightness(prevColor);

    // Compute the difference of the red, green, and blue values
    diffR = abs(currR - prevR);
    diffG = abs(currG - prevG);
    diffB = abs(currB - prevB);

    diffT = abs(currT - prevT);

    movementSum = diffT;

    // Render the difference image to the screen

    pixels[i] = round(diffT);

    // Save the current color into the 'previous' buffer
    previousFrame[i] = currColor;
    
  }
  
// >>>>>>>>>>>>>>>>>>> END MOTION DETECTION 

// >>>>>>>>>>>>>>>>>>> GENERATE PARTICLES <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

  for (int i=0;i>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 

// <<<<<<<<<<<<<<<<<<< START MOTION SENSITIVITY >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  if (movementSum <= 0) { 
    
    brightestCellsX.clear();
    brightestCellsY.clear();
    brightestCellsZ.clear();
    brightestCells.clear();
    
  } else  { 
        
    for (int j=0; j < video.height; j+=ratio) {

      for (int k=0; k < video.width; k+=ratio) {

        int loc = (video.width - k - 1) + (j*video.width); // MIRROR

        float c = pixels[loc];
        

        if ( c > th ) { // GRAYSCALE BRIGHTNESS > THRESHHOLD 
          
          newX = k * stageW/videoW;
          newY = j * stageH/videoH;  

// DETERMINE AREA WITH HIGHEST CONTRAST THEREFORE HIGHEST MOTION

          brightestCellsX.add(new Float(newX));

          brightestCellsY.add(new Float(newY));
          
          brightestCellsZ.add(new Float(newZ));

          brightestCells.add(c);

/////////////////////////////////////////// DRAWS THE GREEN CELLS 

switch(flag) {
  
          case 1:
          
            fill(0, 255 , 0);

            noStroke();

            pushMatrix();

            translate(newX, newY, newZ);

            box(int(cW*3/4),int(cR*3/4),1);
            
            popMatrix();
            
            break;
            
          case -1:
            
            break;  
          
}
        }  // END PIXELS loc
        
      }  
    }
  }
}