takos-faceosc

Gif:
crying-simulator-4crying-simulator-3

Video:

Process:
While working through hard assignments and preparing for midterms with friends, I often hear people say “I’m crying”, even when they aren’t. I wanted to make those dreams a reality. I wanted to create a crying simulator that reacts to your facial expressions and allows you to see a visual representation of your tears as you drown in them. I started with th eraw data template that was available on the deliverables page, then I found a particle simulator on https://www.openprocessing.org/sketch/204560 by Peter Farrell, which I adapted to shoot particles out of your eyes when you are not smiling (It senses smiling by checking if your eyes are open and your mouth is not closed). When the face is in a resting position, it cries, and when it is in a screaming expression (closed eyes, open mouth), maximum tears are achieved. For the tears that are pooling up at the bottom of the screen, I used https://www.openprocessing.org/sketch/376964 By Ilyas Shafigin, and adapted it so that it reacts to the locations of each individual tears instead of the mouse click.

Sketch:
p_20161014_181532

Code:
//I took these initial commnets out of the code part because it causes my embed to glitch on wordpress
// Processing 3.0x template for receiving raw points from
// Kyle McDonald’s FaceOSC v.1.1
// https://github.com/kylemcdonald/ofxFaceTracker
//
// Adapted by Kaleb Crawford, 2016, after:
// 2012 Dan Wilcox danomatika.com
// for the IACD Spring 2012 class at the CMU School of Art
// adapted from from Greg Borenstein’s 2011 example
// https://gist.github.com/1603230
//
//
// tears modified from code on openprocessing sketch/204560
// water from Ilyas Shafigin https://www.openprocessing.org/sketch/376964

import oscP5.*;
import java.util.Iterator;
int num = 1; //number of particles
ArrayList particles;

int mode = 0;

OscP5 oscP5;
int found;
int isSaved;
float[] rawArray;
int highlighted; //which point is selected
int totalTears;

float waterYValue;
//---------
int md = 3, area = 3, number, offset;
int w =2, hw = w / 2;
float lev = -30.0f, k1 = 0.06f, k2 = 0.09f;
float damping = 0.96f;
float mouseAdd = 14.0f, md1 = md - 1.0f;
float[] value, speed;
int fps = 60;
float dt = fps / 1000f;
//--------------------------------------------
//from openprocessing
class Particle {
  float x; //x-position
  float y; //y-position
  float dx; //horizontal velocity
  float dy; //vertical velocity
  float lifespan; 

  //Constructor
  Particle(float _x, float _y, float _dx, float _dy) {
    x = _x;
    y = _y;
    dx = _dx;
    dy = _dy;
    lifespan = 0; //countdown from white to black
  }

  void update() {
    dy += 0.15; //accelerationGRAVITY
    x += dx; //update position by velocity
    y += dy;
    lifespan += 6; 

    ellipse(x, y, 6, 6);
    
    if (abs(y-(height-waterYValue))<3) {
      //x = x < 0 ? 0 : x > width - 1 ? width - 1 : x;
      int ix = int((x - hw) / w);
      float m = 3;
      if (ix < 0) {ix = 0;} else if (ix > 300) {ix = 300;}
      speed[ix] += m;

      for (int i = 1; i < md; i++) { int i2 = ix - i; if (i2 >= 0) speed[i2] += m * herm((md1 - i) / md1);

        i2 = ix + i;
        if (i2 < number) speed[i2] += m * herm((md1 - i) / md1); } } } boolean isDead() { if (lifespan >255.0) {
      return true;
    } else {
      return false;
    }
  }
}


//--------------------------------------------
void setup() {
  textAlign(CENTER); 
  size(640, 480);
  frameRate(30);
  oscP5 = new OscP5(this, 8338);
  oscP5.plug(this, "found", "/found");
  oscP5.plug(this, "rawData", "/raw");

  particles = new ArrayList();

  smooth();

  number = width / w;
  offset = height / 2;
  waterYValue = 0; 

  value = new float[number];
  speed = new float[number];

  for (int i = 0; i < number; i++) {
    value[i] = speed[i] = 0.0f;
  }
}


//-------
void update() {
  for (int i = 0; i < number; i++) {
    float v = value[i];
    speed[i] -= k1 * (v - lev);

    for (int j = 1; j <= area; j++) {
      speed[i] += k2 * (value[max(i - j, 0)] + value[min(i + j, number - 1)] - 2 * v) / j;
    }
  }

  for (int i = 0; i < number; i++) { value[i] += speed[i] *= damping; } } //-------- float herm(float t) { return t * t * (3.0f - 2.0f * t); } //-------------------------------------------- boolean isSmiling() { if ( abs(rawArray[123]-rawArray[129]) > 6) {
    return true;
  }
  return false;
}
int centerLeftEyeX() {
  return(int((rawArray[74]+rawArray[76]+rawArray[82]+rawArray[80])/4));
}
int centerLeftEyeY() {
  return(int((rawArray[75]+rawArray[77]+rawArray[83]+rawArray[81])/4));
}
int centerRightEyeX() {
  return(int((rawArray[86]+rawArray[88]+rawArray[94]+rawArray[92])/4));
}
int centerRightEyeY() {
  return(int((rawArray[87]+rawArray[89]+rawArray[95]+rawArray[93])/4));
}

//-----------------------
void lineFace() {

  

  for (int i = 0; i<=128; i=i+2) {
    if (i !=32 && i !=52 && i != 42 && i != 70 && i!=82 && i!=94 && i!=118 && i!=124) {
      line(rawArray[i], rawArray[i+1], rawArray[i+2], rawArray[i+3]);
    }
  }
  line(rawArray[96], rawArray[97], rawArray[120], rawArray[121]);
  line(rawArray[124], rawArray[125], rawArray[108], rawArray[109]);  
  line(rawArray[126], rawArray[127], rawArray[108], rawArray[109]);   
  line(rawArray[130], rawArray[131], rawArray[96], rawArray[97]);  
  line(rawArray[118], rawArray[119], rawArray[96], rawArray[97]);  
  line(rawArray[94], rawArray[95], rawArray[84], rawArray[85]); 
  line(rawArray[82], rawArray[83], rawArray[72], rawArray[73]);
  line(rawArray[70], rawArray[71], rawArray[54], rawArray[55]);
  line(rawArray[62], rawArray[63], rawArray[54], rawArray[55]);
  line(rawArray[60], rawArray[61], rawArray[70], rawArray[71]);
}






//-------------
void drawTears() {
  noStroke();
  if (isSmiling() == false) {
    waterYValue += 1;
    totalTears += 1;
    num = 2;
    for (int i = 0; i<num; i++) {
      particles.add(new Particle(centerRightEyeX(), 
        centerRightEyeY(), 
        random(-2, 2), 
        random(-2, 2)));
      particles.add(new Particle(centerLeftEyeX(), 
        centerLeftEyeY(), 
        random(-2, 2), 
        random(-2, 2)));
    }
  } else if ( abs(rawArray[75]-rawArray[83]) < 6) {
    waterYValue += 2;
    totalTears +=4;
    num = 4;
    for (int i = 0; i < num; i++) {
      particles.add(new Particle(centerRightEyeX(), 
        centerRightEyeY(), 
        random(-6, 6), 
        random(-6, 6)));
      particles.add(new Particle(centerLeftEyeX(), 
        centerLeftEyeY(), 
        random(-6, 6), 
        random(-6, 6)));
    }
  } else {
    waterYValue -= 2;
    if (waterYValue < 0) { waterYValue = 0; } } if (waterYValue > height) {
    waterYValue = height;
  }


  Iterator it = particles.iterator();
  while (it.hasNext()) {
    Particle b = it.next();
    b.update();
    if (b.isDead()) {
      it.remove();
    }
  }
}
//--------------
void draw() { 
  stroke(204, 225, 226);

    if (rawArray != null) {
      background(228, 241, 242);
      lineFace();
    
    noStroke();
    //text("Crying Simulator 2.0 : You've cried " + totalTears + " tears", width/2, 10);

    if (found != 0) {
      if (rawArray == null) {
        fill(0); 
        text("Make sure FaceOSC is sending raw data.", 20, 20);
      }

      drawTears();
      println(waterYValue);
    }
    update();
    noStroke();
    fill(59, 180, 250);
    for (int i = 0; i < number - 1; i++) {
      float x1 = hw + i * w;
      float y1 = height - waterYValue - value[i];
      float x2 = hw + (i + 1) * w;
      float y2 = height - waterYValue - value[i + 1];
      beginShape(QUAD);
      vertex(x1, height);
      vertex(x1, y1);
      vertex(x2, y2);
      vertex(x2, height);
      endShape(CLOSE);
    }    
    if ( (height-waterYValue) < rawArray[39]) {
          stroke(79, 190, 255);
          lineFace();
          stroke(204, 225, 226);
        } }
}

//--------------------------------------------
public void found(int i) {
  // println("found: " + i);
  found = i;
}
public void rawData(float[] raw) {
  rawArray = raw; // stash data in array
}

//--------------------------------------------
void keyPressed() {
  if (keyCode == RIGHT) {
    highlighted = (highlighted + 2) % rawArray.length;
  }
  if (keyCode == LEFT) {
    highlighted = (highlighted - 2) % rawArray.length;
    if (highlighted < 0) {
      highlighted = rawArray.length-1;
    }
  }
  if (keyCode == DOWN) {
    mode = 1;
  }
}