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:
//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);
}
}