FaceOSC Bot

While I was brainstorming ideas for my FaceOSC character, my first ideas were three dimensional creatures like giraffes or other animals. These are ideas that I would actually like to animate in the future, but I have yet to fully understand how to create complex 3D shapes in Processing.

Anyways, I decided to shift gears to a deconstructed face, so I could work with the component parts: the eyes, eyebrows, nose, and mouth. And I think the webcam icon on my laptop subconsciously inspired me, because I didn’t realize how similar my initial eyeball sketches were to the design of a webcam. And that turned out to be just fine because it adds to the idea of how FaceOSC interacts with people.

A great deal of my time was spent figuring out how to animate the propellers. I decided to go with a 3-frame GIF and just import it as an image. But the more I think of it, my program could have had a lot more flexibility and interactivity if I created a 3D sphere and somehow stuck a propeller and pupil on it. Anywho, I am still quite proud of the propeller GIF though.

My second challenge was figuring out the design for the mouth (I scrapped the nose). I couldn’t really imagine a shape that fit with the theme of circular robots, so I went with red laser beams shooting through robo-dimples. I’m not sure if that idea comes across with other people, though. Unfortunately, my mouth portion is a bit glitchy as the endpoints don’t properly line up with the robo-dimples when I move my mouth in certain areas.

Finally, the properties I used from FaceOSC are the x-position and y-position of the face, the face scale, the eyebrow height (the eyebrows move the eyeballs, I thought it would be more fun that way), the mouth width, and the mouth height. And even though the variables I used are limited, the sinusoidal movements kind of make it more enjoyable to look at.

Here are some sketches I did first, followed by my code:
sketchface

facesketch2

//Kyle McDonald's FaceOSC https://github.com/kylemcdonald/ofxFaceTracker
import oscP5.*;
OscP5 oscP5;

Animation animation1, animation2, animation3, animation4;

int found;

float poseScale;
PVector posePosition = new PVector();
PVector poseOrientation = new PVector();

float mouthHeight;
float mouthWidth;
float eyebrowLeft;
float eyebrowRight;

float theta1 = 0;
float theta2 = 0;
float xposLEye;
float yposLEye;
float xposREye;
float yposREye;
float xposLDimple;
float yposLDimple;
float xposRDimple;
float yposRDimple;
float a1;
float b1;
float c1;
float a2;
float b2;
float c2;

void setup() {
  size(500, 550);
  frameRate(30);
  
  oscP5 = new OscP5(this, 8338);
  oscP5.plug(this, "found", "/found");
  oscP5.plug(this, "poseScale", "/pose/scale");
  oscP5.plug(this, "posePosition", "/pose/position");
  oscP5.plug(this, "poseOrientation", "/pose/orientation");
  oscP5.plug(this, "mouthWidthReceived", "/gesture/mouth/width");
  oscP5.plug(this, "mouthHeightReceived", "/gesture/mouth/height");
  oscP5.plug(this, "eyebrowLeftReceived", "/gesture/eyebrow/left");
  oscP5.plug(this, "eyebrowRightReceived", "/gesture/eyebrow/right");
  
  animation1 = new Animation("roboeye", 3);
  animation2 = new Animation("roboeye", 3);
  animation3 = new Animation("roboballL", 3);
  animation4 = new Animation("roboballR", 3);
}

void draw() {
  background(255);
  backdrop(255,210);
  
  float a1 = map(sin(theta1),-1,1,-10,10);
  float b1 = map(cos(theta1),-1,1,-10,10);
  float c1 = map(sin(theta1),-1,1,-0.1,0.1);
  float a2 = map(sin(theta2),-1,1,-15,15);
  float b2 = map(cos(theta2),-1,1,-8,8);
  float c2 = map(sin(theta2),-1,1,-0.1,0.1);
  theta1+=0.15;
  theta2+=0.08;
  
  xposLEye = -width/2;
  yposLEye = eyebrowLeft * -11;
  xposREye = 0;
  yposREye = eyebrowRight * -10;
  xposLDimple = -mouthWidth * 7-75;
  yposLDimple = height/3-50;
  xposRDimple = mouthWidth * 7-75;
  yposRDimple = height/3-50;
  
  if(found>0) {
    translate(posePosition.x-50, posePosition.y-50);
    
    pushMatrix();
    translate(a1,b1);
    rotate(c1);
    animation1.display(xposLEye,yposLEye);
    popMatrix();
    
    pushMatrix();
    scale(0.85);
    translate(b2,a2);
    rotate(c2);
    animation1.display(xposREye,yposREye);
    popMatrix();
    
    pushMatrix();
    translate(b1,a1);
    rotate(c1);
    laserArc(xposLDimple+160,yposLDimple+50,mouthWidth*11.5,mouthHeight*30,0,PI);
    laserArc(xposLDimple+160,yposLDimple+50,mouthWidth*11.5,mouthHeight*2,-PI,0);
    animation3.display(xposLDimple,yposLDimple);
    animation4.display(xposRDimple,yposRDimple);
    popMatrix();
  }
}

// OSC CALLBACK FUNCTIONS

public void found(int i) {
  println("found: " + i);
  found = i;
}

public void poseScale(float s) {
  println("scale: " + s);
  poseScale = s;
}

public void posePosition(float x, float y) {
  println("pose position\tX: " + x + " Y: " + y );
  posePosition.set(x, y, 0);
}

public void poseOrientation(float x, float y, float z) {
  println("pose orientation\tX: " + x + " Y: " + y + " Z: " + z);
  poseOrientation.set(x, y, z);
}

public void mouthWidthReceived(float w) {
  println("mouth Width: " + w);
  mouthWidth = w;
}

public void mouthHeightReceived(float h) {
  println("mouth height: " + h);
  mouthHeight = h;
}

public void eyebrowLeftReceived(float f) {
  println("eyebrow left: " + f);
  eyebrowLeft = f;
}

public void eyebrowRightReceived(float f) {
  println("eyebrow right: " + f);
  eyebrowRight = f;
}

// all other OSC messages end up here
void oscEvent(OscMessage m) {
  
  /* print the address pattern and the typetag of the received OscMessage */
  println("#received an osc message");
  println("Complete message: "+m);
  println(" addrpattern: "+m.addrPattern());
  println(" typetag: "+m.typetag());
  println(" arguments: "+m.arguments()[0].toString());
  
  if(m.isPlugged() == false) {
    println("UNPLUGGED: " + m);
  }
}

void laserArc(float x0, float y0, float wid, float hgt, float start, float stop){
  noFill();
  
  strokeWeight(15);
  stroke(240,0,0,75);
  arc(x0,y0,wid,hgt,start,stop);
  
  strokeWeight(12);
  stroke(240,0,0,75);
  arc(x0,y0,wid,hgt,start,stop);
  
  strokeWeight(5);
  stroke(255,100);
  arc(x0,y0,wid,hgt,start,stop);
  
  strokeWeight(3);
  arc(x0,y0,wid,hgt,start,stop);
}

void backdrop(color c1, color c2){
  noFill();
  for(int i=0;i< =height-50;i++){
      float inter=map(i,0,height-50,0,1);
      color c=lerpColor(c1,c2,inter);
      stroke(c);
      line(0,i+100,width,i+100);
  }
}

// Class for animating a sequence from Processing.org

class Animation {
  PImage[] images;
  int imageCount;
  int frame;
  
  Animation(String imagePrefix, int count) {
    imageCount = count;
    images = new PImage[imageCount];

    for (int i = 0; i < imageCount; i++) {
      // Use nf() to number format 'i' into four digits
      String filename = imagePrefix + i + ".gif";
      images[i] = loadImage(filename);
    }
  }

  void display(float xpos, float ypos) {
    frame = (frame+1) % imageCount;
    image(images[frame], xpos, ypos);
  }
}

Comments are closed.