weirdie-Body

Interactive Shadow Box

I knew for this project that I wanted to create something that specifically responded to the face. I was interested in how the movement of the face could be translated to control something non-human, such as a butterfly. For this reason I chose to use FaceOSC, as I wanted to utilize the gestures such as head tilt and eye openness to control movement. The goal was to create a sort of interactive shadow box, where the image appears still until it detects a face. I would be interested in creating a whole display set of them in the future with other insects and other control gestures. The wings were drawn in Photoshop, created as two separate images which are rotated about the y-axis based on how open your eyes are.

After being frustrated with how jittery the movement appeared, I utilized a circular buffer that takes a running average of previous points to translate to the actual movement of the butterfly. This helped significantly, though it could certainly be refined further. Additionally, I would like to add a cast shadow from the butterfly to add depth.

GIF of some normal blinking:

GIF with really aggressive squinting:

Still image of just the shadow box:

import oscP5.*;
OscP5 oscP5;
 
int     found; // global variable, indicates if a face is found
PVector poseOrientation = new PVector(); // stores an (x,y,z)
float leftOpen;
float rightOpen;
PImage wingRight;
PImage wingLeft;
PImage bckgrd;
CircularBuffer leftBuff = new CircularBuffer(10);
CircularBuffer rightBuff = new CircularBuffer(10);
 
 
//----------------------------------
void setup() {
  size(800, 800, OPENGL);
  oscP5 = new OscP5(this, 8338);
  oscP5.plug(this, "found", "/found");
  oscP5.plug(this, "poseOrientation", "/pose/orientation");
  oscP5.plug(this, "leftOpen", "/gesture/eye/left");
  oscP5.plug(this, "rightOpen", "/gesture/eye/right");
  oscP5.plug(this, "leftBrow", "/gesture/eyebrow/left");
  oscP5.plug(this, "rightBrow", "/gesture/eyebrow/right");
 
  wingRight = loadImage("wingr.png");
  wingLeft = loadImage("wingl.png");
  bckgrd = loadImage("background.png");
}
 
//----------------------------------
void draw() {
  background (214, 205, 197);
  background(178, 163, 149);
  image(bckgrd, 0, 0, width, height);
 
  noFill();
  float scl = 250;
 
  if (found != 0) {
    pushMatrix(); 
    translate (width/2, height/2, 0);
    rotateZ (poseOrientation.z);
    float rightRotate = filter(rightBuff);
    rotateY (constrain(map(rightRotate, 2.7, 3.7, -PI/2, PI/6), -PI/2+0.1, -0.05)); 
    image(wingRight, 0, -200, scl, 1.4*scl);
    popMatrix();
 
    pushMatrix();
    translate (width/2, height/2, 0);
    rotateZ (poseOrientation.z);
    float leftRotate = filter(leftBuff);
    rotateY (constrain(map(leftRotate, 2.7, 3.7, PI/2, -PI/6), 0.05, PI/2-0.1));
    image(wingLeft, 0, -200, -scl, 1.4*scl);
    popMatrix();
  }
  else
  {
    pushMatrix();
    translate (width/2, height/2, 0);
    image(wingRight, 0, -200, scl, 1.4*scl);
    image(wingLeft, 0, -200, -scl, 1.4*scl);
    popMatrix();
  }
 
  fill(37, 34, 27);
  noStroke();
  rect(0, 0, width, 30);
  rect(0, 0, 30, height);
  rect(0, height-30, width, 30);
  rect(width-30, 0, 30, height);
}
 
//----------------------------------
// Event handlers for receiving FaceOSC data
public void found (int i) { found = i; }
public void poseOrientation(float x, float y, float z) {
  poseOrientation.set(x, y, z);
}
public void leftOpen (float i) {leftOpen = i; leftBuff.store(i);}
public void rightOpen (float i) {rightOpen = i; rightBuff.store(i);}
 
//----------------------------------
// Event handlers for receiving FaceOSC data
public void found (int i) { found = i; }
public void poseOrientation(float x, float y, float z) {
  poseOrientation.set(x, y, z);
}
public void leftOpen (float i) {leftOpen = i; leftBuff.store(i);}
public void rightOpen (float i) {rightOpen = i; rightBuff.store(i);}
public float filter (CircularBuffer buff)
{
  float filt = 0;
  for (int i = 0; i < buff.data.length; i++)
  {
    filt = filt + buff.data[i];
  }
  return filt/buff.data.length;
}
 
//----------------------------------
//CIRCULAR BUFFER CLASS -- keeps past datapoints and calculates average
//to help with smoothing the movement
public class CircularBuffer {
    public float[] data = null;
 
    private int capacity  = 0;
    private int writePos  = 0;
    private int available = 0;
 
    public CircularBuffer(int capacity) {
        this.capacity = capacity;
        this.data = new float[capacity];
    }
 
    public void reset() {
        this.writePos = 0;
        this.available = 0;
    }
 
    public int capacity() { return this.capacity; }
    public int available(){ return this.available; }
 
    public int remainingCapacity() {
        return this.capacity - this.available;
    }
 
    public void store(float element){
 
 
            if(writePos >= capacity){
                writePos = 0;
            }
            data[writePos] = element;
            writePos++;
            available++;
    }
}