Feathered Vessel
Participants pilot a feathered vessel (more commonly known as a pigeon) by gently turning their faces in the direction in which they’d like to travel. Feathered Vessel creates an odd intersection between the militaristic concept of surveillance drones and the more mystic concept of becoming/taking control of an animal.
Click here for concepts and sketches from a nascent version of this project.
Now for the technical stuff.
Above is a significantly scaled down image of Feathered Vessel‘s map. It’s a composite of a bunch of satellite photos taken from 500 feet up. The map is 10,000 pixels by 10,000 with a DPI of 72. Initially, I thought I’d need to split the map into tiles and load them depending on where the view screen was, but Processing was able to handle the strain of repositioning the entire image every frame.
FaceOSC was used to sense the x and y rotations of participant’s faces. As FaceOSC is unable to sense faces in profile, I had to spend a lot of time making sure that side-to-side movement covered enough distance to prevent participants from turning their heads too far in an attempt to move further in a particular direction. I’ve come to appreciate the fine line between gimmicky controls and sleek movement.
In its current iteration, Feathered Vessel tracks face position, the map’s position, and time. If I were to build upon this project, I would try to implement a more complex event system where crossing certain thresholds causes various animated occurrences to happen, such as a car that speeds down a road or a field that caches on fire.
The code from this project can be found below. (Note: It won’t do much without its art assets. Feel free to contact me if you’d like access to them.)
//Miranda Jacoby
//EMS Interactivity Section A
//majacoby@andrew.cmu.edu
//Copyright Miranda Jacoby 2014
//Big thanks to Golan Levin for showing me how to use inputs from
//Kyle McDonald's FaceOSC program.
import oscP5.*;
OscP5 oscP5;
float px, py;
float vx, vy;
int opacity;
int fTimer, countdown;
int moveVal, moveVal2, moveVal3;
boolean hasStarted;
int found;
PVector poseOrientation = new PVector();
PImage map;
PImage falcon;
PImage falcon2;
PImage shadow;
//----------------------------------
void setup() {
size(1920, 1080);//, OPENGL);
map = loadImage("pigeonMapsMerged.png");
falcon = loadImage("falcon.png");
falcon2 = loadImage("falcon2.png");
shadow = loadImage("falconShadow3.png");
imageMode(CENTER);
oscP5 = new OscP5(this, 8338);
oscP5.plug(this, "found", "/found");
oscP5.plug(this, "poseOrientation", "/pose/orientation");
px = width /2;
py = height/2;
vx = 0;
vy = 0;
//falcon timer reset
countdown = 3000;//8000; //3500;
//falcon movement reset
moveVal = 0;
moveVal2 = 0;
moveVal3 = 0;
//Face will not be tracked until mouse is clicked.
noLoop();
}
//----------------------------------
void draw() {
background (0);
float ox = poseOrientation.x;
float oy = poseOrientation.y;
float oz = poseOrientation.z;
drawMap();
//Values for scaling FaceOSC control
float poseOrientationScaleFactor = 2.00;
float velocityDamping = 0.92;
vx = vx - oy*poseOrientationScaleFactor;
vy = vy - ox*poseOrientationScaleFactor;
vx = vx * velocityDamping;
vy = vy * velocityDamping;
px = px + vx;
py = py + vy;
//println (nf(ox,1,3) + "\t" + nf(oy,1,3) + "\t" + nf(oz,1,3));
falconTimer();
falconEvent();
if (px > 6000){
px = 6000;
}
else if (px < -4000){ px = -4000; } else if (py > 5500){
py = 5500;
}
else if (py < -4500){ py = -4500; } println("X: " + px); println("Y: " + py); //If it's been too long since a face was detected, //decrement velocity and come to a stop. int now = millis(); int elapsed = now - lastValidDataReceivedTime; float slowDown = 0.45; println ("The last time I received valid data was " + elapsed + " milliseconds ago.") ; if (elapsed > 1000){
vx = vx * slowDown;
vy = vy * slowDown;
}
}
//---------------------------------
//Reset Values for new player
//when mouse is pressed and then released
void mouseClicked(){
initVariables();
}
//----------------------------------
//Initialize variables
void initVariables(){
px = width /2;
py = height/2;
vx = 0;
vy = 0;
//falcon timer reset
countdown = 3000;//8000;
//falcon movement reset
moveVal = 0;
moveVal2 = 0;
moveVal3 = 0;
loop();
}
//----------------------------------
void drawMap()
{
image(map, px, py);
}
//----------------------------------
void falconTimer()
{
fTimer = millis() - countdown; //2 minutes
if (countdown > 0){
countdown = countdown - 1;
}
else if (countdown < 0){
countdown = 0;
}
print("Countdown: " + countdown);
}
//----------------------------------
void falconEvent()
{
if((countdown < 2001) & (countdown > 1600)){//3999)){
//moveVal = 0;
image(falcon, vx + (width/3), (vy + (height)) - moveVal);
moveVal = moveVal + 15;
if(moveVal > 1600){
moveVal = 1600;
}
println("move: " + moveVal);
}
else if((countdown < 1001) & (countdown > 600)){//1999)){
image(falcon2, (vx - (width/2))+ moveVal2, vy + (height/3));
moveVal2 = moveVal2 + 15;
if(moveVal2 > 10000){
moveVal2 = 10000;
}
}
else if(countdown < 1){ //image(shadow, 0, 0 - moveVal3); image(shadow, vx + (width/2), (vy + (2*height)) - moveVal3); moveVal3 = moveVal3 + 20; if(moveVal3 > 1600){
moveVal3 = 1600;
}
}
println("PX: " + px);
println("PY: " + py);
}
//----------------------------------
public void found (int i) {
found = i;
}
int lastValidDataReceivedTime = 0;
public void poseOrientation(float x, float y, float z) {
poseOrientation.set(x, y, z);
lastValidDataReceivedTime = millis();
}